Add find-in-scrollback, unicode11, and keyboard pane navigation

Three xterm.js features, implemented together because they share the
XtermPane mount + the single attachCustomKeyEventHandler:

- Unicode 11: load @xterm/addon-unicode11, set activeVersion='11' after
  the canvas renderer so emoji/CJK/box-drawing widths stop drifting.
- Find in scrollback: @xterm/addon-search + a new per-pane SearchBar
  overlay (Ctrl+Shift+F to open, Enter/Shift+Enter next/prev, regex +
  case toggles, Esc to close & refocus). Overlay is an absolutely-
  positioned sibling in a position:relative wrapper so fit() is unaffected.
- Pane navigation: Ctrl+Alt+Arrow / Ctrl+Alt+HJKL (spatial neighbour via
  findNeighborInDirection) and Alt+1..9 (Nth leaf in walkLeaves order).
  XtermPane emits a NavigateIntent; App resolves the target leaf and sets
  it active, reusing the existing isActive->focusTrigger refocus chain.

All chords live in one attachCustomKeyEventHandler (xterm replaces the
handler on each call). Shortcuts added to shortcuts.ts (SoT for README +
Help), including the Alt+digit shell-conflict caveat. tsc clean apart
from the three not-yet-installed addon modules.

Needs pnpm install on the Windows host + runtime verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-28 21:51:29 +01:00
parent 8bb080345e
commit baa00dfc5c
8 changed files with 526 additions and 29 deletions

View file

@ -194,6 +194,14 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
[orch.setActive, leaf.id],
);
// Delegate keyboard navigation intents from XtermPane up to App via
// orch.navigateTo. XtermPane stays dumb (emits intent only); App resolves
// the target leaf from the current layout and bumps focusTrigger.
const onPaneNavigate = useCallback(
(intent: Parameters<typeof orch.navigateTo>[0]) => orch.navigateTo(intent),
[orch.navigateTo],
);
const onStatus = useCallback((msg: string, ok: boolean) => {
setStatus(msg);
setStatusOk(ok);
@ -575,6 +583,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
onInput={onTerminalInput}
onDataReceived={onDataReceived}
onFocus={onXtermFocus}
onNavigate={onPaneNavigate}
focusTrigger={focusTrigger}
fontSize={resolveFontSize(leaf.fontSizeOffset)}
/>