Skip to content

Keybindings

Lshika edited this page Jul 28, 2026 · 10 revisions

Keybindings

This page covers how key handling actually works, not just which config keys exist — for the config syntax alone, see Config Reference.

How a key name becomes a key press

keybinds.py's resolve_key() turns a string from your config into a curses key code, once, at startup — it's pure (no live screen needed), so you can check what a name resolves to without running tuicc at all:

>>> import sys; sys.path.insert(0, "src")
>>> from tuicc.keybinds import resolve_key
>>> resolve_key("Left")
258
>>> resolve_key("h")
104
>>> resolve_key("nonsense")
Traceback (most recent call last):
  ...
ValueError: Unknown key name: 'nonsense'. Expected one of [...], or a single character.

Two input shapes are accepted: a named special (Left, Right, Up, Down, Tab, Shift+Tab, Enter, Escape, Space) or any single character, resolved via Python's ord(). Uppercase letters work as an implicit Shift for free — curses reports "A" and "a" as different codes on their own, so resolve_key("A") just works without any special-casing.

What's configurable, and what isn't (yet)

left, right, up, down, tab, switch_module, confirm, and quit are all read from [navigation.keys] in your config — see Config Reference for the full table.

The confirm dialog's y/n keys are still hardcoded (ord("y"), ord("n")) in main.py's pending_confirm handling, separate from the [navigation.keys] table entirely — this is the one remaining gap.

If you want vim-style navigation, you can remap every key covered above today; the confirm-dialog's y/n keys will need to stay as-is until that gap closes.

How a keypress becomes an action

This is worth understanding if you're debugging unexpected navigation behavior, or writing a module that needs to interact with it.

Tab cycles through items within the currently active moduleordered (the full navigable item list) is filtered down to just the items whose id prefix matches ctx.active_module, then advances to the next one with wraparound.

switch_module (Shift+Tab by default) switches which module is "active" — cycles through the module names present in your current layout preset, and jumps selection to the first item in the newly-active module.

Arrow keys behave differently depending on what's currently selected:

  • If the selected item is a "window" (in the preview) and the direction is left/right, tuicc first tries _sibling_in_same_group() — the predictable left-to-right order within the preview, not spatial search. See Architecture: Spatial vs. linear navigation for why.
  • If that finds nothing (you're at the leftmost window) and the direction is left, it falls back to a deterministic rule: return to the sidebar entry for the workspace actually being viewed.
  • Otherwise, it falls back to nearest_in_direction() — genuine spatial search, used for moving between well-separated modules like sidebar ↔ preview.

Whichever of these actually moves the selection also updates ctx.active_module to match — so the highlighted module border (border_selected vs border in your theme) always reflects where the selection actually is, even after an arrow-key move that crosses module boundaries.

Confirm looks up a handler for the selected item's target_kind in ACTION_HANDLERS (built from actions.py's BASE_HANDLERS plus whatever modules register — see Writing a Module) and calls it. The handler decides whether tuicc exits immediately or shows a confirmation prompt first.

Testing keybindings without running tuicc

You don't need a full session to check that a config change resolves the way you expect:

>>> import sys; sys.path.insert(0, "src")
>>> from tuicc.config import load_config
>>> cfg = load_config()
>>> cfg.keybinds
{'left': 260, 'right': 261, 'up': 259, 'down': 258, 'tab': 9, 'confirm': 10, 'quit': 113}

If a value here doesn't match what you put in config.toml, check for the usual culprit first: ~/.config/tuicc/config.toml might be out of date with a newer packaged default (delete it and let tuicc regenerate it) — see the note at the top of Config Reference.

Clone this wiki locally