@@ -25,32 +25,35 @@ export const KEY_CODES = {
2525interface SharedInputState {
2626 dataHandler : ( key : string ) => void ;
2727 instances : Set < TerminalKeypress > ;
28+ activeStack : TerminalKeypress [ ] ; // Stack of active instances, top is current owner
2829 rawModeSet : boolean ;
2930}
3031
3132const sharedInputStates = new WeakMap < Readable , SharedInputState > ( ) ;
3233
33- function getOrCreateSharedState ( input : Readable , proc : ProcessWrapper ) : SharedInputState {
34+ function getOrCreateSharedState ( input : Readable ) : SharedInputState {
3435 let state = sharedInputStates . get ( input ) ;
3536 if ( ! state ) {
3637 const dataHandler = ( key : string ) => {
3738 const currentState = sharedInputStates . get ( input ) ;
3839 if ( ! currentState ) return ;
3940
40- for ( const instance of currentState . instances ) {
41- if ( instance . isActive ( ) ) {
42- instance . handleKey ( key ) ;
41+ // Only dispatch to the top of the active stack (current owner)
42+ const owner = currentState . activeStack [ currentState . activeStack . length - 1 ] ;
43+ if ( owner ) {
44+ owner . handleKey ( key ) ;
45+
46+ // Handle Ctrl+C via the current owner's process wrapper
47+ if ( key === KEY_CODES . CTRL_C ) {
48+ owner . exitProcess ( 0 ) ;
4349 }
4450 }
45-
46- if ( key === KEY_CODES . CTRL_C ) {
47- proc . exit ( 0 ) ;
48- }
4951 } ;
5052
5153 state = {
5254 dataHandler,
5355 instances : new Set ( ) ,
56+ activeStack : [ ] ,
5457 rawModeSet : false
5558 } ;
5659
@@ -66,19 +69,28 @@ function removeFromSharedState(input: Readable, instance: TerminalKeypress): voi
6669
6770 state . instances . delete ( instance ) ;
6871
72+ // Remove from active stack as well
73+ const stackIndex = state . activeStack . indexOf ( instance ) ;
74+ if ( stackIndex !== - 1 ) {
75+ state . activeStack . splice ( stackIndex , 1 ) ;
76+ }
77+
78+ // If stack is now empty, disable raw mode
79+ if ( state . activeStack . length === 0 && state . rawModeSet ) {
80+ if ( typeof ( input as any ) . setRawMode === 'function' ) {
81+ ( input as any ) . setRawMode ( false ) ;
82+ }
83+ state . rawModeSet = false ;
84+ }
85+
6986 if ( state . instances . size === 0 ) {
7087 input . removeListener ( 'data' , state . dataHandler ) ;
7188 sharedInputStates . delete ( input ) ;
72-
73- if ( state . rawModeSet && typeof ( input as any ) . setRawMode === 'function' ) {
74- ( input as any ) . setRawMode ( false ) ;
75- }
7689 }
7790}
7891
7992export class TerminalKeypress {
8093 private listeners : Record < string , KeyHandler [ ] > = { } ;
81- private active : boolean = true ;
8294 private noTty : boolean ;
8395 private input : Readable ;
8496 private proc : ProcessWrapper ;
@@ -101,7 +113,7 @@ export class TerminalKeypress {
101113 }
102114
103115 private registerWithSharedState ( ) : void {
104- const state = getOrCreateSharedState ( this . input , this . proc ) ;
116+ const state = getOrCreateSharedState ( this . input ) ;
105117 state . instances . add ( this ) ;
106118 }
107119
@@ -110,11 +122,19 @@ export class TerminalKeypress {
110122 }
111123
112124 isActive ( ) : boolean {
113- return this . active && ! this . destroyed ;
125+ if ( this . destroyed ) return false ;
126+ const state = sharedInputStates . get ( this . input ) ;
127+ if ( ! state ) return false ;
128+ // Active only if this instance is the current owner (top of stack)
129+ return state . activeStack [ state . activeStack . length - 1 ] === this ;
130+ }
131+
132+ exitProcess ( code ?: number ) : void {
133+ this . proc . exit ( code ) ;
114134 }
115135
116136 handleKey ( key : string ) : void {
117- if ( ! this . active || this . destroyed ) return ;
137+ if ( this . destroyed ) return ;
118138 const handlers = this . listeners [ key ] ;
119139 handlers ?. forEach ( handler => handler ( ) ) ;
120140 }
@@ -140,25 +160,47 @@ export class TerminalKeypress {
140160 }
141161
142162 pause ( ) : void {
143- this . active = false ;
144- this . clearHandlers ( ) ;
163+ const state = sharedInputStates . get ( this . input ) ;
164+ if ( ! state ) return ;
165+
166+ // Remove from active stack (from anywhere, not just top)
167+ const stackIndex = state . activeStack . indexOf ( this ) ;
168+ if ( stackIndex !== - 1 ) {
169+ state . activeStack . splice ( stackIndex , 1 ) ;
170+ }
171+
172+ // If stack is now empty, disable raw mode
173+ if ( state . activeStack . length === 0 && state . rawModeSet ) {
174+ if ( this . isTTY ( ) && typeof ( this . input as any ) . setRawMode === 'function' ) {
175+ ( this . input as any ) . setRawMode ( false ) ;
176+ }
177+ state . rawModeSet = false ;
178+ }
145179 }
146180
147181 resume ( ) : void {
148- this . active = true ;
182+ if ( this . destroyed ) return ;
183+
184+ const state = sharedInputStates . get ( this . input ) ;
185+ if ( ! state ) return ;
186+
187+ // Move-to-top semantics: remove from anywhere in stack, then push to top
188+ const existingIndex = state . activeStack . indexOf ( this ) ;
189+ if ( existingIndex !== - 1 ) {
190+ state . activeStack . splice ( existingIndex , 1 ) ;
191+ }
192+ state . activeStack . push ( this ) ;
193+
194+ // Enable raw mode if TTY
149195 if ( this . isTTY ( ) && typeof ( this . input as any ) . setRawMode === 'function' ) {
150196 ( this . input as any ) . setRawMode ( true ) ;
151- const state = sharedInputStates . get ( this . input ) ;
152- if ( state ) {
153- state . rawModeSet = true ;
154- }
197+ state . rawModeSet = true ;
155198 }
156199 }
157200
158201 destroy ( ) : void {
159202 if ( this . destroyed ) return ;
160203 this . destroyed = true ;
161- this . active = false ;
162204 this . clearHandlers ( ) ;
163205
164206 removeFromSharedState ( this . input , this ) ;
0 commit comments