1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-27 13:48:49 +08:00

Editor can delete runes before the cursor.

This commit is contained in:
Jakub Sobon 2019-04-18 23:57:24 -04:00
parent 72989e7e39
commit c890f0be5f
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
2 changed files with 144 additions and 55 deletions

View File

@ -227,25 +227,17 @@ func (vr *visibleRange) normalizeToWidth(width int) {
// normalizeToiData normalizes the visible range, handles cases where the
// length of the data decreased due to deletion of some runes.
func (vr *visibleRange) normalizeToData(dataLen int) {
if dataLen >= vr.endIdx || vr.startIdx == 0 {
// Nothing to do when data is longer than the range or the range
// already starts all the way left.
func (vr *visibleRange) normalizeToData(fd fieldData) {
if vr.endIdx <= len(fd) || vr.startIdx == 0 {
// Nothing to do when data fills the range or the range already starts
// all the way left.
return
}
diff := vr.endIdx - dataLen
if diff == 1 {
// The data can be one character shorter than the visible range, since
// this is space for the cursor when appending.
return
}
diff--
_, newStartIdx := numbers.MinMaxInts([]int{vr.startIdx - diff, 0})
shift := vr.startIdx - newStartIdx
vr.endIdx -= shift
vr.startIdx = newStartIdx
endIdx := len(fd)
startIdx := fd.cellsBefore(vr.forRunes(), endIdx)
endIdx++ // Space for the cursor within the visible range.
vr.set(startIdx, endIdx)
}
// curRelative returns the relative position of the cursor within the visible
@ -345,7 +337,7 @@ func (fe *fieldEditor) viewFor(width int) (string, int, error) {
return "", -1, fmt.Errorf("width %d is too small, the minimum is %d", width, min)
}
fe.visible.normalizeToWidth(width)
fe.visible.normalizeToData(len(fe.data))
fe.visible.normalizeToData(fe.data)
fe.toCursor()
cur, err := fe.visible.curRelative(fe.curDataPos)
@ -374,6 +366,7 @@ func (fe *fieldEditor) delete() {
func (fe *fieldEditor) deleteBefore() {
if fe.curDataPos == 0 {
// Cursor at the beginning, nothing to do.
return
}
fe.cursorLeft()
fe.delete()

View File

@ -549,52 +549,52 @@ func TestNormalizeToWidth(t *testing.T) {
func TestNormalizeToData(t *testing.T) {
tests := []struct {
desc string
vr *visibleRange
dataLen int
want *visibleRange
desc string
vr *visibleRange
data fieldData
want string
}{
{
desc: "zero values",
vr: &visibleRange{},
dataLen: 0,
want: &visibleRange{},
desc: "zero values",
vr: &visibleRange{},
data: fieldData{},
want: "",
},
{
desc: "dataLen smaller than visible range, range already at the start",
desc: "data smaller than visible range, range already at the start",
vr: &visibleRange{
startIdx: 0,
endIdx: 3,
},
dataLen: 1,
want: &visibleRange{
startIdx: 0,
endIdx: 3,
},
data: fieldData{'a'},
want: "a",
},
{
desc: "dataLen smaller than visible range by exactly one rune - space for the cursor",
desc: "data smaller than visible range by exactly one rune - space for the cursor",
vr: &visibleRange{
startIdx: 4,
endIdx: 6,
},
dataLen: 5,
want: &visibleRange{
startIdx: 4,
endIdx: 6,
},
data: fieldData{'a', 'b', 'c', 'd', 'e'},
want: "e",
},
{
desc: "dataLen smaller than visible range, range is shifted back, not reaching zero",
desc: "data smaller than visible range, range is shifted back, not reaching zero",
vr: &visibleRange{
startIdx: 4,
endIdx: 6,
endIdx: 7,
},
dataLen: 4,
want: &visibleRange{
startIdx: 3,
endIdx: 5,
data: fieldData{'a', 'b', 'c', 'd'},
want: "⇦d",
},
{
desc: "range decreases due to full-width rune",
vr: &visibleRange{
startIdx: 4,
endIdx: 7,
},
data: fieldData{'a', 'b', 'c', '世'},
want: "世",
},
{
desc: "dataLen smaller than visible range, range is shifted back, reaches zero",
@ -602,20 +602,17 @@ func TestNormalizeToData(t *testing.T) {
startIdx: 4,
endIdx: 6,
},
dataLen: 1,
want: &visibleRange{
startIdx: 0,
endIdx: 2,
},
data: fieldData{'a'},
want: "a",
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
got := tc.vr
got.normalizeToData(tc.dataLen)
if diff := pretty.Compare(tc.want, got); diff != "" {
t.Errorf("normalizeToData => unexpected diff (-want, +got):\n%s", diff)
tc.vr.normalizeToData(tc.data)
got := tc.data.runesIn(tc.vr)
if got != tc.want {
t.Errorf("normalizeToData => %q, want %q", got, tc.want)
}
})
}
@ -1007,6 +1004,19 @@ func TestFieldEditor(t *testing.T) {
want: "⇦cd",
wantCurIdx: 3,
},
{
desc: "longer data than the width, cursor at the end, has full-width runes",
width: 4,
ops: func(fe *fieldEditor) error {
fe.insert('a')
fe.insert('b')
fe.insert('c')
fe.insert('世')
return nil
},
want: "⇦世",
wantCurIdx: 2,
},
{
desc: "width decreased, adjusts cursor and shifts data",
width: 4,
@ -1227,7 +1237,7 @@ func TestFieldEditor(t *testing.T) {
wantCurIdx: 3,
},
{
desc: "deletes the last rune",
desc: "deletesBefore when cursor after the data",
width: 4,
ops: func(fe *fieldEditor) error {
fe.insert('a')
@ -1244,9 +1254,95 @@ func TestFieldEditor(t *testing.T) {
want: "⇦cd",
wantCurIdx: 3,
},
{
desc: "deletesBefore when cursor after the data, text has full-width rune",
width: 4,
ops: func(fe *fieldEditor) error {
fe.insert('a')
fe.insert('b')
fe.insert('c')
fe.insert('世')
fe.insert('e')
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.deleteBefore()
return nil
},
want: "⇦世",
wantCurIdx: 2,
},
{
desc: "deletesBefore when cursor in the middle",
width: 4,
ops: func(fe *fieldEditor) error {
fe.insert('a')
fe.insert('b')
fe.insert('c')
fe.insert('d')
fe.insert('e')
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.cursorLeft()
fe.cursorLeft()
fe.cursorLeft()
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.deleteBefore()
return nil
},
want: "acd⇨",
wantCurIdx: 1,
},
{
desc: "deletesBefore when cursor in the middle, full-width runes",
width: 4,
ops: func(fe *fieldEditor) error {
fe.insert('世')
fe.insert('b')
fe.insert('c')
fe.insert('d')
fe.insert('e')
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.cursorLeft()
fe.cursorLeft()
fe.cursorLeft()
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.deleteBefore()
return nil
},
want: "世c⇨",
wantCurIdx: 1,
},
{
desc: "deletesBefore does nothing when cursor at the start",
width: 4,
ops: func(fe *fieldEditor) error {
fe.insert('a')
fe.insert('b')
fe.insert('c')
fe.insert('d')
fe.insert('e')
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.cursorStart()
if _, _, err := fe.viewFor(4); err != nil {
return err
}
fe.deleteBefore()
return nil
},
want: "abc⇨",
wantCurIdx: 0,
},
// deletes the last rune, contains full-width runes
// deleteBefore when in the middle
// deleteBefore when at the start
// delete when at the empty space at the end
// delete when in the middle, last rune visible
// delete when in the middle, last rune hidden