7 dmp "github.com/sergi/go-diff/diffmatchpatch"
8 "github.com/yuin/gopher-lua"
12 // Opposite and undoing events must have opposite values
14 // TextEventInsert repreasents an insertion event
16 // TextEventRemove represents a deletion event
20 // TextEvent holds data for a manipulation on some text that can be undone
21 type TextEvent struct {
31 // ExecuteTextEvent runs a text event
32 func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
33 if t.EventType == TextEventInsert {
34 buf.insert(t.Start, []byte(t.Text))
35 } else if t.EventType == TextEventRemove {
36 t.Text = buf.remove(t.Start, t.End)
40 // UndoTextEvent undoes a text event
41 func UndoTextEvent(t *TextEvent, buf *Buffer) {
42 t.EventType = -t.EventType
43 ExecuteTextEvent(t, buf)
46 // EventHandler executes text manipulations and allows undoing and redoing
47 type EventHandler struct {
53 // NewEventHandler returns a new EventHandler
54 func NewEventHandler(buf *Buffer) *EventHandler {
55 eh := new(EventHandler)
56 eh.UndoStack = new(Stack)
57 eh.RedoStack = new(Stack)
62 // ApplyDiff takes a string and runs the necessary insertion and deletion events to make
63 // the buffer equal to that string
64 // This means that we can transform the buffer into any string and still preserve undo/redo
65 // through insert and delete events
66 func (eh *EventHandler) ApplyDiff(new string) {
68 diff := differ.DiffMain(eh.buf.String(), new, false)
70 for _, d := range diff {
71 if d.Type == dmp.DiffDelete {
72 eh.Remove(loc, loc.Move(Count(d.Text), eh.buf))
74 if d.Type == dmp.DiffInsert {
75 eh.Insert(loc, d.Text)
77 loc = loc.Move(Count(d.Text), eh.buf)
82 // Insert creates an insert text event and executes it
83 func (eh *EventHandler) Insert(start Loc, text string) {
86 EventType: TextEventInsert,
92 e.End = start.Move(Count(text), eh.buf)
95 // Remove creates a remove text event and executes it
96 func (eh *EventHandler) Remove(start, end Loc) {
99 EventType: TextEventRemove,
107 // Replace deletes from start to end and replaces it with the given string
108 func (eh *EventHandler) Replace(start, end Loc, replace string) {
109 eh.Remove(start, end)
110 eh.Insert(start, replace)
113 // Execute a textevent and add it to the undo stack
114 func (eh *EventHandler) Execute(t *TextEvent) {
115 if eh.RedoStack.Len() > 0 {
116 eh.RedoStack = new(Stack)
120 for _, pl := range loadedPlugins {
121 ret, err := Call(pl+".onBeforeTextEvent", t)
122 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
125 if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
130 ExecuteTextEvent(t, eh.buf)
133 // Undo the first event in the undo stack
134 func (eh *EventHandler) Undo() {
135 t := eh.UndoStack.Peek()
140 startTime := t.Time.UnixNano() / int64(time.Millisecond)
145 t = eh.UndoStack.Peek()
150 if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
153 startTime = t.Time.UnixNano() / int64(time.Millisecond)
159 // UndoOneEvent undoes one event
160 func (eh *EventHandler) UndoOneEvent() {
161 // This event should be undone
162 // Pop it off the stack
163 t := eh.UndoStack.Pop()
169 // Modifies the text event
170 UndoTextEvent(t, eh.buf)
172 // Set the cursor in the right place
175 eh.buf.Cursor.Goto(teCursor)
177 // Push it to the redo stack
181 // Redo the first event in the redo stack
182 func (eh *EventHandler) Redo() {
183 t := eh.RedoStack.Peek()
188 startTime := t.Time.UnixNano() / int64(time.Millisecond)
193 t = eh.RedoStack.Peek()
198 if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
206 // RedoOneEvent redoes one event
207 func (eh *EventHandler) RedoOneEvent() {
208 t := eh.RedoStack.Pop()
213 // Modifies the text event
214 UndoTextEvent(t, eh.buf)
218 eh.buf.Cursor.Goto(teCursor)