@@ -81,6 +81,9 @@ const kQuestionCancel = Symbol('kQuestionCancel');
81
81
// GNU readline library - keyseq-timeout is 500ms (default)
82
82
const ESCAPE_CODE_TIMEOUT = 500 ;
83
83
84
+ // Max length of the kill ring
85
+ const kMaxLengthOfKillRing = 32 ;
86
+
84
87
const kAddHistory = Symbol ( '_addHistory' ) ;
85
88
const kBeforeEdit = Symbol ( '_beforeEdit' ) ;
86
89
const kDecoder = Symbol ( '_decoder' ) ;
@@ -96,12 +99,15 @@ const kHistoryPrev = Symbol('_historyPrev');
96
99
const kInsertString = Symbol ( '_insertString' ) ;
97
100
const kLine = Symbol ( '_line' ) ;
98
101
const kLine_buffer = Symbol ( '_line_buffer' ) ;
102
+ const kKillRing = Symbol ( '_killRing' ) ;
103
+ const kKillRingCursor = Symbol ( '_killRingCursor' ) ;
99
104
const kMoveCursor = Symbol ( '_moveCursor' ) ;
100
105
const kNormalWrite = Symbol ( '_normalWrite' ) ;
101
106
const kOldPrompt = Symbol ( '_oldPrompt' ) ;
102
107
const kOnLine = Symbol ( '_onLine' ) ;
103
108
const kPreviousKey = Symbol ( '_previousKey' ) ;
104
109
const kPrompt = Symbol ( '_prompt' ) ;
110
+ const kPushToKillRing = Symbol ( '_pushToKillRing' ) ;
105
111
const kPushToUndoStack = Symbol ( '_pushToUndoStack' ) ;
106
112
const kQuestionCallback = Symbol ( '_questionCallback' ) ;
107
113
const kRedo = Symbol ( '_redo' ) ;
@@ -118,6 +124,9 @@ const kUndoStack = Symbol('_undoStack');
118
124
const kWordLeft = Symbol ( '_wordLeft' ) ;
119
125
const kWordRight = Symbol ( '_wordRight' ) ;
120
126
const kWriteToOutput = Symbol ( '_writeToOutput' ) ;
127
+ const kYank = Symbol ( '_yank' ) ;
128
+ const kYanking = Symbol ( '_yanking' ) ;
129
+ const kYankPop = Symbol ( '_yankPop' ) ;
121
130
122
131
function InterfaceConstructor ( input , output , completer , terminal ) {
123
132
this [ kSawReturnAt ] = 0 ;
@@ -211,6 +220,15 @@ function InterfaceConstructor(input, output, completer, terminal) {
211
220
this [ kRedoStack ] = [ ] ;
212
221
this . history = history ;
213
222
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
+
214
232
this . removeHistoryDuplicates = ! ! removeHistoryDuplicates ;
215
233
this . crlfDelay = crlfDelay ?
216
234
MathMax ( kMincrlfDelay , crlfDelay ) :
@@ -606,10 +624,12 @@ class Interface extends InterfaceConstructor {
606
624
this . cursor += c . length ;
607
625
this [ kRefreshLine ] ( ) ;
608
626
} else {
627
+ const oldPos = this . getCursorPos ( ) ;
609
628
this . line += c ;
610
629
this . cursor += c . length ;
630
+ const newPos = this . getCursorPos ( ) ;
611
631
612
- if ( this . getCursorPos ( ) . cols === 0 ) {
632
+ if ( oldPos . rows < newPos . rows ) {
613
633
this [ kRefreshLine ] ( ) ;
614
634
} else {
615
635
this [ kWriteToOutput ] ( c ) ;
@@ -792,17 +812,57 @@ class Interface extends InterfaceConstructor {
792
812
793
813
[ kDeleteLineLeft ] ( ) {
794
814
this [ kBeforeEdit ] ( this . line , this . cursor ) ;
815
+ const del = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
795
816
this . line = StringPrototypeSlice ( this . line , this . cursor ) ;
796
817
this . cursor = 0 ;
818
+ this [ kPushToKillRing ] ( del ) ;
797
819
this [ kRefreshLine ] ( ) ;
798
820
}
799
821
800
822
[ kDeleteLineRight ] ( ) {
801
823
this [ kBeforeEdit ] ( this . line , this . cursor ) ;
824
+ const del = StringPrototypeSlice ( this . line , this . cursor ) ;
802
825
this . line = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
826
+ this [ kPushToKillRing ] ( del ) ;
803
827
this [ kRefreshLine ] ( ) ;
804
828
}
805
829
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
+
806
866
clearLine ( ) {
807
867
this [ kMoveCursor ] ( + Infinity ) ;
808
868
this [ kWriteToOutput ] ( '\r\n' ) ;
@@ -984,6 +1044,11 @@ class Interface extends InterfaceConstructor {
984
1044
key = key || { } ;
985
1045
this [ kPreviousKey ] = key ;
986
1046
1047
+ if ( ! key . meta || key . name !== 'y' ) {
1048
+ // Reset yanking state unless we are doing yank pop.
1049
+ this [ kYanking ] = false ;
1050
+ }
1051
+
987
1052
// Activate or deactivate substring search.
988
1053
if (
989
1054
( key . name === 'up' || key . name === 'down' ) &&
@@ -1094,6 +1159,10 @@ class Interface extends InterfaceConstructor {
1094
1159
this [ kHistoryPrev ] ( ) ;
1095
1160
break ;
1096
1161
1162
+ case 'y' : // Yank killed string
1163
+ this [ kYank ] ( ) ;
1164
+ break ;
1165
+
1097
1166
case 'z' :
1098
1167
if ( process . platform === 'win32' ) break ;
1099
1168
if ( this . listenerCount ( 'SIGTSTP' ) > 0 ) {
@@ -1158,6 +1227,10 @@ class Interface extends InterfaceConstructor {
1158
1227
case 'backspace' : // Delete backwards to a word boundary
1159
1228
this [ kDeleteWordLeft ] ( ) ;
1160
1229
break ;
1230
+
1231
+ case 'y' : // Doing yank pop
1232
+ this [ kYankPop ] ( ) ;
1233
+ break ;
1161
1234
}
1162
1235
} else {
1163
1236
/* No modifier keys used */
0 commit comments