( onAcceptHistory: (entry: HistoryEntry) => void, currentInput: string, onInputChange: (input: string) => void, onCursorChange: (cursorOffset: number) => void, currentCursorOffset: number, onModeChange: (mode: PromptInputMode) => void, currentMode: PromptInputMode, isSearching: boolean, setIsSearching: (isSearching: boolean) => void, setPastedContents: (pastedContents: HistoryEntry['pastedContents']) => void, currentPastedContents: HistoryEntry['pastedContents'], )
| 13 | import type { HistoryEntry } from '../utils/config.js' |
| 14 | |
| 15 | export function useHistorySearch( |
| 16 | onAcceptHistory: (entry: HistoryEntry) => void, |
| 17 | currentInput: string, |
| 18 | onInputChange: (input: string) => void, |
| 19 | onCursorChange: (cursorOffset: number) => void, |
| 20 | currentCursorOffset: number, |
| 21 | onModeChange: (mode: PromptInputMode) => void, |
| 22 | currentMode: PromptInputMode, |
| 23 | isSearching: boolean, |
| 24 | setIsSearching: (isSearching: boolean) => void, |
| 25 | setPastedContents: (pastedContents: HistoryEntry['pastedContents']) => void, |
| 26 | currentPastedContents: HistoryEntry['pastedContents'], |
| 27 | ): { |
| 28 | historyQuery: string |
| 29 | setHistoryQuery: (query: string) => void |
| 30 | historyMatch: HistoryEntry | undefined |
| 31 | historyFailedMatch: boolean |
| 32 | handleKeyDown: (e: KeyboardEvent) => void |
| 33 | } { |
| 34 | const [historyQuery, setHistoryQuery] = useState('') |
| 35 | const [historyFailedMatch, setHistoryFailedMatch] = useState(false) |
| 36 | const [originalInput, setOriginalInput] = useState('') |
| 37 | const [originalCursorOffset, setOriginalCursorOffset] = useState(0) |
| 38 | const [originalMode, setOriginalMode] = useState<PromptInputMode>('prompt') |
| 39 | const [originalPastedContents, setOriginalPastedContents] = useState< |
| 40 | HistoryEntry['pastedContents'] |
| 41 | >({}) |
| 42 | const [historyMatch, setHistoryMatch] = useState<HistoryEntry | undefined>( |
| 43 | undefined, |
| 44 | ) |
| 45 | const historyReader = useRef<AsyncGenerator<HistoryEntry> | undefined>( |
| 46 | undefined, |
| 47 | ) |
| 48 | const seenPrompts = useRef<Set<string>>(new Set()) |
| 49 | const searchAbortController = useRef<AbortController | null>(null) |
| 50 | |
| 51 | const closeHistoryReader = useCallback((): void => { |
| 52 | if (historyReader.current) { |
| 53 | // Must explicitly call .return() to trigger the finally block in readLinesReverse, |
| 54 | // which closes the file handle. Without this, file descriptors leak. |
| 55 | void historyReader.current.return(undefined) |
| 56 | historyReader.current = undefined |
| 57 | } |
| 58 | }, []) |
| 59 | |
| 60 | const reset = useCallback((): void => { |
| 61 | setIsSearching(false) |
| 62 | setHistoryQuery('') |
| 63 | setHistoryFailedMatch(false) |
| 64 | setOriginalInput('') |
| 65 | setOriginalCursorOffset(0) |
| 66 | setOriginalMode('prompt') |
| 67 | setOriginalPastedContents({}) |
| 68 | setHistoryMatch(undefined) |
| 69 | closeHistoryReader() |
| 70 | seenPrompts.current.clear() |
| 71 | }, [setIsSearching, closeHistoryReader]) |
| 72 |
no test coverage detected