7
7
ArrayPrototypeJoin,
8
8
ArrayPrototypeMap,
9
9
ArrayPrototypePop,
10
+ ArrayPrototypePush,
10
11
ArrayPrototypeReverse,
11
12
ArrayPrototypeSplice,
13
+ ArrayPrototypeShift,
12
14
ArrayPrototypeUnshift,
13
15
DateNow,
14
16
FunctionPrototypeCall,
@@ -68,6 +70,7 @@ const { StringDecoder } = require('string_decoder');
68
70
let Readable ;
69
71
70
72
const kHistorySize = 30 ;
73
+ const kMaxUndoRedoStackSize = 2048 ;
71
74
const kMincrlfDelay = 100 ;
72
75
// \r\n, \n, or \r followed by something other than \n
73
76
const lineEnding = / \r ? \n | \r (? ! \n ) / ;
@@ -79,6 +82,7 @@ const kQuestionCancel = Symbol('kQuestionCancel');
79
82
const ESCAPE_CODE_TIMEOUT = 500 ;
80
83
81
84
const kAddHistory = Symbol ( '_addHistory' ) ;
85
+ const kBeforeEdit = Symbol ( '_beforeEdit' ) ;
82
86
const kDecoder = Symbol ( '_decoder' ) ;
83
87
const kDeleteLeft = Symbol ( '_deleteLeft' ) ;
84
88
const kDeleteLineLeft = Symbol ( '_deleteLineLeft' ) ;
@@ -98,14 +102,19 @@ const kOldPrompt = Symbol('_oldPrompt');
98
102
const kOnLine = Symbol ( '_onLine' ) ;
99
103
const kPreviousKey = Symbol ( '_previousKey' ) ;
100
104
const kPrompt = Symbol ( '_prompt' ) ;
105
+ const kPushToUndoStack = Symbol ( '_pushToUndoStack' ) ;
101
106
const kQuestionCallback = Symbol ( '_questionCallback' ) ;
107
+ const kRedo = Symbol ( '_redo' ) ;
108
+ const kRedoStack = Symbol ( '_redoStack' ) ;
102
109
const kRefreshLine = Symbol ( '_refreshLine' ) ;
103
110
const kSawKeyPress = Symbol ( '_sawKeyPress' ) ;
104
111
const kSawReturnAt = Symbol ( '_sawReturnAt' ) ;
105
112
const kSetRawMode = Symbol ( '_setRawMode' ) ;
106
113
const kTabComplete = Symbol ( '_tabComplete' ) ;
107
114
const kTabCompleter = Symbol ( '_tabCompleter' ) ;
108
115
const kTtyWrite = Symbol ( '_ttyWrite' ) ;
116
+ const kUndo = Symbol ( '_undo' ) ;
117
+ const kUndoStack = Symbol ( '_undoStack' ) ;
109
118
const kWordLeft = Symbol ( '_wordLeft' ) ;
110
119
const kWordRight = Symbol ( '_wordRight' ) ;
111
120
const kWriteToOutput = Symbol ( '_writeToOutput' ) ;
@@ -198,6 +207,8 @@ function InterfaceConstructor(input, output, completer, terminal) {
198
207
this [ kSubstringSearch ] = null ;
199
208
this . output = output ;
200
209
this . input = input ;
210
+ this [ kUndoStack ] = [ ] ;
211
+ this [ kRedoStack ] = [ ] ;
201
212
this . history = history ;
202
213
this . historySize = historySize ;
203
214
this . removeHistoryDuplicates = ! ! removeHistoryDuplicates ;
@@ -390,6 +401,10 @@ class Interface extends InterfaceConstructor {
390
401
}
391
402
}
392
403
404
+ [ kBeforeEdit ] ( oldText , oldCursor ) {
405
+ this [ kPushToUndoStack ] ( oldText , oldCursor ) ;
406
+ }
407
+
393
408
[ kQuestionCancel ] ( ) {
394
409
if ( this [ kQuestionCallback ] ) {
395
410
this [ kQuestionCallback ] = null ;
@@ -579,6 +594,7 @@ class Interface extends InterfaceConstructor {
579
594
}
580
595
581
596
[ kInsertString ] ( c ) {
597
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
582
598
if ( this . cursor < this . line . length ) {
583
599
const beg = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
584
600
const end = StringPrototypeSlice (
@@ -648,6 +664,8 @@ class Interface extends InterfaceConstructor {
648
664
return ;
649
665
}
650
666
667
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
668
+
651
669
// Apply/show completions.
652
670
const completionsWidth = ArrayPrototypeMap ( completions , ( e ) =>
653
671
getStringWidth ( e )
@@ -708,6 +726,7 @@ class Interface extends InterfaceConstructor {
708
726
709
727
[ kDeleteLeft ] ( ) {
710
728
if ( this . cursor > 0 && this . line . length > 0 ) {
729
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
711
730
// The number of UTF-16 units comprising the character to the left
712
731
const charSize = charLengthLeft ( this . line , this . cursor ) ;
713
732
this . line =
@@ -721,6 +740,7 @@ class Interface extends InterfaceConstructor {
721
740
722
741
[ kDeleteRight ] ( ) {
723
742
if ( this . cursor < this . line . length ) {
743
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
724
744
// The number of UTF-16 units comprising the character to the left
725
745
const charSize = charLengthAt ( this . line , this . cursor ) ;
726
746
this . line =
@@ -736,6 +756,7 @@ class Interface extends InterfaceConstructor {
736
756
737
757
[ kDeleteWordLeft ] ( ) {
738
758
if ( this . cursor > 0 ) {
759
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
739
760
// Reverse the string and match a word near beginning
740
761
// to avoid quadratic time complexity
741
762
let leading = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
@@ -759,6 +780,7 @@ class Interface extends InterfaceConstructor {
759
780
760
781
[ kDeleteWordRight ] ( ) {
761
782
if ( this . cursor < this . line . length ) {
783
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
762
784
const trailing = StringPrototypeSlice ( this . line , this . cursor ) ;
763
785
const match = StringPrototypeMatch ( trailing , / ^ (?: \s + | \W + | \w + ) \s * / ) ;
764
786
this . line =
@@ -769,12 +791,14 @@ class Interface extends InterfaceConstructor {
769
791
}
770
792
771
793
[ kDeleteLineLeft ] ( ) {
794
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
772
795
this . line = StringPrototypeSlice ( this . line , this . cursor ) ;
773
796
this . cursor = 0 ;
774
797
this [ kRefreshLine ] ( ) ;
775
798
}
776
799
777
800
[ kDeleteLineRight ] ( ) {
801
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
778
802
this . line = StringPrototypeSlice ( this . line , 0 , this . cursor ) ;
779
803
this [ kRefreshLine ] ( ) ;
780
804
}
@@ -789,10 +813,43 @@ class Interface extends InterfaceConstructor {
789
813
790
814
[ kLine ] ( ) {
791
815
const line = this [ kAddHistory ] ( ) ;
816
+ this [ kUndoStack ] = [ ] ;
817
+ this [ kRedoStack ] = [ ] ;
792
818
this . clearLine ( ) ;
793
819
this [ kOnLine ] ( line ) ;
794
820
}
795
821
822
+ [ kPushToUndoStack ] ( text , cursor ) {
823
+ if ( ArrayPrototypePush ( this [ kUndoStack ] , { text, cursor } ) >
824
+ kMaxUndoRedoStackSize ) {
825
+ ArrayPrototypeShift ( this [ kUndoStack ] ) ;
826
+ }
827
+ }
828
+
829
+ [ kUndo ] ( ) {
830
+ if ( this [ kUndoStack ] . length <= 0 ) return ;
831
+
832
+ const entry = this [ kUndoStack ] . pop ( ) ;
833
+
834
+ this . line = entry . text ;
835
+ this . cursor = entry . cursor ;
836
+
837
+ ArrayPrototypePush ( this [ kRedoStack ] , entry ) ;
838
+ this [ kRefreshLine ] ( ) ;
839
+ }
840
+
841
+ [ kRedo ] ( ) {
842
+ if ( this [ kRedoStack ] . length <= 0 ) return ;
843
+
844
+ const entry = this [ kRedoStack ] . pop ( ) ;
845
+
846
+ this . line = entry . text ;
847
+ this . cursor = entry . cursor ;
848
+
849
+ ArrayPrototypePush ( this [ kUndoStack ] , entry ) ;
850
+ this [ kRefreshLine ] ( ) ;
851
+ }
852
+
796
853
// TODO(BridgeAR): Add underscores to the search part and a red background in
797
854
// case no match is found. This should only be the visual part and not the
798
855
// actual line content!
@@ -802,6 +859,7 @@ class Interface extends InterfaceConstructor {
802
859
// one.
803
860
[ kHistoryNext ] ( ) {
804
861
if ( this . historyIndex >= 0 ) {
862
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
805
863
const search = this [ kSubstringSearch ] || '' ;
806
864
let index = this . historyIndex - 1 ;
807
865
while (
@@ -824,6 +882,7 @@ class Interface extends InterfaceConstructor {
824
882
825
883
[ kHistoryPrev ] ( ) {
826
884
if ( this . historyIndex < this . history . length && this . history . length ) {
885
+ this [ kBeforeEdit ] ( this . line , this . cursor ) ;
827
886
const search = this [ kSubstringSearch ] || '' ;
828
887
let index = this . historyIndex + 1 ;
829
888
while (
@@ -947,6 +1006,13 @@ class Interface extends InterfaceConstructor {
947
1006
}
948
1007
}
949
1008
1009
+ // Undo
1010
+ if ( typeof key . sequence === 'string' &&
1011
+ StringPrototypeCodePointAt ( key . sequence , 0 ) === 0x1f ) {
1012
+ this [ kUndo ] ( ) ;
1013
+ return ;
1014
+ }
1015
+
950
1016
// Ignore escape key, fixes
951
1017
// https://github.com/nodejs/node-v0.x-archive/issues/2876.
952
1018
if ( key . name === 'escape' ) return ;
0 commit comments