Rudimentary terminal search

This commit is contained in:
Evan Simkowitz 2024-12-29 15:38:39 -05:00
parent cb2cd72cd4
commit 5a90debb2e
No known key found for this signature in database
5 changed files with 53 additions and 6 deletions

View File

@ -3,6 +3,7 @@
import { Block, SubBlock } from "@/app/block/block";
import { BlockNodeModel } from "@/app/block/blocktypes";
import { Search, useSearch } from "@/app/element/search";
import { getAllGlobalKeyBindings } from "@/app/store/keymodel";
import { waveEventSubscribe } from "@/app/store/wps";
import { RpcApi } from "@/app/store/wshclientapi";
@ -24,7 +25,7 @@ import {
} from "@/store/global";
import * as services from "@/store/services";
import * as keyutil from "@/util/keyutil";
import { boundNumber } from "@/util/util";
import { boundNumber, fireAndForget } from "@/util/util";
import clsx from "clsx";
import debug from "debug";
import * as jotai from "jotai";
@ -71,6 +72,7 @@ class TermViewModel implements ViewModel {
shellProcStatusUnsubFn: () => void;
isCmdController: jotai.Atom<boolean>;
isRestarting: jotai.PrimitiveAtom<boolean>;
searchAtoms?: SearchAtoms;
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "term";
@ -785,6 +787,22 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
const fullConfig = globalStore.get(atoms.fullConfigAtom);
const connFontFamily = fullConfig.connections?.[blockData?.meta?.connection]?.["term:fontfamily"];
const searchProps = useSearch(viewRef, model);
const searchVal = jotai.useAtomValue<string>(searchProps.searchAtom);
searchProps.onSearch = React.useCallback((searchText: string) => {
if (searchText == "") {
model.termRef.current?.searchAddon.clearDecorations();
return;
}
model.termRef.current?.searchAddon.findNext(searchText);
}, []);
searchProps.onPrev = React.useCallback(() => {
model.termRef.current?.searchAddon.findPrevious(searchVal);
}, [searchVal]);
searchProps.onNext = React.useCallback(() => {
model.termRef.current?.searchAddon.findNext(searchVal);
}, [searchVal]);
React.useEffect(() => {
const fullConfig = globalStore.get(atoms.fullConfigAtom);
const termThemeName = globalStore.get(model.termThemeNameAtom);
@ -822,13 +840,18 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
useWebGl: !termSettings?.["term:disablewebgl"],
}
);
termWrap.onSearchResultsDidChange = (results) => {
console.log("search results", results);
globalStore.set(searchProps.numResultsAtom, results.resultCount);
globalStore.set(searchProps.indexAtom, results.resultIndex);
};
(window as any).term = termWrap;
model.termRef.current = termWrap;
const rszObs = new ResizeObserver(() => {
termWrap.handleResize_debounced();
});
rszObs.observe(connectElemRef.current);
termWrap.initTerminal();
fireAndForget(termWrap.initTerminal.bind(termWrap));
if (wasFocused) {
setTimeout(() => {
model.giveFocus();
@ -867,6 +890,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
cols: model.termRef.current?.terminal.cols ?? 80,
blockId: blockId,
};
return (
<div className={clsx("view-term", "term-mode-" + termMode)} ref={viewRef}>
<TermResyncHandler blockId={blockId} model={model} />
@ -882,6 +906,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
onPointerOver={onScrollbarHideObserver}
/>
</div>
<Search {...searchProps} />
</div>
);
};

View File

@ -9,6 +9,7 @@ import { PLATFORM, WOS, atoms, fetchWaveFile, getSettingsKeyAtom, globalStore, o
import * as services from "@/store/services";
import * as util from "@/util/util";
import { base64ToArray, fireAndForget } from "@/util/util";
import { SearchAddon } from "@xterm/addon-search";
import { SerializeAddon } from "@xterm/addon-serialize";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { WebglAddon } from "@xterm/addon-webgl";
@ -50,12 +51,14 @@ export class TermWrap {
terminal: Terminal;
connectElem: HTMLDivElement;
fitAddon: FitAddon;
searchAddon: SearchAddon;
serializeAddon: SerializeAddon;
mainFileSubject: SubjectWithRef<WSFileEventData>;
loaded: boolean;
heldData: Uint8Array[];
handleResize_debounced: () => void;
hasResized: boolean;
onSearchResultsDidChange?: (result: { resultIndex: number; resultCount: number }) => void;
constructor(
blockId: string,
@ -72,6 +75,8 @@ export class TermWrap {
this.fitAddon = new FitAddon();
this.fitAddon.noScrollbar = PLATFORM == "darwin";
this.serializeAddon = new SerializeAddon();
this.searchAddon = new SearchAddon();
this.terminal.loadAddon(this.searchAddon);
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.serializeAddon);
this.terminal.loadAddon(
@ -149,6 +154,8 @@ export class TermWrap {
}
})
);
if (this.onSearchResultsDidChange)
this.searchAddon.onDidChangeResults(this.onSearchResultsDidChange.bind(this));
this.mainFileSubject = getFileSubject(this.blockId, TermFileName);
this.mainFileSubject.subscribe(this.handleNewFileSubjectData.bind(this));
try {
@ -299,4 +306,8 @@ export class TermWrap {
});
}, 5000);
}
search(search: string) {
this.searchAddon.findNext(search);
}
}

View File

@ -552,7 +552,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
const searchVal = useAtomValue<string>(searchProps.searchAtom);
const setSearchIndex = useSetAtom(searchProps.indexAtom);
const setNumSearchResults = useSetAtom(searchProps.numResultsAtom);
const onSearch = useCallback((search: string) => {
searchProps.onSearch = useCallback((search: string) => {
try {
if (search) {
model.webviewRef.current?.findInPage(search, { findNext: true });
@ -563,7 +563,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
console.error("Failed to search", e);
}
}, []);
const onSearchNext = useCallback(() => {
searchProps.onNext = useCallback(() => {
try {
console.log("search next", searchVal);
model.webviewRef.current?.findInPage(searchVal, { findNext: false, forward: true });
@ -571,7 +571,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
console.error("Failed to search next", e);
}
}, [searchVal]);
const onSearchPrev = useCallback(() => {
searchProps.onPrev = useCallback(() => {
try {
console.log("search prev", searchVal);
model.webviewRef.current?.findInPage(searchVal, { findNext: false, forward: false });
@ -760,7 +760,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
<div>{errorText}</div>
</div>
)}
<Search {...searchProps} onSearch={onSearch} onNext={onSearchNext} onPrev={onSearchPrev} />
<Search {...searchProps} />
</Fragment>
);
});

View File

@ -93,6 +93,7 @@
"@table-nav/react": "^0.0.7",
"@tanstack/react-table": "^8.20.5",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-search": "^0.15.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",

View File

@ -7314,6 +7314,15 @@ __metadata:
languageName: node
linkType: hard
"@xterm/addon-search@npm:^0.15.0":
version: 0.15.0
resolution: "@xterm/addon-search@npm:0.15.0"
peerDependencies:
"@xterm/xterm": ^5.0.0
checksum: 10c0/2d68233d234eabc9ffe1bc9e4fcd28cd50c1f8c316b0e71a81ee2005b5e4da87c1c0361f2aa117ec566afbbbc35cd37456c7eb889ebc936416d14953c82e5a2a
languageName: node
linkType: hard
"@xterm/addon-serialize@npm:^0.13.0":
version: 0.13.0
resolution: "@xterm/addon-serialize@npm:0.13.0"
@ -21982,6 +21991,7 @@ __metadata:
"@vitejs/plugin-react-swc": "npm:^3.7.2"
"@vitest/coverage-istanbul": "npm:^2.1.8"
"@xterm/addon-fit": "npm:^0.10.0"
"@xterm/addon-search": "npm:^0.15.0"
"@xterm/addon-serialize": "npm:^0.13.0"
"@xterm/addon-web-links": "npm:^0.11.0"
"@xterm/addon-webgl": "npm:^0.18.0"