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