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