tmux.conf, every binding
The file as it ships, docs/tmux.conf.full.example.
# tmux-companion with everything turned on.## docs/tmux.conf.example is the bar I actually run: one `#()` call, 29.71 ms/s# of CPU, which is 3.0% of one core, spawn, tmux server and daemon added up.# This file is the other end of the range, and every block carries what it# costs, so you're choosing with the number in front of you rather than after# your fans come on.## Measurements are from docs/BENCHMARKS.md, and the five-call figure from# docs/BENCHMARKS-before-port.md, both taken on one machine. Yours won't match the# absolute numbers and will match the shape.## each extra #() on the bar 14.6 ms of CPU per second per client# sh-jobs on the bar 16.25 ms of server CPU per call# clients on the bar 4.93 ms per call# sh-jobs' process-table scan 18.5 ms when the git segment does it# window in window-status-format one process spawn per window per redraw# all of it, five #() calls 153.77 ms/s, 15.4% of one core## ── What this file takes over ───────────────────────────────────────────────## Four of tmux's own prefix keys are rebound here, and one in copy mode:## prefix Space next-layout -> jump to any text on screen# prefix c new-window -> the new-window picker# prefix z zoom this pane -> zen# prefix ? list-keys -> the keys picker# copy-mode-vi o other-end -> open what is under the cursor# copy-mode-vi g history-top -> a menu of open and search, whose own g# still goes to the top## Three keys sit in the root table (`-n`), so they fire without the prefix and# take Alt-s, Alt-a and Alt-A away from every program in the pane, including# an editor or an agent that wanted them:## M-s the project picker# M-a the next window in this session# M-A the previous window, tmux's last-window, so two windows flip## Drop the `-n` on any of them to put it behind the prefix instead.
set -g status-interval 1
# What tmux-sensible used to set, written out, since that plugin stopped in# April 2024 and a setting in this file is one you can see. `tmux-companion# doctor` lists the ones a tmux.conf is missing and what each costs.## escape-time how long Esc waits to see if it starts a sequence# history-limit lines a pane keeps, which is all `search` can read; it# applies to panes opened after it is set# display-time how long a message stays on the bar# focus-events tells an editor in a pane when you come back to itset -s escape-time 10set -g history-limit 50000set -g display-time 4000set -s focus-events on
# ── The bar itself ──────────────────────────────────────────────────────────## Not optional. The segments draw their own backgrounds against colour233,# which `src/segments/window.rs` calls BG_BAR, and the current window rises to# 236 on top of it. Leave this out and tmux's default green shows through# everywhere a segment does not reach, which is most of the bar.set -g status-style bg=colour233,fg=colour251set -g window-status-style defaultset -g window-status-current-style defaultset -g status-left-style defaultset -g status-right-style default
# ── The right side ──────────────────────────────────────────────────────────## Two calls, and the order is deliberate: tmux builds status-right left to# right, so the jobs marker goes first. What you suspended belongs next to the# branch you suspended it in, not out past the battery where it reads as part# of the hardware readout.## sh-jobs is a #() of its own, so 14.6 ms/s of spawn plus 16.25 ms of server# CPU per call for the process-table scan. Configure what it draws in# [[sh_jobs.job]]. Drop this line if the marker is not worth that to you.set -g status-right "#(tmux-companion sh-jobs #{pane_pid})"
# git, bandwidth and battery, computed concurrently and returned in one call.## It takes a path and nothing else. There is deliberately no #{pane_pid} here:# a pid makes the git segment resolve suspended jobs too, which enumerates# every process on the machine and cost 18.5 ms of that segment's 26.0. That# marker is given up here on purpose, the line above is the cheaper way to get# it, and `gst <path> <pid>` still shows it by hand.set -ga status-right "#(tmux-companion status-right --branch-max-len 40 #{pane_current_path})"set -g status-right-length 150
# ── The left side ───────────────────────────────────────────────────────────## This has to be `set`, not `set -ga`. Appending to a status-left nobody set# appends to tmux's default, which is `[#S] ` under a status-left-length of# 10, so the session name comes out cut in half as `[playgroun` and everything# appended after it is past the limit and never drawn at all.## No subprocess here: the session name is tmux's own, and the colours are the# options `theme apply` writes, so picking a theme repaints this block without# anything having to run.set -g status-left "#[fg=#{@theme-session-name-fg},bg=#{@theme-session-name-bg}] #S "
# The hostname, only when this server was started over ssh, because on a local# machine it is a word you already know.if-shell '[ -n "$SSH_CONNECTION" ]' \ 'set -ga status-left "#[fg=color203,bg=color233] #h"'
set -ga status-left "#[fg=color240,bg=color233] %H:%M:%S"
# The prefix, copy mode and synchronised panes, each said on the bar for as# long as it lasts. tmux knows all three, so nothing runs and nothing is# installed: tmux-prefix-highlight and tmux-mode-indicator are plugins for# these two lines. A comma inside a `#{?...}` has to be written `#,` or tmux# takes it for the end of the branch.set -ga status-left "#{?client_prefix,#[fg=color233#,bg=color214] prefix ,}"set -ga status-left "#{?pane_in_mode,#[fg=color233#,bg=color114] copy ,}#{?pane_synchronized,#[fg=color233#,bg=color203] sync ,}"# A gap, so the window list does not start against the clock.set -ga status-left "#[fg=color235,bg=color233] "
# ── Other clients attached ──────────────────────────────────────────────────## A third #() call. Cheap to compute at 4.93 ms and it still costs a spawn.set -ga status-left "#(tmux-companion clients #{session_attached} #{window_active_clients})"
# Long enough for the session name, an ssh hostname and the clock. The default# is 10, which is shorter than most session names on their own.set -g status-left-length 80
# ── Windows drawn by the companion ──────────────────────────────────────────## One spawn per window per redraw. Eight windows is eight spawns, and tmux# redraws on pane output as well as on the timer, so this isn't eight per# second, it's eight per anything happening.set -g window-status-format "#(tmux-companion window -i #I -n '#W' -w '#{pane_current_path}' -p '#{pane_current_command}' -f '#{window_flags}' -P #{window_panes} -A #{pane_index})"set -g window-status-current-format "#(tmux-companion window -c -i #I -n '#W' -w '#{pane_current_path}' -p '#{pane_current_command}' -f '#{window_flags}' -P #{window_panes} -A #{pane_index})"
# ── The pickers ─────────────────────────────────────────────────────────────## These cost nothing until they're pressed. display-popup is tmux's own 33 ms,# and the picker inside it replaces fzf, which was 50 ms of the old 93 ms.# `display-popup -B` (tmux 3.3 and later) leaves tmux's own border off, because# the pickers draw theirs. tmux 3.2 has no -B, so there the %else line binds# the same popup with both borders.%if "#{>=:#{version},3.3}"bind -N "companion: help search every key binding" ? \ display-popup -B -E -w 80% -h 60% "tmux-companion keys"%elsebind -N "companion: help search every key binding" ? \ display-popup -E -w 80% -h 60% "tmux-companion keys"%endif%if "#{>=:#{version},3.3}"bind -N "companion: help cheat sheet of my bindings" C-c \ display-popup -B -E -w 90% -h 80% "tmux-companion cheatsheet"%elsebind -N "companion: help cheat sheet of my bindings" C-c \ display-popup -E -w 90% -h 80% "tmux-companion cheatsheet"%endif# The same search over tmux's own bindings as well, for when the answer is one# of the hundred defaults rather than one of yours.%if "#{>=:#{version},3.3}"bind -N "companion: help search every key binding, tmux's own too" M-/ \ display-popup -B -E -w 80% -h 60% "tmux-companion keys --all"%elsebind -N "companion: help search every key binding, tmux's own too" M-/ \ display-popup -E -w 80% -h 60% "tmux-companion keys --all"%endif# tmux's own prefix+c opens a window in the pane's directory and gives you no# say in it. This keeps that as the zero-keystroke case, since the query starts# on that directory, and adds the frecency list and any path typed in full.%if "#{>=:#{version},3.3}"bind -N "companion: window new one here, or at any directory" c \ display-popup -B -E -w 65% -h 65% "tmux-companion new-window"%elsebind -N "companion: window new one here, or at any directory" c \ display-popup -E -w 65% -h 65% "tmux-companion new-window"%endif%if "#{>=:#{version},3.3}"bind -N "companion: project pick a project" -n M-s \ display-popup -B -E -w 80% -h 70% "tmux-companion project"%elsebind -N "companion: project pick a project" -n M-s \ display-popup -E -w 80% -h 70% "tmux-companion project"%endif# No --pane here, unlike the copy-mode `o` binding below, and the reason is# worth writing down: tmux expands #{pane_id} in a run-shell command and does# not expand it in a display-popup one, so the obvious version passes the# eleven characters `#{pane_id}` and the split lands nowhere. Wrapping the# popup in run-shell does expand it, and then hands over the pane tmux last# touched rather than the one the key was pressed in, which in a session you# just switched away from is worse than not asking. Given no pane, `run` puts# the side pane in the session an attached client is looking at, which is the# one whose key you pressed.%if "#{>=:#{version},3.3}"bind -N "companion: window run a command from history" e \ display-popup -B -E -w 70% -h 60% "tmux-companion run"%elsebind -N "companion: window run a command from history" e \ display-popup -E -w 70% -h 60% "tmux-companion run"%endif%if "#{>=:#{version},3.3}"bind -N "companion: config pick a colour theme" C-t \ display-popup -B -E -w 70% -h 70% "tmux-companion theme pick"%elsebind -N "companion: config pick a colour theme" C-t \ display-popup -E -w 70% -h 70% "tmux-companion theme pick"%endif# Every pane on the server, with what it runs, whether it has gone quiet and# the last lines of its screen; enter jumps there. `g` and `G` are free in# tmux's own prefix table. The second one keeps only the agents, which is the# list to reach for when the bar says one of them is waiting.%if "#{>=:#{version},3.3}"bind -N "companion: window jump to any pane" g \ display-popup -B -E -w 80% -h 70% "tmux-companion panes"%elsebind -N "companion: window jump to any pane" g \ display-popup -E -w 80% -h 70% "tmux-companion panes"%endif# What the agents stopped to ask, oldest first; enter jumps to the one picked.# Where did I see that: every line of every pane's scrollback in one list,# and enter lands on the line in copy mode. `--pane '#{pane_id}'` on the end# keeps it to the pane the key was pressed in. tmux's own prefix+/ describes# a key, which the key search on prefix+? already does.%if "#{>=:#{version},3.3}"bind -N "companion: copy search every pane's scrollback" / \ display-popup -B -E -w 90% -h 80% "tmux-companion search"%elsebind -N "companion: copy search every pane's scrollback" / \ display-popup -E -w 90% -h 80% "tmux-companion search"%endif# Jump to any text on screen, flash.nvim's way: type a few characters of it,# then the letter that appears beside the one you meant, and copy mode lands# there in whichever pane of the window it is. `jump` opens its own popup over# the window, because only it can work out where the window sits under a# status line at the top. Space is what tmux-jump used, and takes tmux's# next-layout, which `select-layout` and prefix M-1..M-5 still reach.%if "#{>=:#{version},3.3}"bind -N "companion: copy jump to any text on screen" Space \ run-shell -b "tmux-companion jump --pane '#{pane_id}' --client '#{client_name}'"# And `s` in copy mode, flash's own key, which copy-mode-vi leaves unbound.bind -N "companion: copy jump to any text on screen" -T copy-mode-vi s \ run-shell -b "tmux-companion jump --pane '#{pane_id}' --client '#{client_name}'"%endif# "address already in use": which pane is listening on what. Enter goes to# the pane. tmux has nothing on prefix+P.%if "#{>=:#{version},3.3}"bind -N "companion: window which pane listens on which port" P \ display-popup -B -E -w 80% -h 70% "tmux-companion ports"%elsebind -N "companion: window which pane listens on which port" P \ display-popup -E -w 80% -h 70% "tmux-companion ports"%endif%if "#{>=:#{version},3.3}"bind -N "companion: project the agents waiting on you" M-g \ display-popup -B -E -w 80% -h 70% "tmux-companion inbox"%elsebind -N "companion: project the agents waiting on you" M-g \ display-popup -E -w 80% -h 70% "tmux-companion inbox"%endif# One screen of what needs you: the waiting agents, the health reasons, the# idle sessions, the numbers. The same screen opens by itself on attach when# there is news, through the client-attached hook below.%if "#{>=:#{version},3.3}"bind -N "companion: help what needs me" b \ display-popup -B -E -w 90% -h 75% "tmux-companion brief"%elsebind -N "companion: help what needs me" b \ display-popup -E -w 90% -h 75% "tmux-companion brief"%endif# A click on the right side of the bar: the agent count opens the inbox, the# health mark the brief. Needs `set -g mouse on`.bind -T root MouseDown1StatusRight \ run-shell "tmux-companion click '#{mouse_status_range}'"# What happened in each project today, newest first: what ran long, what the# agents asked, what opened and closed.%if "#{>=:#{version},3.3}"bind -N "companion: help what happened today" J \ display-popup -B -E -w 80% -h 70% "tmux-companion journal"%elsebind -N "companion: help what happened today" J \ display-popup -E -w 80% -h 70% "tmux-companion journal"%endif# Quiet hours from a prompt: `45m`, `2h`, or `off`. The health mark says# `quiet` while it lasts.bind -N "companion: config quiet hours, no nagging for a while" Q \ command-prompt -p "quiet for (45m, 2h, off):" "run-shell \"tmux-companion quiet %%\""# The chunk clock's snooze, with [chunk] on: past the budget, one more five# minutes and one more sound, said on the message line. Root table, no# prefix: M-z is bound by nothing in tmux by default.bind -N "companion: config snooze the chunk clock, once a sitting" -n M-z \ run-shell 'tmux display-message "$(tmux-companion chunk snooze)"'%if "#{>=:#{version},3.3}"bind -N "companion: window jump to an agent" G \ display-popup -B -E -w 80% -h 70% "tmux-companion panes --agents"%elsebind -N "companion: window jump to an agent" G \ display-popup -E -w 80% -h 70% "tmux-companion panes --agents"%endifbind -N "companion: project toggle the tools in this session" -n M-a \ run-shell "tmux-companion toggle '#{session_name}'"# The flip between the two most recent windows, for a session with more than two.bind -N "companion: project flip to the last window" -n M-A \ run-shell "tmux-companion toggle --last '#{session_name}'"# Closing captures the layout on the way out, before anything is asked to quit,# so the project comes back next time with the windows and panes it had. Add# --no-save to close without touching the saved layout.bind -N "companion: session close this project cleanly" X \ confirm-before -p "close #{session_name}? (y/n)" "run-shell 'tmux-companion project close'"
# Capture this session's windows and panes as the layout this project opens# with next time. A saved layout wins over [[layout]] in the config, and# `project forget` puts the config back in charge.## Costs nothing until pressed: two tmux calls and a file write. Bind it rather# than typing it, because a pane you type it into is running tmux-companion at# the moment it looks and that is the command it records for that pane.bind -N "companion: project save this layout for this project" S \ run-shell 'tmux-companion project save'bind -N "companion: project forget this project's saved layout" M-S \ confirm-before -p "forget the saved layout of #{session_name}? (y/n)" \ "run-shell 'tmux-companion project forget'"
# ── Prompt marks, which tmux has had since 3.3 and nobody turned on ─────────## next-prompt and previous-prompt do nothing until the shell says where a# prompt begins. One line in your shell rc file is the whole cost:## eval "$(tmux-companion shell-init zsh)" # or bash# tmux-companion shell-init fish | source # fish## Then jumping between prompts in copy mode costs nothing at all, because tmux# is doing it. C-p and C-n are a suggestion; pick whatever is free for you.bind -N "companion: copy jump to the previous prompt" -T copy-mode-vi C-p \ send-keys -X previous-promptbind -N "companion: copy jump to the next prompt" -T copy-mode-vi C-n \ send-keys -X next-prompt
# ── Copy mode, opening what is under the cursor ─────────────────────────────# No selection needed: the cursor is already on the thing. `#{copy_cursor_line}`# is the line under it and `#{copy_cursor_x}` the column, so a line naming two# paths opens the one you are actually on. `#{q:...}` shell-quotes the line,# which matters the first time a log line contains an apostrophe.bind -N "companion: copy open what is under the cursor" -T copy-mode-vi o \ run-shell "tmux-companion open --pane '#{pane_id}' --cursor-x '#{copy_cursor_x}' -- #{q:copy_cursor_line}"# The same and more behind one key, as a menu at the cursor: open the thing# under it or the selection, search the line or the selection, or go to the# top, which is what tmux had on g. The menu expands its commands as formats# once and run-shell does it again, hence ##{...} for the second pass. O and S# are dimmed with no selection.bind -N "companion: copy menu of open and search, g again for the top" -T copy-mode-vi g \ display-menu -x '#{e|+:#{popup_pane_left},#{copy_cursor_x}}' -y '#{e|+:#{popup_pane_top},#{e|+:#{copy_cursor_y},1}}' \ "open URL or file under the cursor" o { run-shell "tmux-companion open --pane '##{pane_id}' --cursor-x '##{copy_cursor_x}' -- ##{q:copy_cursor_line}" } \ "#{?selection_present,,-}open the selection" O { send-keys -X copy-selection-no-clear ; run-shell "tmux-companion open --pane '##{pane_id}' -s" } \ "" \ "search the line, in a popup" s { run-shell -C "display-popup -h 65% -w 65% -B -E \"tmux-companion open --choose --pane '##{pane_id}' --cursor-x '##{copy_cursor_x}' -- ##{q:copy_cursor_line}\"" } \ "#{?selection_present,,-}search the selection, in a popup" S { send-keys -X copy-selection-no-clear ; run-shell -C "display-popup -h 65% -w 65% -B -E 'tmux-companion open --choose -s'" } \ "" \ "jump to the top of history" g { send-keys -X history-top }# Picking the thing by a hint instead of moving the cursor to it is# tmux-fingers, which is alive and does it well, so there is no fourth# implementation of it here. It pipes the match to a command, and `open`# reads what it is given on standard input, so with fingers installed this# makes ctrl and a hint open the match the way `o` above does:## set -g @plugin 'Morantron/tmux-fingers'# set -g @fingers-ctrl-action 'tmux-companion open'## `open` resolves a relative path against the directory of the pane tmux# calls current. These two lines have not been run against an installed# fingers, so treat a relative path as the part to check first.bind -N "companion: copy yank to the system clipboard" -T copy-mode-vi y \ send-keys -X copy-selection-no-clear \; \ run-shell "tmux-companion clipboard"
# ── Logic that used to live in this file ────────────────────────────────────## The clipboard was two if-shell branches on `uname`, and zoom was a run-shell# wrapping an if-shell, which is tmux shelling out to ask tmux how many panes# there are. Neither's a thing a config file should be doing.# Zen: everything but this pane goes. With other panes open that is a zoom;# with none it is the status bar, because a lone pane already fills the window# and tmux's own `prefix z` does nothing there.bind -N "companion: pane zen, everything but this one goes" z \ run-shell "tmux-companion zen --pane '#{pane_id}'"
# The pane that started as a look at another repository and became the work:# it moves to a session of its own, named for its directory the way `project`# names one, with its process and scrollback. tmux has nothing on prefix+@.bind -N "companion: session give this pane a session of its own" @ \ run-shell "tmux-companion promote --pane '#{pane_id}'"
# For a program that stopped answering C-c: TERM to what is in front in this# pane, KILL three seconds later if it is still there. The scrollback stays,# which kill-pane on prefix+x can't say. A shell at its prompt is refused.# --ask looks first, so the confirmation names the program and its pid, and a# bare prompt gets no question at all.bind -N "companion: pane stop what hangs in it" K \ run-shell "tmux-companion kill --ask --pane '#{pane_id}'"
# The pocket: a shell you pull out beside this pane on the first press,# goes away into a window called `_pocket` on the second, and comes back,# process and scrollback kept, on the third. `pocket logs` is a second one# under its own name, for a key of its own.bind -N "companion: pane the pocket shell, out and away" ` \ run-shell "tmux-companion pocket --pane '#{pane_id}'"
# A one-line note on this pane: tmux's pane title, which `panes` shows beside# the program. An empty answer takes it off. %1 rather than %%, because# command-prompt replaces only the first %% in a template.bind -N "companion: pane note what this pane is doing" N \ command-prompt -p "note (empty clears):" "run-shell \"tmux-companion note --pane '#{pane_id}' -- '%1'\""
# ── Every session, kept in generations ──────────────────────────────────────## A project layout answers what one project looks like. This answers what the# whole server was doing at 09:23, and the two are separate stores because a# session with no project to be keyed on, a scratch one you named yourself, only# exists in the second.## The timer lives in the daemon and `[sessions] autosave` turns it on. These are# the keys for taking one by hand and for the cycle that picks up a new binary,# a freshly sourced config and a rebuilt daemon in one go.bind -N "companion: sessions save every session now" M-s \ run-shell "tmux-companion sessions save"# Bring back the sessions a snapshot has and this server doesn't, after a crash# took some of them. --merge because plain resurrect refuses a server that is# running, and from in here the server is always running.bind -N "companion: sessions bring back the ones that are missing" C-r \ display-popup -E -w 85% -h 75% "tmux-companion sessions resurrect --merge"# Sessions nobody has attached to or touched in three days; enter closes the# one picked the way prefix X would. C-i is Tab in most terminals, so drop it if# that clashes with something.%if "#{>=:#{version},3.3}"bind -N "companion: sessions list the idle ones, enter closes it" C-i \ display-popup -B -E -w 85% -h 65% "tmux-companion sessions idle"%elsebind -N "companion: sessions list the idle ones, enter closes it" C-i \ display-popup -E -w 85% -h 65% "tmux-companion sessions idle"%endif
# Not bound to a key, because it stops the server this binding would be typed# into and refuses to run from in here at all:## tmux-companion sessions shutdown save everything, then stop the server# tmux-companion sessions restart the same, then bring it back# tmux-companion sessions resurrect bring back what was saved, onto an# empty server (prefix C-r above is the# --merge one that works from in here)## `restart` bounces the daemon by default, which is how config.toml is reread.
# ── Landing somewhere, when you typed plain `tmux` ──────────────────────────## `tmux` on its own makes a session called `0` with one bare shell in it, and# everything here is a keystroke further on from there. From a shell the way in# is `tmux-companion start`, which opens the project picker and attaches to# what you choose; this is the same thing for anybody who types `tmux` out of# habit, or whose terminal is configured to run it at startup.## It opens the picker only for a session tmux named itself -- a name that is# all digits -- with one window, one pane and a shell in it. A session you# asked for by name, or one with anything already happening in it, is left# alone, so this never interrupts work.## Commented out because a popup on attach is a strong opinion. Turn it on if# the first thing you do after `tmux` is press M-s anyway.## set-hook -g client-attached 'run-shell "tmux-companion start --hook"'# And the brief, only when something is waiting or wrong; a quiet server# stays quiet on attach.# set-hook -ga client-attached 'run-shell "tmux-companion brief --hook"'
# ── The theme, applied when a session is created ────────────────────────────## The session name twice, not #{session_id}: tmux 3.5 leaves #{session_id}# empty inside a run-shell and 3.7 resolves it, so the id version painted# whichever session was current instead of the one being created. The binary# falls back to the name when the target is empty, so an older copy of this# line still works.## With no themes on disk this does nothing and says nothing, which is the state# every install is in until `tmux-companion theme init` is run.set-hook -g session-created "run-shell 'tmux-companion theme apply \"#{session_name}\" -t \"#{session_name}\"'"
# ── Keys an app in a pane wants too ─────────────────────────────────────────## Last, after every bind above: it reads the root table as this file left it.# With [keys] route off, which is the default, it changes nothing. With it on,# a root key that the app in front also wants is held for [keys] hold_ms: a# second press in that time goes to the app, anything else lets tmux have it.# `tmux-companion keys collide` shows which keys that is. Needs tmux 3.4.run-shell "tmux-companion keys route"