@@ -81,6 +81,9 @@ const kQuestionCancel = Symbol('kQuestionCancel');
8181// GNU readline library - keyseq-timeout is 500ms (default)
8282const ESCAPE_CODE_TIMEOUT = 500 ;
8383
84+ // Max length of the kill ring
85+ const kMaxLengthOfKillRing = 32 ;
86+
8487const kAddHistory = Symbol ( '_addHistory' ) ;
8588const kBeforeEdit = Symbol ( '_beforeEdit' ) ;
8689const kDecoder = Symbol ( '_decoder' ) ;
@@ -96,12 +99,15 @@ const kHistoryPrev = Symbol('_historyPrev');
9699const kInsertString = Symbol ( '_insertString' ) ;
97100const kLine = Symbol ( '_line' ) ;
98101const kLine_buffer = Symbol ( '_line_buffer' ) ;
102+ const kKillRing = Symbol ( '_killRing' ) ;
103+ const kKillRingCursor = Symbol ( '_killRingCursor' ) ;
99104const kMoveCursor = Symbol ( '_moveCursor' ) ;
100105const kNormalWrite = Symbol ( '_normalWrite' ) ;
101106const kOldPrompt = Symbol ( '_oldPrompt' ) ;
102107const kOnLine = Symbol ( '_onLine' ) ;
103108const kPreviousKey = Symbol ( '_previousKey' ) ;
104109const kPrompt = Symbol ( '_prompt' ) ;
110+ const kPushToKillRing = Symbol ( '_pushToKillRing' ) ;
105111const kPushToUndoStack = Symbol ( '_pushToUndoStack' ) ;
106112const kQuestionCallback = Symbol ( '_questionCallback' ) ;
107113const kRedo = Symbol ( '_redo' ) ;
@@ -118,6 +124,9 @@ const kUndoStack = Symbol('_undoStack');
118124const kWordLeft = Symbol ( '_wordLeft' ) ;
119125const kWordRight = Symbol ( '_wordRight' ) ;
120126const kWriteToOutput = Symbol ( '_writeToOutput' ) ;
127+ const kYank = Symbol ( '_yank' ) ;
128+ const kYanking = Symbol ( '_yanking' ) ;
129+ const kYankPop = Symbol ( '_yankPop' ) ;
121130
122131function InterfaceConstructor ( input , output , completer , terminal ) {
123132 this [ kSawReturnAt ] = 0 ;
@@ -211,6 +220,15 @@ function InterfaceConstructor(input, output, completer, terminal) {
211220 this [ kRedoStack ] = [ ] ;
212221 this . history = history ;
213222 this . historySize = historySize ;
223+
224+ // The kill ring is a global list of blocks of text that were previously
225+ // killed (deleted). If its size exceeds kMaxLengthOfKillRing, the oldest
226+ // element will be removed to make room for the latest deletion. With kill
227+ // ring, users are able to recall (yank) or cycle (yank pop) among previously
228+ // killed texts, quite similar to the behavior of Emacs.
229+ this [ kKillRing ] = [ ] ;
230+ this [ kKillRingCursor ] = 0 ;
231+
214232 this . removeHistoryDuplicates = ! ! removeHistoryDuplicates ;
215233 this . crlfDelay = crlfDelay ?
216234 MathMax ( kMincrlfDelay , crlfDelay ) :
@@ -606,10 +624,12 @@ class Interface extends InterfaceConstructor {
606624 this . cursor += c . length ;
607625 this [ kRefreshLine ] ( ) ;
608626 } else {
627+ const oldPos = this . getCursorPos ( ) ;
609628 this . line += c ;
610629 this . cursor += c . length ;
630+ const newPos = this . getCursorPos ( ) ;
611631
612- if ( this . getCursorPos ( ) . cols === 0 ) {
632+ if ( oldPos . rows < newPos . rows ) {
613633 this [ kRefreshLine ] ( ) ;
614634 } else {
615635 this [ kWriteToOutput ] ( c ) ;
@@ -792,17 +812,57 @@ class Interface extends InterfaceConstructor {
792812
793813 [ kDeleteLineLeft ] ( ) {
794814 this [ kBeforeEdit ] ( this . line , this . cursor ) ;
815+ const del = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
795816 this . line = StringPrototypeSlice ( this . line , this . cursor ) ;
796817 this . cursor = 0 ;
818+ this [ kPushToKillRing ] ( del ) ;
797819 this [ kRefreshLine ] ( ) ;
798820 }
799821
800822 [ kDeleteLineRight ] ( ) {
801823 this [ kBeforeEdit ] ( this . line , this . cursor ) ;
824+ const del = StringPrototypeSlice ( this . line , this . cursor ) ;
802825 this . line = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
826+ this [ kPushToKillRing ] ( del ) ;
803827 this [ kRefreshLine ] ( ) ;
804828 }
805829
830+ [ kPushToKillRing ] ( del ) {
831+ if ( ! del || del === this [ kKillRing ] [ 0 ] ) return ;
832+ ArrayPrototypeUnshift ( this [ kKillRing ] , del ) ;
833+ this [ kKillRingCursor ] = 0 ;
834+ while ( this [ kKillRing ] . length > kMaxLengthOfKillRing )
835+ ArrayPrototypePop ( this [ kKillRing ] ) ;
836+ }
837+
838+ [ kYank ] ( ) {
839+ if ( this [ kKillRing ] . length > 0 ) {
840+ this [ kYanking ] = true ;
841+ this [ kInsertString ] ( this [ kKillRing ] [ this [ kKillRingCursor ] ] ) ;
842+ }
843+ }
844+
845+ [ kYankPop ] ( ) {
846+ if ( ! this [ kYanking ] ) {
847+ return ;
848+ }
849+ if ( this [ kKillRing ] . length > 1 ) {
850+ const lastYank = this [ kKillRing ] [ this [ kKillRingCursor ] ] ;
851+ this [ kKillRingCursor ] ++ ;
852+ if ( this [ kKillRingCursor ] >= this [ kKillRing ] . length ) {
853+ this [ kKillRingCursor ] = 0 ;
854+ }
855+ const currentYank = this [ kKillRing ] [ this [ kKillRingCursor ] ] ;
856+ const head =
857+ StringPrototypeSlice ( this . line , 0 , this . cursor - lastYank . length ) ;
858+ const tail =
859+ StringPrototypeSlice ( this . line , this . cursor ) ;
860+ this . line = head + currentYank + tail ;
861+ this . cursor = head . length + currentYank . length ;
862+ this [ kRefreshLine ] ( ) ;
863+ }
864+ }
865+
806866 clearLine ( ) {
807867 this [ kMoveCursor ] ( + Infinity ) ;
808868 this [ kWriteToOutput ] ( '\r\n' ) ;
@@ -984,6 +1044,11 @@ class Interface extends InterfaceConstructor {
9841044 key = key || { } ;
9851045 this [ kPreviousKey ] = key ;
9861046
1047+ if ( ! key . meta || key . name !== 'y' ) {
1048+ // Reset yanking state unless we are doing yank pop.
1049+ this [ kYanking ] = false ;
1050+ }
1051+
9871052 // Activate or deactivate substring search.
9881053 if (
9891054 ( key . name === 'up' || key . name === 'down' ) &&
@@ -1094,6 +1159,10 @@ class Interface extends InterfaceConstructor {
10941159 this [ kHistoryPrev ] ( ) ;
10951160 break ;
10961161
1162+ case 'y' : // Yank killed string
1163+ this [ kYank ] ( ) ;
1164+ break ;
1165+
10971166 case 'z' :
10981167 if ( process . platform === 'win32' ) break ;
10991168 if ( this . listenerCount ( 'SIGTSTP' ) > 0 ) {
@@ -1158,6 +1227,10 @@ class Interface extends InterfaceConstructor {
11581227 case 'backspace' : // Delete backwards to a word boundary
11591228 this [ kDeleteWordLeft ] ( ) ;
11601229 break ;
1230+
1231+ case 'y' : // Doing yank pop
1232+ this [ kYankPop ] ( ) ;
1233+ break ;
11611234 }
11621235 } else {
11631236 /* No modifier keys used */
0 commit comments