phux docs

Key bindings

A key binding maps a key to an action, such as splitting a pane or switching sessions. You change bindings in the configuration file, under [keybindings].

[keybindings.prefix-table]
"v" = { action = "split-pane", direction = "vertical" }
"r" = "reload-config"

[keybindings.global]
"M-Enter" = "toggle-zoom"

Run phux config reload after changing the file for the bindings to take effect.

The prefix

Most phux keys are two presses: the prefix, C-a, then a key. C-a z zooms the focused pane, and C-a d detaches.

Press the prefix and hesitate, and after 400 milliseconds a popup lists every key you can press next, your own bindings included. The popup never gets in the way: whatever you press runs as if it weren’t there, and Esc cancels the prefix. Change the delay or turn the popup off:

[keybindings]
which-key = true
which-key-delay-ms = 400

After the prefix, a key with no binding goes to the program in the pane; only the prefix itself is swallowed. So C-a C-a sends one literal C-a to the program, which is how you reach readline’s beginning-of-line.

Default key bindings lists every key phux binds out of the box, grouped the way the popup groups them.

Binding a key

Bindings live in two tables:

TableFires
[keybindings.prefix-table]After the prefix. "z" here is C-a z.
[keybindings.global]On its own, with no prefix. phux ships none.
[keybindings.modes.<name>]On its own, while that key mode is on.

A binding’s value is an action name, or an inline table with the action and its arguments:

[keybindings.prefix-table]
"r" = "reload-config"
"R" = { action = "resize-pane", direction = "right", amount = 10 }
"," = { action = "rename-window", name = "build" }

Some actions require arguments and some take optional ones. resize-pane needs a direction and an amount; rename-window opens a prompt when you leave out name and renames straight away when you give one.

A plugin’s action is plugin-action with the plugin’s id and the action’s id:

[keybindings.prefix-table]
"Y" = { action = "plugin-action", plugin = "example.pane-tools", id = "summarize" }

A global binding takes the key away from every program. A global M-Enter never reaches your editor or shell again. Pick global keys that your programs don’t use.

Finding actions

  • Press C-a Space to open the command palette. It lists every action you can run, searchable, with the key bound to it.
  • Actions lists every action name with its arguments.
  • phux config keys prints every binding in effect (see Inspecting bindings).

The actions keep tmux’s word “window” for a tab: new-window opens a tab, select-window jumps to one.

Writing keys

A key is written as zero or more modifiers followed by one key, joined by -. C-a is Ctrl+A and C-M-x is Ctrl+Alt+X.

ModifierMeans
C-Ctrl
M-Alt (Meta)
A-Alt, the same as M-
S-Shift

Modifier letters are uppercase. Ctrl-a is not a key; write C-a.

The key after the modifiers is one of:

  • A letter, digit, or ASCII punctuation character: a, 7, [, |.
  • A named key: Tab, BackTab, Enter, Esc (or Escape), Space, Backspace, Delete, Insert, Up, Down, Left, Right, Home, End, PageUp, PageDown, and F1 through F24. Names are case-sensitive, so enter is not a key.

A key names the physical key you press, not the character it types:

  • A capital letter means Shift. "X" and "S-x" are the same key, and so are "C-X" and "C-S-x".
  • Punctuation is a key on a US layout. "|" is Shift plus the backslash key, the same as "S-\\", and "?" is Shift plus /. On another layout, write the shifted key with an explicit S-.
  • BackTab is Shift+Tab.

A binding can also be a sequence: several keys pressed one after another, separated by spaces. In the prefix table, "t s" is C-a t s:

[keybindings.prefix-table]
"t s" = "session-picker"
"t w" = "window-picker"

A key either runs an action or starts a sequence; it can’t do both, because phux could never tell which one you meant. The first key of a sequence must be free, a shipped binding included: g already runs give-input, so "g s" is an error until you unbind "g".

Quote keys that TOML would read differently: '"' = ... binds the double quote, "\\" = ... the backslash.

Replacing a default

To change what a default key does, bind it. There is no need to unbind it first:

[keybindings.prefix-table]
# C-a o goes back to the last pane instead of cycling through them
"o" = "last-pane"

Your binding replaces the shipped one for that key whole, arguments included, whatever spelling you use: "S-x" in your file replaces the shipped "X".

Within one file, two spellings of the same key conflict. "X" and "S-x" in the same file is an error, and phux config check reports it.

Removing a binding

Set a key to "none" to remove its binding, a shipped default included:

[keybindings.prefix-table]
"x" = "none"

[keybindings.global]
"M-Enter" = "none"

Unbinding a key gives it back to the program. An unbound global key goes straight to the program in the pane. An unbound prefix-table key goes to the program after the prefix, so C-a x now types an x.

An unbound key stays unbound. A plugin whose manifest keys names it doesn’t get it, whatever spelling either of you uses.

Key modes

A key mode is a set of keys that work without the prefix while the mode is on, so ordinary keys can do something else for a while. phux ships one: press C-a R for resize mode, then h j k l or the arrows resize the focused pane one cell at a time (H J K L by five). Enter or Esc leaves. While a mode is on, the right end of the status bar shows its name.

A mode is a table under [keybindings.modes], entered with enter-mode:

[keybindings.prefix-table]
"v" = { action = "enter-mode", name = "views" }

[keybindings.modes.views]
"n" = "next-window"
"p" = "previous-window"
"z" = "toggle-zoom"
"q" = "exit-mode"
  • Leaving. exit-mode leaves the mode, and so does Esc unless the mode binds Esc itself. Add once = true to enter-mode to leave after the first of the mode’s keys runs.

  • Other keys. A key the mode doesn’t bind does what it does outside the mode: the prefix and global keys still work, and anything else goes to the program in the pane. Make the mode exclusive to ignore those keys instead, so a stray key can’t type into the pane. The shipped resize mode is exclusive:

    [keybindings.mode-options.resize]
    exclusive = true
  • One mode at a time. Entering a mode from inside another switches to it.

  • Changing a mode. Mode tables layer like the others: your keys add to or replace the shipped ones, and "none" removes one from that mode only. [keybindings.modes.resize] "h" = "none" leaves h bound everywhere else.

A mode name is lowercase letters, digits, -, and _. phux config check reports an enter-mode naming a mode no table defines, and options for a mode that doesn’t exist.

Changing the prefix

[keybindings]
prefix = "C-b"

Every prefix-table key moves with it: C-b z now zooms. The popup, the palette, and phux config keys all show the new prefix.

A global binding on exactly the prefix key can never fire, because the prefix always wins. phux drops it and phux config check reports it. A prefix that does not parse falls back to C-a.

Coming from tmux

To bring over your current tmux bindings automatically, preview phux config import-tmux, then use --apply after reviewing its report. The tmux importer guide covers supported bindings, native layering, differences and undo.

This file keeps tmux’s prefix and brings back a few tmux habits:

[keybindings]
prefix = "C-b"

[keybindings.prefix-table]
# tmux's & closes the tab after asking (phux ships this on X)
"&" = "kill-window"

# tmux's ( and ) step through sessions (phux ships N and P)
"(" = "previous-session"
")" = "next-session"

# r to reload, the way many .tmux.conf files bind source-file
"r" = "reload-config"

Splits (” %), new tab (c), next and previous tab (n p), copy mode ([), and detach (d) already match tmux. One difference to know: phux’s x closes the pane without asking. The getting started guide lists what else differs from tmux.

Inspecting bindings

phux config keys prints every binding in effect: the shipped defaults, your file and the files it extends, and keys from enabled plugins, merged exactly as phux merges them. Prefix-table keys are shown after the prefix:

$ phux config keys split-pane
prefix  C-a  (defaults)

KEY    ACTION      ARGS                  SOURCE
C-a "  split-pane  direction=horizontal  defaults
C-a %  split-pane  direction=vertical    defaults
C-a -  split-pane  direction=horizontal  defaults
C-a |  split-pane  direction=vertical    defaults

Give it an action name to see only that action’s keys, or none to see everything. A key mode’s keys read mode:<name> <key>, such as mode:resize h. SOURCE is defaults, the name of the file that set the binding (config.toml, or a file you extend), or plugin <id>. The first line shows the prefix and where it came from.

Bindings phux would skip are listed as warnings after the table, so a binding that does nothing never hides:

phux: warn: skipped keybinding "q": unknown action `kill-pain` (did you mean `kill-pane`?)

--json prints the same as a stable JSON document.

Debugging

phux config check reports every binding problem with its key path. The same problems appear as phux config keys warnings:

$ phux config check
keybindings.prefix: bad chord: unknown key name: Ctrl-a
keybindings.prefix-table.q: unknown name: unknown action `kill-pain` (did you mean `kill-pane`?)
keybindings.prefix-table."g s": bad chord: ambiguous prefix: 'g s' is both a binding and a prefix
3 problems
FindingCauseFix
unknown key nameThe key isn’t a character or named key, often a lowercase name or Ctrl-.Use the syntax in Writing keys.
invalid chord syntax at NA malformed key, such as a trailing -.Fix the character at offset N.
unknown actionA misspelled action. The finding suggests the closest one.Check Actions.
ambiguous prefix on a sequenceOne key both runs an action and starts a sequence ("g" and "g s").Remove one of them.
ambiguous prefix on a plain keyTwo spellings of one key in the same file ("X" and "S-x").Keep one spelling.
ambiguous prefix in globalA global binding on the prefix key.Bind another key, or change the prefix.

A bad binding disables only itself when you attach; every other binding still works. phux config reload is stricter: it refuses a file with any bad binding and keeps your current bindings, so fix the finding and reload again.

If a key seems to do nothing, run phux config keys and look for it. A plugin key loses to yours when both bind the same key, and a key you removed with "none" is absent from the table, even when a plugin’s keys names it.