Ver código fonte

Rework a lot of buffer edit logic, add undo/redo, but no edit grouping

Johan Rong 1 mês atrás
pai
commit
c5966540f9
3 arquivos alterados com 189 adições e 10 exclusões
  1. 36 10
      internal/buffer/buffer.go
  2. 117 0
      internal/buffer/edit.go
  3. 36 0
      internal/editor/action.go

+ 36 - 10
internal/buffer/buffer.go

@@ -23,6 +23,7 @@ type Buffer struct {
 	CM      CursorManager
 	Path    string
 	TopLine int
+	History UndoStack
 }
 
 func NewBuffer() Buffer {
@@ -49,10 +50,14 @@ func (buf *Buffer) Insert(content string) {
 	buf.ensureRope()
 	buf.CM.DeduplicateAndSort()
 
+	cursorsBefore, primaryBefore := buf.begin()
+
 	delta := 0
 	data := []byte(content)
 	shift := len(data)
 
+	edits := make([]Edit, 0, len(buf.CM.Cursors))
+
 	for i := range buf.CM.Cursors {
 		cur := &buf.CM.Cursors[i]
 
@@ -65,8 +70,9 @@ func (buf *Buffer) Insert(content string) {
 			pos = buf.Rope.Len()
 		}
 
-		buf.Rope.Insert(pos, data)
-		buf.LI.InsertAt(pos, data)
+		edit := Edit{Offset: pos, Inserted: data}
+		buf.apply(edit)
+		edits = append(edits, edit)
 
 		cur.Offset = pos + shift
 		_, goal := LineCol(buf, cur.Offset)
@@ -74,6 +80,8 @@ func (buf *Buffer) Insert(content string) {
 
 		delta += shift
 	}
+
+	buf.commit(cursorsBefore, primaryBefore, edits)
 }
 
 func (buf *Buffer) Delete() {
@@ -81,7 +89,10 @@ func (buf *Buffer) Delete() {
 
 	buf.CM.DeduplicateAndSort()
 
+	cursorsBefore, primaryBefore := buf.begin()
+
 	delta := 0
+	var edits []Edit
 
 	for i := range buf.CM.Cursors {
 		cur := &buf.CM.Cursors[i]
@@ -108,11 +119,12 @@ func (buf *Buffer) Delete() {
 			start = 0
 		}
 
-		buf.Rope.Remove(start, pos)
-		buf.LI.RemoveAt(start, pos)
+		deleted := append([]byte{}, buf.Rope.Slice(start, pos)...)
+		edit := Edit{Offset: start, Deleted: deleted}
+		buf.apply(edit)
+		edits = append(edits, edit)
 
-		deleted := pos - start
-		delta -= deleted
+		delta -= len(deleted)
 
 		cur.Offset = start
 		_, goal := LineCol(buf, cur.Offset)
@@ -120,6 +132,7 @@ func (buf *Buffer) Delete() {
 	}
 
 	buf.CM.DeduplicateAndSort()
+	buf.commit(cursorsBefore, primaryBefore, edits)
 }
 
 func (buf *Buffer) DeleteLine() {
@@ -130,6 +143,8 @@ func (buf *Buffer) DeleteLine() {
         return
     }
 
+    cursorsBefore, primaryBefore := buf.begin()
+
     primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
     line, _ := LineCol(buf, primary.Offset)
 
@@ -146,8 +161,9 @@ func (buf *Buffer) DeleteLine() {
         return
     }
 
-    buf.Rope.Remove(start, end)
-    buf.LI.RemoveAt(start, end)
+    deleted := append([]byte{}, buf.Rope.Slice(start, end)...)
+    edit := Edit{Offset: start, Deleted: deleted}
+    buf.apply(edit)
 
     newLine := line
     if newLine >= LineCount(buf) {
@@ -164,11 +180,14 @@ func (buf *Buffer) DeleteLine() {
     }
 
     buf.CM.DeduplicateAndSort()
+    buf.commit(cursorsBefore, primaryBefore, []Edit{edit})
 }
 
 func (buf *Buffer) Clear() {
 	buf.ensureRope()
 
+	cursorsBefore, primaryBefore := buf.begin()
+
 	primaryCursor := &Cursor{
 		Offset: 0,
 		Goal:   0,
@@ -178,8 +197,15 @@ func (buf *Buffer) Clear() {
 	buf.CM.Cursors = append(buf.CM.Cursors, *primaryCursor)
 	buf.CM.PrimaryIdx = 0
 
-	buf.Rope.Remove(0, buf.Rope.Len())
-	buf.LI = NewLineIndex()
+	var edits []Edit
+	if buf.Rope.Len() > 0 {
+		deleted := append([]byte{}, buf.Rope.Value()...)
+		edit := Edit{Offset: 0, Deleted: deleted}
+		buf.apply(edit)
+		edits = append(edits, edit)
+	}
+
+	buf.commit(cursorsBefore, primaryBefore, edits)
 }
 
 func isWordRune(r rune) bool {

+ 117 - 0
internal/buffer/edit.go

@@ -0,0 +1,117 @@
+package buffer
+
+type Edit struct {
+	Offset   int
+	Deleted  []byte
+	Inserted []byte
+}
+
+func (e Edit) Invert() Edit {
+	return Edit{
+		Offset:   e.Offset,
+		Deleted:  e.Inserted,
+		Inserted: e.Deleted,
+	}
+}
+
+func (buf *Buffer) apply(e Edit) {
+	buf.ensureRope()
+
+	if len(e.Deleted) > 0 {
+		end := e.Offset + len(e.Deleted)
+		buf.Rope.Remove(e.Offset, end)
+		buf.LI.RemoveAt(e.Offset, end)
+	}
+	if len(e.Inserted) > 0 {
+		buf.Rope.Insert(e.Offset, e.Inserted)
+		buf.LI.InsertAt(e.Offset, e.Inserted)
+	}
+}
+
+type BufferTransformation struct {
+	Edits         []Edit
+	CursorsBefore []Cursor
+	PrimaryBefore int
+	CursorsAfter  []Cursor
+	PrimaryAfter  int
+}
+
+type UndoStack struct {
+	done   []BufferTransformation
+	undone []BufferTransformation
+}
+
+func (s *UndoStack) push(t BufferTransformation) {
+	s.done = append(s.done, t)
+	s.undone = s.undone[:0]
+}
+
+func (s *UndoStack) CanUndo() bool {
+	return len(s.done) > 0
+}
+
+func (s *UndoStack) CanRedo() bool {
+	return len(s.undone) > 0
+}
+
+func cloneCursors(cs []Cursor) []Cursor {
+	out := make([]Cursor, len(cs))
+	copy(out, cs)
+	return out
+}
+
+func (buf *Buffer) begin() (cursorsBefore []Cursor, primaryBefore int) {
+	return cloneCursors(buf.CM.Cursors), buf.CM.PrimaryIdx
+}
+
+func (buf *Buffer) commit(cursorsBefore []Cursor, primaryBefore int, edits []Edit) {
+	if len(edits) == 0 {
+		return
+	}
+
+	buf.History.push(BufferTransformation{
+		Edits:         edits,
+		CursorsBefore: cursorsBefore,
+		PrimaryBefore: primaryBefore,
+		CursorsAfter:  cloneCursors(buf.CM.Cursors),
+		PrimaryAfter:  buf.CM.PrimaryIdx,
+	})
+}
+
+func (buf *Buffer) Undo() bool {
+	if !buf.History.CanUndo() {
+		return false
+	}
+
+	t := buf.History.done[len(buf.History.done)-1]
+	buf.History.done = buf.History.done[:len(buf.History.done)-1]
+
+	for i := len(t.Edits) - 1; i >= 0; i-- {
+		buf.apply(t.Edits[i].Invert())
+	}
+
+	buf.CM.Cursors = cloneCursors(t.CursorsBefore)
+	buf.CM.PrimaryIdx = t.PrimaryBefore
+
+	buf.History.undone = append(buf.History.undone, t)
+	return true
+}
+
+func (buf *Buffer) Redo() bool {
+	if !buf.History.CanRedo() {
+		return false
+	}
+
+	t := buf.History.undone[len(buf.History.undone)-1]
+	buf.History.undone = buf.History.undone[:len(buf.History.undone)-1]
+
+	for _, e := range t.Edits {
+		buf.apply(e)
+	}
+
+	buf.CM.Cursors = cloneCursors(t.CursorsAfter)
+	buf.CM.PrimaryIdx = t.PrimaryAfter
+
+	buf.History.done = append(buf.History.done, t)
+	return true
+}

+ 36 - 0
internal/editor/action.go

@@ -7,6 +7,7 @@ import(
 	"os"
 	"github.com/zyedidia/rope"
 	"golang.design/x/clipboard"
+	"moose/internal/buffer"
 	"moose/internal/util"
 )
 
@@ -156,6 +157,7 @@ func DefaultActionManager() ActionManager {
 					m.BM.Current().Rope = rope.New(content)
 					m.BM.Current().LI.Rebuild(m.BM.Current().Rope)
 					m.BM.Current().Path = args[0]
+					m.BM.Current().History = buffer.UndoStack{}
 				},
 			},
 			Action{
@@ -248,6 +250,40 @@ func DefaultActionManager() ActionManager {
 			},
 		},
 		Normal:  []Action{
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "z",
+					},
+				},
+				Commands: []string{"u", "undo"},
+				AskArgs:  false,
+				Callback: func(m *Model, args []string) {
+					if m.Mode == ModePalette {
+						m.BM.PaletteBuffer.Undo()
+					} else {
+						m.BM.Current().Undo()
+					}
+				},
+			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "y",
+					},
+				},
+				Commands: []string{"r", "redo"},
+				AskArgs:  false,
+				Callback: func(m *Model, args []string) {
+					if m.Mode == ModePalette {
+						m.BM.PaletteBuffer.Redo()
+					} else {
+						m.BM.Current().Redo()
+					}
+				},
+			},
 			Action{
 				Bindings: []Binding{
 					Binding{