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:
parent
8bb080345e
commit
baa00dfc5c
8 changed files with 526 additions and 29 deletions
34
src/App.tsx
34
src/App.tsx
|
|
@ -98,7 +98,7 @@ import {
|
|||
presetTwoRows,
|
||||
presetTwoByTwo,
|
||||
} from "./lib/layout/tree";
|
||||
import { OrchestrationProvider, type Orchestration } from "./lib/layout/orchestration";
|
||||
import { OrchestrationProvider, type Orchestration, type NavigateIntent } from "./lib/layout/orchestration";
|
||||
import LeafPane from "./lib/layout/LeafPane";
|
||||
import Gutter from "./lib/layout/Gutter";
|
||||
import Notifications, { type Toast } from "./components/Notifications";
|
||||
|
|
@ -717,6 +717,36 @@ export default function App() {
|
|||
setActiveLeafId(leafId);
|
||||
}, []);
|
||||
|
||||
// navigateTo — called from XtermPane's attachCustomKeyEventHandler via
|
||||
// LeafPane's onNavigate prop. Resolves the target leaf from the current
|
||||
// layout tree and sets it active; the LeafPane isActive→focusTrigger
|
||||
// effect then refocuses the xterm textarea automatically.
|
||||
const navigateTo = useCallback((intent: NavigateIntent) => {
|
||||
const currentTree = treeRef.current;
|
||||
const currentActiveId = activeLeafId;
|
||||
|
||||
if (intent.kind === "direction") {
|
||||
const layout = flattenLayout(currentTree);
|
||||
if (!currentActiveId) {
|
||||
const first = layout.leaves[0]?.leaf.id;
|
||||
if (first) setActiveLeafId(first);
|
||||
return;
|
||||
}
|
||||
const nextId = findNeighborInDirection(
|
||||
layout.leaves,
|
||||
currentActiveId,
|
||||
intent.dir,
|
||||
);
|
||||
if (nextId) setActiveLeafId(nextId);
|
||||
} else {
|
||||
// intent.kind === "index"
|
||||
const leaves = Array.from(walkLeaves(currentTree));
|
||||
// Clamp: Alt+9 with 3 panes picks the 3rd pane.
|
||||
const target = leaves[Math.min(intent.n, leaves.length) - 1];
|
||||
if (target) setActiveLeafId(target.id);
|
||||
}
|
||||
}, [activeLeafId]); // treeRef is a ref — stable, intentionally not listed
|
||||
|
||||
const openHostManager = useCallback(() => setHostManagerOpen(true), []);
|
||||
const closeHostManager = useCallback(() => setHostManagerOpen(false), []);
|
||||
|
||||
|
|
@ -1242,6 +1272,7 @@ export default function App() {
|
|||
toggleMcpAllow,
|
||||
openHostManager,
|
||||
setActive,
|
||||
navigateTo,
|
||||
registerPaneId,
|
||||
broadcastFrom,
|
||||
notify,
|
||||
|
|
@ -1266,6 +1297,7 @@ export default function App() {
|
|||
toggleMcpAllow,
|
||||
openHostManager,
|
||||
setActive,
|
||||
navigateTo,
|
||||
registerPaneId,
|
||||
broadcastFrom,
|
||||
notify,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue