feat(app): load xterm search addon + surface→addon registry

This commit is contained in:
2026-06-10 12:35:41 +07:00
parent daf87d3c09
commit 52a502c38b
4 changed files with 33 additions and 1 deletions
+7
View File
@@ -12,6 +12,7 @@
"@fontsource/inter": "^5.2.8", "@fontsource/inter": "^5.2.8",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-notification": "^2", "@tauri-apps/plugin-notification": "^2",
"@xterm/addon-search": "^0.16.0",
"@xterm/addon-webgl": "^0.18.0", "@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"lucide-react": "^1.17.0", "lucide-react": "^1.17.0",
@@ -1462,6 +1463,12 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
} }
}, },
"node_modules/@xterm/addon-search": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0.tgz",
"integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==",
"license": "MIT"
},
"node_modules/@xterm/addon-webgl": { "node_modules/@xterm/addon-webgl": {
"version": "0.18.0", "version": "0.18.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.18.0.tgz", "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.18.0.tgz",
+1
View File
@@ -13,6 +13,7 @@
"@fontsource/inter": "^5.2.8", "@fontsource/inter": "^5.2.8",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-notification": "^2", "@tauri-apps/plugin-notification": "^2",
"@xterm/addon-search": "^0.16.0",
"@xterm/addon-webgl": "^0.18.0", "@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"lucide-react": "^1.17.0", "lucide-react": "^1.17.0",
+8 -1
View File
@@ -1,7 +1,9 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Terminal } from "@xterm/xterm"; import { Terminal } from "@xterm/xterm";
import { WebglAddon } from "@xterm/addon-webgl"; import { WebglAddon } from "@xterm/addon-webgl";
import { SearchAddon } from "@xterm/addon-search";
import { attachSurface, detachSurface, sendInput, resizeSurface } from "./socketBridge"; import { attachSurface, detachSurface, sendInput, resizeSurface } from "./socketBridge";
import { registerSearch, unregisterSearch } from "./searchRegistry";
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const encoder = new TextEncoder(); const encoder = new TextEncoder();
@@ -11,7 +13,7 @@ export function TerminalView({ surfaceId }: { surfaceId: string }) {
useEffect(() => { useEffect(() => {
if (!ref.current) return; if (!ref.current) return;
const term = new Terminal({ fontFamily: "'JetBrains Mono Variable', 'JetBrains Mono', monospace", fontSize: 13, convertEol: false }); const term = new Terminal({ fontFamily: "'JetBrains Mono Variable', 'JetBrains Mono', monospace", fontSize: 13, convertEol: false, scrollback: 10000 });
try { try {
term.loadAddon(new WebglAddon()); term.loadAddon(new WebglAddon());
} catch { } catch {
@@ -19,6 +21,10 @@ export function TerminalView({ surfaceId }: { surfaceId: string }) {
} }
term.open(ref.current); term.open(ref.current);
const search = new SearchAddon();
term.loadAddon(search);
registerSearch(surfaceId, search);
// Input → daemon. // Input → daemon.
const inputDisposable = term.onData((data) => { const inputDisposable = term.onData((data) => {
void sendInput(surfaceId, encoder.encode(data)); void sendInput(surfaceId, encoder.encode(data));
@@ -42,6 +48,7 @@ export function TerminalView({ surfaceId }: { surfaceId: string }) {
disposed = true; disposed = true;
inputDisposable.dispose(); inputDisposable.dispose();
void detachSurface(surfaceId); void detachSurface(surfaceId);
unregisterSearch(surfaceId);
term.dispose(); term.dispose();
}; };
}, [surfaceId]); }, [surfaceId]);
+17
View File
@@ -0,0 +1,17 @@
import type { SearchAddon } from "@xterm/addon-search";
/** Maps a surfaceId to its terminal's SearchAddon so the search bar can reach
* the focused panel without prop-drilling through the layout tree. */
const registry = new Map<string, SearchAddon>();
export function registerSearch(surfaceId: string, addon: SearchAddon): void {
registry.set(surfaceId, addon);
}
export function unregisterSearch(surfaceId: string): void {
registry.delete(surfaceId);
}
export function getSearch(surfaceId: string): SearchAddon | undefined {
return registry.get(surfaceId);
}