]> git.lizzy.rs Git - micro.git/blob - internal/buffer/eventhandler.go
Merge pull request #1327 from Osmose/git-commit-diff
[micro.git] / internal / buffer / eventhandler.go
1 package buffer
2
3 import (
4         "time"
5         "unicode/utf8"
6
7         dmp "github.com/sergi/go-diff/diffmatchpatch"
8 )
9
10 const (
11         // Opposite and undoing events must have opposite values
12
13         // TextEventInsert represents an insertion event
14         TextEventInsert = 1
15         // TextEventRemove represents a deletion event
16         TextEventRemove = -1
17         // TextEventReplace represents a replace event
18         TextEventReplace = 0
19
20         undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
21 )
22
23 // TextEvent holds data for a manipulation on some text that can be undone
24 type TextEvent struct {
25         C Cursor
26
27         EventType int
28         Deltas    []Delta
29         Time      time.Time
30 }
31
32 // A Delta is a change to the buffer
33 type Delta struct {
34         Text  []byte
35         Start Loc
36         End   Loc
37 }
38
39 // ExecuteTextEvent runs a text event
40 func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
41         if t.EventType == TextEventInsert {
42                 for _, d := range t.Deltas {
43                         buf.insert(d.Start, d.Text)
44                 }
45         } else if t.EventType == TextEventRemove {
46                 for i, d := range t.Deltas {
47                         t.Deltas[i].Text = buf.remove(d.Start, d.End)
48                 }
49         } else if t.EventType == TextEventReplace {
50                 for i, d := range t.Deltas {
51                         t.Deltas[i].Text = buf.remove(d.Start, d.End)
52                         buf.insert(d.Start, d.Text)
53                         t.Deltas[i].Start = d.Start
54                         t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
55                 }
56                 for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
57                         t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
58                 }
59         }
60 }
61
62 // UndoTextEvent undoes a text event
63 func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
64         t.EventType = -t.EventType
65         ExecuteTextEvent(t, buf)
66 }
67
68 // EventHandler executes text manipulations and allows undoing and redoing
69 type EventHandler struct {
70         buf       *SharedBuffer
71         cursors   []*Cursor
72         active    int
73         UndoStack *TEStack
74         RedoStack *TEStack
75 }
76
77 // NewEventHandler returns a new EventHandler
78 func NewEventHandler(buf *SharedBuffer, cursors []*Cursor) *EventHandler {
79         eh := new(EventHandler)
80         eh.UndoStack = new(TEStack)
81         eh.RedoStack = new(TEStack)
82         eh.buf = buf
83         eh.cursors = cursors
84         return eh
85 }
86
87 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
88 // the buffer equal to that string
89 // This means that we can transform the buffer into any string and still preserve undo/redo
90 // through insert and delete events
91 func (eh *EventHandler) ApplyDiff(new string) {
92         differ := dmp.New()
93         diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
94         loc := eh.buf.Start()
95         for _, d := range diff {
96                 if d.Type == dmp.DiffDelete {
97                         eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
98                 } else {
99                         if d.Type == dmp.DiffInsert {
100                                 eh.Insert(loc, d.Text)
101                         }
102                         loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
103                 }
104         }
105 }
106
107 // Insert creates an insert text event and executes it
108 func (eh *EventHandler) Insert(start Loc, textStr string) {
109         text := []byte(textStr)
110         e := &TextEvent{
111                 C:         *eh.cursors[eh.active],
112                 EventType: TextEventInsert,
113                 Deltas:    []Delta{{text, start, Loc{0, 0}}},
114                 Time:      time.Now(),
115         }
116         eh.Execute(e)
117         e.Deltas[0].End = start.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
118         end := e.Deltas[0].End
119
120         for _, c := range eh.cursors {
121                 move := func(loc Loc) Loc {
122                         if start.Y != end.Y && loc.GreaterThan(start) {
123                                 loc.Y += end.Y - start.Y
124                         } else if loc.Y == start.Y && loc.GreaterEqual(start) {
125                                 loc = loc.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
126                         }
127                         return loc
128                 }
129                 c.Loc = move(c.Loc)
130                 c.CurSelection[0] = move(c.CurSelection[0])
131                 c.CurSelection[1] = move(c.CurSelection[1])
132                 c.OrigSelection[0] = move(c.OrigSelection[0])
133                 c.OrigSelection[1] = move(c.OrigSelection[1])
134                 c.LastVisualX = c.GetVisualX()
135         }
136 }
137
138 // Remove creates a remove text event and executes it
139 func (eh *EventHandler) Remove(start, end Loc) {
140         e := &TextEvent{
141                 C:         *eh.cursors[eh.active],
142                 EventType: TextEventRemove,
143                 Deltas:    []Delta{{[]byte{}, start, end}},
144                 Time:      time.Now(),
145         }
146         eh.Execute(e)
147
148         for _, c := range eh.cursors {
149                 move := func(loc Loc) Loc {
150                         if start.Y != end.Y && loc.GreaterThan(end) {
151                                 loc.Y -= end.Y - start.Y
152                         } else if loc.Y == end.Y && loc.GreaterEqual(end) {
153                                 loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
154                         }
155                         return loc
156                 }
157                 c.Loc = move(c.Loc)
158                 c.CurSelection[0] = move(c.CurSelection[0])
159                 c.CurSelection[1] = move(c.CurSelection[1])
160                 c.OrigSelection[0] = move(c.OrigSelection[0])
161                 c.OrigSelection[1] = move(c.OrigSelection[1])
162                 c.LastVisualX = c.GetVisualX()
163         }
164 }
165
166 // MultipleReplace creates an multiple insertions executes them
167 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
168         e := &TextEvent{
169                 C:         *eh.cursors[eh.active],
170                 EventType: TextEventReplace,
171                 Deltas:    deltas,
172                 Time:      time.Now(),
173         }
174         eh.Execute(e)
175 }
176
177 // Replace deletes from start to end and replaces it with the given string
178 func (eh *EventHandler) Replace(start, end Loc, replace string) {
179         eh.Remove(start, end)
180         eh.Insert(start, replace)
181 }
182
183 // Execute a textevent and add it to the undo stack
184 func (eh *EventHandler) Execute(t *TextEvent) {
185         if eh.RedoStack.Len() > 0 {
186                 eh.RedoStack = new(TEStack)
187         }
188         eh.UndoStack.Push(t)
189
190         // TODO: Call plugins on text events
191         // for pl := range loadedPlugins {
192         //      ret, err := Call(pl+".onBeforeTextEvent", t)
193         //      if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
194         //              screen.TermMessage(err)
195         //      }
196         //      if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
197         //              return
198         //      }
199         // }
200
201         ExecuteTextEvent(t, eh.buf)
202 }
203
204 // Undo the first event in the undo stack
205 func (eh *EventHandler) Undo() {
206         t := eh.UndoStack.Peek()
207         if t == nil {
208                 return
209         }
210
211         startTime := t.Time.UnixNano() / int64(time.Millisecond)
212
213         eh.UndoOneEvent()
214
215         for {
216                 t = eh.UndoStack.Peek()
217                 if t == nil {
218                         return
219                 }
220
221                 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
222                         return
223                 }
224                 startTime = t.Time.UnixNano() / int64(time.Millisecond)
225
226                 eh.UndoOneEvent()
227         }
228 }
229
230 // UndoOneEvent undoes one event
231 func (eh *EventHandler) UndoOneEvent() {
232         // This event should be undone
233         // Pop it off the stack
234         t := eh.UndoStack.Pop()
235         if t == nil {
236                 return
237         }
238
239         // Undo it
240         // Modifies the text event
241         UndoTextEvent(t, eh.buf)
242
243         // Set the cursor in the right place
244         teCursor := t.C
245         if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
246                 t.C = *eh.cursors[teCursor.Num]
247                 eh.cursors[teCursor.Num].Goto(teCursor)
248         } else {
249                 teCursor.Num = -1
250         }
251
252         // Push it to the redo stack
253         eh.RedoStack.Push(t)
254 }
255
256 // Redo the first event in the redo stack
257 func (eh *EventHandler) Redo() {
258         t := eh.RedoStack.Peek()
259         if t == nil {
260                 return
261         }
262
263         startTime := t.Time.UnixNano() / int64(time.Millisecond)
264
265         eh.RedoOneEvent()
266
267         for {
268                 t = eh.RedoStack.Peek()
269                 if t == nil {
270                         return
271                 }
272
273                 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
274                         return
275                 }
276
277                 eh.RedoOneEvent()
278         }
279 }
280
281 // RedoOneEvent redoes one event
282 func (eh *EventHandler) RedoOneEvent() {
283         t := eh.RedoStack.Pop()
284         if t == nil {
285                 return
286         }
287
288         // Modifies the text event
289         UndoTextEvent(t, eh.buf)
290
291         teCursor := t.C
292         if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
293                 t.C = *eh.cursors[teCursor.Num]
294                 eh.cursors[teCursor.Num].Goto(teCursor)
295         } else {
296                 teCursor.Num = -1
297         }
298
299         eh.UndoStack.Push(t)
300 }