micro/internal/buffer/eventhandler.go

406 lines
10 KiB
Go
Raw Permalink Normal View History

2018-08-27 22:53:10 +03:00
package buffer
2016-03-20 01:16:10 +03:00
import (
2020-02-09 22:30:20 +03:00
"bytes"
2016-03-20 01:16:10 +03:00
"time"
dmp "github.com/sergi/go-diff/diffmatchpatch"
2020-05-04 17:16:15 +03:00
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
2020-05-20 23:47:08 +03:00
"github.com/zyedidia/micro/v2/internal/util"
2020-01-31 08:56:15 +03:00
luar "layeh.com/gopher-luar"
2016-03-20 01:16:10 +03:00
)
const (
2016-03-25 19:14:22 +03:00
// Opposite and undoing events must have opposite values
// TextEventInsert represents an insertion event
2016-03-20 01:16:10 +03:00
TextEventInsert = 1
// TextEventRemove represents a deletion event
TextEventRemove = -1
// TextEventReplace represents a replace event
2017-04-16 18:11:04 +03:00
TextEventReplace = 0
2018-08-27 22:53:10 +03:00
undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
2016-03-20 01:16:10 +03:00
)
// TextEvent holds data for a manipulation on some text that can be undone
type TextEvent struct {
2016-05-29 18:02:56 +03:00
C Cursor
2016-03-20 01:16:10 +03:00
2016-05-29 18:02:56 +03:00
EventType int
2017-04-16 18:11:04 +03:00
Deltas []Delta
2016-05-29 18:02:56 +03:00
Time time.Time
2016-03-20 01:16:10 +03:00
}
2018-01-05 01:14:51 +03:00
// A Delta is a change to the buffer
2017-04-16 18:11:04 +03:00
type Delta struct {
2018-08-26 06:06:44 +03:00
Text []byte
2017-04-16 18:11:04 +03:00
Start Loc
End Loc
}
// DoTextEvent runs a text event
func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
oldl := eh.buf.LinesNum()
if useUndo {
eh.Execute(t)
} else {
ExecuteTextEvent(t, eh.buf)
}
if len(t.Deltas) != 1 {
return
}
text := t.Deltas[0].Text
start := t.Deltas[0].Start
lastnl := -1
var endX int
var textX int
if t.EventType == TextEventInsert {
linecount := eh.buf.LinesNum() - oldl
2020-05-20 23:47:08 +03:00
textcount := util.CharacterCount(text)
lastnl = bytes.LastIndex(text, []byte{'\n'})
if lastnl >= 0 {
2020-05-20 23:47:08 +03:00
endX = util.CharacterCount(text[lastnl+1:])
textX = endX
} else {
endX = start.X + textcount
textX = textcount
}
t.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
}
end := t.Deltas[0].End
for _, c := range eh.cursors {
move := func(loc Loc) Loc {
if t.EventType == TextEventInsert {
if start.Y != loc.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc.Y += end.Y - start.Y
if lastnl >= 0 {
loc.X += textX - start.X
} else {
loc.X += textX
}
}
return loc
} else {
if loc.Y != end.Y && loc.GreaterThan(end) {
loc.Y -= end.Y - start.Y
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
}
return loc
}
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.Relocate()
c.LastVisualX = c.GetVisualX()
}
if useUndo {
eh.updateTrailingWs(t)
}
}
2016-03-20 01:16:10 +03:00
// ExecuteTextEvent runs a text event
2019-01-15 00:52:25 +03:00
func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
2016-05-29 18:02:56 +03:00
if t.EventType == TextEventInsert {
2017-04-16 18:11:04 +03:00
for _, d := range t.Deltas {
2018-08-26 06:06:44 +03:00
buf.insert(d.Start, d.Text)
2017-04-16 18:11:04 +03:00
}
2016-05-29 18:02:56 +03:00
} else if t.EventType == TextEventRemove {
2017-04-16 18:11:04 +03:00
for i, d := range t.Deltas {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
}
} else if t.EventType == TextEventReplace {
for i, d := range t.Deltas {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
2018-08-26 06:06:44 +03:00
buf.insert(d.Start, d.Text)
2017-09-04 23:21:08 +03:00
t.Deltas[i].Start = d.Start
2020-05-20 23:47:08 +03:00
t.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(d.Text), d.Start.Y}
2017-09-04 23:21:08 +03:00
}
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
2017-04-16 18:11:04 +03:00
}
2016-03-20 01:16:10 +03:00
}
}
// UndoTextEvent undoes a text event
func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
2016-05-29 18:02:56 +03:00
t.EventType = -t.EventType
eh.DoTextEvent(t, false)
2016-03-20 01:16:10 +03:00
}
// EventHandler executes text manipulations and allows undoing and redoing
type EventHandler struct {
2019-01-15 00:52:25 +03:00
buf *SharedBuffer
cursors []*Cursor
2019-01-17 01:52:30 +03:00
active int
2018-08-26 06:06:44 +03:00
UndoStack *TEStack
RedoStack *TEStack
2016-03-20 01:16:10 +03:00
}
// NewEventHandler returns a new EventHandler
2019-01-15 00:52:25 +03:00
func NewEventHandler(buf *SharedBuffer, cursors []*Cursor) *EventHandler {
2016-03-20 01:16:10 +03:00
eh := new(EventHandler)
2018-08-26 06:06:44 +03:00
eh.UndoStack = new(TEStack)
eh.RedoStack = new(TEStack)
2019-01-15 00:52:25 +03:00
eh.buf = buf
eh.cursors = cursors
2016-03-20 01:16:10 +03:00
return eh
}
// ApplyDiff takes a string and runs the necessary insertion and deletion events to make
// the buffer equal to that string
// This means that we can transform the buffer into any string and still preserve undo/redo
// through insert and delete events
func (eh *EventHandler) ApplyDiff(new string) {
differ := dmp.New()
diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
2016-06-07 18:43:28 +03:00
loc := eh.buf.Start()
for _, d := range diff {
if d.Type == dmp.DiffDelete {
2020-05-20 23:47:08 +03:00
eh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))
} else {
if d.Type == dmp.DiffInsert {
eh.Insert(loc, d.Text)
}
2020-05-20 23:47:08 +03:00
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
}
}
}
2016-03-20 01:16:10 +03:00
// Insert creates an insert text event and executes it
func (eh *EventHandler) Insert(start Loc, textStr string) {
text := []byte(textStr)
eh.InsertBytes(start, text)
}
// InsertBytes creates an insert text event and executes it
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
if len(text) == 0 {
return
}
2020-02-10 08:18:08 +03:00
start = clamp(start, eh.buf.LineArray)
2016-03-20 01:16:10 +03:00
e := &TextEvent{
2019-01-17 01:52:30 +03:00
C: *eh.cursors[eh.active],
2016-05-29 18:02:56 +03:00
EventType: TextEventInsert,
2017-08-08 18:30:09 +03:00
Deltas: []Delta{{text, start, Loc{0, 0}}},
2016-05-29 18:02:56 +03:00
Time: time.Now(),
2016-03-20 01:16:10 +03:00
}
eh.DoTextEvent(e, true)
2016-03-20 01:16:10 +03:00
}
// Remove creates a remove text event and executes it
2016-06-07 18:43:28 +03:00
func (eh *EventHandler) Remove(start, end Loc) {
if start == end {
return
}
2020-02-10 08:18:08 +03:00
start = clamp(start, eh.buf.LineArray)
end = clamp(end, eh.buf.LineArray)
2016-03-20 01:16:10 +03:00
e := &TextEvent{
2019-01-17 01:52:30 +03:00
C: *eh.cursors[eh.active],
2016-05-29 18:02:56 +03:00
EventType: TextEventRemove,
2018-08-26 06:06:44 +03:00
Deltas: []Delta{{[]byte{}, start, end}},
2017-04-16 18:11:04 +03:00
Time: time.Now(),
}
eh.DoTextEvent(e, true)
2017-04-16 18:11:04 +03:00
}
// MultipleReplace creates an multiple insertions executes them
2017-04-16 18:11:04 +03:00
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
e := &TextEvent{
2019-01-17 01:52:30 +03:00
C: *eh.cursors[eh.active],
2017-04-16 18:11:04 +03:00
EventType: TextEventReplace,
Deltas: deltas,
2016-05-29 18:02:56 +03:00
Time: time.Now(),
2016-03-20 01:16:10 +03:00
}
eh.Execute(e)
}
2016-04-03 22:22:31 +03:00
// Replace deletes from start to end and replaces it with the given string
func (eh *EventHandler) Replace(start, end Loc, replace string) {
2016-04-03 22:22:31 +03:00
eh.Remove(start, end)
eh.Insert(start, replace)
}
2016-03-20 01:16:10 +03:00
// Execute a textevent and add it to the undo stack
func (eh *EventHandler) Execute(t *TextEvent) {
2016-05-29 18:02:56 +03:00
if eh.RedoStack.Len() > 0 {
2018-08-26 06:06:44 +03:00
eh.RedoStack = new(TEStack)
2016-03-20 03:32:14 +03:00
}
2016-05-29 18:02:56 +03:00
eh.UndoStack.Push(t)
b, err := config.RunPluginFnBool(nil, "onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
2020-01-31 08:56:15 +03:00
if err != nil {
screen.TermMessage(err)
}
if !b {
return
}
ExecuteTextEvent(t, eh.buf)
2016-03-20 01:16:10 +03:00
}
// Undo the first event in the undo stack
func (eh *EventHandler) Undo() {
2016-05-29 18:02:56 +03:00
t := eh.UndoStack.Peek()
2016-04-01 16:54:10 +03:00
if t == nil {
return
}
2016-05-29 18:02:56 +03:00
startTime := t.Time.UnixNano() / int64(time.Millisecond)
endTime := startTime - (startTime % undoThreshold)
2016-04-01 16:54:10 +03:00
for {
2016-05-29 18:02:56 +03:00
t = eh.UndoStack.Peek()
2016-04-01 16:54:10 +03:00
if t == nil {
return
}
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
2016-04-01 16:54:10 +03:00
return
}
eh.UndoOneEvent()
}
}
// UndoOneEvent undoes one event
func (eh *EventHandler) UndoOneEvent() {
// This event should be undone
// Pop it off the stack
2016-05-29 18:02:56 +03:00
t := eh.UndoStack.Pop()
2016-03-20 01:16:10 +03:00
if t == nil {
return
}
2016-04-01 16:54:10 +03:00
// Undo it
2016-03-20 01:16:10 +03:00
// Modifies the text event
eh.UndoTextEvent(t)
2016-03-20 01:16:10 +03:00
2016-04-01 16:54:10 +03:00
// Set the cursor in the right place
2016-05-29 18:02:56 +03:00
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor)
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
} else {
teCursor.Num = -1
}
2016-03-20 03:32:14 +03:00
2016-04-01 16:54:10 +03:00
// Push it to the redo stack
2016-05-29 18:02:56 +03:00
eh.RedoStack.Push(t)
2016-03-20 01:16:10 +03:00
}
// Redo the first event in the redo stack
func (eh *EventHandler) Redo() {
2016-05-29 18:02:56 +03:00
t := eh.RedoStack.Peek()
2016-04-01 16:54:10 +03:00
if t == nil {
return
}
2016-05-29 18:02:56 +03:00
startTime := t.Time.UnixNano() / int64(time.Millisecond)
endTime := startTime - (startTime % undoThreshold) + undoThreshold
2016-04-01 16:54:10 +03:00
for {
2016-05-29 18:02:56 +03:00
t = eh.RedoStack.Peek()
2016-04-01 16:54:10 +03:00
if t == nil {
return
}
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
2016-04-01 16:54:10 +03:00
return
}
eh.RedoOneEvent()
}
}
// RedoOneEvent redoes one event
func (eh *EventHandler) RedoOneEvent() {
2016-05-29 18:02:56 +03:00
t := eh.RedoStack.Pop()
2016-03-20 01:16:10 +03:00
if t == nil {
return
}
2016-05-29 18:02:56 +03:00
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num]
eh.cursors[teCursor.Num].Goto(teCursor)
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
} else {
teCursor.Num = -1
}
2016-03-20 03:32:14 +03:00
// Modifies the text event
eh.UndoTextEvent(t)
2016-05-29 18:02:56 +03:00
eh.UndoStack.Push(t)
2016-03-20 01:16:10 +03:00
}
// updateTrailingWs updates the cursor's trailing whitespace status after a text event
func (eh *EventHandler) updateTrailingWs(t *TextEvent) {
if len(t.Deltas) != 1 {
return
}
text := t.Deltas[0].Text
start := t.Deltas[0].Start
end := t.Deltas[0].End
c := eh.cursors[eh.active]
isEol := func(loc Loc) bool {
return loc.X == util.CharacterCount(eh.buf.LineBytes(loc.Y))
}
if t.EventType == TextEventInsert && c.Loc == end && isEol(end) {
var addedTrailingWs bool
addedAfterWs := false
addedWsOnly := false
if start.Y == end.Y {
addedTrailingWs = util.HasTrailingWhitespace(text)
addedWsOnly = util.IsBytesWhitespace(text)
addedAfterWs = start.X > 0 && util.IsWhitespace(c.buf.RuneAt(Loc{start.X - 1, start.Y}))
} else {
lastnl := bytes.LastIndex(text, []byte{'\n'})
addedTrailingWs = util.HasTrailingWhitespace(text[lastnl+1:])
}
if addedTrailingWs && !(addedAfterWs && addedWsOnly) {
c.NewTrailingWsY = c.Y
} else if !addedTrailingWs {
c.NewTrailingWsY = -1
}
} else if t.EventType == TextEventRemove && c.Loc == start && isEol(start) {
removedAfterWs := util.HasTrailingWhitespace(eh.buf.LineBytes(start.Y))
var removedWsOnly bool
if start.Y == end.Y {
removedWsOnly = util.IsBytesWhitespace(text)
} else {
firstnl := bytes.Index(text, []byte{'\n'})
removedWsOnly = util.IsBytesWhitespace(text[:firstnl])
}
if removedAfterWs && !removedWsOnly {
c.NewTrailingWsY = c.Y
} else if !removedAfterWs {
c.NewTrailingWsY = -1
}
} else if c.NewTrailingWsY != -1 && start.Y != end.Y && c.Loc.GreaterThan(start) &&
((t.EventType == TextEventInsert && c.Y == c.NewTrailingWsY+(end.Y-start.Y)) ||
(t.EventType == TextEventRemove && c.Y == c.NewTrailingWsY-(end.Y-start.Y))) {
// The cursor still has its new trailingws
// but its line number was shifted by insert or remove of lines above
c.NewTrailingWsY = c.Y
}
}