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 {
31 // A Delta is a change to the buffer
38 // ExecuteTextEvent runs a text event
39 func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
40 if t.EventType == TextEventInsert {
41 for _, d := range t.Deltas {
42 buf.insert(d.Start, []byte(d.Text))
44 } else if t.EventType == TextEventRemove {
45 for i, d := range t.Deltas {
46 t.Deltas[i].Text = buf.remove(d.Start, d.End)
48 } else if t.EventType == TextEventReplace {
49 for i, d := range t.Deltas {
50 t.Deltas[i].Text = buf.remove(d.Start, d.End)
51 buf.insert(d.Start, []byte(d.Text))
52 t.Deltas[i].Start = d.Start
53 t.Deltas[i].End = Loc{d.Start.X + Count(d.Text), d.Start.Y}
55 for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
56 t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
61 // UndoTextEvent undoes a text event
62 func UndoTextEvent(t *TextEvent, buf *Buffer) {
63 t.EventType = -t.EventType
64 ExecuteTextEvent(t, buf)
67 // EventHandler executes text manipulations and allows undoing and redoing
68 type EventHandler struct {
74 // NewEventHandler returns a new EventHandler
75 func NewEventHandler(buf *Buffer) *EventHandler {
76 eh := new(EventHandler)
77 eh.UndoStack = new(Stack)
78 eh.RedoStack = new(Stack)
83 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
84 // the buffer equal to that string
85 // This means that we can transform the buffer into any string and still preserve undo/redo
86 // through insert and delete events
87 func (eh *EventHandler) ApplyDiff(new string) {
89 diff := differ.DiffMain(eh.buf.String(), new, false)
91 for _, d := range diff {
92 if d.Type == dmp.DiffDelete {
93 eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
95 if d.Type == dmp.DiffInsert {
96 eh.Insert(loc, d.Text)
98 loc = loc.Move(Count(d.Text), eh.buf)
103 // Insert creates an insert text event and executes it
104 func (eh *EventHandler) Insert(start Loc, text string) {
106 C: *eh.buf.cursors[eh.buf.curCursor],
107 EventType: TextEventInsert,
108 Deltas: []Delta{{text, start, Loc{0, 0}}},
112 e.Deltas[0].End = start.Move(Count(text), eh.buf)
113 end := e.Deltas[0].End
115 for _, c := range eh.buf.cursors {
116 move := func(loc Loc) Loc {
117 if start.Y != end.Y && loc.GreaterThan(start) {
118 loc.Y += end.Y - start.Y
119 } else if loc.Y == start.Y && loc.GreaterEqual(start) {
120 loc = loc.Move(Count(text), eh.buf)
125 c.CurSelection[0] = move(c.CurSelection[0])
126 c.CurSelection[1] = move(c.CurSelection[1])
127 c.OrigSelection[0] = move(c.OrigSelection[0])
128 c.OrigSelection[1] = move(c.OrigSelection[1])
129 c.LastVisualX = c.GetVisualX()
133 // Remove creates a remove text event and executes it
134 func (eh *EventHandler) Remove(start, end Loc) {
136 C: *eh.buf.cursors[eh.buf.curCursor],
137 EventType: TextEventRemove,
138 Deltas: []Delta{{"", start, end}},
143 for _, c := range eh.buf.cursors {
144 move := func(loc Loc) Loc {
145 if start.Y != end.Y && loc.GreaterThan(end) {
146 loc.Y -= end.Y - start.Y
147 } else if loc.Y == end.Y && loc.GreaterEqual(end) {
148 loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
153 c.CurSelection[0] = move(c.CurSelection[0])
154 c.CurSelection[1] = move(c.CurSelection[1])
155 c.OrigSelection[0] = move(c.OrigSelection[0])
156 c.OrigSelection[1] = move(c.OrigSelection[1])
157 c.LastVisualX = c.GetVisualX()
161 // MultipleReplace creates an multiple insertions executes them
162 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
164 C: *eh.buf.cursors[eh.buf.curCursor],
165 EventType: TextEventReplace,
172 // Replace deletes from start to end and replaces it with the given string
173 func (eh *EventHandler) Replace(start, end Loc, replace string) {
174 eh.Remove(start, end)
175 eh.Insert(start, replace)
178 // Execute a textevent and add it to the undo stack
179 func (eh *EventHandler) Execute(t *TextEvent) {
180 if eh.RedoStack.Len() > 0 {
181 eh.RedoStack = new(Stack)
185 for pl := range loadedPlugins {
186 ret, err := Call(pl+".onBeforeTextEvent", t)
187 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
190 if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
195 ExecuteTextEvent(t, eh.buf)
198 // Undo the first event in the undo stack
199 func (eh *EventHandler) Undo() {
200 t := eh.UndoStack.Peek()
205 startTime := t.Time.UnixNano() / int64(time.Millisecond)
210 t = eh.UndoStack.Peek()
215 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
218 startTime = t.Time.UnixNano() / int64(time.Millisecond)
224 // UndoOneEvent undoes one event
225 func (eh *EventHandler) UndoOneEvent() {
226 // This event should be undone
227 // Pop it off the stack
228 t := eh.UndoStack.Pop()
234 // Modifies the text event
235 UndoTextEvent(t, eh.buf)
237 // Set the cursor in the right place
239 if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
240 t.C = *eh.buf.cursors[teCursor.Num]
241 eh.buf.cursors[teCursor.Num].Goto(teCursor)
246 // Push it to the redo stack
250 // Redo the first event in the redo stack
251 func (eh *EventHandler) Redo() {
252 t := eh.RedoStack.Peek()
257 startTime := t.Time.UnixNano() / int64(time.Millisecond)
262 t = eh.RedoStack.Peek()
267 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
275 // RedoOneEvent redoes one event
276 func (eh *EventHandler) RedoOneEvent() {
277 t := eh.RedoStack.Pop()
282 // Modifies the text event
283 UndoTextEvent(t, eh.buf)
286 if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
287 t.C = *eh.buf.cursors[teCursor.Num]
288 eh.buf.cursors[teCursor.Num].Goto(teCursor)