@@ -11,6 +11,8 @@ interface RawModeCapable {
1111 resume ?: ( ) => unknown ;
1212}
1313
14+ const pendingPromptInput = new WeakMap < NodeJS . ReadableStream , string > ( ) ;
15+
1416export async function promptText ( question : string , streams : PromptStreams = { } ) : Promise < string > {
1517 const input = streams . input ?? process . stdin ;
1618 const output = streams . output ?? process . stdout ;
@@ -41,18 +43,29 @@ function readLine(
4143 return new Promise < string > ( ( resolve , reject ) => {
4244 let buffer = '' ;
4345 let resolved = false ;
46+ let listening = false ;
4447
4548 const onData = ( chunk : Buffer | string ) : void => {
4649 const str = typeof chunk === 'string' ? chunk : chunk . toString ( 'utf-8' ) ;
47- for ( const ch of str ) {
48- const code = ch . charCodeAt ( 0 ) ;
50+ processText ( str ) ;
51+ } ;
52+
53+ const processText = ( str : string ) : void => {
54+ for ( let i = 0 ; i < str . length ; i += 1 ) {
55+ const code = str . charCodeAt ( i ) ;
4956 // Enter (CR or LF)
5057 if ( code === 13 || code === 10 ) {
58+ let nextIndex = i + 1 ;
59+ if ( code === 13 && str . charCodeAt ( nextIndex ) === 10 ) {
60+ nextIndex += 1 ;
61+ }
62+ savePendingInput ( str . slice ( nextIndex ) ) ;
5163 finish ( ) ;
5264 return ;
5365 }
5466 // Ctrl-C
5567 if ( code === 3 ) {
68+ savePendingInput ( '' ) ;
5669 cleanup ( ) ;
5770 output . write ( '\n' ) ;
5871 if ( ! resolved ) {
@@ -71,7 +84,7 @@ function readLine(
7184 }
7285 // Drop other control chars
7386 if ( code < 32 ) continue ;
74- buffer += ch ;
87+ buffer += str [ i ] ;
7588 if ( mask ) output . write ( '*' ) ;
7689 }
7790 } ;
@@ -89,9 +102,12 @@ function readLine(
89102 } ;
90103
91104 const cleanup = ( ) : void => {
92- input . off ( 'data' , onData ) ;
93- input . off ( 'end' , onEnd ) ;
94- input . off ( 'error' , onError ) ;
105+ if ( listening ) {
106+ input . off ( 'data' , onData ) ;
107+ input . off ( 'end' , onEnd ) ;
108+ input . off ( 'error' , onError ) ;
109+ listening = false ;
110+ }
95111 const pausable = input as { pause ?: ( ) => unknown } ;
96112 if ( typeof pausable . pause === 'function' ) pausable . pause ( ) ;
97113 } ;
@@ -105,12 +121,37 @@ function readLine(
105121 }
106122 } ;
107123
124+ const savePendingInput = ( text : string ) : void => {
125+ if ( text . length > 0 ) {
126+ pendingPromptInput . set ( input , text ) ;
127+ } else {
128+ pendingPromptInput . delete ( input ) ;
129+ }
130+ } ;
131+
132+ const pending = pendingPromptInput . get ( input ) ;
133+ if ( pending !== undefined ) {
134+ pendingPromptInput . delete ( input ) ;
135+ processText ( pending ) ;
136+ if ( resolved ) return ;
137+ if ( isInputEnded ( input ) ) {
138+ finish ( ) ;
139+ return ;
140+ }
141+ }
142+
108143 input . on ( 'data' , onData ) ;
109144 input . on ( 'end' , onEnd ) ;
110145 input . on ( 'error' , onError ) ;
146+ listening = true ;
111147 const resumable = input as { resume ?: ( ) => unknown } ;
112148 if ( typeof resumable . resume === 'function' ) resumable . resume ( ) ;
113149 } ) ;
114150}
115151
152+ function isInputEnded ( input : NodeJS . ReadableStream ) : boolean {
153+ const state = input as { readableEnded ?: boolean ; destroyed ?: boolean ; closed ?: boolean } ;
154+ return state . readableEnded === true || state . destroyed === true || state . closed === true ;
155+ }
156+
116157export type { Writable } ;
0 commit comments