8 dmp "github.com/sergi/go-diff/diffmatchpatch"
9 "github.com/zyedidia/micro/internal/config"
10 ulua "github.com/zyedidia/micro/internal/lua"
11 "github.com/zyedidia/micro/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 // ExecuteTextEvent runs a text event
45 func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
46 if t.EventType == TextEventInsert {
47 for _, d := range t.Deltas {
48 buf.insert(d.Start, d.Text)
50 } else if t.EventType == TextEventRemove {
51 for i, d := range t.Deltas {
52 t.Deltas[i].Text = buf.remove(d.Start, d.End)
54 } else if t.EventType == TextEventReplace {
55 for i, d := range t.Deltas {
56 t.Deltas[i].Text = buf.remove(d.Start, d.End)
57 buf.insert(d.Start, d.Text)
58 t.Deltas[i].Start = d.Start
59 t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
61 for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
62 t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
67 // UndoTextEvent undoes a text event
68 func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
69 t.EventType = -t.EventType
70 ExecuteTextEvent(t, buf)
73 // EventHandler executes text manipulations and allows undoing and redoing
74 type EventHandler struct {
82 // NewEventHandler returns a new EventHandler
83 func NewEventHandler(buf *SharedBuffer, cursors []*Cursor) *EventHandler {
84 eh := new(EventHandler)
85 eh.UndoStack = new(TEStack)
86 eh.RedoStack = new(TEStack)
92 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
93 // the buffer equal to that string
94 // This means that we can transform the buffer into any string and still preserve undo/redo
95 // through insert and delete events
96 func (eh *EventHandler) ApplyDiff(new string) {
98 diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
100 for _, d := range diff {
101 if d.Type == dmp.DiffDelete {
102 eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
104 if d.Type == dmp.DiffInsert {
105 eh.Insert(loc, d.Text)
107 loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
112 // Insert creates an insert text event and executes it
113 func (eh *EventHandler) Insert(start Loc, textStr string) {
114 text := []byte(textStr)
115 eh.InsertBytes(start, text)
118 // InsertBytes creates an insert text event and executes it
119 func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
120 start = clamp(start, eh.buf.LineArray)
122 C: *eh.cursors[eh.active],
123 EventType: TextEventInsert,
124 Deltas: []Delta{{text, start, Loc{0, 0}}},
127 oldl := eh.buf.LinesNum()
129 linecount := eh.buf.LinesNum() - oldl
130 textcount := utf8.RuneCount(text)
131 lastnl := bytes.LastIndex(text, []byte{'\n'})
135 endX = utf8.RuneCount(text[lastnl+1:])
138 endX = start.X + textcount
142 e.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
143 end := e.Deltas[0].End
145 for _, c := range eh.cursors {
146 move := func(loc Loc) Loc {
147 if start.Y != loc.Y && loc.GreaterThan(start) {
148 loc.Y += end.Y - start.Y
149 } else if loc.Y == start.Y && loc.GreaterEqual(start) {
150 loc.Y += end.Y - start.Y
152 loc.X += textX - start.X
161 c.CurSelection[0] = move(c.CurSelection[0])
162 c.CurSelection[1] = move(c.CurSelection[1])
163 c.OrigSelection[0] = move(c.OrigSelection[0])
164 c.OrigSelection[1] = move(c.OrigSelection[1])
165 c.LastVisualX = c.GetVisualX()
169 // Remove creates a remove text event and executes it
170 func (eh *EventHandler) Remove(start, end Loc) {
171 start = clamp(start, eh.buf.LineArray)
172 end = clamp(end, eh.buf.LineArray)
174 C: *eh.cursors[eh.active],
175 EventType: TextEventRemove,
176 Deltas: []Delta{{[]byte{}, start, end}},
181 for _, c := range eh.cursors {
182 move := func(loc Loc) Loc {
183 if loc.Y != end.Y && loc.GreaterThan(end) {
184 loc.Y -= end.Y - start.Y
185 } else if loc.Y == end.Y && loc.GreaterEqual(end) {
186 loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
191 c.CurSelection[0] = move(c.CurSelection[0])
192 c.CurSelection[1] = move(c.CurSelection[1])
193 c.OrigSelection[0] = move(c.OrigSelection[0])
194 c.OrigSelection[1] = move(c.OrigSelection[1])
195 c.LastVisualX = c.GetVisualX()
199 // MultipleReplace creates an multiple insertions executes them
200 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
202 C: *eh.cursors[eh.active],
203 EventType: TextEventReplace,
210 // Replace deletes from start to end and replaces it with the given string
211 func (eh *EventHandler) Replace(start, end Loc, replace string) {
212 eh.Remove(start, end)
213 eh.Insert(start, replace)
216 // Execute a textevent and add it to the undo stack
217 func (eh *EventHandler) Execute(t *TextEvent) {
218 if eh.RedoStack.Len() > 0 {
219 eh.RedoStack = new(TEStack)
223 b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
225 screen.TermMessage(err)
232 ExecuteTextEvent(t, eh.buf)
235 // Undo the first event in the undo stack
236 func (eh *EventHandler) Undo() {
237 t := eh.UndoStack.Peek()
242 startTime := t.Time.UnixNano() / int64(time.Millisecond)
243 endTime := startTime - (startTime % undoThreshold)
246 t = eh.UndoStack.Peek()
251 if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
259 // UndoOneEvent undoes one event
260 func (eh *EventHandler) UndoOneEvent() {
261 // This event should be undone
262 // Pop it off the stack
263 t := eh.UndoStack.Pop()
269 // Modifies the text event
270 UndoTextEvent(t, eh.buf)
272 // Set the cursor in the right place
274 if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
275 t.C = *eh.cursors[teCursor.Num]
276 eh.cursors[teCursor.Num].Goto(teCursor)
281 // Push it to the redo stack
285 // Redo the first event in the redo stack
286 func (eh *EventHandler) Redo() {
287 t := eh.RedoStack.Peek()
292 startTime := t.Time.UnixNano() / int64(time.Millisecond)
293 endTime := startTime - (startTime % undoThreshold) + undoThreshold
296 t = eh.RedoStack.Peek()
301 if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
309 // RedoOneEvent redoes one event
310 func (eh *EventHandler) RedoOneEvent() {
311 t := eh.RedoStack.Pop()
316 // Modifies the text event
317 UndoTextEvent(t, eh.buf)
320 if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
321 t.C = *eh.cursors[teCursor.Num]
322 eh.cursors[teCursor.Num].Goto(teCursor)