diff options
| author | Michelle Tilley <michelle@michelletilley.net> | 2026-02-05 10:37:58 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-05 10:37:58 -0800 |
| commit | b26e7ff265ccde52c6e6bc4987d7af08de3b8f64 (patch) | |
| tree | 8e09c98b63011696f72fd37f9ae00b801d19b10e /docs | |
| parent | feat(dotfiles): add sort and filter options to alias/var list (#3131) (diff) | |
| download | atuin-b26e7ff265ccde52c6e6bc4987d7af08de3b8f64.zip | |
feat: Add new custom keybinding system for search TUI (#3127)
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/docs/configuration/advanced-key-binding.md | 371 | ||||
| -rw-r--r-- | docs/docs/configuration/key-binding.md | 2 |
2 files changed, 371 insertions, 2 deletions
diff --git a/docs/docs/configuration/advanced-key-binding.md b/docs/docs/configuration/advanced-key-binding.md new file mode 100644 index 00000000..ecd7bf8c --- /dev/null +++ b/docs/docs/configuration/advanced-key-binding.md @@ -0,0 +1,371 @@ +# Advanced Atuin UI Keybinding + +Atuin includes a powerful keybinding system that can be used to fully customize the TUI keyboard shortcuts. Many of the configuration options, like `enter_accept`, `exit_past_line_start`, and `accept_past_line_end`, can be explicitly expressed with this new configuration. + +The `[keymap]` section in your config replaces the older `[keys]` section. If any `[keymap]` settings are present, the `[keys]` section is ignored entirely. + +!!! warning + Modifier keys and some special characters work best with a terminal that implements the kitty keyboard protocol. Notably, the default macOS Terminal app does _not_ include this feature. For more information and a list of terminals that are known to support this protocol, see https://sw.kovidgoyal.net/kitty/keyboard-protocol/ + +## Keymaps + +The Atuin TUI has multiple modes, each with its own keymap. You configure each one under a separate TOML table: + +| Config section | When it is active | +|----------------------|-------------------| +| `[keymap.emacs]` | Search tab, `keymap_mode = "emacs"` | +| `[keymap.vim-normal]`| Search tab, `keymap_mode = "vim"`, normal mode | +| `[keymap.vim-insert]`| Search tab, `keymap_mode = "vim"`, insert mode | +| `[keymap.inspector]` | Inspector tab (opened with `ctrl-o`) | +| `[keymap.prefix]` | After pressing the prefix key (`ctrl-a` by default) | + +Vim-insert mode inherits all emacs bindings by default, then overrides `esc` and `ctrl-[` to enter normal mode instead of exiting. + +You only need to specify the keys you want to change. Unmentioned keys keep their default bindings. + +!!! warning + If you specify a key in your keymap that would normally be changed by an option, like the `enter` key with the `enter_accept` setting, the setting will not take any affect. Those options modify the default keymap based on their setting, but if you override the key in the keymap, you're responsible for managing correct behavior. + +## Key format + +Keys are specified as TOML string keys using a human-readable format. + +### Basic keys + +Lowercase letters, digits, and named keys: + +``` +"a", "z", "1", "9" +"enter", "esc", "tab", "space", "backspace", "delete" +"up", "down", "left", "right" +"home", "end", "pageup", "pagedown" +``` + +`return` is an alias for `enter`. `escape` is an alias for `esc`. `del` is an alias for `delete`. + +!!! warning "macOS delete key" + The key labeled "delete" on Mac keyboards sends `backspace` (it deletes the character *before* the cursor). The `delete` key in Atuin refers to forward-delete, which is `fn+delete` on a Mac keyboard. + +### Modifiers + +Modifiers are prefixed with a dash separator. Multiple modifiers can be combined: + +``` +"ctrl-c", "alt-f", "ctrl-alt-x" +``` + +Available modifiers: `ctrl`, `alt`, `shift`, `super` (also accepted as `cmd` or `win`). + +!!! warning + The `super` modifier (Cmd on macOS, Win on Windows) **requires** the kitty keyboard protocol. Only terminals that implement this protocol will report the Super modifier to applications. Even in supported terminals, some Super+key combinations may be intercepted by the terminal or OS (e.g. Cmd+C for copy, Cmd+V for paste, or Cmd+T for opening a new tab). + +### Uppercase letters + +An uppercase letter represents itself without needing a `shift` modifier. For example, `"G"` matches the `shift+g` key press. + +### Special characters + +Some special characters are written out directly: + +``` +"?", "/", "[", "]" +``` + +### Multi-key sequences + +Separate keys with a space to define a sequence. The first key is buffered until the second key arrives: + +``` +"g g" +``` + +If the second key does not complete a known sequence, both keys are handled individually. + +## Keymap format + +Each entry in a keymap section maps a key to either a simple action or a conditional rule list. + +### Simple binding + +Maps a key directly to a single action, with no conditions: + +```toml +[keymap.emacs] +"ctrl-c" = "return-original" +"enter" = "accept" +``` + +### Conditional binding + +Maps a key to an ordered list of rules. Each rule has an `action` and an optional `when` condition. Rules are evaluated top-to-bottom; the first rule whose condition matches (or that has no condition) wins. + +```toml +[keymap.emacs] +"left" = [ + { when = "cursor-at-start", action = "exit" }, + { action = "cursor-left" }, +] +``` + +In this example, pressing left when the cursor is at position 0 exits the TUI. Otherwise, it moves the cursor left. + +A rule without a `when` field is unconditional and always matches. It is typically placed last as a fallback. + +!!! warning "Override semantics" + When you specify a key in `[keymap]`, it **replaces** the **entire** default binding for that key. Other keys you don't mention keep their defaults. + +## Actions + +Actions are specified as kebab-case strings. + +### Cursor movement + +| Action | Description | +|--------|-------------| +| `cursor-left` | Move cursor one character left | +| `cursor-right` | Move cursor one character right | +| `cursor-word-left` | Move cursor one word left | +| `cursor-word-right` | Move cursor one word right | +| `cursor-start` | Move cursor to start of line | +| `cursor-end` | Move cursor to end of line | + +### Editing + +| Action | Description | +|--------|-------------| +| `delete-char-before` | Delete the character before the cursor (backspace) | +| `delete-char-after` | Delete the character after the cursor (delete) | +| `delete-word-before` | Delete the word before the cursor | +| `delete-word-after` | Delete the word after the cursor | +| `delete-to-word-boundary` | Delete to the next word boundary (like `ctrl-w`) | +| `clear-line` | Clear the entire input line | + +### List navigation + +| Action | Description | +|--------|-------------| +| `select-next` | Move selection to the next item in the results list | +| `select-previous` | Move selection to the previous item in the results list | +| `scroll-half-page-up` | Scroll half a page up | +| `scroll-half-page-down` | Scroll half a page down | +| `scroll-page-up` | Scroll a full page up | +| `scroll-page-down` | Scroll a full page down | +| `scroll-to-top` | Jump to the top of the list | +| `scroll-to-bottom` | Jump to the bottom of the list | +| `scroll-to-screen-top` | Jump to the top of the visible screen | +| `scroll-to-screen-middle` | Jump to the middle of the visible screen | +| `scroll-to-screen-bottom` | Jump to the bottom of the visible screen | + +Note: `select-next` and `select-previous` respect the `invert` setting. When `invert` is true, the visual direction is flipped. + +### Commands + +| Action | Description | +|--------|-------------| +| `accept` | Accept the selected entry and **execute it immediately** | +| `accept-N` | Accept the Nth entry below the selection and execute it (e.g. `accept-1` through `accept-9`) | +| `return-selection` | Return the selected entry to the command line **without executing** | +| `return-selection-N` | Return the Nth entry below the selection without executing (e.g. `return-selection-1` through `return-selection-9`) | +| `return-original` | Close the TUI and return the original command line text | +| `return-query` | Close the TUI and return the current search query | +| `copy` | Copy the selected entry to the clipboard | +| `delete` | Delete the selected entry from history | +| `exit` | Exit the TUI (behavior depends on the `exit_mode` setting) | +| `redraw` | Redraw the screen | +| `cycle-filter-mode` | Cycle through filter modes (global, host, session, directory) | +| `cycle-search-mode` | Cycle through search modes (fuzzy, prefix, fulltext, skim) | +| `toggle-tab` | Toggle between the search tab and inspector tab | + +The difference between `accept` and `return-selection`: `accept` runs the command immediately when the TUI closes, while `return-selection` places it on your command line for further editing before you press enter. The `enter_accept` setting controls which of these the default `enter` key uses. + +### Mode changes + +| Action | Description | +|--------|-------------| +| `vim-enter-normal` | Switch to vim normal mode | +| `vim-enter-insert` | Switch to vim insert mode (cursor stays in place) | +| `vim-enter-insert-after` | Switch to vim insert mode (cursor moves right, like vim `a`) | +| `vim-enter-insert-at-start` | Move to start of line and enter vim insert mode (like vim `I`) | +| `vim-enter-insert-at-end` | Move to end of line and enter vim insert mode (like vim `A`) | +| `vim-search-insert` | Clear the search input and enter vim insert mode (like vim `?` or `/`) | +| `enter-prefix-mode` | Enter prefix mode (waits for one more key, e.g. `d` for delete) | + +### Inspector + +| Action | Description | +|--------|-------------| +| `inspect-previous` | Inspect the previous entry (in the inspector tab) | +| `inspect-next` | Inspect the next entry (in the inspector tab) | + +### Special + +| Action | Description | +|--------|-------------| +| `noop` | Do nothing (useful for disabling a default binding) | + +## Conditions + +Conditions let a single key do different things depending on the current state. They are specified as strings in the `when` field of a rule. + +### Condition atoms + +| Condition | True when | +|-----------|-----------| +| `cursor-at-start` | The cursor is at position 0 | +| `cursor-at-end` | The cursor is at the end of the input | +| `input-empty` | The input line is empty (no text entered) | +| `list-at-start` | The selection is at the first entry (index 0) | +| `list-at-end` | The selection is at the last entry | +| `no-results` | The search returned zero results | +| `has-results` | The search returned at least one result | + +### Boolean expressions + +Conditions support boolean operators with standard precedence (`!` binds tightest, then `&&`, then `||`). Parentheses can override precedence. + +```toml +# Negation +{ when = "!no-results", action = "select-next" } + +# Conjunction (AND) +{ when = "cursor-at-start && input-empty", action = "exit" } + +# Disjunction (OR) +{ when = "list-at-start || no-results", action = "exit" } + +# Grouping with parentheses +{ when = "(cursor-at-start && !input-empty) || no-results", action = "return-original" } +``` + +## Examples + +### Reproducing the default `[keys]` behaviors + +The default keymaps already encode the standard `[keys]` behaviors. Here is what they look like as explicit `[keymap]` entries for reference. + +**`scroll_exits = true`** (default) -- exit when scrolling past the first entry: + +```toml +[keymap.emacs] +"down" = [ + { when = "list-at-start", action = "exit" }, + { action = "select-next" }, +] +``` + +**`exit_past_line_start = true`** (default) -- exit when pressing left at position 0: + +```toml +[keymap.emacs] +"left" = [ + { when = "cursor-at-start", action = "exit" }, + { action = "cursor-left" }, +] +``` + +**`accept_past_line_end = true`** (default) -- accept when pressing right at the end: + +```toml +[keymap.emacs] +"right" = [ + { when = "cursor-at-end", action = "accept" }, + { action = "cursor-right" }, +] +``` + +**`accept_past_line_start = true`** -- accept when pressing left at position 0 (off by default): + +```toml +[keymap.emacs] +"left" = [ + { when = "cursor-at-start", action = "accept" }, + { action = "cursor-left" }, +] +``` + +**`accept_with_backspace = true`** -- accept when pressing backspace with empty input (off by default): + +```toml +[keymap.emacs] +"backspace" = [ + { when = "cursor-at-start", action = "accept" }, + { action = "delete-char-before" }, +] +``` + +### Disabling scroll-exit + +To make `down` always scroll without ever exiting: + +```toml +[keymap.emacs] +"down" = "select-next" +``` + +### Disabling a key entirely + +Use `noop` to make a key do nothing: + +```toml +[keymap.emacs] +"ctrl-d" = "noop" +``` + +### ctrl-d to exit only when input is empty + +```toml +[keymap.emacs] +"ctrl-d" = [ + { when = "input-empty", action = "exit" }, + { action = "delete-char-after" }, +] +``` + +### Making enter return the selection without executing + +```toml +[keymap.emacs] +"enter" = "return-selection" +``` + +This is equivalent to setting `enter_accept = false`, but expressed directly as a keybinding. + +### Custom vim-normal bindings + +```toml +[keymap.vim-normal] +# Use 'q' to quit +"q" = "exit" + +# Use 'x' to delete the selected entry +"x" = "delete" + +# Use 'y' to copy +"y" = "copy" +``` + +### Custom inspector bindings + +```toml +[keymap.inspector] +# Use 'delete' key in inspector to remove entries +"delete" = "delete" +``` + +## Relationship with `[keys]` + +The `[keymap]` section is a more powerful replacement for the `[keys]` section. The two are **mutually exclusive**: + +- If you have any `[keymap]` settings, the entire `[keys]` section is ignored. Defaults are built from the standard `[keys]` values, and then your `[keymap]` overrides are applied on top. +- If you have no `[keymap]` settings, the `[keys]` section works as before for backward compatibility. + +If you are migrating from `[keys]` to `[keymap]`, here is how the old flags map: + +| `[keys]` setting | Equivalent `[keymap]` | +|------------------|-----------------------| +| `scroll_exits = false` | `"down" = "select-next"` and `"up" = "select-previous"` in the relevant keymap | +| `exit_past_line_start = false` | `"left" = "cursor-left"` | +| `accept_past_line_end = false` | `"right" = "cursor-right"` | +| `accept_past_line_start = true` | `"left" = [{ when = "cursor-at-start", action = "accept" }, { action = "cursor-left" }]` | +| `accept_with_backspace = true` | `"backspace" = [{ when = "cursor-at-start", action = "accept" }, { action = "delete-char-before" }]` | +| `prefix = "x"` | Prefix key becomes `ctrl-x` (set in the emacs/vim keymaps) | diff --git a/docs/docs/configuration/key-binding.md b/docs/docs/configuration/key-binding.md index 42940be2..b3ae7aa2 100644 --- a/docs/docs/configuration/key-binding.md +++ b/docs/docs/configuration/key-binding.md @@ -1,7 +1,5 @@ # Key Binding -Atuin does not yet have full key binding customization, though we do allow some changes. - ## Custom up arrow filter mode It can be useful to use a different filter or search mode on the up arrow. For example, you could use ctrl-r for searching globally, but the up arrow for searching history from the current directory only. |
