7 dmp "github.com/sergi/go-diff/diffmatchpatch"
11 // Opposite and undoing events must have opposite values
13 // TextEventInsert represents an insertion event
15 // TextEventRemove represents a deletion event
17 // TextEventReplace represents a replace event
20 undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
23 // TextEvent holds data for a manipulation on some text that can be undone
24 type TextEvent struct {
32 // A Delta is a change to the buffer
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)
45 } else if t.EventType == TextEventRemove {
46 for i, d := range t.Deltas {
47 t.Deltas[i].Text = buf.remove(d.Start, d.End)
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}
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]
62 // UndoTextEvent undoes a text event
63 func UndoTextEvent(t *TextEvent, buf *Buffer) {
64 t.EventType = -t.EventType
65 ExecuteTextEvent(t, buf)
68 // EventHandler executes text manipulations and allows undoing and redoing
69 type EventHandler struct {
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)
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) {
90 diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
92 for _, d := range diff {
93 if d.Type == dmp.DiffDelete {
94 eh.Remove(loc, loc.Move(utf8.RuneCountInString(d.Text), eh.buf))
96 if d.Type == dmp.DiffInsert {
97 eh.Insert(loc, d.Text)
99 loc = loc.Move(utf8.RuneCountInString(d.Text), eh.buf)
104 // Insert creates an insert text event and executes it
105 func (eh *EventHandler) Insert(start Loc, textStr string) {
106 text := []byte(textStr)
108 C: *eh.buf.GetActiveCursor(),
109 EventType: TextEventInsert,
110 Deltas: []Delta{{text, start, Loc{0, 0}}},
114 e.Deltas[0].End = start.Move(utf8.RuneCount(text), eh.buf)
115 end := e.Deltas[0].End
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)
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()
135 // Remove creates a remove text event and executes it
136 func (eh *EventHandler) Remove(start, end Loc) {
138 C: *eh.buf.GetActiveCursor(),
139 EventType: TextEventRemove,
140 Deltas: []Delta{{[]byte{}, start, end}},
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)
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()
163 // MultipleReplace creates an multiple insertions executes them
164 func (eh *EventHandler) MultipleReplace(deltas []Delta) {
166 C: *eh.buf.GetActiveCursor(),
167 EventType: TextEventReplace,
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)
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)
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)
193 // if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
198 ExecuteTextEvent(t, eh.buf)
201 // Undo the first event in the undo stack
202 func (eh *EventHandler) Undo() {
203 t := eh.UndoStack.Peek()
208 startTime := t.Time.UnixNano() / int64(time.Millisecond)
213 t = eh.UndoStack.Peek()
218 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
221 startTime = t.Time.UnixNano() / int64(time.Millisecond)
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()
237 // Modifies the text event
238 UndoTextEvent(t, eh.buf)
240 // Set the cursor in the right place
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)
249 // Push it to the redo stack
253 // Redo the first event in the redo stack
254 func (eh *EventHandler) Redo() {
255 t := eh.RedoStack.Peek()
260 startTime := t.Time.UnixNano() / int64(time.Millisecond)
265 t = eh.RedoStack.Peek()
270 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
278 // RedoOneEvent redoes one event
279 func (eh *EventHandler) RedoOneEvent() {
280 t := eh.RedoStack.Pop()
285 // Modifies the text event
286 UndoTextEvent(t, eh.buf)
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)