1- import { execa , ExecaError } from "execa"
1+ import { execa , ExecaError , ResultPromise } from "execa"
22import psTree from "ps-tree"
33import process from "process"
44
@@ -10,6 +10,17 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
1010 private aborted = false
1111 private pid ?: number
1212
13+ private subprocess ?: ResultPromise < {
14+ shell : true
15+ cwd : string
16+ all : true
17+ stdin : "pipe"
18+ } >
19+
20+ private waitingForInput = false
21+ private lastOutputAt = 0
22+ private inputDetectionTimer ?: NodeJS . Timeout
23+
1324 constructor ( terminal : RooTerminal ) {
1425 super ( )
1526
@@ -35,22 +46,22 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
3546
3647 try {
3748 this . isHot = true
49+ this . lastOutputAt = Date . now ( )
3850
39- const subprocess = execa ( {
40- shell : true ,
41- cwd : this . terminal . getCurrentWorkingDirectory ( ) ,
42- all : true ,
43- } ) `${ command } `
44-
45- this . pid = subprocess . pid
46- const stream = subprocess . iterable ( { from : "all" , preserveNewlines : true } )
47- this . terminal . setActiveStream ( stream , subprocess . pid )
51+ const cwd = this . terminal . getCurrentWorkingDirectory ( )
52+ this . subprocess = execa ( { shell : true , cwd, all : true , stdin : "pipe" } ) `${ command } `
53+ this . pid = this . subprocess . pid
54+ const stream = this . subprocess . iterable ( { from : "all" , preserveNewlines : true } )
55+ this . terminal . setActiveStream ( stream , this . subprocess . pid )
4856
4957 for await ( const line of stream ) {
5058 if ( this . aborted ) {
5159 break
5260 }
5361
62+ this . lastOutputAt = Date . now ( )
63+ this . waitingForInput = false
64+
5465 this . fullOutput += line
5566
5667 const now = Date . now ( )
@@ -61,6 +72,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
6172 }
6273
6374 this . startHotTimer ( line )
75+ this . startInputDetectionTimer ( )
6476 }
6577
6678 if ( this . aborted ) {
@@ -69,15 +81,17 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
6981 const kill = new Promise < void > ( ( resolve ) => {
7082 timeoutId = setTimeout ( ( ) => {
7183 try {
72- subprocess . kill ( "SIGKILL" )
84+ if ( this . subprocess ) {
85+ this . subprocess . kill ( "SIGKILL" )
86+ }
7387 } catch ( e ) { }
7488
7589 resolve ( )
7690 } , 5_000 )
7791 } )
7892
7993 try {
80- await Promise . race ( [ subprocess , kill ] )
94+ await Promise . race ( [ this . subprocess , kill ] )
8195 } catch ( error ) {
8296 console . log (
8397 `[ExecaTerminalProcess] subprocess termination error: ${ error instanceof Error ? error . message : String ( error ) } ` ,
@@ -105,6 +119,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
105119 this . terminal . setActiveStream ( undefined )
106120 this . emitRemainingBufferIfListening ( )
107121 this . stopHotTimer ( )
122+ this . stopInputDetectionTimer ( )
108123 this . emit ( "completed" , this . fullOutput )
109124 this . emit ( "continue" )
110125 }
@@ -113,6 +128,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
113128 this . isListening = false
114129 this . removeAllListeners ( "line" )
115130 this . emit ( "continue" )
131+ this . stopInputDetectionTimer ( )
116132 }
117133
118134 public override abort ( ) {
@@ -148,6 +164,8 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
148164 )
149165 }
150166 }
167+
168+ this . stopInputDetectionTimer ( )
151169 }
152170
153171 public override hasUnretrievedOutput ( ) {
@@ -165,11 +183,6 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
165183 index ++
166184 this . lastRetrievedIndex += index
167185
168- // console.log(
169- // `[ExecaTerminalProcess#getUnretrievedOutput] fullOutput.length=${this.fullOutput.length} lastRetrievedIndex=${this.lastRetrievedIndex}`,
170- // output.slice(0, index),
171- // )
172-
173186 return output . slice ( 0 , index )
174187 }
175188
@@ -184,4 +197,40 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
184197 this . emit ( "line" , output )
185198 }
186199 }
200+
201+ private startInputDetectionTimer ( ) {
202+ this . stopInputDetectionTimer ( )
203+
204+ this . inputDetectionTimer = setInterval ( ( ) => {
205+ const now = Date . now ( )
206+
207+ if ( now - this . lastOutputAt > 500 && this . subprocess && ! this . subprocess . killed ) {
208+ if ( ! this . waitingForInput ) {
209+ this . waitingForInput = true
210+ this . emit ( "input_required" )
211+ }
212+ }
213+ } , 100 )
214+ }
215+
216+ private stopInputDetectionTimer ( ) {
217+ if ( this . inputDetectionTimer ) {
218+ clearInterval ( this . inputDetectionTimer )
219+ this . inputDetectionTimer = undefined
220+ }
221+ }
222+
223+ public override sendInput ( input : string ) : void {
224+ if ( this . subprocess && this . subprocess . stdin ) {
225+ this . subprocess . stdin . write ( input + "\n" )
226+ this . waitingForInput = false
227+ this . lastOutputAt = Date . now ( )
228+ } else {
229+ console . error ( "[ExecaTerminalProcess] Cannot write to stdin: subprocess or stdin not available" )
230+ }
231+ }
232+
233+ public override isWaitingForInput ( ) : boolean {
234+ return this . waitingForInput
235+ }
187236}
0 commit comments