7 dmp "github.com/sergi/go-diff/diffmatchpatch"
8 "github.com/yuin/gopher-lua"
12 // Opposite and undoing events must have opposite values
14 // TextEventInsert represents an insertion event
16 // TextEventRemove represents a deletion event
18 // TextEventReplace represents a replace event
22 // TextEvent holds data for a manipulation on some text that can be undone
23 type TextEvent struct {
37 // ExecuteTextEvent runs a text event
38 func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
39 if t.EventType == TextEventInsert {
40 for _, d := range t.Deltas {
41 buf.insert(d.Start, []byte(d.Text))
43 } else if t.EventType == TextEventRemove {
44 for i, d := range t.Deltas {
45 t.Deltas[i].Text = buf.remove(d.Start, d.End)
47 } else if t.EventType == TextEventReplace {
48 for i, d := range t.Deltas {
49 t.Deltas[i].Text = buf.remove(d.Start, d.End)
50 buf.insert(d.Start, []byte(d.Text))
55 // UndoTextEvent undoes a text event
56 func UndoTextEvent(t *TextEvent, buf *Buffer) {
57 t.EventType = -t.EventType
58 ExecuteTextEvent(t, buf)
61 // EventHandler executes text manipulations and allows undoing and redoing
62 type EventHandler struct {
68 // NewEventHandler returns a new EventHandler
69 func NewEventHandler(buf *Buffer) *EventHandler {
70 eh := new(EventHandler)
71 eh.UndoStack = new(Stack)
72 eh.RedoStack = new(Stack)
77 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
78 // the buffer equal to that string
79 // This means that we can transform the buffer into any string and still preserve undo/redo
80 // through insert and delete events
81 func (eh *EventHandler) ApplyDiff(new string) {
83 diff := differ.DiffMain(eh.buf.String(), new, false)
85 for _, d := range diff {
86 if d.Type == dmp.DiffDelete {
87 eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
89 if d.Type == dmp.DiffInsert {
90 eh.Insert(loc, d.Text)
92 loc = loc.Move(Count(d.Text), eh.buf)
97 // Insert creates an insert text event and executes it
98 func (eh *EventHandler) Insert(start Loc, text string) {
100 C: *eh.buf.cursors[eh.buf.curCursor],
101 EventType: TextEventInsert,
102 Deltas: []Delta{Delta{text, start, Loc{0, 0}}},
106 e.Deltas[0].End = start.Move(Count(text), eh.buf)
107 end := e.Deltas[0].End
109 for _, c := range eh.buf.cursors {
110 move := func(loc Loc) Loc {
111 if start.Y != end.Y && loc.GreaterThan(start) {
112 loc.Y += end.Y - start.Y
113 } else if loc.Y == start.Y && loc.GreaterEqual(start) {
114 loc = loc.Move(Count(text), eh.buf)
119 c.CurSelection[0] = move(c.CurSelection[0])
120 c.CurSelection[1] = move(c.CurSelection[1])
121 c.OrigSelection[0] = move(c.OrigSelection[0])
122 c.OrigSelection[1] = move(c.OrigSelection[1])
123 c.LastVisualX = c.GetVisualX()
127 // Remove creates a remove text event and executes it
128 func (eh *EventHandler) Remove(start, end Loc) {
130 C: *eh.buf.cursors[eh.buf.curCursor],
131 EventType: TextEventRemove,
132 Deltas: []Delta{Delta{"", start, end}},
137 for _, c := range eh.buf.cursors {
138 move := func(loc Loc) Loc {
139 if start.Y != end.Y && loc.GreaterThan(end) {
140 loc.Y -= end.Y - start.Y
141 } else if loc.Y == end.Y && loc.GreaterEqual(end) {
142 loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
147 c.CurSelection[0] = move(c.CurSelection[0])
148 c.CurSelection[1] = move(c.CurSelection[1])
149 c.OrigSelection[0] = move(c.OrigSelection[0])
150 c.OrigSelection[1] = move(c.OrigSelection[1])
151 c.LastVisualX = c.GetVisualX()
155 // MultipleReplace creates an multiple insertions executes them
156 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
158 C: *eh.buf.cursors[eh.buf.curCursor],
159 EventType: TextEventReplace,
166 // Replace deletes from start to end and replaces it with the given string
167 func (eh *EventHandler) Replace(start, end Loc, replace string) {
168 eh.Remove(start, end)
169 eh.Insert(start, replace)
172 // Execute a textevent and add it to the undo stack
173 func (eh *EventHandler) Execute(t *TextEvent) {
174 if eh.RedoStack.Len() > 0 {
175 eh.RedoStack = new(Stack)
179 for pl := range loadedPlugins {
180 ret, err := Call(pl+".onBeforeTextEvent", t)
181 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
184 if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
189 ExecuteTextEvent(t, eh.buf)
192 // Undo the first event in the undo stack
193 func (eh *EventHandler) Undo() {
194 t := eh.UndoStack.Peek()
199 startTime := t.Time.UnixNano() / int64(time.Millisecond)
204 t = eh.UndoStack.Peek()
209 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
212 startTime = t.Time.UnixNano() / int64(time.Millisecond)
218 // UndoOneEvent undoes one event
219 func (eh *EventHandler) UndoOneEvent() {
220 // This event should be undone
221 // Pop it off the stack
222 t := eh.UndoStack.Pop()
228 // Modifies the text event
229 UndoTextEvent(t, eh.buf)
231 // Set the cursor in the right place
233 if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
234 t.C = *eh.buf.cursors[teCursor.Num]
235 eh.buf.cursors[teCursor.Num].Goto(teCursor)
240 // Push it to the redo stack
244 // Redo the first event in the redo stack
245 func (eh *EventHandler) Redo() {
246 t := eh.RedoStack.Peek()
251 startTime := t.Time.UnixNano() / int64(time.Millisecond)
256 t = eh.RedoStack.Peek()
261 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
269 // RedoOneEvent redoes one event
270 func (eh *EventHandler) RedoOneEvent() {
271 t := eh.RedoStack.Pop()
276 // Modifies the text event
277 UndoTextEvent(t, eh.buf)
280 if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
281 t.C = *eh.buf.cursors[teCursor.Num]
282 eh.buf.cursors[teCursor.Num].Goto(teCursor)