Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package backend
import (
"fmt"

"github.com/limetext/backend/render"
"github.com/limetext/text"
)

Expand All @@ -20,6 +21,9 @@ type (
invalid bool
composite text.CompositeAction
savedSel text.RegionSet
finalSel text.RegionSet
savedRegs render.ViewRegionMap
finalRegs render.ViewRegionMap
savedCount int
command string
args Args
Expand All @@ -36,9 +40,36 @@ func newEdit(v *View) *Edit {
for _, r := range v.Sel().Regions() {
ret.savedSel.Add(r)
}
ret.savedRegs = cloneViewRegionMap(v.regions)
return ret
}

func cloneViewRegionMap(src render.ViewRegionMap) render.ViewRegionMap {
if src == nil {
return nil
}
dst := make(render.ViewRegionMap, len(src))
for key, regions := range src {
dst[key] = *regions.Clone()
}
return dst
}

func copyRegionSet(dst *text.RegionSet, src text.RegionSet) {
dst.Clear()
dst.AddAll(src.Regions())
}

func (e *Edit) saveFinalRegions() {
copyRegionSet(&e.finalSel, *e.v.Sel())
e.finalRegs = cloneViewRegionMap(e.v.regions)
}

func (e *Edit) restoreRegions(sel text.RegionSet, regs render.ViewRegionMap) {
copyRegionSet(e.v.Sel(), sel)
e.v.regions = cloneViewRegionMap(regs)
}

// Returns a string describing this Edit object. Should typically not be manually called.
func (e *Edit) String() string {
return fmt.Sprintf("%s: %v, %v, %v", e.command, e.args, e.bypassUndo, e.composite)
Expand All @@ -47,13 +78,11 @@ func (e *Edit) String() string {
// Applies the action of this Edit object. Should typically not be manually called.
func (e *Edit) Apply() {
e.composite.Apply()
e.restoreRegions(e.finalSel, e.finalRegs)
}

// Reverses the application of this Edit object. Should typically not be manually called.
func (e *Edit) Undo() {
e.composite.Undo()
e.v.Sel().Clear()
for _, r := range e.savedSel.Regions() {
e.v.Sel().Add(r)
}
e.restoreRegions(e.savedSel, e.savedRegs)
}
2 changes: 2 additions & 0 deletions undo.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func (us *UndoStack) GlueFrom(mark int) {
}
e.v = us.actions[mark].v
e.savedSel.AddAll(us.actions[mark].savedSel.Regions())
e.savedRegs = cloneViewRegionMap(us.actions[mark].savedRegs)

entries := make([]entry, us.position-mark)
for i := range entries {
Expand All @@ -133,5 +134,6 @@ func (us *UndoStack) GlueFrom(mark int) {
us.actions = us.actions[:mark+1]
e.args = make(Args)
e.args["commands"] = entries
e.saveFinalRegions()
us.Add(&e)
}
85 changes: 85 additions & 0 deletions undo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package backend

import (
"reflect"
"testing"

"github.com/limetext/text"
Expand Down Expand Up @@ -207,3 +208,87 @@ func TestUndoStackGlueFrom(t *testing.T) {
t.Errorf("Expected the UndoStack to only contain 2 things, but it had %d", len(us.actions))
}
}

func TestUndoStackRestoresViewRegions(t *testing.T) {
w := GetEditor().NewWindow()
defer w.Close()

v := w.NewFile()
defer func() {
v.SetScratch(true)
v.Close()
}()

e := v.BeginEdit()
v.Insert(e, 0, "abcdef")
v.EndEdit(e)

const key = "test"
original := []text.Region{{A: 1, B: 3}, {A: 4, B: 6}}
v.AddRegions(key, original, "scope", "icon", 0)

e = v.BeginEdit()
v.Erase(e, text.Region{A: 0, B: v.Size()})
v.EndEdit(e)

afterErase := v.GetRegions(key)
if reflect.DeepEqual(afterErase, original) {
t.Fatalf("expected edit to adjust regions before undo, got unchanged regions %v", afterErase)
}

v.undoStack.Undo(true)
if got := v.GetRegions(key); !reflect.DeepEqual(got, original) {
t.Fatalf("expected undo to restore regions %v, got %v", original, got)
}

v.undoStack.Redo(true)
if got := v.GetRegions(key); !reflect.DeepEqual(got, afterErase) {
t.Fatalf("expected redo to restore edited regions %v, got %v", afterErase, got)
}
}

func TestUndoStackGlueFromRestoresViewRegions(t *testing.T) {
w := GetEditor().NewWindow()
defer w.Close()

v := w.NewFile()
defer func() {
v.SetScratch(true)
v.Close()
}()

e := v.BeginEdit()
v.Insert(e, 0, "abcdef")
v.EndEdit(e)

mark := v.undoStack.Position()

const key = "test"
original := []text.Region{{A: 1, B: 3}, {A: 4, B: 6}}
v.AddRegions(key, original, "scope", "icon", 0)

e = v.BeginEdit()
v.Erase(e, text.Region{A: 1, B: 2})
v.EndEdit(e)

e = v.BeginEdit()
v.Insert(e, 2, "Z")
v.EndEdit(e)

final := v.GetRegions(key)
if reflect.DeepEqual(final, original) {
t.Fatalf("expected edits to adjust regions before glue, got unchanged regions %v", final)
}

v.undoStack.GlueFrom(mark)

v.undoStack.Undo(true)
if got := v.GetRegions(key); !reflect.DeepEqual(got, original) {
t.Fatalf("expected glued undo to restore regions %v, got %v", original, got)
}

v.undoStack.Redo(true)
if got := v.GetRegions(key); !reflect.DeepEqual(got, final) {
t.Fatalf("expected glued redo to restore regions %v, got %v", final, got)
}
}
1 change: 1 addition & 0 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ func (v *View) EndEdit(edit *Edit) {
for j := len(v.editstack) - 1; j >= i; j-- {
current_edit := v.editstack[j]
current_edit.invalid = true
current_edit.saveFinalRegions()
sel_same := reflect.DeepEqual(*v.Sel(), current_edit.savedSel)
buf_same := v.ChangeCount() == current_edit.savedCount
eq := (sel_same && buf_same && current_edit.composite.Len() == 0)
Expand Down