8 dmp "github.com/sergi/go-diff/diffmatchpatch"
9 "github.com/zyedidia/micro/v2/internal/config"
10 ulua "github.com/zyedidia/micro/v2/internal/lua"
11 "github.com/zyedidia/micro/v2/internal/screen"
12 luar "layeh.com/gopher-luar"
16 // Opposite and undoing events must have opposite values
18 // TextEventInsert represents an insertion event
20 // TextEventRemove represents a deletion event
22 // TextEventReplace represents a replace event
25 undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
28 // TextEvent holds data for a manipulation on some text that can be undone
29 type TextEvent struct {
37 // A Delta is a change to the buffer
44 // DoTextEvent runs a text event
45 func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
46 oldl := eh.buf.LinesNum()
51 ExecuteTextEvent(t, eh.buf)
54 if len(t.Deltas) != 1 {
58 text := t.Deltas[0].Text
59 start := t.Deltas[0].Start
63 if t.EventType == TextEventInsert {
64 linecount := eh.buf.LinesNum() - oldl
65 textcount := utf8.RuneCount(text)
66 lastnl = bytes.LastIndex(text, []byte{'\n'})
68 endX = utf8.RuneCount(text[lastnl+1:])
71 endX = start.X + textcount
74 t.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
76 end := t.Deltas[0].End
78 for _, c := range eh.cursors {
79 move := func(loc Loc) Loc {
80 if t.EventType == TextEventInsert {
81 if start.Y != loc.Y && loc.GreaterThan(start) {
82 loc.Y += end.Y - start.Y
83 } else if loc.Y == start.Y && loc.GreaterEqual(start) {
84 loc.Y += end.Y - start.Y
86 loc.X += textX - start.X
93 if loc.Y != end.Y && loc.GreaterThan(end) {
94 loc.Y -= end.Y - start.Y
95 } else if loc.Y == end.Y && loc.GreaterEqual(end) {
96 loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
102 c.CurSelection[0] = move(c.CurSelection[0])
103 c.CurSelection[1] = move(c.CurSelection[1])
104 c.OrigSelection[0] = move(c.OrigSelection[0])
105 c.OrigSelection[1] = move(c.OrigSelection[1])
107 c.LastVisualX = c.GetVisualX()
111 // ExecuteTextEvent runs a text event
112 func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
113 if t.EventType == TextEventInsert {
114 for _, d := range t.Deltas {
115 buf.insert(d.Start, d.Text)
117 } else if t.EventType == TextEventRemove {
118 for i, d := range t.Deltas {
119 t.Deltas[i].Text = buf.remove(d.Start, d.End)
121 } else if t.EventType == TextEventReplace {
122 for i, d := range t.Deltas {
123 t.Deltas[i].Text = buf.remove(d.Start, d.End)
124 buf.insert(d.Start, d.Text)
125 t.Deltas[i].Start = d.Start
126 t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
128 for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
129 t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
134 // UndoTextEvent undoes a text event
135 func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
136 t.EventType = -t.EventType
137 eh.DoTextEvent(t, false)
140 // EventHandler executes text manipulations and allows undoing and redoing
141 type EventHandler struct {
149 // NewEventHandler returns a new EventHandler
150 func NewEventHandler(buf *SharedBuffer, cursors []*Cursor) *EventHandler {
151 eh := new(EventHandler)
152 eh.UndoStack = new(TEStack)
153 eh.RedoStack = new(TEStack)
159 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
160 // the buffer equal to that string
161 // This means that we can transform the buffer into any string and still preserve undo/redo
162 // through insert and delete events
163 func (eh *EventHandler) ApplyDiff(new string) {
165 diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
166 loc := eh.buf.Start()
167 for _, d := range diff {
168 if d.Type == dmp.DiffDelete {
169 eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
171 if d.Type == dmp.DiffInsert {
172 eh.Insert(loc, d.Text)
174 loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
179 // Insert creates an insert text event and executes it
180 func (eh *EventHandler) Insert(start Loc, textStr string) {
181 text := []byte(textStr)
182 eh.InsertBytes(start, text)
185 // InsertBytes creates an insert text event and executes it
186 func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
190 start = clamp(start, eh.buf.LineArray)
192 C: *eh.cursors[eh.active],
193 EventType: TextEventInsert,
194 Deltas: []Delta{{text, start, Loc{0, 0}}},
197 eh.DoTextEvent(e, true)
200 // Remove creates a remove text event and executes it
201 func (eh *EventHandler) Remove(start, end Loc) {
205 start = clamp(start, eh.buf.LineArray)
206 end = clamp(end, eh.buf.LineArray)
208 C: *eh.cursors[eh.active],
209 EventType: TextEventRemove,
210 Deltas: []Delta{{[]byte{}, start, end}},
213 eh.DoTextEvent(e, true)
216 // MultipleReplace creates an multiple insertions executes them
217 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
219 C: *eh.cursors[eh.active],
220 EventType: TextEventReplace,
227 // Replace deletes from start to end and replaces it with the given string
228 func (eh *EventHandler) Replace(start, end Loc, replace string) {
229 eh.Remove(start, end)
230 eh.Insert(start, replace)
233 // Execute a textevent and add it to the undo stack
234 func (eh *EventHandler) Execute(t *TextEvent) {
235 if eh.RedoStack.Len() > 0 {
236 eh.RedoStack = new(TEStack)
240 b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
242 screen.TermMessage(err)
249 ExecuteTextEvent(t, eh.buf)
252 // Undo the first event in the undo stack
253 func (eh *EventHandler) Undo() {
254 t := eh.UndoStack.Peek()
259 startTime := t.Time.UnixNano() / int64(time.Millisecond)
260 endTime := startTime - (startTime % undoThreshold)
263 t = eh.UndoStack.Peek()
268 if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
276 // UndoOneEvent undoes one event
277 func (eh *EventHandler) UndoOneEvent() {
278 // This event should be undone
279 // Pop it off the stack
280 t := eh.UndoStack.Pop()
285 // Modifies the text event
288 // Set the cursor in the right place
290 if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
291 t.C = *eh.cursors[teCursor.Num]
292 eh.cursors[teCursor.Num].Goto(teCursor)
297 // Push it to the redo stack
301 // Redo the first event in the redo stack
302 func (eh *EventHandler) Redo() {
303 t := eh.RedoStack.Peek()
308 startTime := t.Time.UnixNano() / int64(time.Millisecond)
309 endTime := startTime - (startTime % undoThreshold) + undoThreshold
312 t = eh.RedoStack.Peek()
317 if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
325 // RedoOneEvent redoes one event
326 func (eh *EventHandler) RedoOneEvent() {
327 t := eh.RedoStack.Pop()
333 if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
334 t.C = *eh.cursors[teCursor.Num]
335 eh.cursors[teCursor.Num].Goto(teCursor)
340 // Modifies the text event