Skip to content

config.toml, every setting

The file as it ships, docs/config.example.toml.

# tmux-companion configuration.
#
# You do not need this file. Every setting below is already the default, and a
# machine with no config file behaves exactly as the binary did before the file
# existed — which is a test, not a promise.
#
# Where it goes, first one found wins:
#
# 1. --config <path>
# 2. $TMUX_COMPANION_CONFIG
# 3. $XDG_CONFIG_HOME/tmux-companion/config.toml
# (~/.config/tmux-companion/config.toml when XDG_CONFIG_HOME is unset)
# 4. ~/tmux-companion.toml
#
# tmux-companion config path which file is being read
# tmux-companion config check parse it and say what is wrong
# tmux-companion config dump every setting with its default
#
# A key the parser does not recognise is an error naming the key, the line and
# the key you probably meant. A silently ignored setting is worse: it costs an
# evening wondering why nothing changed.
[general]
# Where the daemon's stderr goes. Unset means `daemon.log` in the state
# directory, `~/.local/state/tmux-companion/daemon.log` unless XDG_STATE_HOME
# says otherwise. The daemon writes one line when it starts and one per
# failure, so this is where "autosave failed" and a config that would not
# parse end up. `tmux-companion doctor` prints the last line. Rotated to
# `daemon.log.1` past 1 MiB. A `~` here is expanded, as in every path in
# this file.
# log = "~/.local/state/tmux-companion/daemon.log"
# Path-to-label overrides for the window segment, replacing the abbreviated
# path with a name of your choosing.
#
# This replaces ~/.yrl/lib/dir-aliases, which was a path on one laptop. That
# file is still read when this table is empty, so upgrading drops nothing.
[dirs.aliases]
# "/Users/you/work/some-very-long-project" = "proj"
[git]
# How long a parsed `git status` stays fresh, in seconds. 0 disables the cache,
# which costs a fork on every refresh. At the default, a one-second status bar
# is served from memory four times out of five.
ttl_secs = 5.0
# How long "this path is inside a work tree" is trusted, in seconds.
#
# A directory's repo-ness effectively never changes, so this could be forever,
# except that `git init` in a directory already on the bar would then stay
# invisible until the daemon restarts.
repo_check_ttl_secs = 300.0
# Middle-ellipsize a branch name longer than this many characters. The
# `--branch-max-len` flag on `gst` and `status-right` wins when given; this is
# what applies without it. The tail the ellipsis keeps is fixed.
branch_max_len = 20
# What the segment draws, and in what order.
#
# A part left out is not rendered. Four hundred untracked build artifacts are
# not information, and if you never push, "ahead" and "behind" are two counts
# you will never read.
#
# sync the in-flight and failed-remote block before the branch name
# branch the branch name itself
# state the clean, dirty, new-branch or gone-upstream marker
# ahead commits you have that the upstream does not
# behind commits the upstream has that you do not
# conflicts files with merge conflicts
# untracked untracked files, counted with unstaged additions as git does
# deleted deleted files in the work tree
# renamed renamed files in the work tree
# copied copied files in the work tree
# modified modified files in the work tree
# staged everything in the index, as one group
# stash the stash count
#
# Order is honoured between groups: branch info (ahead, behind, conflicts),
# then the work-tree counts, then staged, then stash. Inside a group the order
# is fixed, because a group is one colour run and reordering its counters would
# move escape sequences rather than glyphs.
parts = ["sync", "branch", "state", "ahead", "behind", "conflicts", "untracked", "deleted", "renamed", "copied", "modified", "staged", "stash"]
# Which branch-name prefixes earn which glyph, tried in the order written.
#
# The prefix is cut off the name the bar draws, so `feat/status-bar` renders as
# the feature glyph and `status-bar`. A name no entry claims keeps the plain
# branch glyph, which is why main, master and dev need no entry of their own.
#
# Matching is by plain prefix, compared without case. Not a regular expression:
# `posts/` is what people actually name branches, and an unanchored pattern
# matching in the middle of a branch name is a bug nobody enjoys finding on a
# status bar.
#
# `icon` takes `{NAME}` for any glyph in src/tmux/icons.rs — FEATURE, BUGFIX,
# HOTFIX, CHORE, RELEASE, TAG, BRANCH, GIT and the rest — so this file stays
# readable without a patched font. Anything else is drawn as written, which is
# how an emoji, a letter or a codepoint your own font has gets in:
#
# [[git.branch_types]]
# icon = "{TAG}"
# prefixes = ["post/", "posts/"]
#
# [[git.branch_types]]
# icon = "\U000F04DF " # a codepoint, TOML's 8-digit escape
# prefixes = ["parked/"]
#
# [[git.branch_types]]
# icon = "wip "
# prefixes = ["wip/", "spike/"]
#
# The list replaces these six rather than adding to them, so copy what you want
# to keep. `tmux-companion config dump` prints the current list in this shape.
[[git.branch_types]]
icon = "{FEATURE}"
prefixes = ["feat/", "feature/", "features/"]
[[git.branch_types]]
icon = "{BUGFIX}"
prefixes = ["fix/", "fixes/", "bugfix/", "bugfixes/"]
[[git.branch_types]]
icon = "{HOTFIX}"
prefixes = ["hotfix/"]
[[git.branch_types]]
icon = "{CHORE}"
prefixes = ["chore/", "chores/"]
[[git.branch_types]]
icon = "{RELEASE}"
prefixes = ["release/", "releases/"]
[[git.branch_types]]
icon = "{TAG}"
prefixes = ["tag/", "tags/"]
[git.autofetch]
# Fetch the repositories the bar has drawn, on a timer inside the daemon, so
# the ahead and behind counts mean something. A status bar reporting a stale
# number confidently is worse than one reporting nothing.
#
# Off by default, and deliberately. This is the only part of tmux-companion
# that touches the network, and a daemon quietly reaching a remote is not a
# surprise anybody should get from a status bar. The plugin this idea comes
# from is thepante/tmux-git-autofetch, which is alive and worth installing if
# you are not running this.
enabled = false
# Seconds between passes.
interval_secs = 600
# How long one repository gets before it is given up on and its git killed. A
# fetch that hangs holds the pass open and everything behind it goes unfetched;
# the fetch runs with every credential and passphrase prompt disabled, which
# stops the usual cause, and this catches the rest.
timeout_secs = 20
# How long a repository stays on the list after the bar last drew it. Without
# it the daemon would keep fetching everything visited since it started.
remember_secs = 3600
[network]
# Below this many bytes per second the bandwidth segment draws nothing. The
# default is 20 KiB/s: a bar that reacts to every background poll is noise.
#
# This setting was read from the config and then ignored: the segment used a
# constant of the same value, so changing it here did nothing at all.
threshold_bps = 20480
# The block each rate is drawn on. The number is written in the bar's own
# background colour on top of these, which is what makes them read as blocks
# rather than as coloured text.
download_colour = "#5cae36"
upload_colour = "#0262a8"
# The unit after the number: `KiB/s` in `20KiB/s`. Empty draws it in the
# figure's colour, in italics. The figure itself is written in the bar's
# background colour, or in near-white on a block that colour doesn't read on.
unit_colour = ""
[battery]
# How long a battery reading stays fresh, in seconds. Reading it is expensive
# and the number does not move fast enough to matter.
ttl_secs = 30.0
[glyphs]
# Which glyph set the bar draws with.
#
# nerd-font-v3 the codepoints in src/tmux/icons.rs, which need a patched
# font; this is the default
# ascii 7-bit, for a terminal whose font nobody controls
#
# If your bar is a row of boxes, you do not have a Nerd Fonts v3 patch, and
# this is the line to change rather than the thing to put up with.
preset = "nerd-font-v3"
# Replace individual glyphs, by the constant name in src/tmux/icons.rs. These
# are applied on top of the preset, so one missing icon is a reason to fix that
# icon rather than to drop to a whole preset below.
[glyphs.icons]
# STAGED = "*"
# ARROW_RIGHT = ""
[status.right]
# Whether the right-hand side ends with a space. tmux draws it flush to the
# terminal edge, and without this the last glyph sits against the border.
trailing_space = true
# The segments, in the order they are drawn, each with the tmux markup that
# goes in front of it.
#
# `separator_before = ""` means nothing between this segment and the one
# before it, which is a real preference and was not expressible at all while
# the literals lived in tmux.conf.
#
# `{NAME}` expands to the glyph of that name in src/tmux/icons.rs, so this file
# stays readable in an editor with no patched font. A name that does not exist
# is left as you wrote it rather than dropped, because a separator rendering
# `{ARROW_RIGH}` is a typo you can see.
#
# A separator is drawn whether or not the segment after it rendered anything,
# which is what the tmux.conf literals did.
[[status.right.segments]]
name = "git"
separator_before = ""
[[status.right.segments]]
name = "net"
separator_before = ""
[[status.right.segments]]
name = "battery"
separator_before = "#[reverse,fg=color237]{ARROW_RIGHT}#[bg=color237,none]"
# How many coding agents are running and how many have gone quiet, as
# `4 agents · 1 waiting`, with the waiting count coloured. Which programs count
# and how long quiet is are `[agents]` below. Draws nothing when no agent is
# running, so the separator goes with it. Not on the side by default: the
# daemon reads the pane list every `[agents] interval_secs` only while this
# block is here, and a machine without agents should pay nothing for it.
#
# [[status.right.segments]]
# name = "agents"
# separator_before = " "
# # What a mouse click on it runs, as a tmux command, with `{me}` for this
# # binary's path; empty is the segment's own default, the inbox here and the
# # brief on `health`. Needs in tmux.conf:
# # bind -T root MouseDown1StatusRight run-shell "tmux-companion click '#{mouse_status_range}'"
# on_click = ""
# One mark when the daemon knows something needs a look: a timer that failed
# in the last hour and hasn't worked since, config.toml edited after the
# daemon started, or a newer
# binary on disk than the one running. The glyph and the word for the first
# reason, `+N` when there are more, and nothing at all when the daemon is
# fine. With [online] turned on, the network being gone is a reason too. `tmux-companion doctor` prints every reason in full on its health
# line, and `tmux-companion health ack` takes a failure you've read off the
# mark. Two stats every five seconds while this block is here.
#
# [[status.right.segments]]
# name = "health"
# separator_before = " "
# The chunk clock, with [chunk] below turned on: a timer and the focus time
# this sitting, `38m`, amber near the budget, and an alert timer and `50m+12`
# in red past it; ` · 1h14m` after either is the agent in front, see
# [chunk] agent_age. No I/O at draw
# time; the daemon's own look every five seconds keeps the number.
#
# [[status.right.segments]]
# name = "chunk"
# separator_before = " "
# Jobs stopped or running under a pane, drawn by `tmux-companion sh-jobs`.
#
# This was `vim-bg`, which asked one question with one answer baked in: is
# there a suspended nvim under this pane. If you suspend vim, or claude, or a
# cargo watch, this is the table that makes the segment say so.
#
# The cost is why it is not on the status bar by default: finding the children
# of one pid means enumerating the whole process table, which measured 16.25 ms
# of server CPU per call. Bind it to a key, or accept the cost knowingly.
[sh_jobs]
# `stopped` counts only jobs suspended with ctrl-z, which is what vim-bg meant.
# `any` also counts jobs running in the background.
states = "stopped"
# How many icons a busy pane may put on the bar.
max = 3
# The table, in priority order: the first entry whose pattern matches a process
# name wins. `match` is a regular expression, so "^vim$" does not match nvim.
# An unparseable pattern costs that row its icon and nothing else.
#
# [[sh_jobs.job]]
# match = "^claude$"
# icon = "󰚩 "
# color = "#d97757"
#
# `icon` may contain `{NAME}` placeholders, which expand to the glyph of that
# name in src/tmux/icons.rs. `color` is a tmux colour; leave it out when the
# icon carries its own markup, as the default entry does.
[[sh_jobs.job]]
match = "nvim"
icon = "#[fg=#0262a8,bg=colour235,none] n#[fg=#539035]󰕷im#[fg=colour235,bg=colour233]"
# What to call a window whose active pane is running this, when
# [window_names] is on. Leave it out and this row says nothing about names.
# window_name = "edit"
color = ""
[usage]
# Whether to record which key bindings get picked in search. The cheat sheet orders each
# box by this, so the keys you actually reach for float to the top of a group.
#
# It does not records what you press or keylogs it, it is your business and not the tool's.
# If don't want to even record what you pick in keybinding search turning it off is one line
# which is a default and doesn't need to be set but you can.
enabled = false
# Where the log lives. Unset means $XDG_STATE_HOME/tmux-companion/.
# path = "~/.local/state/tmux-companion/keys-usage.tsv"
# Days without a lookup after which the cheat sheet counts a binding as
# learned and drops it. 0 means nothing is ever learned.
learned_after_days = 14
[project]
# Which directory jumper the project picker and `new-window` list, by name.
# One of:
#
# zoxide `zoxide query -l`, most frecent first. The default.
# z the `~/.z` database, which rupa/z, zsh-z and z.lua all write.
# Read straight from the file, because `z` is a shell function and
# cannot be run from here. Ordered by rank. Honours $_Z_DATA.
# cdr zsh's own recent directories, `~/.chpwd-recent-dirs`, most recent
# first. Nothing to install: it is already there if you have
# `chpwd_recent_dirs` on. Honours $ZDOTDIR.
# ghq `ghq list -p`, every repository ghq has cloned. Not visits, so the
# list is stable and complete rather than ordered by what you use.
# none no directory list. Live sessions and whatever you type.
#
# A jumper that is not installed, or a database that has never been written, is
# the empty list and not an error: you get live sessions and a typed path.
dirs_source = "zoxide"
# A command that prints one absolute path per line. Set, it wins over
# dirs_source; this is how every jumper that is not in the list above works.
#
# dirs_command = ["fd", "-td", "-d2", ".", "/Users/you/src"]
# dirs_command = ["sh", "-c", "ls -d ~/src/*/"]
#
# autojump, fasd, jump and the rest go here too, with whichever of their flags
# lists directories -- check their own manual for it rather than trusting a
# line copied from this file, because they do not agree on one.
#
# It is a list of words rather than one string, because splitting a command
# line correctly is a parser that does not belong in a config loader. Anything
# that needs a pipe, a glob or a filter goes through `sh -c`, as above.
dirs_command = []
# The command that records a visit, with the directory appended. Run when you
# open a project or a window, so the place you just went floats up the list.
#
# Empty means whatever the source does on its own: `zoxide add` for zoxide, and
# nothing for the rest. z and cdr are written by your shell on every `cd`, and
# ghq has no notion of a visit.
#
# visit_command = ["myjumper", "add"]
visit_command = []
# Deprecated, and going away next release. Use dirs_source = "none".
#
# `zoxide = false` still means no directory list, whatever else is set here, so
# a config written before this section had more than one source keeps working.
# zoxide = true
# The layout a new project session starts with.
layout = "default"
# Paths under which a checkout's own `.tmux-companion.toml` may run commands.
# That file travels with the repository: `[[window]]` rows in the shape of
# [[layout.window]] below, or `layout = "name"` naming a layout this config
# defines. A file in a checkout that can start programs is a file anybody who
# can push to the repository can use to start programs on your machine, so
# its commands count only under a prefix listed here, `~` for home and a
# trailing `*` allowed. Anywhere else the window names still count and every
# command is blanked; `project show` says which happened. Empty trusts
# nowhere.
#
# trusted = ["~/work/*", "~/src/mine"]
trusted = []
# Use a different layout for projects under a path. First match wins.
#
# [[project.override]]
# match = "~/work/*"
# use_layout = "work"
# Which window the picker previews for a live session. The window worth seeing
# is the one you would have switched to in order to answer "what is happening
# over there", which with the layout below is the agent.
#
# A session without a window by this name previews whichever window it is
# currently on, so every session shows something rather than only the ones that
# happen to match. Empty, the default, means never look for a named window;
# with the layout below it would be "ai".
preview_window = ""
# What a new project session starts with. The first window is selected when the
# session opens.
#
# With no [[layout]] at all a project opens as one plain shell, which is the
# shipped default and what this file, being the defaults, leaves it at. The
# pair below is what one laptop runs, an editor beside an agent; uncomment it
# to get that, or write vim and codex, or one window, or five, or a tail -f on
# a log and no editor anywhere.
#
# [[layout]]
# name = "default"
#
# [[layout.window]]
# name = "edit"
# command = "nvim"
# # Hold the name against the running program. Without this an editor window
# # follows whatever is running and an agent window renames itself to its own
# # version string, which is how windows end up called `2.1.278`.
# hold_name = true
#
# [[layout.window]]
# name = "ai"
# command = "claude"
# hold_name = true
# A window with panes. `command` above is the shorthand for a window that holds
# one pane; a [[layout.window.pane]] table takes over when there is more than
# one, and the panes are created in the order they are listed here.
#
# [[layout.window]]
# name = "work"
# # One of tmux's own five names: even-horizontal, even-vertical,
# # main-horizontal, main-vertical, tiled. Panes with nothing set here get
# # tiled, because the shape the splits leave behind is an accident of the
# # order they ran in.
# layout = "main-vertical"
# # main-pane-width for main-vertical, main-pane-height for main-horizontal,
# # ignored by the rest. A percentage needs tmux 3.4; before that, cells.
# main_size = "60%"
#
# [[layout.window.pane]]
# command = "nvim"
# focus = true # the pane selected when the window opens
#
# [[layout.window.pane]]
# command = "claude"
#
# [[layout.window.pane]]
# cwd = "~/src" # empty means the project directory
#
# `layout` also takes a raw tmux layout string, which is what
# `tmux list-windows -F '#{window_layout}'` prints. Nothing parses it, so you
# can arrange a window by hand with your usual bindings and paste the result:
#
# layout = "bb62,272x67,0,0{136x67,0,0,1,135x67,137,0,2}"
#
# The cell sizes in that string are absolute, and tmux rescales them
# proportionally, so a layout captured on a wide display comes back cramped on
# a laptop and a preset name travels better.
[notify]
# Say something when a long command finishes in a pane you were not looking at.
# The last clause is the whole value: a command that finishes in front of you
# needs no announcement, and firing for those is the noise that teaches people
# to ignore the ones that matter.
#
# Off by default.
#
# The plugin this comes from is rickstaa/tmux-notify.
enabled = false
# Seconds between scans. One tmux call each.
interval_secs = 2
# How long something has to run before finishing is news.
threshold_secs = 30
# Only announce what finished out of sight. A pane counts as out of sight if it
# was hidden at any point while the command ran, not only at the end, because
# the usual shape is starting something, switching away, and coming back when
# it is already done.
only_when_unwatched = true
# What to run. Empty means tmux's own display-message, which needs nothing
# installed and behaves the same on every platform. A desktop notification is
# one line away, and is not the default because shelling out to osascript or
# notify-send on a machine that has neither is a failure somebody has to debug:
#
# command = ["notify-send", "{command}", "ran for {duration}"]
#
# {command}, {duration}, {pane} and {message} are substituted in every argument.
command = []
# How long tmux shows the message, in milliseconds, when command is empty. The
# agents' nudges use it too. 0 keeps it up until a key is pressed, for anybody
# four seconds isn't long enough to read it in; prefix ~ shows the ones already
# gone.
message_ms = 4000
# Commands never worth announcing. An editor, a pager or an agent runs for
# hours and finishing one is not news; without this list every `:q` fires a
# notification about a two-hour nvim session.
ignore = [
"nvim", "vim", "vi", "emacs", "nano", "less", "more", "man",
"top", "htop", "btop", "watch", "ssh", "tmux",
"claude", "codex", "gemini", "grok", "agy", "lazygit", "tig", "fzf",
]
[quiet]
# Quiet hours that come round every day, beside the timer `tmux-companion
# quiet 2h` sets: notifications, nudges and the agents count all hold off
# inside them. Local 24-hour times, and a window may cross midnight. For
# anybody who would otherwise have to remember to turn quiet on.
#
# daily = ["22:00-08:00", "13:00-14:00"]
daily = []
[journal]
# What happened in each project, written down as it happens: a command that
# ran past `min_secs` and finished, with how long it took; an agent that
# stopped and what it asked; an agent that worked a turn past `agent_min_secs`
# and said it was done, with the last line of its answer; a project opened or
# closed. `tmux-companion journal` reads it, newest first, and `journal
# --print -t SESSION` is the standup answer for one project. journal.tsv in
# the state directory, rotated past a megabyte. Programs in [notify] ignore
# are not runs worth a line. One list-panes per interval_secs.
#
# Off by default, only need to be set to true if you want.
enabled = false
interval_secs = 5
min_secs = 60
# Only an agent that reports through its hooks (`tmux-companion agent hooks`)
# ever says done, so this is silent for the rest. Five minutes: a turn under
# that is a chat, one over it is work. Zero writes every turn down.
agent_min_secs = 300
[online]
# Whether the network is there, said by the health mark: `offline` on the
# bar, and `offline for 3m: nothing answers at 1.1.1.1:443` from `doctor` and
# the brief. Online draws nothing. It needs the `health` segment in
# [[status.right.segments]] to be seen on the bar.
#
# Off, because the probe is a TCP connection to an address outside this
# machine, opened and dropped every interval_secs, and that's yours to turn
# on. `probe` is any host:port that should always answer; something inside
# your own network is a fair choice if what you care about is the VPN.
# Two probes in a row have to go unanswered before the mark comes up, so a
# lost packet doesn't flash it.
enabled = false
probe = "1.1.1.1:443"
interval_secs = 30
timeout_ms = 2000
[agents]
# One list of what counts as a coding agent, read by everything that asks:
# `tmux-companion panes --agents`, the `agents` segment on the bar, and the
# restore headline that counts them. Matched against pane_current_command,
# and a command that is only a version number, `2.1.283`, counts as well:
# that is what tmux reports for claude, which names its process after its
# version, and nothing else names a process that way.
programs = ["claude", "codex", "gemini", "cursor-agent", "aider", "opencode", "grok", "agy"]
# Seconds without output after which an agent counts as waiting for you, when
# it has said nothing better through `tmux-companion agent` and rung no bell.
# tmux has no per-pane activity time, so this is measured on the window the
# agent is in: a shell you are typing into beside it keeps it reading as busy.
# A working claude redraws its spinner every second and never goes quiet, so
# this only ever tells one sitting at its prompt from one that drew a moment
# ago.
waiting_secs = 10
# How often the bar's agents segment and the inbox re-read the pane list, in
# seconds. One tmux call each, shared by every attached client.
interval_secs = 2
# The inbox: the agents that have stopped, with the last lines of each one's
# screen captured at the moment it stopped, so `tmux-companion inbox` can
# show the question even for a window nobody has looked at. One capture-pane
# per stop. Off, and `inbox` has nothing to show.
inbox = true
# Say so out loud once an agent has waited this many seconds. Zero never
# nudges; the bar already counts them. 300 is five minutes.
nudge_after_secs = 0
# What says it. Empty is tmux's own display-message. `{program}`, `{at}`,
# `{waited}` and `{question}` are substituted anywhere they appear:
#
# nudge_command = ["osascript", "-e", "display notification \"{question}\" with title \"{program} at {at} is waiting ({waited})\""]
nudge_command = []
# How the bar's segment reads. `words` is `2 agents · 1 waiting`; `glyphs` is
# the robot and the count, then an arrow, an hourglass and the waiting count,
# for a bar that is already full of words. The glyphs are AGENT, AGENT_TO,
# WAITING and BUSY under [glyphs.icons] if the font disagrees.
style = "words"
# Which count follows the total. `waiting` is the agents that have stopped:
# one that said `asked` or `done` through its hooks, one that rang the bell,
# or one quiet past waiting_secs. `busy` is the agents working, for somebody
# who keeps a dozen open and works one at a time and wants to see the one
# that is going rather than the eleven that are not. `both` draws both, busy
# first. An agent can say which it is: `tmux-companion agent hooks claude`
# prints the settings block that makes claude report busy, asked and done as
# they happen, and a report wins over the window's quiet time.
show = "waiting"
# Lines of an agent's screen that are furniture rather than something it
# said, as regular expressions. The question an inbox row, a nudge and a
# journal line carry is the last line on the screen that matches none of
# these. The input box at the foot of the screen, a rule with a prompt under
# it and whatever sits below, is cut before these are tried, so they are for
# what is left above it. In order: a rule or the edge of a box, an empty
# prompt, a spinner, the line a finished turn leaves, tool output, the mode
# line, two hints, and a dialog's numbered options. A pattern that does not
# parse matches nothing.
question_skip = [
'^[\s─━═╭╮╰╯│┃|-]*$',
'^\s*[❯>]\s*$',
'^\s*[✢✳✶✻✽✺·*]\s+\S+(…|\.\.\.)',
'^\s*[✢✳✶✻✽✺]\s+\S+ for \d',
'^\s*⎿',
'^\s*⏵⏵ ',
'^\s*\? for shortcuts',
'(?i)^\s*esc to (cancel|interrupt)',
'^\s*[❯>]?\s*\d+\.\s',
]
[window_names]
# Name a window after what is running in its active pane, using the same
# [[sh_jobs.job]] table that gives the status bar its icons. A row drives a
# name only when you give it a `window_name`, so every row written before that
# field existed keeps drawing its icon and saying nothing about names.
#
# Off by default: renaming somebody's windows is visible.
#
# This will not take a name away from somebody who set one. A window with
# automatic-rename off was pinned on purpose, by `hold_name` in a layout or by
# hand, and this leaves it alone unless it was the thing that pinned it. When a
# window stops matching, the name is handed back and tmux renames it again.
#
# The plugins this comes from are ofirgall/tmux-window-name, which is a Python
# daemon, and joshmedeski/tmux-nerd-font-window-name for the icon half.
enabled = false
# Seconds between passes. One tmux call each.
interval_secs = 5
[autoreload]
# Watch tmux's config and source it when it changes, so an edit takes effect
# without you reaching for the reload binding. The daemon already watches this
# file, because the rows behind `keys` and `cheatsheet` are rebuilt when it is
# newer than they are, so this is a stat it was going to do anyway.
#
# Off by default: reloading somebody's tmux config without being asked is a
# thing that happens to their running sessions.
#
# The plugin this comes from is b0o/tmux-autoreload, which watches with entr or
# inotifywait where this compares a modification time. It is archived, since
# February 2024, and its author named nothing to use instead, which is why this
# setting exists rather than a link.
enabled = false
# Seconds between checks. One stat per file, so this can be small.
interval_secs = 2
# Which files to watch. Empty means the tmux config the daemon already watches,
# which is $TMUX_COMPANION_TMUX_CONF when set, else the first of
# $XDG_CONFIG_HOME/tmux/tmux.conf and ~/.tmux.conf that exists.
files = []
# The first pass after the daemon starts never reloads: otherwise a daemon
# started right after a config edit would source the file at startup, which is
# a surprise, and a loop when the config is what starts the daemon.
# Deprecated: `[sessions]` below does this and more. This shells out to a
# plugin's save script and keeps one file; that keeps generations of its own
# and records what each pane was running, arguments and all. It still works,
# and `config check` says so. It is off by default now.
[autosave]
# Whether the daemon saves the session list on a timer, so a reboot does not
# cost the layout.
#
# This is the saving half of tmux-resurrect. Restoring stays on a keybinding on
# purpose: an automatic restore would resurrect a stale layout over a session
# you have already started working in, which is a worse failure than losing a
# layout to a reboot.
#
# Not tmux-continuum, which is the usual answer: continuum drives its timer by
# appending #{continuum_save} to status-right, and status-right here is a
# single #() into this binary, tuned down from five spawns a second.
#
# Off since `[sessions]` arrived. A config that still asks for this keeps it.
enabled = false
# Seconds between saves.
interval_secs = 900
# The script that does the saving. Unset means tmux-resurrect's.
# script = "~/.config/tmux/plugins/tmux-resurrect/scripts/save.sh"
[sessions]
# Snapshots of the whole tmux server, kept in generations, so a reboot does not
# cost the sessions you had open.
#
# This is the saving half. Restoring stays a command you run, because an
# automatic restore drops a stale layout over a session you have already
# started working in, which is a worse failure than losing a layout to a
# reboot.
#
# `[autosave]` above is the older, narrower version of this and is going away:
# it shells out to a plugin's save script and keeps one file. This keeps
# generations of its own and knows what each pane was running.
# When the daemon takes one by itself.
#
# off never; a snapshot is whatever you ask for by hand
# interval every interval_secs
# cron on the schedule below, for people who want it on the hour
#
# An earlier design had a fourth mode that wrote on every change the daemon
# noticed. It is gone, because setting interval_secs = 10 buys the same thing
# with the cost written down instead of hidden behind a word.
autosave = "off"
# Seconds between snapshots under `interval`. The floor is 10: below that the
# writes start overlapping the capture on a busy machine, and a number under it
# is an error rather than something quietly rounded up.
#
# What it costs, measured on one laptop with 7 sessions and 14 panes. The
# 40-pane column is that rate extrapolated, not measured:
#
# interval_secs 14 panes 40 panes what keep = 20 spans
# 900 (default) 0.015% 0.04% 5 hours
# 60 0.2% 0.6% 20 minutes
# 10 1.3% 3.7% 200 seconds
# 10, no history 0.01% 0.03% 200 seconds
#
# That last column is the one nobody expects. Generations are counted, not
# timed, so a ten second interval with the default `keep` holds under four
# minutes of history, and a crash you notice after lunch has already rolled off
# the end of it. Short interval wants a large `keep`, or `keep_days`.
interval_secs = 900
# The schedule under `cron`. Five fields, the usual order.
cron = "0 * * * *"
# How many generations to keep. Pruning runs after a successful write, oldest
# first, and never touches the one the `last` pointer names.
keep = 20
# Also keep anything younger than this many days, however many files that turns
# out to be. This is how you keep a month of hourly snapshots without setting
# `keep` to 700. Zero leaves the count to decide on its own.
keep_days = 0
# Whether to capture what was on each pane's screen.
#
# This is the expensive half, at 9.3 ms per pane against 1 ms for the metadata
# of a whole server, and it is what makes a short interval costly. Turning it
# off leaves the half that carries what each pane was running and where, which
# is the half worth having often.
#
# It is also the half that holds secrets. Pane history is the text that was on
# your screen, so it contains whatever you printed, including the token you
# echoed and the .env you catted. The directory is created 0700 and every file
# in it 0600, and this is how you keep it off the disk entirely.
pane_history = true
# How many lines of each pane to capture.
pane_history_lines = 2000
# Sessions never captured, by name.
#
# On a shutdown this means the session is not saved and does not come back,
# since stopping the server takes every session with it either way. The command
# says so before it acts rather than leaving you to find out.
exclude = []
# How long the restore summary counts down before going ahead.
#
# It opens only when the restore does not know something: a pane no
# [[restore.program]] row claims, one a row refused, or a command the capture
# had to read off a running process so its arguments are already gone. A
# restore where everything is known goes straight through, however many panes
# that is, because a confirmation on every restore is one people turn off.
#
# Touch any key and the countdown stops. Zero never draws it, which is `--yes`
# made permanent: the panes it would have asked about still open, in the right
# directory, at a prompt.
confirm_secs = 5
# No countdown at all: the summary opens and waits for a key. For anyone a
# clock is in the way of: reading slowly, listening through a screen reader,
# pressing keys slowly. Wins over confirm_secs, zero included.
confirm_wait = false
# What a restore is allowed to run in a pane it is rebuilding.
#
# Default deny. Restoring means executing commands your own machine recorded
# weeks ago, and "re-run anything I saw" is one bad afternoon away from
# restoring a `curl | sh` that was in a pane six weeks back. A command no row
# claims is captured, shown, and left to you.
#
# `match` is a regular expression against the whole saved command, not against
# the process name. The process name of an agent is its version string --
# `2.1.281` rather than `claude` -- and everything worth matching on is in the
# arguments.
#
# `command` is what actually runs. `{command}` is the saved command verbatim and
# `{cwd}` the pane's directory.
#
# `run = false` is how you say "never bring this back" without leaving it to
# fall through to the unknown pile and be asked about every time.
#
# Writing this table replaces the shipped rows rather than adding to them, so
# copy what you want to keep. `tmux-companion config dump` prints them.
[[restore.program]]
# An agent keeps which conversation it is in inside its own arguments, so
# replaying them verbatim is what brings the conversation back. A bare `claude`
# stays bare. `claude --continue` here instead would take the most recent
# conversation in that directory, which survives a stale snapshot and picks
# wrong when two agents were running in one repository.
match = "^claude( |$)"
command = "{command}"
[[restore.program]]
match = "^(codex|gemini|cursor-agent|aider|opencode|grok|agy)( |$)"
command = "{command}"
[[restore.program]]
# An editor with a session plugin restores itself from the directory, and one
# without it opens empty. Either way the saved arguments are a file list from an
# hour ago, so they are dropped. Reopening your buffers is the editor's job:
# auto-session and persistence.nvim both do it per directory.
match = "^n?vim( |$)"
command = "nvim"
[[restore.program]]
match = "^(lazygit|tig|gitui)( |$)"
command = "{command}"
[[restore.program]]
match = "^(htop|top|btop|watch)( |$)"
command = "{command}"
[[restore.program]]
match = "^(tail|less|journalctl)( |$)"
command = "{command}"
[[restore.program]]
match = "^ssh( |$)"
command = "{command}"
[run]
# Where the commands come from: auto, zsh, bash, fish or atuin.
#
# `auto` reads whichever shell $SHELL names, and is the default. It used to be
# `zsh`, which meant a bash user's picker read a ~/.zsh_history that was not
# there and came up empty with nothing said -- the one failure shape that looks
# like the feature having nothing to offer. Name a shell here and it is used
# whatever $SHELL says, which is the reason the setting still exists.
#
# atuin is worth naming because anybody using it has no shell history worth
# reading: atuin keeps the history in its own database and answers through its
# own command.
history = "auto"
# The history file, when it is not where the shell usually puts it.
# history_file = "~/.zsh_history"
# How wide the pane is, as a percentage of the window. A percentage rather than
# a column count, so a narrower terminal still splits sensibly.
width_percent = 33
# The pane slides out rather than appearing. tmux has no animation primitive,
# so this is a stepped resize-pane, eased so it reads as a slide rather than a
# jump.
#
# Every step sends SIGWINCH to the neighbouring pane, whose shell repaints its
# prompt, so more steps is smoother here and flickerier next door. Zero opens
# the pane at its full width at once.
slide_steps = 5
slide_ms = 150
# The shell the command runs under. Empty means $SHELL.
#
# This used to be `zsh`, so on a machine without zsh -- most Linux boxes -- the
# pane slid out and the command never ran.
shell = ""
[earcons]
# A short sound when something happens that a sighted person would see on the
# bar: an agent that asked, one that finished, a health reason that appeared.
# For somebody who hears the screen rather than glancing at it. A tone says
# something happened, not what: inbox, brief and read-bar say what. Off by
# default, held by quiet hours, and one sound per pass however many agents
# arrived in it. `tmux-companion earcon asked` plays one, on or off, so you can
# hear it first.
enabled = false
# Which events sound. `done` is left out because an agent finishing is not
# something waiting on you.
on = ["asked", "health"]
# The command for each, as a list of words. Empty plays the platform's own:
# macOS's Glass, Pop and Basso through afplay, or the freedesktop theme's
# message-new-instant, complete and dialog-warning through paplay.
#
# asked = ["afplay", "/System/Library/Sounds/Ping.aiff"]
asked = []
done = []
health = []
# The chunk clock's sound, Hero on macOS. Whether it plays is [chunk] sound,
# not `enabled` or `on` here.
chunk = []
[chunk]
# How long you've been at tmux this sitting, across every session, with
# nothing to start: time counts while a client has the OS focus (focus-events
# on) and saw a key within break_after. At budget, one sound at the next
# boundary, a window switch, a prompt coming back or an agent finishing its
# turn, at most boundary_grace late. `tmux-companion chunk snooze` grants one
# extension, then one more sound, then only the bar. Off by default.
# docs/dev/design-chunk-clock.md is the design.
enabled = false
# 45m, 1h, 90s. Nothing proves one length best; 50m is a choice.
budget = "50m"
# The share of the budget past which the bar turns amber.
warn_at = 0.8
# Away this long, in another app or with no key, is a break: the sitting
# ends and the next one starts at 0. A shorter gap counts as work, so reading
# an agent's long reply doesn't stop the clock.
break_after = "10m"
# How long the sound may wait past the budget for a boundary.
boundary_grace = "5m"
# The one extension `chunk snooze` grants.
snooze = "5m"
# The sound plays through quiet hours, since it only comes while you're at
# the keyboard. The desktop banner is held by them, and off by default.
sound = true
banner = false
# After the time, ` · 1h14m`: how long the agent in the pane in front has been
# running. It spends no budget. Off saves a `ps` every five seconds while an
# agent is in front.
agent_age = true
[clipboard]
# The command to pipe a copy into. Unset picks one for the platform: pbcopy on
# macOS, then wl-copy, then xclip.
#
# This was two if-shell branches on `uname` in tmux.conf. One binary picking
# the right command is one less thing the Linux branch has to special-case.
# copy = "xclip -selection clipboard"
[theme]
# Which theme a session gets before anybody has picked one.
#
# docs/tmux.conf.full.example sets a `session-created` hook that runs
# `theme apply` for every new session, and tmux.conf.starter.example ships the
# same line commented out under "Later"; with that hook in place this decides
# the colour of a session that nothing else claims. A session a project map
# already claims keeps its own colour; this is only the answer for the rest.
#
# `theme init` writes six: ember, pine, slate, plum, sand and ink, each with a
# lighter and a darker sibling once `theme gen --shades` has run. Name one of
# those, or one of your own.
#
# `by-name` is the other answer: every unclaimed session gets one of the six
# chosen from its name, the same one every time and on every machine, so a
# server full of sessions is told apart at a glance with nothing picked and
# nothing written down. The project picker shows a directory in the colour its
# session is about to get. A theme picked with `theme pick` still wins.
#
# Empty leaves unclaimed sessions unpainted, which is also what happens when
# the theme named here is not on disk: there is nothing to source and nothing
# is said about it, because this runs once per session created and a message
# here lands on the terminal at the moment somebody opens a session.
default = "ink"
# A theme per session-name prefix, where the prefix is everything before the
# first `/`, so `w/api` and `w/web` both match `w`. Useful when session names
# already carry a namespace; skip it otherwise and every session takes the
# default above.
#
# [theme.namespace]
# w = "slate"
# a = "plum"
[bar]
# What your status bar itself looks like. Every segment here ends in a
# powerline cap, and a cap is two colours: the segment's, and whatever is
# behind it. That second one used to be a compiled-in `colour233`, which is one
# person's tmux.conf and nobody else's, so a bar set to anything else got
# wedges and outline backgrounds in a colour that appears nowhere on screen.
#
# Set this to whatever `status-style` says. Anything tmux takes works, so
# `colour233`, `#121212`, `black` and `default` are all fine; `default` leaves
# the terminal's own background showing through, which is what a transparent
# bar wants.
#
# `tmux-companion doctor` reads the live `status-style` and says when the two
# have drifted apart.
background = "color233"
# The background behind the current window in the window list. Only the
# `window` segment draws this, and only when that segment is turned on.
current_window_background = "color236"
# Draw the segments in colour. false keeps every count, glyph and percentage
# and takes the colours away, which is also what NO_COLOR in the daemon's
# environment does. The one thing colour alone says is that a branch has no
# upstream yet; without colour that branch looks clean.
colour = true
[picker]
# Everything under here is the answer for every picker at once, and every one
# of them is commented out because unset is not the same as set to the value
# that happens to be the default: unset lets each picker use its own answer,
# and writing one here takes that away from all six. The commented lines are
# what you get when nobody writes anything.
#
# How every picker is laid out: the key search, the cheat sheet, the project
# list, the run history and the theme list all take their shape from here, so
# one setting changes all of them rather than five drifting apart.
#
# A calmer screen, for anybody a lot at once is too much: the list and the
# line saying what the keys do, and nothing else.
#
# preview = "none"
# counter = false
# rules = false
#
# Where the preview pane goes: right, left, bottom, top, or none for no
# preview at all. A picker whose rows have nothing to preview gets the whole
# width for its list whatever this says.
#
# Left out, each picker uses its own answer, and that is the useful default
# because what goes in a preview is not the same thing twice:
#
# keys bottom, 30% three lines: the chord and what it runs
# project right, 80% a screen of whatever that session is doing
# window right, 40% a directory listing
# theme right, 70% a card showing what the theme paints, framed,
# beside a narrow column of names
# run none the command is the row
# open bottom, 30% the command the application would run
# panes right, 50% the last lines of that pane's screen
#
# Setting it here overrides all seven at once, which is almost never what you
# want; set it under `[picker.<name>]` instead.
# preview = "right"
# The preview's share of the popup, as a percentage. Clamped to 20-80, because
# outside that one half of the split is too narrow to read.
#
# Left out, each picker uses its own share; the table above `preview` has them.
#
# ctrl-p inside a picker cycles this through 30, 50, 70 and off, because there
# is no drag-resize to reach for: a row too long for the list column can only be
# read by giving the column more room.
# preview_percent = 55
# How big the popup itself is belongs in tmux.conf, on the `display-popup -w`
# and `-h` of the binding that opens it. Everything below is what the picker
# draws inside whatever tmux gives it.
# The line the box is drawn with: none, plain, rounded, double or thick.
#
# `none` is the one to set when the popup already has a border of its own --
# `display-popup -B` turns tmux's off, and without that flag two borders end up
# nested one inside the other.
# border = "rounded"
# Where the picker says which picker it is: hidden, top-left, top-center,
# top-right, bottom-left, bottom-center or bottom-right.
#
# The label itself belongs to the picker rather than here, because only the
# picker knows whether it is the key search or the theme list. This is where it
# sits, and it is a setting because a label on the bottom border is fzf's habit
# and a title over the list is skim's, and people arrive with one of the two.
# label_position = "bottom-right"
# How many cells of border are left showing beyond the label, the corner
# counted among them. Two is what fzf's `--border-label-pos=-3:bottom` draws,
# and those cells are the difference between a label sitting on the line and a
# label that has eaten the corner.
# label_offset = 2
# Which end the line explaining the keys sits at: top, bottom or hidden. The
# brief, the restore screen, the run dialog and the cheat sheet follow it too,
# so "hidden" takes the line off every popup; a restore that is counting down
# still says when it goes ahead. A hint of your own is best written the way the
# shipped ones are: enter first, the picker's own keys, ctrl-a, the key that
# leaves, then plain words, three spaces apart. Too wide for the popup, groups
# drop from the right and the first and the leaving key stay.
# hint_position = "top"
# Whether that line is a row across the whole popup, over the list and the
# preview both, rather than a line in the list's column.
# hint_across = false
# Which end the query sits at: top or bottom. `bottom` is fzf's default layout,
# with the list growing up towards it; `top` is what `--reverse` does.
# prompt_position = "bottom"
# Which end the first row sits at: top or bottom.
#
# `bottom` is fzf's default, and it is not a cosmetic choice. The best match
# ends up nearest the query, which is where your eye already is, and in a list
# whose first rows are the sessions you were last in it makes the key, Up and
# Enter a toggle between the last two. `top` is fzf's `--reverse-list`, and it
# is the default here because a list that fills downwards is what everything
# else on a screen does.
# list_from = "top"
# Whether the `matched/total` counter is drawn beside the query. This is fzf's
# `--no-info` inverted, and it is off here because on a list of fifteen keys the
# count is a number nobody reads.
# counter = false
# Rules between the hint, the list and the query. Without them the three run
# together and the list has no edges of its own.
# rules = true
# Drawn in front of the row the cursor is on. A bar rather than fzf's `>`,
# because the row is also bold and a second marker made of punctuation reads as
# part of the row.
# marker = "▌"
# The order a row's columns are drawn in, by the position the picker built them
# at. Empty draws them as built.
#
# Whether the key or what it does comes first is the kind of thing people have
# an opinion about and no argument for, so it is a list rather than a decision.
# The key search builds `[key, what it does]`, so `[1, 0]` turns it round. A
# position no row has is skipped, and a position left out is a column that is
# not drawn, which is how a picker is made narrower without touching the code
# that fills it.
# column_order = []
# The fewest columns a list may be left with before a preview beside it is
# moved underneath instead.
#
# A percentage-sized popup on a small terminal is a small popup, and a split of
# whatever it is given can leave the list too narrow to read a row in. What
# decides it is the columns the list keeps, not the width of the popup: the
# same popup leaves 37 columns at a 55% preview and 25 at 70%.
#
# Zero turns the rule off, which is what fzf does: it splits whatever it is
# given and truncates the rows.
# min_list_width = 24
# How much of a border the preview pane gets: none, edge or full.
#
# `edge` draws only the side facing the list, which is fzf's `border-left` and
# the rest of that family. `full` draws a box round it. Which one is right
# depends on what is in there: a directory listing wants a divider, a theme
# card wants a frame.
#
# The line is the one `border` above draws, or a rounded one when that is
# `none`, so a picker inside a popup that already has a border can turn its own
# off without losing the frame round its preview.
# preview_border = "edge"
# Where the preview's label sits. The same seven positions as `label_position`.
#
# A label sits on the nearest border line to the edge it names. With the
# preview above or below the list that is the preview's own top or bottom
# border. Beside the list, the preview's only border is the one-cell edge
# between the two columns, with nowhere on it for words, so the label goes on
# the outer box's border instead -- still within the preview's own columns, so
# two labels on one bottom line each sit under the half they name.
# preview_label_position = "bottom-center"
# How many cells of that line are left showing beyond the preview's label.
# preview_label_offset = 2
# One picker's answer where it differs from the rest. Every key above can be
# written again under one of these seven, and the preview is the one people
# actually want to set per picker: a theme wants a tall pane beside a narrow
# list, a key binding wants three lines under a wide one, and a shell history
# wants no pane at all.
#
# [picker.keys] the key search
# [picker.project] the project and session list
# [picker.window] the directory list a new window opens at
# [picker.theme] the colour themes
# [picker.run] the shell history
# [picker.open] the application chooser
# [picker.panes] every pane on the server, or the agents among them
# [picker.setup] the checklist `setup` shows
#
# Three keys exist only in these tables, because they are the words one picker
# says and not a shape every picker shares: `label` is what it calls itself on
# its border, `hint` is the line at the top naming the keys, and
# `preview_label` is what it calls its preview. Each defaults to the tool's own
# wording, and each is settable because a line somebody reads every day should
# be in their words rather than in mine. Setting `preview_label` on a picker
# that has nothing to preview does not give it a pane.
#
# A key left out here takes whatever `[picker]` says, so these tables hold the
# exceptions rather than a second copy of everything:
#
# [picker.keys]
# label = "[ Keys ]"
# hint = "enter run ctrl-a show all esc cancel"
# preview_label = "[ What it runs ]"
# preview = "bottom"
# preview_percent = 30
# preview_label_position = "top-center"
# # a keys row is [chord, filed under, what it does]. What it does goes
# # last: its column is as wide as the longest one, a bare command included,
# # and anything after it is pushed off the edge. A position left out isn't
# # drawn, so [1, 0] hides the description.
# column_order = [1, 0, 2]
#
# [picker.run]
# preview = "none"
[open]
# What happens when `open` finds a file rather than a URL: which pane the
# editor opens in, and which editor.
#
# `right` puts it beside the pane you were in, `bottom` puts it underneath.
# Right by default, because a file and the log you found it in read better
# side by side; bottom is better on a narrow terminal, where two columns of
# sixty are two columns nobody can read.
split = "right"
# How much of the window that pane takes, as a percentage. Zero lets tmux
# halve it, which is what it did before this was a setting.
size_percent = 0
# The editor, and how it is told to jump to a line and column. `{path}`,
# `{line}` and `{column}` are replaced; the path arrives already quoted for
# the shell, so do not put quotes around `{path}` yourself.
#
# The quotes around the `+call` matter. tmux runs a split's command through
# `sh`, and `nvim +call cursor(2,22) file` is a shell syntax error, so the
# pane opened, complained to nobody, and closed again. Opening a file at a
# line had never worked until they were added.
#
# Some others:
# helix "hx {path}:{line}:{column}"
# emacs "emacsclient -nw +{line}:{column} {path}"
# vim "vim '+call cursor({line},{column})' {path}"
editor = "nvim '+call cursor({line},{column})' {path}"
# What `open --choose` (or `open -i`) offers. Empty means no chooser, and
# `--choose` then opens what it would have opened anyway rather than showing a
# picker with nothing in it.
#
# `{url}` and `{path}` both take the thing being opened, so a browser template
# and an editor template each name whichever reads better and neither has to
# know which kind of thing it was handed. `{line}` and `{column}` are zero for
# a URL. The command goes through the shell, because a template is where the
# quoting lives: an application name with a space in it only reaches `-a`
# intact if you wrote the quotes.
#
# `pane` decides whether it opens in a tmux pane beside the one you are in, the
# way the editor above does, or is launched and left alone. An editor wants a
# pane and a browser does not, and the wrong answer is either a browser holding
# a pane open forever or an editor with nowhere to draw.
#
# [[open.application]]
# name = "chrome"
# command = "open -a 'Google Chrome' {url}"
#
# [[open.application]]
# name = "nvim"
# command = "nvim '+call cursor({line},{column})' {path}"
# pane = true
[keys]
# Keys tmux and the apps in its panes both want. `keys discover` reads what
# each layer binds on its own, nvim live over its socket; a table here adds
# claims for an app nothing can read, matched on the name tmux shows as the
# pane's command. `keys collide` shows where they meet.
# Whether `keys route`, the last line of tmux.conf, wraps tmux's root bindings
# so a key the app in front wants is held rather than taken. Off by default:
# it rewrites your root table. Needs tmux 3.4.
route = false
# How long a held key waits, in milliseconds. A second press inside it goes to
# the app; when it runs out, or another key comes, tmux runs its binding.
hold_ms = 170
# [keys.app.nano]
# claims = ["C-o", "C-x", "C-w"]
#
# Which modes of what discovery found count as claims. Unset is normal mode
# for nvim and vim, so M-1 mapped in normal mode isn't held in insert mode,
# and every mode for anything else.
# [keys.app.nvim]
# modes = ["n", "x"]
# One table per key, in tmux's spelling. `route` settles a key both sides
# claim: "hold" (the default) waits hold_ms for a second press, "app" hands it
# to the app at once while it claims it, the way vim-tmux-navigator shares
# M-h/j/k/l, and "tmux" never lets the app have it. `hold_ms` overrides the one
# above. Any other field names a layer and a word or two its binding's note,
# description or command should contain; `keys collide` lists one that
# doesn't as drift. `intent` is a label for you.
#
# These tables, `[keys.app.*]` and the two settings above can also live in
# keys.toml beside this file, without the "keys." prefix. Both count, and a
# name set in both is an error.
#
# [keys.key."M-h"]
# route = "app"
#
# [keys.key."M-1"]
# intent = "tree"
# tmux = "choose-tree"
# nvim = "NvimTree"