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"
15 // Opposite and undoing events must have opposite values
17 // TextEventInsert represents an insertion event
19 // TextEventRemove represents a deletion event
21 // TextEventReplace represents a replace event
24 undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
27 // TextEvent holds data for a manipulation on some text that can be undone
28 type TextEvent struct {
36 // A Delta is a change to the buffer
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)
49 } else if t.EventType == TextEventRemove {
50 for i, d := range t.Deltas {
51 t.Deltas[i].Text = buf.remove(d.Start, d.End)
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}
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]
66 // UndoTextEvent undoes a text event
67 func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
68 t.EventType = -t.EventType
69 ExecuteTextEvent(t, buf)
72 // EventHandler executes text manipulations and allows undoing and redoing
73 type EventHandler struct {
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)
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) {
97 diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
99 for _, d := range diff {
100 if d.Type == dmp.DiffDelete {
101 eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
103 if d.Type == dmp.DiffInsert {
104 eh.Insert(loc, d.Text)
106 loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
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)
117 // InsertBytes creates an insert text event and executes it
118 func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
120 C: *eh.cursors[eh.active],
121 EventType: TextEventInsert,
122 Deltas: []Delta{{text, start, Loc{0, 0}}},
126 textcount := utf8.RuneCount(text)
127 e.Deltas[0].End = start.MoveLA(textcount, eh.buf.LineArray)
128 end := e.Deltas[0].End
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)
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()
148 // Remove creates a remove text event and executes it
149 func (eh *EventHandler) Remove(start, end Loc) {
151 C: *eh.cursors[eh.active],
152 EventType: TextEventRemove,
153 Deltas: []Delta{{[]byte{}, start, end}},
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)
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()
176 // MultipleReplace creates an multiple insertions executes them
177 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
179 C: *eh.cursors[eh.active],
180 EventType: TextEventReplace,
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)
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)
200 b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
202 screen.TermMessage(err)
209 ExecuteTextEvent(t, eh.buf)
212 // Undo the first event in the undo stack
213 func (eh *EventHandler) Undo() {
214 t := eh.UndoStack.Peek()
219 startTime := t.Time.UnixNano() / int64(time.Millisecond)
220 endTime := startTime - (startTime % undoThreshold)
223 t = eh.UndoStack.Peek()
228 if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
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()
246 // Modifies the text event
247 UndoTextEvent(t, eh.buf)
249 // Set the cursor in the right place
251 if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
252 t.C = *eh.cursors[teCursor.Num]
253 eh.cursors[teCursor.Num].Goto(teCursor)
258 // Push it to the redo stack
262 // Redo the first event in the redo stack
263 func (eh *EventHandler) Redo() {
264 t := eh.RedoStack.Peek()
269 startTime := t.Time.UnixNano() / int64(time.Millisecond)
270 endTime := startTime - (startTime % undoThreshold) + undoThreshold
273 t = eh.RedoStack.Peek()
278 if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
286 // RedoOneEvent redoes one event
287 func (eh *EventHandler) RedoOneEvent() {
288 t := eh.RedoStack.Pop()
293 // Modifies the text event
294 UndoTextEvent(t, eh.buf)
297 if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
298 t.C = *eh.cursors[teCursor.Num]
299 eh.cursors[teCursor.Num].Goto(teCursor)