@@ -45,6 +45,7 @@ export const useExitHandler = ({
4545 null ,
4646 )
4747 const exitScheduledRef = useRef ( false )
48+ const lastCtrlCHandledAtRef = useRef < number > ( 0 )
4849
4950 useEffect ( ( ) => {
5051 setupExitMessageHandler ( )
@@ -87,6 +88,12 @@ export const useExitHandler = ({
8788 } , [ ] )
8889
8990 const handleCtrlC = useCallback ( ( ) => {
91+ const now = Date . now ( )
92+ if ( now - lastCtrlCHandledAtRef . current < 50 ) {
93+ return true
94+ }
95+ lastCtrlCHandledAtRef . current = now
96+
9097 if ( inputValue ) {
9198 setInputValue ( { text : '' , cursorPosition : 0 , lastEditDueToNav : false } )
9299 return true
@@ -114,6 +121,35 @@ export const useExitHandler = ({
114121 return true
115122 } , [ flushAnalyticsWithTimeout , exitNow , inputValue , setInputValue , nextCtrlCWillExit ] )
116123
124+ useEffect ( ( ) => {
125+ if ( ! process . stdin || typeof process . stdin . on !== 'function' ) return
126+
127+ const handleRawCtrlC = ( chunk : Buffer | string ) => {
128+ const data = typeof chunk === 'string' ? chunk : chunk . toString ( 'utf8' )
129+ if ( ! data . includes ( '\u0003' ) ) {
130+ return
131+ }
132+
133+ const now = Date . now ( )
134+ // Avoid double-handling the same Ctrl+C event from both keypress and raw listeners
135+ if ( now - lastCtrlCHandledAtRef . current < 50 ) {
136+ return
137+ }
138+
139+ handleCtrlC ( )
140+ }
141+
142+ process . stdin . on ( 'data' , handleRawCtrlC )
143+
144+ return ( ) => {
145+ if ( typeof process . stdin . off === 'function' ) {
146+ process . stdin . off ( 'data' , handleRawCtrlC )
147+ } else {
148+ process . stdin . removeListener ( 'data' , handleRawCtrlC as any )
149+ }
150+ }
151+ } , [ handleCtrlC ] )
152+
117153 useEffect ( ( ) => {
118154 const handleSigint = ( ) => {
119155 console . log ( '[exit-handler] SIGINT received; exiting' )
0 commit comments