(onSetInput: (value: string, mode: HistoryMode, pastedContents: Record<number, PastedContent>) => void, currentInput: string, pastedContents: Record<number, PastedContent>, setCursorOffset?: (offset: number) => void, currentMode?: HistoryMode)
| 61 | } |
| 62 | } |
| 63 | export function useArrowKeyHistory(onSetInput: (value: string, mode: HistoryMode, pastedContents: Record<number, PastedContent>) => void, currentInput: string, pastedContents: Record<number, PastedContent>, setCursorOffset?: (offset: number) => void, currentMode?: HistoryMode): { |
| 64 | historyIndex: number; |
| 65 | setHistoryIndex: (index: number) => void; |
| 66 | onHistoryUp: () => void; |
| 67 | onHistoryDown: () => boolean; |
| 68 | resetHistory: () => void; |
| 69 | dismissSearchHint: () => void; |
| 70 | } { |
| 71 | const [historyIndex, setHistoryIndex] = useState(0); |
| 72 | const [lastShownHistoryEntry, setLastShownHistoryEntry] = useState<(HistoryEntry & { |
| 73 | mode?: HistoryMode; |
| 74 | }) | undefined>(undefined); |
| 75 | const hasShownSearchHintRef = useRef(false); |
| 76 | const { |
| 77 | addNotification, |
| 78 | removeNotification |
| 79 | } = useNotifications(); |
| 80 | |
| 81 | // Cache loaded history entries |
| 82 | const historyCache = useRef<HistoryEntry[]>([]); |
| 83 | // Track which mode filter the cache was loaded with |
| 84 | const historyCacheModeFilter = useRef<HistoryMode | undefined>(undefined); |
| 85 | |
| 86 | // Synchronous tracker for history index to avoid stale closure issues |
| 87 | // React state updates are async, so rapid keypresses can see stale values |
| 88 | const historyIndexRef = useRef(0); |
| 89 | |
| 90 | // Track the mode filter that was active when history navigation started |
| 91 | // This is set on the first arrow press and stays fixed until reset |
| 92 | const initialModeFilterRef = useRef<HistoryMode | undefined>(undefined); |
| 93 | |
| 94 | // Refs to track current input values for draft preservation |
| 95 | // These ensure we capture the draft with the latest values, not stale closure values |
| 96 | const currentInputRef = useRef(currentInput); |
| 97 | const pastedContentsRef = useRef(pastedContents); |
| 98 | const currentModeRef = useRef(currentMode); |
| 99 | |
| 100 | // Keep refs in sync with props (synchronous update on each render) |
| 101 | currentInputRef.current = currentInput; |
| 102 | pastedContentsRef.current = pastedContents; |
| 103 | currentModeRef.current = currentMode; |
| 104 | const setInputWithCursor = useCallback((value: string, mode: HistoryMode, contents: Record<number, PastedContent>, cursorToStart = false): void => { |
| 105 | onSetInput(value, mode, contents); |
| 106 | setCursorOffset?.(cursorToStart ? 0 : value.length); |
| 107 | }, [onSetInput, setCursorOffset]); |
| 108 | const updateInput = useCallback((input: HistoryEntry | undefined, cursorToStart_0 = false): void => { |
| 109 | if (!input || !input.display) return; |
| 110 | const mode_0 = getModeFromInput(input.display); |
| 111 | const value_0 = mode_0 === 'bash' ? input.display.slice(1) : input.display; |
| 112 | setInputWithCursor(value_0, mode_0, input.pastedContents ?? {}, cursorToStart_0); |
| 113 | }, [setInputWithCursor]); |
| 114 | const showSearchHint = useCallback((): void => { |
| 115 | addNotification({ |
| 116 | key: 'search-history-hint', |
| 117 | jsx: <Text dimColor> |
| 118 | <ConfigurableShortcutHint action="history:search" context="Global" fallback="ctrl+r" description="search history" /> |
| 119 | </Text>, |
| 120 | priority: 'immediate', |
no test coverage detected