@@ -140,170 +140,178 @@ if (process.env.TERM === 'dumb') {
140140}
141141
142142{
143- class VirtualScreen {
144- constructor ( ) {
145- this . rows = [ [ ] ] ;
146- this . row = 0 ;
147- this . col = 0 ;
148- }
149-
150- ensureRow ( row ) {
151- while ( this . rows . length <= row ) this . rows . push ( [ ] ) ;
152- }
153-
154- setChar ( row , col , ch ) {
155- this . ensureRow ( row ) ;
156- const target = this . rows [ row ] ;
157- while ( target . length <= col ) target . push ( ' ' ) ;
158- target [ col ] = ch ;
159- }
160-
161- clearLineRight ( ) {
162- this . ensureRow ( this . row ) ;
163- const target = this . rows [ this . row ] ;
164- if ( this . col < target . length ) {
165- target . length = this . col ;
166- }
167- }
168-
169- clearFromCursor ( ) {
170- this . clearLineRight ( ) ;
171- if ( this . row + 1 < this . rows . length ) {
172- this . rows . length = this . row + 1 ;
173- }
174- }
175-
176- moveCursor ( dx , dy ) {
177- this . row = Math . max ( 0 , this . row + dy ) ;
178- this . ensureRow ( this . row ) ;
179- this . col = Math . max ( 0 , this . col + dx ) ;
180- }
181-
182- handleEscape ( params , code ) {
183- switch ( code ) {
184- case 'A' : // Cursor Up
185- this . moveCursor ( 0 , - ( Number ( params ) || 1 ) ) ;
186- break ;
187- case 'B' : // Cursor Down
188- this . moveCursor ( 0 , Number ( params ) || 1 ) ;
189- break ;
190- case 'C' : // Cursor Forward
191- this . moveCursor ( Number ( params ) || 1 , 0 ) ;
192- break ;
193- case 'D' : // Cursor Backward
194- this . moveCursor ( - ( Number ( params ) || 1 ) , 0 ) ;
195- break ;
196- case 'G' : // Cursor Horizontal Absolute
197- this . col = Math . max ( 0 , ( Number ( params ) || 1 ) - 1 ) ;
198- break ;
199- case 'H' :
200- case 'f' : { // Cursor Position
201- const [ row , col ] = params . split ( ';' ) . map ( ( n ) => Number ( n ) || 1 ) ;
202- this . row = Math . max ( 0 , row - 1 ) ;
203- this . col = Math . max ( 0 , ( col ?? 1 ) - 1 ) ;
204- this . ensureRow ( this . row ) ;
205- break ;
206- }
207- case 'J' :
208- this . clearFromCursor ( ) ;
209- break ;
210- case 'K' :
211- this . clearLineRight ( ) ;
212- break ;
213- default :
214- break ;
215- }
216- }
217-
218- write ( chunk ) {
219- for ( let i = 0 ; i < chunk . length ; i ++ ) {
220- const ch = chunk [ i ] ;
221- if ( ch === '\r' ) {
222- this . col = 0 ;
223- continue ;
224- }
225- if ( ch === '\n' ) {
226- this . row ++ ;
227- this . col = 0 ;
228- this . ensureRow ( this . row ) ;
229- continue ;
230- }
231- if ( ch === '\u001b' && chunk [ i + 1 ] === '[' ) {
232- const match = / ^ \u001b \[ ( [ 0 - 9 ; ] * ) ( [ A - Z a - z ] ) / . exec ( chunk . slice ( i ) ) ;
233- if ( match ) {
234- this . handleEscape ( match [ 1 ] , match [ 2 ] ) ;
235- i += match [ 0 ] . length - 1 ;
236- continue ;
237- }
238- }
239- this . setChar ( this . row , this . col , ch ) ;
240- this . col ++ ;
241- }
242- }
243-
244- getLines ( ) {
245- return this . rows . map ( ( row ) => row . join ( '' ) . trimEnd ( ) ) ;
246- }
247- }
248-
249- class FakeTTY extends EventEmitter {
250- columns = 80 ;
251- rows = 24 ;
252- isTTY = true ;
253-
254- constructor ( screen ) {
255- super ( ) ;
256- this . screen = screen ;
257- }
258-
259- write ( data ) {
260- this . screen . write ( data ) ;
261- return true ;
262- }
263-
264- resume ( ) { }
265-
266- pause ( ) { }
267-
268- end ( ) { }
269-
270- setRawMode ( mode ) {
271- this . isRaw = mode ;
272- }
273- }
274-
275- const screen = new VirtualScreen ( ) ;
276- const fi = new FakeTTY ( screen ) ;
277-
278- const rli = new readline . Interface ( {
279- input : fi ,
280- output : fi ,
281- terminal : true ,
282- completer : ( line ) => [ [ 'foobar' , 'foobaz' ] , line ] ,
283- } ) ;
284-
285- const promptLines = [ 'multiline' , 'prompt' , 'eats' , 'output' , '> ' ] ;
286- rli . setPrompt ( promptLines . join ( '\n' ) ) ;
287- rli . prompt ( ) ;
288-
289- [ 'f' , 'o' , 'o' , '\t' , '\t' ] . forEach ( ( ch ) => fi . emit ( 'data' , ch ) ) ;
290-
291- const display = screen . getLines ( ) ;
292-
293- assert . strictEqual ( display [ 0 ] , 'multiline' ) ;
294- assert . strictEqual ( display [ 1 ] , 'prompt' ) ;
295- assert . strictEqual ( display [ 2 ] , 'eats' ) ;
296- assert . strictEqual ( display [ 3 ] , 'output' ) ;
297-
298- const inputLineIndex = 4 ;
299- assert . ok (
300- display [ inputLineIndex ] . includes ( '> fooba' ) ,
301- 'prompt line should keep completed input' ,
302- ) ;
303-
304- const completionLineExists =
305- display . some ( ( l ) => l . includes ( 'foobar' ) && l . includes ( 'foobaz' ) ) ;
306- assert . ok ( completionLineExists , 'completion list should be visible' ) ;
307-
308- rli . close ( ) ;
143+ class VirtualScreen {
144+ constructor ( ) {
145+ this . rows = [ [ ] ] ;
146+ this . row = 0 ;
147+ this . col = 0 ;
148+ }
149+
150+ ensureRow ( row ) {
151+ while ( this . rows . length <= row ) this . rows . push ( [ ] ) ;
152+ }
153+
154+ setChar ( row , col , ch ) {
155+ this . ensureRow ( row ) ;
156+ const target = this . rows [ row ] ;
157+ while ( target . length <= col ) target . push ( ' ' ) ;
158+ target [ col ] = ch ;
159+ }
160+
161+ clearLineRight ( ) {
162+ this . ensureRow ( this . row ) ;
163+ const target = this . rows [ this . row ] ;
164+ if ( this . col < target . length ) {
165+ target . length = this . col ;
166+ }
167+ }
168+
169+ clearFromCursor ( ) {
170+ this . clearLineRight ( ) ;
171+ if ( this . row + 1 < this . rows . length ) {
172+ this . rows . length = this . row + 1 ;
173+ }
174+ }
175+
176+ moveCursor ( dx , dy ) {
177+ this . row = Math . max ( 0 , this . row + dy ) ;
178+ this . ensureRow ( this . row ) ;
179+ this . col = Math . max ( 0 , this . col + dx ) ;
180+ }
181+
182+ handleEscape ( params , code ) {
183+ switch ( code ) {
184+ case 'A' : // Cursor Up
185+ this . moveCursor ( 0 , - ( Number ( params ) || 1 ) ) ;
186+ break ;
187+ case 'B' : // Cursor Down
188+ this . moveCursor ( 0 , Number ( params ) || 1 ) ;
189+ break ;
190+ case 'C' : // Cursor Forward
191+ this . moveCursor ( Number ( params ) || 1 , 0 ) ;
192+ break ;
193+ case 'D' : // Cursor Backward
194+ this . moveCursor ( - ( Number ( params ) || 1 ) , 0 ) ;
195+ break ;
196+ case 'G' : // Cursor Horizontal Absolute
197+ this . col = Math . max ( 0 , ( Number ( params ) || 1 ) - 1 ) ;
198+ break ;
199+ case 'H' :
200+ case 'f' : { // Cursor Position
201+ const [ row , col ] = params . split ( ';' ) . map ( ( n ) => Number ( n ) || 1 ) ;
202+ this . row = Math . max ( 0 , row - 1 ) ;
203+ this . col = Math . max ( 0 , ( col ?? 1 ) - 1 ) ;
204+ this . ensureRow ( this . row ) ;
205+ break ;
206+ }
207+ case 'J' :
208+ this . clearFromCursor ( ) ;
209+ break ;
210+ case 'K' :
211+ this . clearLineRight ( ) ;
212+ break ;
213+ default :
214+ break ;
215+ }
216+ }
217+
218+ write ( chunk ) {
219+ for ( let i = 0 ; i < chunk . length ; i ++ ) {
220+ const ch = chunk [ i ] ;
221+ if ( ch === '\r' ) {
222+ this . col = 0 ;
223+ continue ;
224+ }
225+ if ( ch === '\n' ) {
226+ this . row ++ ;
227+ this . col = 0 ;
228+ this . ensureRow ( this . row ) ;
229+ continue ;
230+ }
231+ if ( ch === '\u001b' && chunk [ i + 1 ] === '[' ) {
232+ let j = i + 2 ;
233+ let params = '' ;
234+ while ( j < chunk . length ) {
235+ const code = chunk [ j ] ;
236+ if ( ( code >= '0' && code <= '9' ) || code === ';' ) {
237+ params += code ;
238+ j ++ ;
239+ continue ;
240+ }
241+ this . handleEscape ( params , code ) ;
242+ i = j ;
243+ break ;
244+ }
245+ continue ;
246+ }
247+ this . setChar ( this . row , this . col , ch ) ;
248+ this . col ++ ;
249+ }
250+ }
251+
252+ getLines ( ) {
253+ return this . rows . map ( ( row ) => row . join ( '' ) . trimEnd ( ) ) ;
254+ }
255+ }
256+
257+ class FakeTTY extends EventEmitter {
258+ columns = 80 ;
259+ rows = 24 ;
260+ isTTY = true ;
261+
262+ constructor ( screen ) {
263+ super ( ) ;
264+ this . screen = screen ;
265+ }
266+
267+ write ( data ) {
268+ this . screen . write ( data ) ;
269+ return true ;
270+ }
271+
272+ resume ( ) { }
273+
274+ pause ( ) { }
275+
276+ end ( ) { }
277+
278+ setRawMode ( mode ) {
279+ this . isRaw = mode ;
280+ }
281+ }
282+
283+ const screen = new VirtualScreen ( ) ;
284+ const fi = new FakeTTY ( screen ) ;
285+
286+ const rli = new readline . Interface ( {
287+ input : fi ,
288+ output : fi ,
289+ terminal : true ,
290+ completer : ( line ) => [ [ 'foobar' , 'foobaz' ] , line ] ,
291+ } ) ;
292+
293+ const promptLines = [ 'multiline' , 'prompt' , 'eats' , 'output' , '> ' ] ;
294+ rli . setPrompt ( promptLines . join ( '\n' ) ) ;
295+ rli . prompt ( ) ;
296+
297+ [ 'f' , 'o' , 'o' , '\t' , '\t' ] . forEach ( ( ch ) => fi . emit ( 'data' , ch ) ) ;
298+
299+ const display = screen . getLines ( ) ;
300+
301+ assert . strictEqual ( display [ 0 ] , 'multiline' ) ;
302+ assert . strictEqual ( display [ 1 ] , 'prompt' ) ;
303+ assert . strictEqual ( display [ 2 ] , 'eats' ) ;
304+ assert . strictEqual ( display [ 3 ] , 'output' ) ;
305+
306+ const inputLineIndex = 4 ;
307+ assert . ok (
308+ display [ inputLineIndex ] . includes ( '> fooba' ) ,
309+ 'prompt line should keep completed input' ,
310+ ) ;
311+
312+ const completionLineExists =
313+ display . some ( ( l ) => l . includes ( 'foobar' ) && l . includes ( 'foobaz' ) ) ;
314+ assert . ok ( completionLineExists , 'completion list should be visible' ) ;
315+
316+ rli . close ( ) ;
309317}
0 commit comments