]> git.lizzy.rs Git - micro.git/blob - cmd/micro/eventhandler.go
OutdentSelection works on all cases (whatever tabsize or tabstospace values)
[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 type Delta struct {
32         Text  string
33         Start Loc
34         End   Loc
35 }
36
37 // ExecuteTextEvent runs a text event
38 func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
39         if t.EventType == TextEventInsert {
40                 for _, d := range t.Deltas {
41                         buf.insert(d.Start, []byte(d.Text))
42                 }
43         } else if t.EventType == TextEventRemove {
44                 for i, d := range t.Deltas {
45                         t.Deltas[i].Text = buf.remove(d.Start, d.End)
46                 }
47         } else if t.EventType == TextEventReplace {
48                 for i, d := range t.Deltas {
49                         t.Deltas[i].Text = buf.remove(d.Start, d.End)
50                         buf.insert(d.Start, []byte(d.Text))
51                 }
52         }
53 }
54
55 // UndoTextEvent undoes a text event
56 func UndoTextEvent(t *TextEvent, buf *Buffer) {
57         t.EventType = -t.EventType
58         ExecuteTextEvent(t, buf)
59 }
60
61 // EventHandler executes text manipulations and allows undoing and redoing
62 type EventHandler struct {
63         buf       *Buffer
64         UndoStack *Stack
65         RedoStack *Stack
66 }
67
68 // NewEventHandler returns a new EventHandler
69 func NewEventHandler(buf *Buffer) *EventHandler {
70         eh := new(EventHandler)
71         eh.UndoStack = new(Stack)
72         eh.RedoStack = new(Stack)
73         eh.buf = buf
74         return eh
75 }
76
77 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
78 // the buffer equal to that string
79 // This means that we can transform the buffer into any string and still preserve undo/redo
80 // through insert and delete events
81 func (eh *EventHandler) ApplyDiff(new string) {
82         differ := dmp.New()
83         diff := differ.DiffMain(eh.buf.String(), new, false)
84         loc := eh.buf.Start()
85         for _, d := range diff {
86                 if d.Type == dmp.DiffDelete {
87                         eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
88                 } else {
89                         if d.Type == dmp.DiffInsert {
90                                 eh.Insert(loc, d.Text)
91                         }
92                         loc = loc.Move(Count(d.Text), eh.buf)
93                 }
94         }
95 }
96
97 // Insert creates an insert text event and executes it
98 func (eh *EventHandler) Insert(start Loc, text string) {
99         e := &TextEvent{
100                 C:         *eh.buf.cursors[eh.buf.curCursor],
101                 EventType: TextEventInsert,
102                 Deltas:    []Delta{Delta{text, start, Loc{0, 0}}},
103                 Time:      time.Now(),
104         }
105         eh.Execute(e)
106         e.Deltas[0].End = start.Move(Count(text), eh.buf)
107         end := e.Deltas[0].End
108
109         for _, c := range eh.buf.cursors {
110                 move := func(loc Loc) Loc {
111                         if start.Y != end.Y && loc.GreaterThan(start) {
112                                 loc.Y += end.Y - start.Y
113                         } else if loc.Y == start.Y && loc.GreaterEqual(start) {
114                                 loc = loc.Move(Count(text), eh.buf)
115                         }
116                         return loc
117                 }
118                 c.Loc = move(c.Loc)
119                 c.CurSelection[0] = move(c.CurSelection[0])
120                 c.CurSelection[1] = move(c.CurSelection[1])
121                 c.OrigSelection[0] = move(c.OrigSelection[0])
122                 c.OrigSelection[1] = move(c.OrigSelection[1])
123                 c.LastVisualX = c.GetVisualX()
124         }
125 }
126
127 // Remove creates a remove text event and executes it
128 func (eh *EventHandler) Remove(start, end Loc) {
129         e := &TextEvent{
130                 C:         *eh.buf.cursors[eh.buf.curCursor],
131                 EventType: TextEventRemove,
132                 Deltas:    []Delta{Delta{"", start, end}},
133                 Time:      time.Now(),
134         }
135         eh.Execute(e)
136
137         for _, c := range eh.buf.cursors {
138                 move := func(loc Loc) Loc {
139                         if start.Y != end.Y && loc.GreaterThan(end) {
140                                 loc.Y -= end.Y - start.Y
141                         } else if loc.Y == end.Y && loc.GreaterEqual(end) {
142                                 loc = loc.Move(-Diff(start, end, eh.buf), eh.buf)
143                         }
144                         return loc
145                 }
146                 c.Loc = move(c.Loc)
147                 c.CurSelection[0] = move(c.CurSelection[0])
148                 c.CurSelection[1] = move(c.CurSelection[1])
149                 c.OrigSelection[0] = move(c.OrigSelection[0])
150                 c.OrigSelection[1] = move(c.OrigSelection[1])
151                 c.LastVisualX = c.GetVisualX()
152         }
153 }
154
155 // MultipleReplace creates an multiple insertions executes them
156 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
157         e := &TextEvent{
158                 C:         *eh.buf.cursors[eh.buf.curCursor],
159                 EventType: TextEventReplace,
160                 Deltas:    deltas,
161                 Time:      time.Now(),
162         }
163         eh.Execute(e)
164 }
165
166 // Replace deletes from start to end and replaces it with the given string
167 func (eh *EventHandler) Replace(start, end Loc, replace string) {
168         eh.Remove(start, end)
169         eh.Insert(start, replace)
170 }
171
172 // Execute a textevent and add it to the undo stack
173 func (eh *EventHandler) Execute(t *TextEvent) {
174         if eh.RedoStack.Len() > 0 {
175                 eh.RedoStack = new(Stack)
176         }
177         eh.UndoStack.Push(t)
178
179         for pl := range loadedPlugins {
180                 ret, err := Call(pl+".onBeforeTextEvent", t)
181                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
182                         TermMessage(err)
183                 }
184                 if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
185                         return
186                 }
187         }
188
189         ExecuteTextEvent(t, eh.buf)
190 }
191
192 // Undo the first event in the undo stack
193 func (eh *EventHandler) Undo() {
194         t := eh.UndoStack.Peek()
195         if t == nil {
196                 return
197         }
198
199         startTime := t.Time.UnixNano() / int64(time.Millisecond)
200
201         eh.UndoOneEvent()
202
203         for {
204                 t = eh.UndoStack.Peek()
205                 if t == nil {
206                         return
207                 }
208
209                 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
210                         return
211                 }
212                 startTime = t.Time.UnixNano() / int64(time.Millisecond)
213
214                 eh.UndoOneEvent()
215         }
216 }
217
218 // UndoOneEvent undoes one event
219 func (eh *EventHandler) UndoOneEvent() {
220         // This event should be undone
221         // Pop it off the stack
222         t := eh.UndoStack.Pop()
223         if t == nil {
224                 return
225         }
226
227         // Undo it
228         // Modifies the text event
229         UndoTextEvent(t, eh.buf)
230
231         // Set the cursor in the right place
232         teCursor := t.C
233         if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
234                 t.C = *eh.buf.cursors[teCursor.Num]
235                 eh.buf.cursors[teCursor.Num].Goto(teCursor)
236         } else {
237                 teCursor.Num = -1
238         }
239
240         // Push it to the redo stack
241         eh.RedoStack.Push(t)
242 }
243
244 // Redo the first event in the redo stack
245 func (eh *EventHandler) Redo() {
246         t := eh.RedoStack.Peek()
247         if t == nil {
248                 return
249         }
250
251         startTime := t.Time.UnixNano() / int64(time.Millisecond)
252
253         eh.RedoOneEvent()
254
255         for {
256                 t = eh.RedoStack.Peek()
257                 if t == nil {
258                         return
259                 }
260
261                 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
262                         return
263                 }
264
265                 eh.RedoOneEvent()
266         }
267 }
268
269 // RedoOneEvent redoes one event
270 func (eh *EventHandler) RedoOneEvent() {
271         t := eh.RedoStack.Pop()
272         if t == nil {
273                 return
274         }
275
276         // Modifies the text event
277         UndoTextEvent(t, eh.buf)
278
279         teCursor := t.C
280         if teCursor.Num >= 0 && teCursor.Num < len(eh.buf.cursors) {
281                 t.C = *eh.buf.cursors[teCursor.Num]
282                 eh.buf.cursors[teCursor.Num].Goto(teCursor)
283         } else {
284                 teCursor.Num = -1
285         }
286
287         eh.UndoStack.Push(t)
288 }