Files
Netcatty/components/terminal/hooks/useTerminalSearch.ts
T
LAPTOP-O016UC3M\Qi Chen 21c07ffe11 Replaces MutableRefObject with RefObject for refs
Updates type annotations across terminal-related hooks and runtime modules to use RefObject instead of MutableRefObject for React refs. Aligns with React's convention for refs not directly assigned via useRef, improving type safety and clarity.
2025-12-15 19:15:43 +08:00

123 lines
3.4 KiB
TypeScript

import type { SearchAddon } from "@xterm/addon-search";
import type { Terminal as XTerm } from "@xterm/xterm";
import { useCallback, useRef, useState } from "react";
import type { RefObject } from "react";
type SearchMatchCount = { current: number; total: number } | null;
export const useTerminalSearch = ({
searchAddonRef,
termRef,
}: {
searchAddonRef: RefObject<SearchAddon | null>;
termRef: RefObject<XTerm | null>;
}) => {
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchMatchCount, setSearchMatchCount] = useState<SearchMatchCount>(null);
const searchTermRef = useRef<string>("");
const clearSearchDecorations = useCallback(() => {
searchAddonRef.current?.clearDecorations();
}, [searchAddonRef]);
const handleToggleSearch = useCallback(() => {
setIsSearchOpen((prev) => !prev);
if (isSearchOpen) {
setSearchMatchCount(null);
clearSearchDecorations();
}
}, [clearSearchDecorations, isSearchOpen]);
const handleSearch = useCallback(
(term: string): boolean => {
const searchAddon = searchAddonRef.current;
if (!searchAddon || !term) {
setSearchMatchCount(null);
return false;
}
searchTermRef.current = term;
searchAddon.clearDecorations();
const found = searchAddon.findNext(term, {
regex: false,
caseSensitive: false,
wholeWord: false,
decorations: {
matchBackground: "#FFFF0044",
matchBorder: "#FFFF00",
matchOverviewRuler: "#FFFF00",
activeMatchBackground: "#FF880088",
activeMatchBorder: "#FF8800",
activeMatchColorOverviewRuler: "#FF8800",
},
});
if (found) {
setSearchMatchCount({ current: 1, total: 1 });
} else {
setSearchMatchCount({ current: 0, total: 0 });
}
return found;
},
[searchAddonRef],
);
const handleFindNext = useCallback((): boolean => {
const searchAddon = searchAddonRef.current;
const term = searchTermRef.current;
if (!searchAddon || !term) return false;
return searchAddon.findNext(term, {
regex: false,
caseSensitive: false,
wholeWord: false,
decorations: {
matchBackground: "#FFFF0044",
matchBorder: "#FFFF00",
matchOverviewRuler: "#FFFF00",
activeMatchBackground: "#FF880088",
activeMatchBorder: "#FF8800",
activeMatchColorOverviewRuler: "#FF8800",
},
});
}, [searchAddonRef]);
const handleFindPrevious = useCallback((): boolean => {
const searchAddon = searchAddonRef.current;
const term = searchTermRef.current;
if (!searchAddon || !term) return false;
return searchAddon.findPrevious(term, {
regex: false,
caseSensitive: false,
wholeWord: false,
decorations: {
matchBackground: "#FFFF0044",
matchBorder: "#FFFF00",
matchOverviewRuler: "#FFFF00",
activeMatchBackground: "#FF880088",
activeMatchBorder: "#FF8800",
activeMatchColorOverviewRuler: "#FF8800",
},
});
}, [searchAddonRef]);
const handleCloseSearch = useCallback(() => {
setIsSearchOpen(false);
setSearchMatchCount(null);
clearSearchDecorations();
termRef.current?.focus();
}, [clearSearchDecorations, termRef]);
return {
isSearchOpen,
setIsSearchOpen,
searchMatchCount,
handleToggleSearch,
handleSearch,
handleFindNext,
handleFindPrevious,
handleCloseSearch,
};
};