@@ -433,14 +433,6 @@ if (options.version || options.V) {
433
433
setupHistory ( rl , terminal ? env . LIPS_REPL_HISTORY : '' , run_repl ) ;
434
434
}
435
435
436
- function unify_prompt ( a , b ) {
437
- var result = a ;
438
- if ( a . length < b . length ) {
439
- result += ' ' . repeat ( b . length - a . length ) ;
440
- }
441
- return result ;
442
- }
443
-
444
436
function is_open ( token ) {
445
437
return [ '(' , '[' ] . includes ( token ) ;
446
438
}
@@ -449,25 +441,83 @@ function is_close(token) {
449
441
return [ ')' , ']' ] . includes ( token ) ;
450
442
}
451
443
452
- function strip_ansi ( string ) {
453
- return string . replace ( / \x1b \[ [ 0 - 9 ; ] * m / g, '' ) ;
444
+ // find matching open parentheses. The last token is always
445
+ // a closing parenthesis
446
+ function matched_token ( code ) {
447
+ let count = 0 ;
448
+ // true to tokenize return tokens with meta data
449
+ return tokenize ( code , true ) . reverse ( ) . find ( token => {
450
+ if ( is_open ( token . token ) ) {
451
+ count -- ;
452
+ } else if ( is_close ( token . token ) ) {
453
+ count ++ ;
454
+ }
455
+ return is_open ( token . token ) && count === 0 ;
456
+ } ) ;
457
+ }
458
+
459
+ // hightlight the open parentheses based on token metadata
460
+ function mark_paren ( code , token ) {
461
+ const re = / ( \x1b \[ [ 0 - 9 ; ] * m ) / ;
462
+ let str_len = 0 , found ;
463
+ return code . split ( re ) . reduce ( ( acc , str ) => {
464
+ let result
465
+ if ( str . match ( re ) ) {
466
+ result = str ;
467
+ } else if ( found ) {
468
+ result = str ;
469
+ } else if ( str_len + str . length <= token . offset ) {
470
+ result = str ;
471
+ str_len += str . length ;
472
+ } else {
473
+ const pos = token . offset - str_len ;
474
+ const before = str . substring ( 0 , pos ) ;
475
+ const after = str . substring ( pos + 1 ) ;
476
+ result = `${ before } \x1b[7m(\x1b[m${ after } ` ;
477
+ found = true ;
478
+ }
479
+ return acc + result ;
480
+ } , '' ) ;
481
+ }
482
+
483
+ // function accept ANSI (syntax highlighted code) and
484
+ // return ANSI escapes to overwrite the code
485
+ // this is needed to make sure that syntax hightlighting
486
+ // is always correct
487
+ function ansi_rewrite_above ( ansi_code ) {
488
+ const lines = ansi_code . split ( '\n' ) ;
489
+ const stdout = lines . map ( ( line , i ) => {
490
+ const prefix = i === 0 ? prompt : continue_prompt ;
491
+ return '\x1b[K' + prefix + line ;
492
+ } ) . join ( '\n' ) ;
493
+ const len = lines . length ;
494
+ // overwrite all lines to get rid of any artifacts left my stdin
495
+ // mostly because of parenthesis matching
496
+ return `\x1b[${ len } F${ stdout } \n` ;
454
497
}
455
498
456
499
function run_repl ( err , rl ) {
457
500
const dynamic = options . d || options . dynamic ;
458
501
let cmd = '' ;
459
502
let multiline = false ;
460
503
let resolve ;
504
+ const brackets_re = / \x1b \[ ( 2 0 0 | 2 0 1 ) ~ / g;
461
505
// we use promise loop to fix issue when copy paste list of S-Expression
462
506
let prev_eval = Promise . resolve ( ) ;
463
507
if ( process . stdin . isTTY ) {
464
508
rl . prompt ( ) ;
465
509
}
466
510
let prev_line ;
467
511
const is_emacs = process . env . INSIDE_EMACS ;
512
+ function is_brackets_mode ( ) {
513
+ return cmd . match ( brackets_re ) ;
514
+ }
468
515
function continue_multiline ( code ) {
469
516
multiline = true ;
470
- if ( cmd . match ( / \x1b \[ 2 0 0 ~ / ) || ! supports_paste_brackets ) {
517
+ // we don't do indentation for paste bracket mode
518
+ // indentation will also not work for old Node.js
519
+ // becase it's too problematice to make it right
520
+ if ( is_brackets_mode ( ) || ! supports_paste_brackets ) {
471
521
rl . prompt ( ) ;
472
522
if ( is_emacs ) {
473
523
rl . setPrompt ( '' ) ;
@@ -495,98 +545,75 @@ function run_repl(err, rl) {
495
545
}
496
546
}
497
547
}
498
- function matched_token ( code ) {
499
- let count = 0 ;
500
- return tokenize ( code , true ) . reverse ( ) . find ( token => {
501
- if ( is_open ( token . token ) ) {
502
- count -- ;
503
- } else if ( is_close ( token . token ) ) {
504
- count ++ ;
505
- }
506
- return is_open ( token . token ) && count === 0 ;
507
- } ) ;
508
- }
509
- function mark_paren ( code , token ) {
510
- const re = / ( \x1b \[ [ 0 - 9 ; ] * m ) / ;
511
- let str_len = 0 , found ;
512
- return code . split ( re ) . reduce ( ( acc , str ) => {
513
- let result
514
- if ( str . match ( re ) ) {
515
- result = str ;
516
- } else if ( found ) {
517
- result = str ;
518
- } else if ( str_len + str . length <= token . offset ) {
519
- result = str ;
520
- str_len += str . length ;
521
- } else {
522
- const pos = token . offset - str_len ;
523
- const before = str . substring ( 0 , pos ) ;
524
- const after = str . substring ( pos + 1 ) ;
525
- result = `${ before } \x1b[7m(\x1b[m${ after } ` ;
526
- found = true ;
527
- }
528
- return acc + result ;
529
- } , '' ) ;
530
- }
531
- function ansi_rewrite_above ( ansi_code ) {
532
- const lines = ansi_code . split ( '\n' ) ;
533
- const stdout = lines . map ( ( line , i ) => {
534
- const prefix = i === 0 ? prompt : continue_prompt ;
535
- return '\x1b[K' + prefix + line ;
536
- } ) . join ( '\n' ) ;
537
- const len = lines . length ;
538
- // overwrite all lines to get rid of any artifacts left my stdin
539
- // mostly because of parenthesis matching
540
- return `\x1b[${ len } F${ stdout } \n` ;
548
+ function char_before_cursor ( ) {
549
+ return rl . line [ rl . cursor - 1 ] ;
541
550
}
542
551
rl . _writeToOutput = function _writeToOutput ( string ) {
543
552
try {
544
553
const prefix = multiline ? continue_prompt : prompt ;
545
554
const current_line = prefix + rl . line ;
546
555
let code = scheme ( string ) ;
547
- let code_above = cmd && scheme ( cmd . substring ( 0 , cmd . length - 1 ) ) ;
548
- if ( rl . line [ rl . cursor - 1 ] === ')' ) {
549
- const substring = rl . line . substring ( 0 , rl . cursor ) ;
550
- const input = prefix + substring ;
551
- const token = matched_token ( input ) ;
552
- if ( token ) {
553
- code = mark_paren ( code , token ) ;
554
- } else if ( cmd ) {
555
- const input = cmd + rl . line ;
556
+ if ( ! is_brackets_mode ( ) ) {
557
+ // we remove traling newline from cmd
558
+ let code_above = cmd && scheme ( cmd . substring ( 0 , cmd . length - 1 ) ) ;
559
+ if ( char_before_cursor ( ) === ')' ) {
560
+ const substring = rl . line . substring ( 0 , rl . cursor ) ;
561
+ const input = prefix + substring ;
556
562
const token = matched_token ( input ) ;
557
563
if ( token ) {
558
- code_above = mark_paren ( code_above , token ) ;
564
+ code = mark_paren ( code , token ) ;
565
+ } else if ( cmd ) {
566
+ const input = cmd + rl . line ;
567
+ // we match paren above the current line
568
+ // but we need whole code with rl.line
569
+ // so we need to ignore rl.line
570
+ const token = matched_token ( input ) ;
571
+ if ( token ) {
572
+ // code_above don't include the current line that
573
+ // is added after user press enter
574
+ code_above = mark_paren ( code_above , token ) ;
575
+ }
559
576
}
560
577
}
578
+ // ignore the process when user press enter
579
+ if ( code_above && ! string . match ( / ^ [ \n \r ] + $ / ) ) {
580
+ // overwrite lines above the cursor this is side effect
581
+ process . stdout . write ( ansi_rewrite_above ( code_above ) ) ;
582
+ }
561
583
}
562
- // ignore the process when user press enter
563
- if ( code_above && ! string . match ( / ^ [ \n \r ] + $ / ) ) {
564
- process . stdout . write ( ansi_rewrite_above ( code_above ) ) ;
565
- }
584
+ // this always need to be executed inside rl._writeToOutput
585
+ // even if nothing changes, this make sure that the input
586
+ // stay intact while editing the command line
566
587
rl . output . write ( code ) ;
567
588
} catch ( e ) {
568
589
console . error ( e ) ;
569
590
}
570
591
} ;
571
592
process . stdin . on ( 'keypress' , ( c , k ) => {
572
593
setTimeout ( function ( ) {
573
- rl . _refreshLine ( ) ; // force refresh colors
594
+ // we force triggering rl._writeToOutput function
595
+ // so we have the change to syntax highlight the command line
596
+ // this nees to happen on next tick so the string have time
597
+ // to updated with a given key
598
+ rl . _refreshLine ( ) ;
574
599
} , 0 ) ;
575
600
} ) ;
576
601
bootstrap ( interp ) . then ( function ( ) {
577
602
if ( supports_paste_brackets ) {
603
+ // this make sure that the paste brackets ANSI escape
604
+ // is added to cmd so they can be processed in 'line' event
578
605
process . stdin . on ( 'keypress' , ( c , k ) => {
579
606
if ( k ?. name ?. match ( / ^ p a s t e - / ) ) {
580
607
cmd += k . sequence ;
581
608
}
582
609
} ) ;
610
+ // enable paste bracket mode by the terminal
583
611
process . stdout . write ( '\x1b[?2004h' ) ;
584
612
}
585
- const re = / \x1b \[ ( 2 0 0 | 2 0 1 ) ~ / g;
586
613
rl . on ( 'line' , function ( line ) {
587
614
try {
588
615
cmd += line ;
589
- const code = cmd . replace ( re , '' ) ;
616
+ const code = cmd . replace ( brackets_re , '' ) ;
590
617
if ( cmd . match ( / \x1b \[ 2 0 1 ~ $ / ) ) {
591
618
cmd = code ;
592
619
}
0 commit comments