11package editor
22
3- import "time"
3+ import (
4+ "time"
5+ )
46
57const undoRedoJoinDuration = time .Second
68
7- type undoRedoState struct {
8- prefix int
9- suffix int
10- oldCode string
11- newCode string
12- oldSel Selection
13- newSel Selection
14- }
15-
169type UndoRedoStack struct {
1710 undos []* undoRedoState
1811 redos []* undoRedoState
@@ -27,63 +20,104 @@ func NewUndoRedoStack() *UndoRedoStack {
2720}
2821
2922func (s * UndoRedoStack ) PerformUndo (cb CodeBoxWrapper ) {
30- // TODO(grantnelson-wf): Finish implmementing
23+ if maxIndex := len (s .undos ) - 1 ; maxIndex >= 0 {
24+ undoChange := s .undos [maxIndex ]
25+ s .undos = s .undos [:maxIndex ]
26+ s .redos = append (s .redos , revertChange (cb , undoChange ))
27+ }
3128}
3229
3330func (s * UndoRedoStack ) PerformRedo (cb CodeBoxWrapper ) {
34- // TODO(grantnelson-wf): Finish implmementing
31+ if maxIndex := len (s .redos ) - 1 ; maxIndex >= 0 {
32+ redoChange := s .redos [maxIndex ]
33+ s .redos = s .redos [:maxIndex ]
34+ s .undos = append (s .undos , revertChange (cb , redoChange ))
35+ }
3536}
3637
37- func (s * UndoRedoStack ) RecordSelectionChange (cb CodeBoxWrapper ) {
38- // TODO(grantnelson-wf): Finish implmementing
38+ // AddBreak makes any prior changes not joined into any future changes.
39+ //
40+ // This should be called when the user makes a change and there is an
41+ // automatic code modification such as auto-formatting or auto-completion.
42+ // This allows the user to unto the autmatic change separately from their
43+ // own change.
44+ //
45+ // TODO(grantnelson-wf): Insert AddBreak into editor where appropriate.
46+ func (s * UndoRedoStack ) AddBreak () {
47+ // zeroing time prevents joining with prior changes.
48+ s .lastChange = time.Time {}
49+ }
3950
51+ // RecordSelectionChange records a selection change without a code change
52+ // and adds a break to prevent joining with prior changes.
53+ func (s * UndoRedoStack ) RecordSelectionChange (cb CodeBoxWrapper ) {
54+ s .lastSel = cb .GetSelection ()
55+ s .AddBreak ()
4056}
4157
4258func (s * UndoRedoStack ) RecordCodeChange (cb CodeBoxWrapper , priorCode string ) {
43- //now := s.getTime()
44-
45- // TODO(grantnelson-wf): Finish implmementing
59+ now := s .getTime ()
60+ newSel := cb .GetSelection ()
61+ newer := newState (priorCode , cb .Code (), s .lastSel , newSel )
62+ s .lastChange = now
63+ s .lastSel = newSel
64+ s .redos = nil
65+
66+ if now .Sub (s .lastChange ) <= undoRedoJoinDuration {
67+ // changes happened close enough in time to consider joining
68+ maxIndex := len (s .undos ) - 1
69+ if joined := joinStates (s .undos [maxIndex ], newer ); joined != nil {
70+ s .undos [maxIndex ] = joined
71+ return
72+ }
73+ }
4674
47- s .redos = nil // clear redo stack on new code change
75+ // cannot join, just add the new state
76+ s .undos = append (s .undos , newer )
4877}
4978
50- /*
51- func (s *UndoRedoStack) joinStates(older, newer *undoRedoState) bool {
52- if newer.start.Sub(older.start) > undoRedoJoinDuration {
53- return false // changes happened too far apart in time to join
54- }
79+ type undoRedoState struct {
80+ // prefix the length of the unchanged prefix
81+ prefix int
5582
56- if older.prefix > newer.prefix+len(newer.newCode) ||
57- older.suffix > newer.suffix+len(newer.newCode) {
58- return false // changes don't overlap so are too far apart in code to join
59- }
83+ // suffix the length of the unchanged suffix
84+ suffix int
85+
86+ // oldCode the changed portion of the old code
87+ oldCode string
88+
89+ // newCode the changed portion of the new code
90+ newCode string
6091
61- // TODO(grantnelson-wf): Finish implmementing
92+ // oldSel the selection before the change
93+ oldSel Selection
6294
63- return true
95+ // newSel the selection after the change
96+ newSel Selection
6497}
6598
66- func newState(priorCode, newCode string, priorSel, afterSel Selection, start time.Time) *undoRedoState {
67- prefixLen, suffixLen := diffTrim(priorCode, newCode)
99+ // newState creates a new undoRedoState representing the change
100+ // from oldCode to newCode, with the given old and new selections.
101+ func newState (oldCode , newCode string , oldSel , newSel Selection ) * undoRedoState {
102+ prefixLen , suffixLen := diffTrim (oldCode , newCode )
68103 return & undoRedoState {
69104 prefix : prefixLen ,
70105 suffix : suffixLen ,
106+ oldCode : oldCode [prefixLen : len (oldCode )- suffixLen ],
71107 newCode : newCode [prefixLen : len (newCode )- suffixLen ],
72- prior: priorSel,
73- after: afterSel,
74- start: start,
108+ oldSel : oldSel ,
109+ newSel : newSel ,
75110 }
76111}
77- */
78112
79- // diffTrim returns the lengths of the common prefix and suffix between
80- // prior and after strings .
81- func diffTrim (prior , after string ) (int , int ) {
82- priorLen , afterLen := len (prior ), len (after )
83- minLen := min (priorLen , afterLen )
113+ // diffTrim returns the lengths of the common prefix and suffix
114+ // between old and new code .
115+ func diffTrim (oldCode , newCode string ) (int , int ) {
116+ oldLen , newLen := len (oldCode ), len (newCode )
117+ minLen := min (oldLen , newLen )
84118
85119 prefixLen := 0
86- for prefixLen < minLen && prior [prefixLen ] == after [prefixLen ] {
120+ for prefixLen < minLen && oldCode [prefixLen ] == newCode [prefixLen ] {
87121 prefixLen ++
88122 }
89123 if prefixLen >= minLen {
@@ -92,15 +126,86 @@ func diffTrim(prior, after string) (int, int) {
92126
93127 suffixLen := 0
94128 minLen -= prefixLen
95- priorMax , afterMax := priorLen - 1 , afterLen - 1
96- for suffixLen < minLen && prior [ priorMax ] == after [ afterMax ] {
129+ oldMax , newMax := oldLen - 1 , newLen - 1
130+ for suffixLen < minLen && oldCode [ oldMax ] == newCode [ newMax ] {
97131 suffixLen ++
98- priorMax --
99- afterMax --
132+ oldMax --
133+ newMax --
100134 }
101135 return prefixLen , suffixLen
102136}
103137
138+ // joinStates returns the joined state if the two states can be joined,
139+ // otherwise returns nil.
140+ //
141+ // The two states must be adjacent meaning that the newCode in the older state
142+ // must come from the same code as the oldCode in the newer state.
143+ // If the states overlap or are close enough in the code, they can be joined.
144+ func joinStates (older , newer * undoRedoState ) * undoRedoState {
145+ oldDiffLen := len (older .newCode )
146+ newDiffLen := len (newer .oldCode )
147+
148+ // Check that the total length of the code shared between the two states is the same.
149+ if older .prefix + oldDiffLen + older .suffix != newer .prefix + newDiffLen + newer .suffix {
150+ // The states are not sharing a common code base between them.
151+ // This shouldn't happen if the states were recorded correctly,
152+ // but better to be safe.
153+ return nil
154+ }
155+
156+ if older .prefix + oldDiffLen < newer .prefix ||
157+ newer .prefix + newDiffLen < older .prefix {
158+ // There is a gap between the changes so we want to
159+ // keep them as separate states.
160+ return nil
161+ }
162+
163+ // for any of the newer state's old code that extends beyond the older
164+ // state's old code, add it to the joined old code.
165+ joinedOldCode := older .oldCode
166+ if newer .prefix < older .prefix {
167+ joinedOldCode = newer .oldCode [:older .prefix - newer .prefix ] + joinedOldCode
168+ }
169+ if newer .prefix + newDiffLen > older .prefix + oldDiffLen {
170+ joinedOldCode += newer .oldCode [(older .prefix + oldDiffLen )- newer .prefix :]
171+ }
172+
173+ // for any of the older state's new code that extends beyond the newer
174+ // state's new code, add it to the joined new code.
175+ joinedNewCode := newer .newCode
176+ if older .prefix < newer .prefix {
177+ joinedNewCode = older .newCode [:newer .prefix - older .prefix ] + joinedNewCode
178+ }
179+ if older .prefix + oldDiffLen > newer .prefix + newDiffLen {
180+ joinedNewCode += older .newCode [(newer .prefix + newDiffLen )- older .prefix :]
181+ }
182+
183+ return & undoRedoState {
184+ prefix : min (older .prefix , newer .prefix ),
185+ suffix : min (older .suffix , newer .suffix ),
186+ oldCode : joinedOldCode ,
187+ newCode : joinedNewCode ,
188+ oldSel : older .oldSel ,
189+ newSel : newer .newSel ,
190+ }
191+ }
192+
193+ func revertChange (cb CodeBoxWrapper , state * undoRedoState ) * undoRedoState {
194+ code := cb .Code ()
195+ oldCode := code [:state .prefix ] + state .oldCode + code [len (code )- state .suffix :]
196+ cb .SetCode (state .oldSel , oldCode )
197+
198+ // return the inverse state to undo the revert if needed (i.e. redo)
199+ return & undoRedoState {
200+ prefix : state .prefix ,
201+ suffix : state .suffix ,
202+ oldCode : state .newCode ,
203+ newCode : state .oldCode ,
204+ oldSel : state .newSel ,
205+ newSel : state .oldSel ,
206+ }
207+ }
208+
104209// TODO(grantnelson-wf): Remove when `min` is available in go1.21.
105210// See https://pkg.go.dev/builtin#min
106211func min (a , b int ) int {
0 commit comments