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"
|
2016-05-31 04:01:40 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2017-05-29 21:18:10 +03:00
|
|
|
// TextEventInsert represents an insertion event
|
2016-03-20 01:16:10 +03:00
|
|
|
TextEventInsert = 1
|
|
|
|
// TextEventRemove represents a deletion event
|
|
|
|
TextEventRemove = -1
|
2017-05-29 21:18:10 +03:00
|
|
|
// TextEventReplace represents a replace event
|
2017-04-16 18:11:04 +03:00
|
|
|
TextEventReplace = 0
|
2018-08-27 22:53:10 +03:00
|
|
|
|
2020-02-02 08:14:56 +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
|
|
|
|
}
|
|
|
|
|
2020-02-26 04:24:02 +03:00
|
|
|
// 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)
|
2020-02-26 04:24:02 +03:00
|
|
|
lastnl = bytes.LastIndex(text, []byte{'\n'})
|
|
|
|
if lastnl >= 0 {
|
2020-05-20 23:47:08 +03:00
|
|
|
endX = util.CharacterCount(text[lastnl+1:])
|
2020-02-26 04:24:02 +03:00
|
|
|
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()
|
|
|
|
}
|
2020-10-20 23:52:49 +03:00
|
|
|
|
2020-10-23 01:17:22 +03:00
|
|
|
if useUndo {
|
|
|
|
eh.updateTrailingWs(t)
|
|
|
|
}
|
2020-02-26 04:24:02 +03:00
|
|
|
}
|
|
|
|
|
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
|
2020-02-26 04:24:02 +03:00
|
|
|
func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
|
2016-05-29 18:02:56 +03:00
|
|
|
t.EventType = -t.EventType
|
2020-02-26 04:24:02 +03:00
|
|
|
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
|
2019-01-14 08:18:49 +03:00
|
|
|
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
|
2019-01-14 08:18:49 +03:00
|
|
|
eh.cursors = cursors
|
2016-03-20 01:16:10 +03:00
|
|
|
return eh
|
|
|
|
}
|
|
|
|
|
2016-05-31 04:01:40 +03:00
|
|
|
// 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
|
2019-01-17 06:32:33 +03:00
|
|
|
func (eh *EventHandler) ApplyDiff(new string) {
|
2016-05-31 04:01:40 +03:00
|
|
|
differ := dmp.New()
|
2019-01-17 06:32:33 +03:00
|
|
|
diff := differ.DiffMain(string(eh.buf.Bytes()), new, false)
|
2016-06-07 18:43:28 +03:00
|
|
|
loc := eh.buf.Start()
|
2016-05-31 04:01:40 +03:00
|
|
|
for _, d := range diff {
|
2016-06-11 18:23:05 +03:00
|
|
|
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))
|
2016-06-11 18:23:05 +03:00
|
|
|
} else {
|
|
|
|
if d.Type == dmp.DiffInsert {
|
2019-01-17 06:32:33 +03:00
|
|
|
eh.Insert(loc, d.Text)
|
2016-06-11 18:23:05 +03:00
|
|
|
}
|
2020-05-20 23:47:08 +03:00
|
|
|
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
|
2016-05-31 04:01:40 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-20 01:16:10 +03:00
|
|
|
// Insert creates an insert text event and executes it
|
2019-01-17 06:32:33 +03:00
|
|
|
func (eh *EventHandler) Insert(start Loc, textStr string) {
|
|
|
|
text := []byte(textStr)
|
2020-02-02 22:20:39 +03:00
|
|
|
eh.InsertBytes(start, text)
|
|
|
|
}
|
|
|
|
|
|
|
|
// InsertBytes creates an insert text event and executes it
|
|
|
|
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
|
2020-02-26 04:24:02 +03:00
|
|
|
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
|
|
|
}
|
2020-02-26 04:24:02 +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) {
|
2020-02-26 04:24:02 +03:00
|
|
|
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(),
|
|
|
|
}
|
2020-02-26 04:24:02 +03:00
|
|
|
eh.DoTextEvent(e, true)
|
2017-04-16 18:11:04 +03:00
|
|
|
}
|
|
|
|
|
2017-05-29 21:18:10 +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
|
2019-01-17 06:32:33 +03:00
|
|
|
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)
|
2016-09-19 14:23:47 +03:00
|
|
|
|
2023-06-06 03:38:33 +03:00
|
|
|
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
|
|
|
|
}
|
2016-09-19 14:23:47 +03:00
|
|
|
|
2016-05-22 22:01:02 +03:00
|
|
|
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)
|
2020-02-02 08:14:56 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-02 08:14:56 +03:00
|
|
|
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
|
2020-02-26 04:24:02 +03:00
|
|
|
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
|
2019-01-14 08:18:49 +03:00
|
|
|
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
|
|
|
t.C = *eh.cursors[teCursor.Num]
|
|
|
|
eh.cursors[teCursor.Num].Goto(teCursor)
|
2020-10-23 01:17:22 +03:00
|
|
|
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
|
2017-06-17 05:19:33 +03:00
|
|
|
} 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)
|
2020-02-02 08:14:56 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-02 08:14:56 +03:00
|
|
|
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
|
2019-01-14 08:18:49 +03:00
|
|
|
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
|
|
|
|
t.C = *eh.cursors[teCursor.Num]
|
|
|
|
eh.cursors[teCursor.Num].Goto(teCursor)
|
2020-10-23 01:17:22 +03:00
|
|
|
eh.cursors[teCursor.Num].NewTrailingWsY = teCursor.NewTrailingWsY
|
2017-06-17 05:19:33 +03:00
|
|
|
} else {
|
|
|
|
teCursor.Num = -1
|
|
|
|
}
|
2016-03-20 03:32:14 +03:00
|
|
|
|
2020-02-26 04:24:02 +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
|
|
|
}
|
2020-10-20 23:52:49 +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
|
|
|
|
}
|
hltrailingws: improve updateTrailingWs logic
Handle the case when the cursor itself hasn't really moved to
another line, but its line number has changed due to insert
or remove of some lines above.
In this case, if the cursor is still at its new trailingws,
we should not reset NewTrailingWsY to -1 but update it to the
new line number.
A scenario exemplifying this issue:
Bind some key, e.g. Alt-r, to such a lua function:
function insertNewlineAbove(bp)
bp.Buf:Insert(buffer.Loc(0, bp.Cursor.Y), "\n")
end
Then in a file containing these lines:
aaa
bbb
ccc
insert a space at the end of bbb line, and then press Alt-r.
bbb and ccc are moved one line down, but also the trailing space
after bbb becomes highlighted, which isn't what we expect.
This commit fixes that.
2020-10-22 23:29:16 +03:00
|
|
|
} 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
|
2020-10-20 23:52:49 +03:00
|
|
|
}
|
|
|
|
}
|