(Alleged) Leaked source of Claude Code
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 303 lines 9.5 kB view raw
1import { feature } from 'bun:bundle' 2import { useCallback, useEffect, useMemo, useRef, useState } from 'react' 3import { 4 getModeFromInput, 5 getValueFromInput, 6} from '../components/PromptInput/inputModes.js' 7import { makeHistoryReader } from '../history.js' 8import { KeyboardEvent } from '../ink/events/keyboard-event.js' 9// eslint-disable-next-line custom-rules/prefer-use-keybindings -- backward-compat bridge until consumers wire handleKeyDown to <Box onKeyDown> 10import { useInput } from '../ink.js' 11import { useKeybinding, useKeybindings } from '../keybindings/useKeybinding.js' 12import type { PromptInputMode } from '../types/textInputTypes.js' 13import type { HistoryEntry } from '../utils/config.js' 14 15export 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 73 const searchHistory = useCallback( 74 async (resume: boolean, signal?: AbortSignal): Promise<void> => { 75 if (!isSearching) { 76 return 77 } 78 79 if (historyQuery.length === 0) { 80 closeHistoryReader() 81 seenPrompts.current.clear() 82 setHistoryMatch(undefined) 83 setHistoryFailedMatch(false) 84 onInputChange(originalInput) 85 onCursorChange(originalCursorOffset) 86 onModeChange(originalMode) 87 setPastedContents(originalPastedContents) 88 return 89 } 90 91 if (!resume) { 92 closeHistoryReader() 93 historyReader.current = makeHistoryReader() 94 seenPrompts.current.clear() 95 } 96 97 if (!historyReader.current) { 98 return 99 } 100 101 while (true) { 102 if (signal?.aborted) { 103 return 104 } 105 106 const item = await historyReader.current.next() 107 if (item.done) { 108 // No match found - keep last match but mark as failed 109 setHistoryFailedMatch(true) 110 return 111 } 112 113 const display = item.value.display 114 115 const matchPosition = display.lastIndexOf(historyQuery) 116 if (matchPosition !== -1 && !seenPrompts.current.has(display)) { 117 seenPrompts.current.add(display) 118 setHistoryMatch(item.value) 119 setHistoryFailedMatch(false) 120 const mode = getModeFromInput(display) 121 onModeChange(mode) 122 onInputChange(display) 123 setPastedContents(item.value.pastedContents) 124 125 // Position cursor relative to the clean value, not the display 126 const value = getValueFromInput(display) 127 const cleanMatchPosition = value.lastIndexOf(historyQuery) 128 onCursorChange( 129 cleanMatchPosition !== -1 ? cleanMatchPosition : matchPosition, 130 ) 131 return 132 } 133 } 134 }, 135 [ 136 isSearching, 137 historyQuery, 138 closeHistoryReader, 139 onInputChange, 140 onCursorChange, 141 onModeChange, 142 setPastedContents, 143 originalInput, 144 originalCursorOffset, 145 originalMode, 146 originalPastedContents, 147 ], 148 ) 149 150 // Handler: Start history search (when not searching) 151 const handleStartSearch = useCallback(() => { 152 setIsSearching(true) 153 setOriginalInput(currentInput) 154 setOriginalCursorOffset(currentCursorOffset) 155 setOriginalMode(currentMode) 156 setOriginalPastedContents(currentPastedContents) 157 historyReader.current = makeHistoryReader() 158 seenPrompts.current.clear() 159 }, [ 160 setIsSearching, 161 currentInput, 162 currentCursorOffset, 163 currentMode, 164 currentPastedContents, 165 ]) 166 167 // Handler: Find next match (when searching) 168 const handleNextMatch = useCallback(() => { 169 void searchHistory(true) 170 }, [searchHistory]) 171 172 // Handler: Accept current match and exit search 173 const handleAccept = useCallback(() => { 174 if (historyMatch) { 175 const mode = getModeFromInput(historyMatch.display) 176 const value = getValueFromInput(historyMatch.display) 177 onInputChange(value) 178 onModeChange(mode) 179 setPastedContents(historyMatch.pastedContents) 180 } else { 181 // No match - restore original pasted contents 182 setPastedContents(originalPastedContents) 183 } 184 reset() 185 }, [ 186 historyMatch, 187 onInputChange, 188 onModeChange, 189 setPastedContents, 190 originalPastedContents, 191 reset, 192 ]) 193 194 // Handler: Cancel search and restore original input 195 const handleCancel = useCallback(() => { 196 onInputChange(originalInput) 197 onCursorChange(originalCursorOffset) 198 setPastedContents(originalPastedContents) 199 reset() 200 }, [ 201 onInputChange, 202 onCursorChange, 203 setPastedContents, 204 originalInput, 205 originalCursorOffset, 206 originalPastedContents, 207 reset, 208 ]) 209 210 // Handler: Execute (accept and submit) 211 const handleExecute = useCallback(() => { 212 if (historyQuery.length === 0) { 213 onAcceptHistory({ 214 display: originalInput, 215 pastedContents: originalPastedContents, 216 }) 217 } else if (historyMatch) { 218 const mode = getModeFromInput(historyMatch.display) 219 const value = getValueFromInput(historyMatch.display) 220 onModeChange(mode) 221 onAcceptHistory({ 222 display: value, 223 pastedContents: historyMatch.pastedContents, 224 }) 225 } 226 reset() 227 }, [ 228 historyQuery, 229 historyMatch, 230 onAcceptHistory, 231 onModeChange, 232 originalInput, 233 originalPastedContents, 234 reset, 235 ]) 236 237 // Gated off under HISTORY_PICKER — the modal dialog owns ctrl+r there. 238 useKeybinding('history:search', handleStartSearch, { 239 context: 'Global', 240 isActive: feature('HISTORY_PICKER') ? false : !isSearching, 241 }) 242 243 // History search context keybindings (only active when searching) 244 const historySearchHandlers = useMemo( 245 () => ({ 246 'historySearch:next': handleNextMatch, 247 'historySearch:accept': handleAccept, 248 'historySearch:cancel': handleCancel, 249 'historySearch:execute': handleExecute, 250 }), 251 [handleNextMatch, handleAccept, handleCancel, handleExecute], 252 ) 253 254 useKeybindings(historySearchHandlers, { 255 context: 'HistorySearch', 256 isActive: isSearching, 257 }) 258 259 // Handle backspace when query is empty (cancels search) 260 // This is a conditional behavior that doesn't fit the keybinding model 261 // well (backspace only cancels when query is empty) 262 const handleKeyDown = (e: KeyboardEvent): void => { 263 if (!isSearching) return 264 if (e.key === 'backspace' && historyQuery === '') { 265 e.preventDefault() 266 handleCancel() 267 } 268 } 269 270 // Backward-compat bridge: PromptInput doesn't yet wire handleKeyDown to 271 // <Box onKeyDown>. Subscribe via useInput and adapt InputEvent → 272 // KeyboardEvent until the consumer is migrated (separate PR). 273 // TODO(onKeyDown-migration): remove once PromptInput passes handleKeyDown. 274 useInput( 275 (_input, _key, event) => { 276 handleKeyDown(new KeyboardEvent(event.keypress)) 277 }, 278 { isActive: isSearching }, 279 ) 280 281 // Keep a ref to searchHistory to avoid it being a dependency of useEffect 282 const searchHistoryRef = useRef(searchHistory) 283 searchHistoryRef.current = searchHistory 284 285 // Reset history search when query changes 286 useEffect(() => { 287 searchAbortController.current?.abort() 288 const controller = new AbortController() 289 searchAbortController.current = controller 290 void searchHistoryRef.current(false, controller.signal) 291 return () => { 292 controller.abort() 293 } 294 }, [historyQuery]) 295 296 return { 297 historyQuery, 298 setHistoryQuery, 299 historyMatch, 300 historyFailedMatch, 301 handleKeyDown, 302 } 303}