Skip to content

Configure Navigation Keys

ListViewWidget and TreeViewWidget accept a NavigableKeymap. The default is vim-like, but apps can provide another map without subclassing widgets.

from lazy_cuh import NavigableKeymap
from lazy_cuh.widgets import ListViewWidget
keymap = NavigableKeymap(
down=("n",),
up=("p",),
first_prefix=("f",),
first=("f",),
last=("end",),
select=("o",),
)
yield ListViewWidget(model, keymap=keymap, id="items")

This maps:

  • n to move down
  • p to move up
  • f f to jump to the first item
  • end to jump to the last item
  • o to select the current item

Use ItemActionMessage when the app needs semantic item actions.

from lazy_cuh.widgets import ItemActionMessage, NavigableAction
def on_item_action_message(self, message: ItemActionMessage) -> None:
if message.action == NavigableAction.SELECT:
self.open_item(message.item_id)

ItemSelectedMessage is still emitted for selection compatibility, but ItemActionMessage is the better fit when an app wants one event shape for select, expand, collapse, and toggle.

Action bindings may use symbolic prefixes for configurable leader-style keys. Resolution and display are separate: KeyPrefixMap expands a symbolic prefix for input handling, while KeyDisplayMap controls how the same binding appears in keybars and help text.

from lazy_cuh import (
ActionBinding,
ActionId,
ActionMap,
HintVisibility,
KeyDisplayMap,
KeyPrefixMap,
PrefixedKeySequence,
keybar_spec_from_actions,
)
actions = ActionMap(
(
ActionBinding(
(PrefixedKeySequence.of("pane", "h"),),
ActionId.SELECT,
label="Pane left",
visibility=HintVisibility.COMPACT,
),
)
)
prefixes = KeyPrefixMap.from_mapping({"pane": ("z",)})
key_display = KeyDisplayMap(prefixes=prefixes, separator="+")
spec = keybar_spec_from_actions(actions, key_display=key_display)

That keybar renders Pane left: z+h. Without a configured display map, the same binding renders symbolically as Pane left: <pane>h.

When multiple keys mean the same thing, keep them on one binding with ActionBinding.from_keys(...). That produces one help row such as Left: h/left instead of duplicate rows for the same action.

Count prefixes are opt-in. The framework does not reserve number keys globally. Use VIM_NAVIGATION_KEYS only for the scopes that should treat leading digits as counts, and use SIMPLE_KEYS where bare numbers should remain normal bindings.

from lazy_cuh import (
ActionBinding,
ActionId,
BindingContext,
BindingRegistry,
ScopedBinding,
SIMPLE_KEYS,
VIM_NAVIGATION_KEYS,
)
registry = BindingRegistry(
bindings=(
ScopedBinding(
ActionBinding.from_key("1", ActionId.SELECT),
context=BindingContext(panel="tabs"),
profile=SIMPLE_KEYS,
),
ScopedBinding(
ActionBinding.from_key("j", ActionId.SELECT),
context=BindingContext(widget="tree"),
profile=VIM_NAVIGATION_KEYS,
),
)
)
diagnostics = registry.validate()

With VIM_NAVIGATION_KEYS, a bare 1 binding reports a warning because it may conflict with count input such as 10j. Prefix sequences such as z1 are still valid because the digit is no longer a bare leading count.

Validation is explicit. The framework does not automatically validate every registry during app startup. Run BindingRegistry.validate() in tests, CI, or a development-only startup check, then decide whether warnings should fail your app.

from lazy_cuh import format_binding_diagnostics
diagnostics = registry.validate()
for line in format_binding_diagnostics(diagnostics):
print(line)

Diagnostics include duplicate bindings, global/local override conflicts, ambiguous sequence prefixes, unknown symbolic prefixes, and bare digit bindings inside count-enabled scopes.

Use collision_policy=CollisionPolicy.WARN when duplicate/override/prefix diagnostics should be warnings instead of errors. Use allow_override=True on a local ScopedBinding when a local key intentionally takes over a global key. Use CollisionPolicy.ALLOW_OVERRIDE only when that is the desired default for the whole registry.

The widget keymap decides how to navigate and which UI action happened. It does not decide what selecting an item means in your app. App controllers should translate item actions into commands when side effects are needed.