Skip to content

feat(tui): add C-x o / C-2..C-5 / Shift+Arrow split-pane focus keys#618

Merged
edwin-zvs merged 1 commit into
mainfrom
feat/tui-split-pane-focus-keys
Jun 29, 2026
Merged

feat(tui): add C-x o / C-2..C-5 / Shift+Arrow split-pane focus keys#618
edwin-zvs merged 1 commit into
mainfrom
feat/tui-split-pane-focus-keys

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

What

Adds Emacs-style fast focus-movement keys for the TUI split-session layout, so you can hop between panes without cycling.

Key Behavior
C-x o Cycle focus list → split windows → list (Emacs other-window) — already existed, unchanged.
C-2C-5 Focus a pane directly. Pane 1 is the session list, pane 2 is the first split window, etc. — so C-2 focuses the first split window, C-3 the second, … No-op when that pane doesn't exist.
Shift+↑/↓/←/→ Move focus to the spatially adjacent split window in that direction (Emacs windmove). Scoped to a focused, unzoomed multi-pane split; a press with no neighbor that way is a consumed no-op.

How

  • New helpers in app.rs: focus_pane_by_index (shared [list, …windows] ordering, same order C-x o walks) and adjacent_window_id / focus_adjacent_window (closest pane whose perpendicular span overlaps the active one). Spatial adjacency reuses the per-frame main_window_areas geometry already tracked for mouse hit-testing.
  • Intercepted in on_key ahead of PTY forwarding and chord dispatch, so these keys escape a focused child's PTY exactly the way the C-x prefix does. Without that they'd be useless in their main scenario — leaving a pane that's running an interactive child. This mirrors the existing PageUp/PageDown scrollback intercept.
  • Contextual Shift+Arrow: only takes over the arrows when the view pane is focused in an actual multi-pane split (not zoomed). On the list, Shift+↑/↓ keep doing list-reorder; single-pane views pass the key through to the child. No keymap bindings were repurposed.

Why an on_key intercept instead of new keymap entries

SwitchFocus/C-x o reaches the TUI from inside a live PTY because C-x is the escape prefix. C-2..C-5 and Shift+Arrow are not C-x chords, so a keymap binding alone would be swallowed by the focused child. Handling them in on_key (the same place PageUp/PageDown and the C-x C-x literal-escape live) is the established pattern for "global keys that shadow the child".

Tradeoffs

A full-screen child in a focused split no longer receives those exact keys while a neighbor exists — the same accepted tradeoff already made for scrollback paging. Documented in spec 0061-split-pane-focus-keys-are-global.md (complements 0055, whose non-goal previously said the global-chord set wasn't changing).

Tests

cargo build + cargo clippy clean. New unit tests:

  • c_digit_focuses_pane_by_indexC-2/3/4 focus windows 1/2/3; C-5 no-op with 3 windows.
  • focus_pane_index_zero_is_the_list — pane 1 is the list.
  • shift_arrow_focuses_spatially_adjacent_window — 2×2 grid: right/down/left/up neighbor resolution + no-neighbor no-op.

Existing switch_focus_cycles_*, keymap::*, and help-modal tests still pass.

Where the code lives

Only crates/cli → the relevant binary is construct (target/debug/construct).

Fast keyboard focus movement in the split-session layout:

- C-x o already cycled list → split windows → list (unchanged).
- C-2..C-5 focus a pane directly (pane 1 = list, pane 2 = the first
  split window, …); a no-op when that pane doesn't exist.
- Shift+Up/Down/Left/Right move focus to the spatially adjacent split
  window (emacs windmove), scoped to a focused, unzoomed multi-pane
  split; a no-neighbor press is a consumed no-op. Outside that context
  the keys keep their prior meaning (Shift+Up/Down list reorder).

These are intercepted in on_key ahead of PTY forwarding so they escape a
focused child's PTY the same way the C-x prefix does — otherwise they'd
be useless in their main scenario (jumping out of a busy pane). Spatial
adjacency uses last-frame pane geometry already tracked for mouse
hit-testing.

Adds spec 0061 and help-text entries; covered by unit tests for
focus-by-index, list pane, and 2x2-grid spatial movement.
@edwin-zvs
edwin-zvs merged commit 76eeee4 into main Jun 29, 2026
1 check passed
edwin-zvs added a commit that referenced this pull request Jun 29, 2026
PR #618 added Shift+Arrow split-pane focus (emacs windmove). Shift+Left
and Shift+Right work, but Shift+Up/Shift+Down appear to do nothing.

Root cause is the terminal, not construct's dispatch: iTerm2, macOS
Terminal.app, and GNOME Terminal all reserve Shift+Up/Shift+Down for
their own line-scrollback and never forward them to the application,
while Shift+Left/Shift+Right are forwarded. So the vertical axis of the
feature silently dies on exactly the terminals the team uses — the same
reason PR #618's own Shift+Up/Down list-reorder binding is unreliable
there. The on_key dispatch and adjacency math are correct (now covered
by full-path tests that drive on_key end to end, including real rendered
geometry for a vertical split).

Fix: add C-x <arrow> as a directional-focus alias in both keymap
profiles. The C-x prefix is the one input every terminal forwards — it's
how C-x o already escapes a focused child PTY — so directional pane
focus now works everywhere, with Shift+Arrow kept as the fast path for
cooperative terminals. C-x <arrow> reports "no split window <dir>" when
there's no neighbor, since an explicit chord is a deliberate request.

Updates spec 0061: directional focus must be reachable through the
escape prefix, not only the bare modified-arrow keys. Adds keymap and
full on_key dispatch tests for both the new chord and the existing
Shift+Arrow path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant