11package engine
22
33import (
4- "bytes"
54 "fmt"
65
76 "github.com/chojs23/ec/internal/markers"
87)
98
10- // State manages resolution state for a conflict document with undo support .
9+ // State manages resolution state for a conflict document.
1110type State struct {
12- doc markers.Document
13- undoStack []markers.Document
14- redoStack []markers.Document
15- maxUndoSize int
11+ doc markers.Document
1612}
1713
1814// NewState creates a new State from a parsed document.
19- // maxUndoSize controls how many undo operations to retain (must be >= 1).
20- func NewState (doc markers.Document , maxUndoSize int ) (* State , error ) {
21- if maxUndoSize < 1 {
22- return nil , fmt .Errorf ("maxUndoSize must be >= 1, got %d" , maxUndoSize )
23- }
15+ func NewState (doc markers.Document ) (* State , error ) {
2416 return & State {
25- doc : doc ,
26- undoStack : make ([]markers.Document , 0 , maxUndoSize ),
27- redoStack : make ([]markers.Document , 0 , maxUndoSize ),
28- maxUndoSize : maxUndoSize ,
17+ doc : doc ,
2918 }, nil
3019}
3120
@@ -54,9 +43,6 @@ func (s *State) ApplyResolution(conflictIndex int, resolution markers.Resolution
5443 return nil
5544 }
5645
57- // Save current state to undo stack before modifying, and invalidate redo history.
58- s .beginMutation ()
59-
6046 seg .Resolution = resolution
6147 s .doc .Segments [ref .SegmentIndex ] = seg
6248
@@ -88,9 +74,6 @@ func (s *State) ApplyAll(resolution markers.Resolution) error {
8874 return nil
8975 }
9076
91- // Save current state to undo stack before modifying, and invalidate redo history.
92- s .beginMutation ()
93-
9477 for _ , ref := range s .doc .Conflicts {
9578 seg , ok := s .doc .Segments [ref .SegmentIndex ].(markers.ConflictSegment )
9679 if ! ok {
@@ -103,55 +86,12 @@ func (s *State) ApplyAll(resolution markers.Resolution) error {
10386 return nil
10487}
10588
106- // Undo restores the previous state.
107- // Returns error if no undo history is available.
108- func (s * State ) Undo () error {
109- if len (s .undoStack ) == 0 {
110- return fmt .Errorf ("no undo history available" )
111- }
112-
113- // Save current state to redo stack before restoring previous state.
114- s .pushWithLimit (& s .redoStack , s .doc )
115-
116- // Pop the last state
117- lastIdx := len (s .undoStack ) - 1
118- s .doc = s .undoStack [lastIdx ]
119- s .undoStack = s .undoStack [:lastIdx ]
120-
121- return nil
122- }
123-
124- // Redo reapplies a previously undone state.
125- // Returns error if no redo history is available.
126- func (s * State ) Redo () error {
127- if len (s .redoStack ) == 0 {
128- return fmt .Errorf ("no redo history available" )
129- }
130-
131- // Save current state to undo stack before restoring redone state.
132- s .pushWithLimit (& s .undoStack , s .doc )
133-
134- lastIdx := len (s .redoStack ) - 1
135- s .doc = s .redoStack [lastIdx ]
136- s .redoStack = s .redoStack [:lastIdx ]
137-
138- return nil
139- }
140-
141- // ReplaceDocument replaces the current document as a single undoable mutation.
142- // If the incoming document is identical to current state, no history is added.
89+ // ReplaceDocument replaces the current document.
14390func (s * State ) ReplaceDocument (doc markers.Document ) {
144- if documentsEqual (s .doc , doc ) {
91+ if markers . DocumentsEqual (s .doc , doc ) {
14592 return
14693 }
147- s .beginMutation ()
148- s .doc = cloneDocument (doc )
149- }
150-
151- // PushUndoPoint records the current state as an undo step.
152- // Useful for undoable metadata changes outside Document.
153- func (s * State ) PushUndoPoint () {
154- s .beginMutation ()
94+ s .doc = markers .CloneDocument (doc )
15595}
15696
15797// Preview generates the resolved output by concatenating segments with resolutions applied.
@@ -161,90 +101,6 @@ func (s *State) Preview() ([]byte, error) {
161101 return markers .RenderResolved (s .doc )
162102}
163103
164- // Document returns a copy of the current document state.
165104func (s * State ) Document () markers.Document {
166- return s .doc
167- }
168-
169- // UndoDepth returns the current number of undo operations available.
170- func (s * State ) UndoDepth () int {
171- return len (s .undoStack )
172- }
173-
174- // RedoDepth returns the current number of redo operations available.
175- func (s * State ) RedoDepth () int {
176- return len (s .redoStack )
177- }
178-
179- // beginMutation saves the current state to undo and clears redo history.
180- func (s * State ) beginMutation () {
181- s .pushWithLimit (& s .undoStack , s .doc )
182- s .redoStack = s .redoStack [:0 ]
183- }
184-
185- // pushWithLimit saves a document snapshot into the stack and enforces max size.
186- func (s * State ) pushWithLimit (stack * []markers.Document , doc markers.Document ) {
187- * stack = append (* stack , cloneDocument (doc ))
188- if len (* stack ) > s .maxUndoSize {
189- * stack = (* stack )[1 :]
190- }
191- }
192-
193- func cloneDocument (doc markers.Document ) markers.Document {
194- // Deep copy the document to preserve state
195- docCopy := markers.Document {
196- Segments : make ([]markers.Segment , len (doc .Segments )),
197- Conflicts : make ([]markers.ConflictRef , len (doc .Conflicts )),
198- }
199-
200- for i , seg := range doc .Segments {
201- switch v := seg .(type ) {
202- case markers.TextSegment :
203- // TextSegment.Bytes is immutable (we never modify it), so shallow copy is safe
204- docCopy .Segments [i ] = v
205- case markers.ConflictSegment :
206- // ConflictSegment fields are immutable byte slices and Resolution enum, shallow copy is safe
207- docCopy .Segments [i ] = v
208- }
209- }
210-
211- copy (docCopy .Conflicts , doc .Conflicts )
212- return docCopy
213- }
214-
215- func documentsEqual (left , right markers.Document ) bool {
216- if len (left .Conflicts ) != len (right .Conflicts ) || len (left .Segments ) != len (right .Segments ) {
217- return false
218- }
219- for i := range left .Conflicts {
220- if left .Conflicts [i ] != right .Conflicts [i ] {
221- return false
222- }
223- }
224- for i := range left .Segments {
225- switch l := left .Segments [i ].(type ) {
226- case markers.TextSegment :
227- r , ok := right .Segments [i ].(markers.TextSegment )
228- if ! ok || ! bytes .Equal (l .Bytes , r .Bytes ) {
229- return false
230- }
231- case markers.ConflictSegment :
232- r , ok := right .Segments [i ].(markers.ConflictSegment )
233- if ! ok {
234- return false
235- }
236- if ! bytes .Equal (l .Ours , r .Ours ) || ! bytes .Equal (l .Base , r .Base ) || ! bytes .Equal (l .Theirs , r .Theirs ) {
237- return false
238- }
239- if l .OursLabel != r .OursLabel || l .BaseLabel != r .BaseLabel || l .TheirsLabel != r .TheirsLabel {
240- return false
241- }
242- if l .Resolution != r .Resolution {
243- return false
244- }
245- default :
246- return false
247- }
248- }
249- return true
105+ return markers .CloneDocument (s .doc )
250106}
0 commit comments