]> git.lizzy.rs Git - micro.git/blob - cmd/micro/eventhandler.go
Code optimisation (#1117)
[micro.git] / cmd / micro / eventhandler.go
1 package main
2
3 import (
4         "strings"
5         "time"
6
7         dmp "github.com/sergi/go-diff/diffmatchpatch"
8         "github.com/yuin/gopher-lua"
9 )
10
11 const (
12         // Opposite and undoing events must have opposite values
13
14         // TextEventInsert represents an insertion event
15         TextEventInsert = 1
16         // TextEventRemove represents a deletion event
17         TextEventRemove = -1
18         // TextEventReplace represents a replace event
19         TextEventReplace = 0
20 )
21
22 // TextEvent holds data for a manipulation on some text that can be undone
23 type TextEvent struct {
24         C Cursor
25
26         EventType int
27         Deltas    []Delta
28         Time      time.Time
29 }
30
31 // A Delta is a change to the buffer
32 type Delta struct {
33         Text  string
34         Start Loc
35         End   Loc
36 }
37
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))
43                 }
44         } else if t.EventType == TextEventRemove {
45                 for i, d := range t.Deltas {
46                         t.Deltas[i].Text = buf.remove(d.Start, d.End)
47                 }
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}
54                 }
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]
57                 }
58         }
59 }
60
61 // UndoTextEvent undoes a text event
62 func UndoTextEvent(t *TextEvent, buf *Buffer) {
63         t.EventType = -t.EventType
64         ExecuteTextEvent(t, buf)
65 }
66
67 // EventHandler executes text manipulations and allows undoing and redoing
68 type EventHandler struct {
69         buf       *Buffer
70         UndoStack *Stack
71         RedoStack *Stack
72 }
73
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)
79         eh.buf = buf
80         return eh
81 }
82
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) {
88         differ := dmp.New()
89         diff := differ.DiffMain(eh.buf.String(), new, false)
90         loc := eh.buf.Start()
91         for _, d := range diff {
92                 if d.Type == dmp.DiffDelete {
93                         eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
94                 } else {
95                         if d.Type == dmp.DiffInsert {
96                                 eh.Insert(loc, d.Text)
97                         }
98                         loc = loc.Move(Count(d.Text), eh.buf)
99                 }
100         }
101 }
102
103 // Insert creates an insert text event and executes it
104 func (eh *EventHandler) Insert(start Loc, text string) {
105         e := &TextEvent{
106                 C:         *eh.buf.cursors[eh.buf.curCursor],
107                 EventType: TextEventInsert,
108                 Deltas:    []Delta{{text, start, Loc{0, 0}}},
109                 Time:      time.Now(),
110         }
111         eh.Execute(e)
112         e.Deltas[0].End = start.Move(Count(text), eh.buf)
113         end := e.Deltas[0].End
114
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)
121                         }
122                         return loc
123                 }
124                 c.Loc = move(c.Loc)
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()
130         }
131 }
132
133 // Remove creates a remove text event and executes it
134 func (eh *EventHandler) Remove(start, end Loc) {
135         e := &TextEvent{
136                 C:         *eh.buf.cursors[eh.buf.curCursor],
137                 EventType: TextEventRemove,
138                 Deltas:    []Delta{{"", start, end}},
139                 Time:      time.Now(),
140         }
141         eh.Execute(e)
142
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)
149                         }
150                         return loc
151                 }
152                 c.Loc = move(c.Loc)
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()
158         }
159 }
160
161 // MultipleReplace creates an multiple insertions executes them
162 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
163         e := &TextEvent{
164                 C:         *eh.buf.cursors[eh.buf.curCursor],
165                 EventType: TextEventReplace,
166                 Deltas:    deltas,
167                 Time:      time.Now(),
168         }
169         eh.Execute(e)
170 }
171
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)
176 }
177
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)
182         }
183         eh.UndoStack.Push(t)
184
185         for pl := range loadedPlugins {
186                 ret, err := Call(pl+".onBeforeTextEvent", t)
187                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
188                         TermMessage(err)
189                 }
190                 if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
191                         return
192                 }
193         }
194
195         ExecuteTextEvent(t, eh.buf)
196 }
197
198 // Undo the first event in the undo stack
199 func (eh *EventHandler) Undo() {
200         t := eh.UndoStack.Peek()
201         if t == nil {
202                 return
203         }
204
205         startTime := t.Time.UnixNano() / int64(time.Millisecond)
206
207         eh.UndoOneEvent()
208
209         for {
210                 t = eh.UndoStack.Peek()
211                 if t == nil {
212                         return
213                 }
214
215                 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
216                         return
217                 }
218                 startTime = t.Time.UnixNano() / int64(time.Millisecond)
219
220                 eh.UndoOneEvent()
221         }
222 }
223
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()
229         if t == nil {
230                 return
231         }
232
233         // Undo it
234         // Modifies the text event
235         UndoTextEvent(t, eh.buf)
236
237         // Set the cursor in the right place
238         teCursor := t.C
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)
242         } else {
243                 teCursor.Num = -1
244         }
245
246         // Push it to the redo stack
247         eh.RedoStack.Push(t)
248 }
249
250 // Redo the first event in the redo stack
251 func (eh *EventHandler) Redo() {
252         t := eh.RedoStack.Peek()
253         if t == nil {
254                 return
255         }
256
257         startTime := t.Time.UnixNano() / int64(time.Millisecond)
258
259         eh.RedoOneEvent()
260
261         for {
262                 t = eh.RedoStack.Peek()
263                 if t == nil {
264                         return
265                 }
266
267                 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
268                         return
269                 }
270
271                 eh.RedoOneEvent()
272         }
273 }
274
275 // RedoOneEvent redoes one event
276 func (eh *EventHandler) RedoOneEvent() {
277         t := eh.RedoStack.Pop()
278         if t == nil {
279                 return
280         }
281
282         // Modifies the text event
283         UndoTextEvent(t, eh.buf)
284
285         teCursor := t.C
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)
289         } else {
290                 teCursor.Num = -1
291         }
292
293         eh.UndoStack.Push(t)
294 }