1- import { RefObject , useState } from 'react'
1+ import { RefObject , useState , useEffect } from 'react'
22import { PromptFieldProps } from '../PromptField'
3- import { get_display_text } from '../utils/get-display-text'
43import {
54 get_caret_position_from_div ,
65 set_caret_position_for_div
@@ -84,15 +83,31 @@ export const use_handlers = (
8483) => {
8584 const [ history_index , set_history_index ] = useState ( - 1 )
8685 const [ is_history_enabled , set_is_history_enabled ] = useState ( ! props . value )
87- const [ saved_value_before_at_sign , set_saved_value_before_at_sign ] = useState <
88- string | null
89- > ( null )
86+ const [ undo_stack , set_undo_stack ] = useState < string [ ] > ( [ ] )
87+ const [ redo_stack , set_redo_stack ] = useState < string [ ] > ( [ ] )
88+
89+ useEffect ( ( ) => {
90+ set_is_history_enabled ( history_index >= 0 || ! props . value )
91+ } , [ history_index , props . value ] )
92+
93+ const update_value = ( new_value : string , caret_pos ?: number ) => {
94+ if ( new_value === props . value ) return
95+ set_undo_stack ( ( prev ) => [ ...prev , props . value ] )
96+ set_redo_stack ( [ ] )
97+ props . on_change ( new_value )
98+ if ( caret_pos !== undefined ) {
99+ set_caret_position_after_change (
100+ input_ref ,
101+ caret_pos ,
102+ new_value ,
103+ props . context_file_paths ?? [ ]
104+ )
105+ }
106+ }
90107
91108 const handle_clear = ( ) => {
92- props . on_change ( '' )
109+ update_value ( '' )
93110 set_history_index ( - 1 )
94- set_is_history_enabled ( true )
95- set_saved_value_before_at_sign ( null )
96111 }
97112
98113 const handle_input_change = ( e : React . FormEvent < HTMLDivElement > ) => {
@@ -102,17 +117,14 @@ export const use_handlers = (
102117 if ( new_value === props . value ) {
103118 return
104119 }
105-
106- props . on_change ( new_value )
120+ update_value ( new_value )
107121 set_history_index ( - 1 )
108- set_saved_value_before_at_sign ( null )
109122
110123 const new_display_value = currentTarget . textContent ?? ''
111124 const caret_position = get_caret_position_from_div ( currentTarget )
112125 const char_before_caret = new_display_value . charAt ( caret_position - 1 )
113126
114127 if ( char_before_caret === '@' ) {
115- set_saved_value_before_at_sign ( props . value )
116128 setTimeout ( ( ) => {
117129 props . on_at_sign_click ( )
118130 } , 150 )
@@ -121,10 +133,6 @@ export const use_handlers = (
121133 props . on_hash_sign_click ( )
122134 } , 150 )
123135 }
124-
125- if ( ! new_value ) {
126- set_is_history_enabled ( true )
127- }
128136 }
129137
130138 const handle_submit = (
@@ -169,14 +177,7 @@ export const use_handlers = (
169177 props . value . substring ( 0 , start_of_path ) +
170178 props . value . substring ( end_of_path + 1 )
171179 const new_raw_cursor_pos = start_of_path
172- props . on_change ( new_value )
173-
174- set_caret_position_after_change (
175- input_ref ,
176- new_raw_cursor_pos ,
177- new_value ,
178- context_file_paths
179- )
180+ update_value ( new_value , new_raw_cursor_pos )
180181 return true
181182 }
182183 }
@@ -196,14 +197,7 @@ export const use_handlers = (
196197 props . value . substring ( 0 , start_of_match ) +
197198 props . value . substring ( raw_pos )
198199 const new_raw_cursor_pos = start_of_match
199- props . on_change ( new_value )
200-
201- set_caret_position_after_change (
202- input_ref ,
203- new_raw_cursor_pos ,
204- new_value ,
205- context_file_paths
206- )
200+ update_value ( new_value , new_raw_cursor_pos )
207201 return true
208202 }
209203 return false
@@ -224,14 +218,7 @@ export const use_handlers = (
224218 props . value . substring ( 0 , start_of_match ) +
225219 props . value . substring ( raw_pos )
226220 const new_raw_cursor_pos = start_of_match
227- props . on_change ( new_value )
228-
229- set_caret_position_after_change (
230- input_ref ,
231- new_raw_cursor_pos ,
232- new_value ,
233- context_file_paths
234- )
221+ update_value ( new_value , new_raw_cursor_pos )
235222 return true
236223 }
237224 return false
@@ -250,14 +237,7 @@ export const use_handlers = (
250237 props . value . substring ( 0 , start_of_match ) +
251238 props . value . substring ( raw_pos )
252239 const new_raw_cursor_pos = start_of_match
253- props . on_change ( new_value )
254-
255- set_caret_position_after_change (
256- input_ref ,
257- new_raw_cursor_pos ,
258- new_value ,
259- context_file_paths
260- )
240+ update_value ( new_value , new_raw_cursor_pos )
261241 return true
262242 }
263243 return false
@@ -276,14 +256,7 @@ export const use_handlers = (
276256 props . value . substring ( 0 , start_of_match ) +
277257 props . value . substring ( raw_pos )
278258 const new_raw_cursor_pos = start_of_match
279- props . on_change ( new_value )
280-
281- set_caret_position_after_change (
282- input_ref ,
283- new_raw_cursor_pos ,
284- new_value ,
285- context_file_paths
286- )
259+ update_value ( new_value , new_raw_cursor_pos )
287260 return true
288261 }
289262 return false
@@ -393,22 +366,14 @@ export const use_handlers = (
393366 const handle_history_navigation = (
394367 e : React . KeyboardEvent < HTMLDivElement >
395368 ) => {
396- set_saved_value_before_at_sign ( null )
397-
398369 const active_history = props . chat_history
399370
400371 if ( active_history . length === 0 ) return
401372
402373 e . preventDefault ( )
403374
404375 const update_and_set_caret = ( value : string ) => {
405- props . on_change ( value )
406- set_caret_position_after_change (
407- input_ref ,
408- value . length ,
409- value ,
410- props . context_file_paths ?? [ ]
411- )
376+ update_value ( value , value . length )
412377 }
413378
414379 if ( e . key === 'ArrowUp' ) {
@@ -429,27 +394,45 @@ export const use_handlers = (
429394 }
430395 }
431396
432- const handle_undo_at_sign = ( e : React . KeyboardEvent < HTMLDivElement > ) => {
433- if ( saved_value_before_at_sign === null ) return
434-
435- e . preventDefault ( )
436- const restored_value = saved_value_before_at_sign
437- set_saved_value_before_at_sign ( null )
438- props . on_change ( restored_value )
439-
440- // Restore caret position
441- setTimeout ( ( ) => {
442- const display_restored = get_display_text (
443- restored_value ,
444- props . context_file_paths ?? [ ]
445- )
446- set_caret_position_for_div ( e . currentTarget , display_restored . length )
447- } , 0 )
448- }
449-
450397 const handle_key_down = ( e : React . KeyboardEvent < HTMLDivElement > ) => {
451- const { key, shiftKey, ctrlKey, metaKey } = e
452-
398+ const { key, shiftKey } = e
399+ if ( ( e . ctrlKey || e . metaKey ) && ! shiftKey && key . toLowerCase ( ) === 'z' ) {
400+ e . preventDefault ( )
401+ if ( undo_stack . length > 0 ) {
402+ const prev_value = undo_stack [ undo_stack . length - 1 ]
403+ set_undo_stack ( ( prev ) => prev . slice ( 0 , - 1 ) )
404+ set_redo_stack ( ( prev ) => [ ...prev , props . value ] )
405+ props . on_change ( prev_value )
406+ set_caret_position_after_change (
407+ input_ref ,
408+ prev_value . length ,
409+ prev_value ,
410+ props . context_file_paths ?? [ ]
411+ )
412+ set_history_index ( - 1 )
413+ }
414+ return
415+ }
416+ if (
417+ ( ( e . ctrlKey || e . metaKey ) && key . toLowerCase ( ) === 'y' ) ||
418+ ( ( e . ctrlKey || e . metaKey ) && shiftKey && key . toLowerCase ( ) === 'z' )
419+ ) {
420+ e . preventDefault ( )
421+ if ( redo_stack . length > 0 ) {
422+ const next_value = redo_stack [ redo_stack . length - 1 ]
423+ set_redo_stack ( ( prev ) => prev . slice ( 0 , - 1 ) )
424+ set_undo_stack ( ( prev ) => [ ...prev , props . value ] )
425+ props . on_change ( next_value )
426+ set_caret_position_after_change (
427+ input_ref ,
428+ next_value . length ,
429+ next_value ,
430+ props . context_file_paths ?? [ ]
431+ )
432+ set_history_index ( - 1 )
433+ }
434+ return
435+ }
453436 if ( key == 'Tab' && ! shiftKey ) {
454437 handle_tab_key ( e )
455438 return
@@ -470,18 +453,6 @@ export const use_handlers = (
470453 handle_history_navigation ( e )
471454 return
472455 }
473-
474- if ( key == 'z' && ( ctrlKey || metaKey ) && ! shiftKey ) {
475- if ( saved_value_before_at_sign !== null ) {
476- handle_undo_at_sign ( e )
477- return
478- }
479- // Otherwise, we don't return, allowing the default undo behavior
480- }
481-
482- if ( props . value ) {
483- set_is_history_enabled ( false )
484- }
485456 }
486457
487458 return {
0 commit comments