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