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, F1-F24 keys, and some special characters work best - or only work - 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"
"f1", "f2", ... "f12", ... "f24"
return is an alias for enter. escape is an alias for esc. del is an alias for delete.
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:
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:
Shifted and punctuation keys
When you press a key like Shift+1, your terminal sends the resulting character (!) rather than "shift-1". To bind shifted punctuation keys, use the character directly:
[keymap.emacs]
"!" = "some-action" # Binds to Shift+1
"@" = "some-action" # Binds to Shift+2
"#" = "some-action" # Binds to Shift+3
"$" = "cursor-end" # Binds to Shift+4 (vim $ motion)
Any single character can be used as a key binding.
Note
The shift modifier is still valid for non-character keys like "shift-tab" or "shift-up".
Media keys
Media keys are supported on terminals that implement the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES enabled:
"play", "pause", "playpause", "stop"
"fastforward", "rewind", "tracknext", "trackprevious"
"record", "lowervolume", "raisevolume", "mutevolume", "mute"
Multi-key sequences
Separate keys with a space to define a sequence. The first key is buffered until the second key arrives:
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:
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.
[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.
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-word-end |
Move cursor to end of current/next word (vim e motion) |
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 |
clear-to-start |
Clear the start of input line |
clear-to-end |
Clear the end of 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 |
switch-context |
Switch to the context of the currently selected command |
clear-context |
Return to the initial context |
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 /) |
vim-change-to-end |
Delete to end of line and enter vim insert mode (like vim C) |
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) |
original-input-empty |
The original query passed to the TUI was empty |
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 |
has-context |
The context comes from a previously selected command (switch-context) |
Boolean expressions
Conditions support boolean operators with standard precedence (! binds tightest, then &&, then ||). Parentheses can override precedence.
# 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:
[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:
[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:
[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):
[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):
[keymap.emacs]
"backspace" = [
{ when = "cursor-at-start", action = "accept" },
{ action = "delete-char-before" },
]
Disabling scroll-exit
To make down always scroll without ever exiting:
Disabling a key entirely
Use noop to make a key do nothing:
ctrl-d to exit only when input is empty
[keymap.emacs]
"ctrl-d" = [
{ when = "input-empty", action = "exit" },
{ action = "delete-char-after" },
]
Making enter return the selection without executing
This is equivalent to setting enter_accept = false, but expressed directly as a keybinding.
Custom vim-normal bindings
[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
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) |