source dump of claude code
at main 132 lines 3.4 kB view raw
1import { useCallback, useRef, useState } from 'react' 2import type { PastedContent } from '../utils/config.js' 3 4export type BufferEntry = { 5 text: string 6 cursorOffset: number 7 pastedContents: Record<number, PastedContent> 8 timestamp: number 9} 10 11export type UseInputBufferProps = { 12 maxBufferSize: number 13 debounceMs: number 14} 15 16export type UseInputBufferResult = { 17 pushToBuffer: ( 18 text: string, 19 cursorOffset: number, 20 pastedContents?: Record<number, PastedContent>, 21 ) => void 22 undo: () => BufferEntry | undefined 23 canUndo: boolean 24 clearBuffer: () => void 25} 26 27export function useInputBuffer({ 28 maxBufferSize, 29 debounceMs, 30}: UseInputBufferProps): UseInputBufferResult { 31 const [buffer, setBuffer] = useState<BufferEntry[]>([]) 32 const [currentIndex, setCurrentIndex] = useState(-1) 33 const lastPushTime = useRef<number>(0) 34 const pendingPush = useRef<ReturnType<typeof setTimeout> | null>(null) 35 36 const pushToBuffer = useCallback( 37 ( 38 text: string, 39 cursorOffset: number, 40 pastedContents: Record<number, PastedContent> = {}, 41 ) => { 42 const now = Date.now() 43 44 // Clear any pending push 45 if (pendingPush.current) { 46 clearTimeout(pendingPush.current) 47 pendingPush.current = null 48 } 49 50 // Debounce rapid changes 51 if (now - lastPushTime.current < debounceMs) { 52 pendingPush.current = setTimeout( 53 pushToBuffer, 54 debounceMs, 55 text, 56 cursorOffset, 57 pastedContents, 58 ) 59 return 60 } 61 62 lastPushTime.current = now 63 64 setBuffer(prevBuffer => { 65 // If we're not at the end of the buffer, truncate everything after current position 66 const newBuffer = 67 currentIndex >= 0 ? prevBuffer.slice(0, currentIndex + 1) : prevBuffer 68 69 // Don't add if it's the same as the last entry 70 const lastEntry = newBuffer[newBuffer.length - 1] 71 if (lastEntry && lastEntry.text === text) { 72 return newBuffer 73 } 74 75 // Add new entry 76 const updatedBuffer = [ 77 ...newBuffer, 78 { text, cursorOffset, pastedContents, timestamp: now }, 79 ] 80 81 // Limit buffer size 82 if (updatedBuffer.length > maxBufferSize) { 83 return updatedBuffer.slice(-maxBufferSize) 84 } 85 86 return updatedBuffer 87 }) 88 89 // Update current index to point to the new entry 90 setCurrentIndex(prev => { 91 const newIndex = prev >= 0 ? prev + 1 : buffer.length 92 return Math.min(newIndex, maxBufferSize - 1) 93 }) 94 }, 95 [debounceMs, maxBufferSize, currentIndex, buffer.length], 96 ) 97 98 const undo = useCallback((): BufferEntry | undefined => { 99 if (currentIndex < 0 || buffer.length === 0) { 100 return undefined 101 } 102 103 const targetIndex = Math.max(0, currentIndex - 1) 104 const entry = buffer[targetIndex] 105 106 if (entry) { 107 setCurrentIndex(targetIndex) 108 return entry 109 } 110 111 return undefined 112 }, [buffer, currentIndex]) 113 114 const clearBuffer = useCallback(() => { 115 setBuffer([]) 116 setCurrentIndex(-1) 117 lastPushTime.current = 0 118 if (pendingPush.current) { 119 clearTimeout(pendingPush.current) 120 pendingPush.current = null 121 } 122 }, [lastPushTime, pendingPush]) 123 124 const canUndo = currentIndex > 0 && buffer.length > 1 125 126 return { 127 pushToBuffer, 128 undo, 129 canUndo, 130 clearBuffer, 131 } 132}