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:
parent
72989e7e39
commit
c890f0be5f
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user