diff --git a/frontend/app/element/search.tsx b/frontend/app/element/search.tsx index 031bcd97d8..351639aecd 100644 --- a/frontend/app/element/search.tsx +++ b/frontend/app/element/search.tsx @@ -11,6 +11,7 @@ import "./search.scss"; type SearchProps = SearchAtoms & { anchorRef?: React.RefObject; + searchInputRef?: React.RefObject; offsetX?: number; offsetY?: number; onSearch?: (search: string) => void; @@ -27,12 +28,15 @@ const SearchComponent = ({ wholeWord: wholeWordAtom, isOpen: isOpenAtom, anchorRef, + searchInputRef: providedInputRef, offsetX = 10, offsetY = 10, onSearch, onNext, onPrev, }: SearchProps) => { + const localInputRef = useRef(null); + const inputRef = providedInputRef || localInputRef; const [isOpen, setIsOpen] = useAtom(isOpenAtom); const [search, setSearch] = useAtom(searchAtom); const [index, setIndex] = useAtom(indexAtom); @@ -146,6 +150,7 @@ const SearchComponent = ({
(null); useEffect(() => { if (options?.viewModel) { options.viewModel.searchAtoms = searchAtoms; + // Store inputRef on viewModel for external access (e.g., keymodel.ts) + (options.viewModel as any).searchInputRef = searchInputRef; return () => { options.viewModel.searchAtoms = undefined; + (options.viewModel as any).searchInputRef = undefined; }; } - }, [options?.viewModel]); - return { ...searchAtoms, anchorRef }; + }, [options?.viewModel, searchAtoms]); + return { ...searchAtoms, anchorRef, searchInputRef }; } const createToggleButtonDecl = ( diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index 3ade22c748..e66a317f88 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -21,6 +21,7 @@ import { WOS, } from "@/app/store/global"; import { TabBarModel } from "@/app/tab/tabbar-model"; +import type { TermViewModel } from "@/app/view/term/term-model"; import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; import { deleteLayoutModelForTab, getLayoutModelForStaticTab, NavigateDirection } from "@/layout/index"; import * as keyutil from "@/util/keyutil"; @@ -628,14 +629,48 @@ function registerGlobalKeys() { return true; }); } + function getSelectedText(): string { + // Check for terminal selection first + const bcm = getBlockComponentModel(getFocusedBlockInStaticTab()); + if (bcm?.viewModel?.viewType === "term") { + const termViewModel = bcm.viewModel as TermViewModel; + if (termViewModel.termRef?.current?.terminal) { + const terminalSelection = termViewModel.termRef.current.terminal.getSelection(); + if (terminalSelection && terminalSelection.length > 0) { + return terminalSelection.trim(); + } + } + } + + // Check for regular text selection + const selection = window.getSelection(); + if (selection && selection.rangeCount > 0 && !selection.isCollapsed) { + const selectedText = selection.toString().trim(); + if (selectedText.length > 0) { + return selectedText; + } + } + return ""; + } + function activateSearch(event: WaveKeyboardEvent): boolean { const bcm = getBlockComponentModel(getFocusedBlockInStaticTab()); - // Ctrl+f is reserved in most shells + // Ctrl+f is reserved in most shells. if (event.control && bcm.viewModel.viewType == "term") { return false; } if (bcm.viewModel.searchAtoms) { - globalStore.set(bcm.viewModel.searchAtoms.isOpen, true); + let selectedText = getSelectedText(); + globalStore.set(bcm.viewModel.searchAtoms.isOpen, true); + globalStore.set(bcm.viewModel.searchAtoms.searchValue, selectedText); + + // Focus the search input using the exposed searchInputRef + const searchInputRef = bcm.viewModel.searchInputRef; + if (searchInputRef?.current) { + setTimeout(() => { + searchInputRef.current?.focus(); + }, 10); + } return true; } return false;