Skip to content

Keybindings

This page is the authoritative key reference for tmnl. Every row maps a chord to a command id in src/command.rs, and every chord is one entry in the keymap built by src/keymap.rs::Keymap::build. The same registry feeds the in-app help overlay (⌘⇧/) and the command palette (⌘⇧P) — this page is just the flat searchable form.

Pressing ⌘⇧/ opens the in-app help overlay — the same registry rendered as a categorized cheat sheet

The overlay also supports a substring filter — start typing while it’s open and the list narrows to bindings whose chord or title contains what you’ve typed (case-insensitive). Section headers disappear when no binding under them survives. Backspace pops one character; Esc closes the overlay. Useful when you know roughly what a command does but don’t remember the chord.

Typing in the help overlay narrows the list — `tab` then `git` filter to the relevant bindings; backspace clears back to the full list

A few facts that shape the table:

  • is super_key() in winit terms — macOS Command, Windows Win key. Cross-platform chord specs use cmd+… interchangeably with super+….
  • Forwarded chords. When a chord’s command id starts with fwd. or is marked “forward to Native”, tmnl translates the keystroke into the Ctrl-equivalent and sends it to the focused Native pane’s protocol server. That’s how ⌘S / ⌘P / ⌘B / ⌘/ reach mnml’s own bindings (Ctrl+S / Ctrl+P / etc.) without tmnl needing to know what they do. Shell panes are unaffected — these chords no-op there.
  • Pane-kind gates. Some chords only fire when a specific pane type is focused: no_modal_open_native_focus (Native — mnml / mixr / etc.), no_modal_open_browser_focus (Browser). The rest gate on no_modal_open — they don’t fire while a settings, help, palette, find, rename, or confirm dialog is up.
  • Modal supersede. ⌘⇧P (palette) and ⌘R (recents) use the palette_open_or_supersede predicate — they close any transient overlay (find bar, tab search, discovery, context menu) before opening themselves, so the chord works from inside another overlay instead of being a silent no-op. Destructive modals (rename, confirm-close, confirm-paste, settings, help) still block.
Side-by-side of the four major overlays — palette, settings, help, discovery — each greedy-modal with its own escape hatch

Add a [[keybind]] block to ~/.config/tmnl/config.toml:

[[keybind]]
key = "cmd+t"
action = "tabs.cycle_next" # any command id from the tables below
[[keybind]]
key = "cmd+shift+w"
action = "unbind" # remove the chord entirely

The keymap builder walks defaults first, then applies overrides on top — last-write-wins. Unparseable chord specs log a warning and are skipped; the rest of the overrides still apply. Modifier order is free (shift+cmd+t == cmd+shift+t); chord parsing is case-insensitive.

| Chord | Command id | What it does | Notes | | --- | --- | --- | --- | | ⌘T | tab.new | New tab — same kind as the window (shell or Native) | With macos_native_tabs = true, spawns a sibling NSWindow that AppKit auto-merges into the tab bar | | ⌘W | tab.close_or_forward | Close tab — or forward ⌃W to Native | Native panes get the forward so mnml’s buffer-close confirmation fires | | ⌘⇧W | pane.close | Close focused split pane | Collapses the split when siblings remain; closes the tab if it’s the last pane | | ⌘⇧T | tab.reopen_closed | Reopen the most recently closed shell tab | Restores custom_name + cwd; Native tabs are not stacked | | ⌘⇧[ | tab.cycle_back | Cycle to previous tab | Wraps | | ⌘⇧] | tab.cycle_forward | Cycle to next tab | Wraps | | ⌘1⌘8 | tab.goto_1tab.goto_8 | Jump to tab N | Native panes get the chord forwarded as ⌥N so mnml’s tab.goto_N fires inside mnml | | ⌘9 | tab.goto_9 | Jump to last tab | Clamps to tabs.len() - 1 when fewer than 9 tabs are open | | F2 | tab.rename | Rename focused tab | VS Code parity — keyboard surface for the right-click-rename mouse path | | — | tab.move_left / tab.move_right | Reorder active tab | Chord-less; bind via [[keybind]] |

| Chord | Command id | What it does | Notes | | --- | --- | --- | --- | | ⌘D | split.right | Split focused pane right (new shell pane) | Inside a Native pane, forwarded as ⌃D so mnml’s multi-cursor “select next occurrence” fires | | ⌘⇧D | split.down | Split focused pane down | | | ⌘⇧↩ | pane.toggle_zoom | Maximize/restore focused pane | iTerm convention; preserves the split tree underneath | | ⌘⌥← | focus.left | Focus pane left | | | ⌘⌥→ | focus.right | Focus pane right | | | ⌘⌥↑ | focus.up | Focus pane up | | | ⌘⌥↓ | focus.down | Focus pane down | | | ⌘⌥B | split.browser_right | Split right with a Browser pane at duckduckgo.com | | | ⌘⌥V | split.browser_clipboard | Split right with Browser at the clipboard URL | Clipboard must start with http:// or https:// | | ⌘⌥D | browser.attach_dashboard | Spawn or attach Playwright dashboard in a Browser split | Polls CDP :9222 for up to ~5s for the dashboard target | | ⌘⌥⇧D | browser.attach_dashboard_auto | Auto-attach Playwright dashboard, click first session, hide chrome | Injects an init-script that runs before page scripts on every navigation | | ⌘⌥H | browser.toggle_dashboard_chrome | Toggle the dashboard’s sidebar + sash | Idempotent — re-running undoes the previous toggle by checking <style id="tmnl-hide-dashboard-chrome"> | | — | pane.resize_wider / pane.resize_narrower / pane.resize_taller / pane.resize_shorter | Resize focused pane ±5% | Surfaced via Shell ▸ Splits | | — | pane.equalize | Equalize all splits | | | — | split.native_editor_right | Split right with mnml (Native) | Requires editor_template to be set in cfg |

| Chord | Command id | What it does | Notes | | --- | --- | --- | --- | | ⌘⌥` | window.cycle_next | Cycle to next background window | No-op when only one window is open | | — | window.new | New window (in-process) | Menu-bound; AppKit owns ⌘N for the “New Window” menu accelerator, so a default chord here would never reach the keymap. Bind a non-⌘N chord via [[keybind]] | | ` | tmnl.quick_terminal_spawn | Spawn a sibling tmnl in Quick Terminal mode | Borderless, always-on-top, half-screen-height. For a true system-wide hotkey, bind one to tmnl --quick-terminal at the OS level | | — | tmnl.new_tabbed_window | New native-tab window | Chord-less by default — ⌘⇧T belongs to tab.reopen_closed |

| Chord | Command id | What it does | | --- | --- | --- | | ⌘⇧1 | launcher.open_1 | Open the 1st configured launcher in a new tab | | ⌘⇧2 | launcher.open_2 | Open the 2nd configured launcher | | ⌘⇧3⌘⇧9 | launcher.open_3launcher.open_9 | … through the 9th |

Indices past the configured count silently no-op (the chord matched but new_launcher_tab(idx) short-circuits when no icon exists at that position). The launchers are sourced from [[ui.launcher_icon]] in ~/.config/tmnl/config.toml. See Integrations for what gets pinned to the rail.

| Chord | Command id | What it does | Notes | | --- | --- | --- | --- | | ⌘A | edit.select_all | Select all in focused pane | Shell: whole scrollback; Native: forwards to client | | ⌘C | edit.copy | Copy selection to clipboard | Falls through to ⌃C forward when there’s no selection — so the chord still interrupts a running shell command | | ⌘V | fwd.cmd_v | Paste | Shell panes: clipboard is sanitized (strips C0/C1 controls, ESC, DEL, NUL) then sent with bracketed-paste markers when the program enabled them. Multi-line pastes open a confirm overlay when clipboard_paste_protection = true. Native panes: forwarded as ⌃V | | ⌘X | fwd.cmd_x | Cut (⌘X⌃X forwarded to Native) | Native-focus only | | ⌘Z | fwd.cmd_z | Undo (⌘Z⌃Z forwarded to Native) | Native-focus only | | ⌘S | fwd.cmd_s | Save (⌘S⌃S forwarded to Native) | Native-focus only | | ⌘N | fwd.cmd_n | New (⌘N⌃N forwarded to Native) | Native-focus only. Note: AppKit intercepts ⌘N for the Shell ▸ New Window menu accelerator before winit fires, so this only reaches the keymap when no menu is bound to the chord | | ⌘P | fwd.cmd_p | File picker (⌘P⌃P forwarded to Native) | Native-focus only | | ⌘B | fwd.cmd_b | Toggle tree (⌘B⌃B forwarded to Native) | Native-focus only | | ⌘G | fwd.cmd_g | Goto line (⌘G⌃G forwarded to Native) | Native-focus only | | ⌘/ | fwd.cmd_slash | Toggle comment (⌘/⌃/ forwarded to Native) | Native-focus only | | ⌘F | find.open | Open in-pane find bar | Returns a session that survives close; Enter / Shift-Enter step matches | | — | selection.save | Save selection to a tmnl-selection-*.txt file under ~/Desktop | Chord-less; surfaced via the palette |

⌘C and ⌘V are the two most chord-aware verbs. Both inspect the focused pane’s kind before deciding what to do:

  • Shell with selection⌘C writes the text to the system clipboard (pbcopy / wl-copy / PowerShell Set-Clipboard) and clears the selection.
  • Shell, no selection⌘C falls through to a ⌃C forward so the chord still interrupts the running command.
  • Native — both ⌘C and ⌘V are forwarded as ⌃C / ⌃V so mnml’s own bindings handle them.

These chords address the focused Shell pane’s vt100 scrollback. They no-op on Native and Browser panes.

| Chord | Command id | What it does | Notes | | --- | --- | --- | --- | | ⌘↑ | scrollback.prev_prompt | Jump to previous OSC 133 prompt mark | Toasts “no earlier prompt tracked” when the integration snippet isn’t installed. See OSC 133 shell integration | | ⌘↓ | scrollback.next_prompt | Jump to next prompt mark | | | ⌘Home | scroll.top | Jump to the top of scrollback | | | ⌘End | scroll.bottom | Snap back to the live cursor | | | ⇧PageUp | scroll.page_up | Scroll one page up | Page size = gpu.grid.rows − 1 | | ⇧PageDown | scroll.page_down | Scroll one page down | | | — | scrollback.clear | Wipe scrollback | Talks to the vt100 parser directly, so it works mid-command (unlike typing clear) | | — | scrollback.save | Write the focused pane’s scrollback to a file under ~/Desktop | | | — | scrollback.dump_all_panes | Dump every shell pane across every tab into ~/Desktop/tmnl-scrollback-dump-<ts>/ with an INDEX.txt | See Scrollback dump |

⌘⇧P opens the command palette — fuzzy-filter over every registered command Typing in the palette narrows the list to commands matching the substring — same fuzzy-filter UX as the help overlay ⌘, opens the in-grid Settings overlay — sectioned discrete-choice rows with ▸ focus marker

| Chord | Command id | What it does | Notes | | --- | --- | --- | --- | | ⌘⇧P / F1 | palette.open_or_forward | Open the command palette | Inside a Native pane, forwarded as ⌃⇧P so mnml’s palette opens instead. Toggles closed when re-pressed. Supersedes transient overlays (find, tab-search, discovery, context menu) — closes them first so the palette gets clean ownership | | ⌘, | view.settings | Toggle the in-grid Settings modal | Re-press with settings open is treated as Esc — reverts to the snapshot taken when the modal opened | | ⌘R | view.recents | Toggle the recents picker | Inside a Browser pane this becomes location.reload() instead (Safari convention) | | ⌘⇧/ / ⌘? | view.help | Toggle the in-app help overlay | macOS Help-key convention; the overlay reads command::help_rows() so every chord on this page is also listed there | | ⌘⇧I | view.discovery | Toggle the ”+ Add integration” overlay | Same overlay the + chip in the launcher rail opens. Typing into the open overlay filters the catalog | | ⌘F | find.open | Open in-pane find | See above | | — | view.tab_search | Toggle tab search (filter chips by name) | Chord-less — ⌘⇧T belongs to tab.reopen_closed. Surfaced via the search chip in the sidebar header | | — | view.toggle_sidebar | Show/hide the launcher-rail sidebar | Persists in cfg | | — | view.reset_sidebar_width | Clear a drag-resized sidebar’s width override | Auto-fit takes over again | | — | view.toggle_tab_bar | Show/hide the tab bar | Persists in cfg |

| Chord | Command id | What it does | | --- | --- | --- | | ⌘= / ⌘⇧= | view.zoom_in | Increase font size by one FONT_ZOOM_STEP | | ⌘- / ⌘⇧- | view.zoom_out | Decrease font size | | ⌘0 | view.zoom_reset | Reset zoom to the cfg default |

| Chord | Command id | What it does | | --- | --- | --- | | ⌘⌥] | theme.cycle_next | Cycle the focused window’s theme override forward | | ⌘⌥[ | theme.cycle_prev | Cycle backward | | — | theme.refresh | Reload the chrome palette from mnml’s config |

The cycle walks (global) followed by the alphabetically-sorted theme names from theme::list_theme_names(). Picking (global) clears the per-window override so the global theme governs the chrome. The tick loop polls mnml’s config-file mtime for live reload — theme.refresh is the manual escape hatch when the auto-poll misses (network-mounted homedir, out-of-band writes, etc.). See Themes.

These four chords only fire when a Browser pane has focus (no_modal_open_browser_focus).

| Chord | Command id | What it does | | --- | --- | --- | | ⌘[ | browser.back | history.back() | | ⌘] | browser.forward | history.forward() | | ⌘L | browser.focus_url_bar | Focus the URL bar | | — | browser.reload | location.reload() | ⌘R is the gate — when a Browser pane is focused, view.recents reroutes to reload instead of opening recents |

| Chord | Command id | What it does | | --- | --- | --- | | ⌘I | ai.completion | Complete the current command line in the focused Shell pane | | ⌘K | ai.generate | Generate a command from a description prompt |

Both run offline against the local fim-engine model. The model loads lazily on first use; subsequent invocations are warm. See AI completion (offline) for the engine details and the per-shell prompt extraction.

| Chord | Command id | What it does | | --- | --- | --- | | ⌃⌘D | tmnl.quick_look | Quick Look (define selection) — opens dict://<word> in the macOS Dictionary app | macOS only | | ` | tmnl.quick_terminal_spawn | Spawn a sibling tmnl in Quick Terminal mode | | | ⌘⌥` | window.cycle_next | Cycle to next window | |

A handful of chords are bound to menu items in src/menu.rs and reach the menu dispatcher (drain_menu_events) instead of the keymap. They show up in the keymap registry too, but the menu accelerator preempts the winit KeyboardInput:

  • ⌘N — Shell ▸ New Window (window.new)
  • ⌘⇧K — Edit ▸ Clear Scrollback (scrollback.clear)

Both still end up calling the same App method as their registry counterparts, via command::dispatch_by_id in the menu router. The practical consequence: a [[keybind]] override on ⌘N won’t take effect on macOS because AppKit consumes the chord before tmnl sees it. Pick a different chord for these.

The headless harness (tmnl --headless --app) can’t construct winit’s KeyEvent (it has private platform-specific fields), so synthetic chord dispatch routes through Keymap::resolve_all_chord(&Key, ModifiersState) instead. Behavior matches the live keymap; the differences are in the event envelope, not the resolution.