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