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

address comments

This commit is contained in:
LQR471814 2023-01-30 17:56:04 -08:00
parent e87b1cb791
commit 45f26a878d
4 changed files with 288 additions and 294 deletions

View File

@ -280,8 +280,8 @@ type fieldEditor struct {
} }
// newFieldEditor returns a new fieldEditor instance. // newFieldEditor returns a new fieldEditor instance.
func newFieldEditor() *fieldEditor { func newFieldEditor(onChange ChangeFn) *fieldEditor {
return &fieldEditor{} return &fieldEditor{onChange: onChange}
} }
// minFieldWidth is the minimum supported width of the text input field. // minFieldWidth is the minimum supported width of the text input field.
@ -329,12 +329,7 @@ func (fe *fieldEditor) content() string {
// reset resets the content back to zero. // reset resets the content back to zero.
func (fe *fieldEditor) reset() { func (fe *fieldEditor) reset() {
newValue := newFieldEditor() *fe = *newFieldEditor(fe.onChange)
if fe.onChange != nil {
fe.onChange("")
newValue.onChange = fe.onChange
}
*fe = *newValue
} }
// insert inserts the rune at the current position of the cursor. // insert inserts the rune at the current position of the cursor.

View File

@ -309,7 +309,7 @@ func TestCurCell(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
fe := newFieldEditor() fe := newFieldEditor(nil)
fe.data = tc.data fe.data = tc.data
fe.firstRune = tc.firstRune fe.firstRune = tc.firstRune
fe.curDataPos = tc.curDataPos fe.curDataPos = tc.curDataPos
@ -323,14 +323,14 @@ func TestCurCell(t *testing.T) {
func TestFieldEditor(t *testing.T) { func TestFieldEditor(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
width int width int
ops func(*fieldEditor) error ops func(*fieldEditor) error
wantView string wantView string
wantContent string wantContent string
wantCurIdx int wantCurIdx int
wantErr bool wantErr bool
mutations int wantOnChangeCalls int
}{ }{
{ {
desc: "fails for width too small", desc: "fails for width too small",
@ -353,10 +353,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('c') fe.insert('c')
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "longer data than the width, cursor at the end", desc: "longer data than the width, cursor at the end",
@ -368,10 +368,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('d') fe.insert('d')
return nil return nil
}, },
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "longer data than the width, cursor at the end, has full-width runes", desc: "longer data than the width, cursor at the end, has full-width runes",
@ -383,10 +383,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('世') fe.insert('世')
return nil return nil
}, },
wantView: "⇦世", wantView: "⇦世",
wantContent: "abc世", wantContent: "abc世",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "width decreased, adjusts cursor and shifts data", desc: "width decreased, adjusts cursor and shifts data",
@ -401,10 +401,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('d') fe.insert('d')
return nil return nil
}, },
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "cursor won't go right beyond the end of the data", desc: "cursor won't go right beyond the end of the data",
@ -419,10 +419,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "moves cursor to the left", desc: "moves cursor to the left",
@ -438,10 +438,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls content to the left, start becomes visible", desc: "scrolls content to the left, start becomes visible",
@ -459,10 +459,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls content to the left, both ends invisible", desc: "scrolls content to the left, both ends invisible",
@ -481,10 +481,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls left, then back right to make end visible again", desc: "scrolls left, then back right to make end visible again",
@ -509,10 +509,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls left, won't go beyond the start of data", desc: "scrolls left, won't go beyond the start of data",
@ -534,10 +534,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls left, then back right won't go beyond the end of data", desc: "scrolls left, then back right won't go beyond the end of data",
@ -563,10 +563,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "have less data than width, all fits", desc: "have less data than width, all fits",
@ -580,10 +580,10 @@ func TestFieldEditor(t *testing.T) {
} }
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "moves cursor to the start", desc: "moves cursor to the start",
@ -600,10 +600,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorStart() fe.cursorStart()
return nil return nil
}, },
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor to the end", desc: "moves cursor to the end",
@ -624,10 +624,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorEnd() fe.cursorEnd()
return nil return nil
}, },
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "deletesBefore when cursor after the data", desc: "deletesBefore when cursor after the data",
@ -644,10 +644,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "⇦cd", wantView: "⇦cd",
wantContent: "abcd", wantContent: "abcd",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore when cursor after the data, text has full-width rune", desc: "deletesBefore when cursor after the data, text has full-width rune",
@ -664,10 +664,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "⇦世", wantView: "⇦世",
wantContent: "abc世", wantContent: "abc世",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore when cursor in the middle", desc: "deletesBefore when cursor in the middle",
@ -690,10 +690,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "acd⇨", wantView: "acd⇨",
wantContent: "acde", wantContent: "acde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore when cursor in the middle, full-width runes", desc: "deletesBefore when cursor in the middle, full-width runes",
@ -716,10 +716,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "世c⇨", wantView: "世c⇨",
wantContent: "世cde", wantContent: "世cde",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "deletesBefore does nothing when cursor at the start", desc: "deletesBefore does nothing when cursor at the start",
@ -740,10 +740,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "delete does nothing when cursor at the end", desc: "delete does nothing when cursor at the end",
@ -760,10 +760,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "delete in the middle, last rune remains hidden", desc: "delete in the middle, last rune remains hidden",
@ -785,10 +785,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "acd⇨", wantView: "acd⇨",
wantContent: "acde", wantContent: "acde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "delete in the middle, last rune becomes visible", desc: "delete in the middle, last rune becomes visible",
@ -811,10 +811,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "ade", wantView: "ade",
wantContent: "ade", wantContent: "ade",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 7, wantOnChangeCalls: 7,
}, },
{ {
desc: "delete in the middle, last full-width rune would be invisible, shifts to keep cursor in window", desc: "delete in the middle, last full-width rune would be invisible, shifts to keep cursor in window",
@ -838,10 +838,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "⇦世", wantView: "⇦世",
wantContent: "ab世", wantContent: "ab世",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 7, wantOnChangeCalls: 7,
}, },
{ {
desc: "delete in the middle, last rune was and is visible", desc: "delete in the middle, last rune was and is visible",
@ -861,10 +861,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "ac", wantView: "ac",
wantContent: "ac", wantContent: "ac",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "delete in the middle, last full-width rune was and is visible", desc: "delete in the middle, last full-width rune was and is visible",
@ -884,10 +884,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "a世", wantView: "a世",
wantContent: "a世", wantContent: "a世",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "delete last rune, contains full-width runes", desc: "delete last rune, contains full-width runes",
@ -908,10 +908,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "", wantView: "",
wantContent: "", wantContent: "",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "half-width runes only, exact fit", desc: "half-width runes only, exact fit",
@ -925,10 +925,10 @@ func TestFieldEditor(t *testing.T) {
} }
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "full-width runes only, exact fit", desc: "full-width runes only, exact fit",
@ -942,10 +942,10 @@ func TestFieldEditor(t *testing.T) {
} }
return nil return nil
}, },
wantView: "你好世", wantView: "你好世",
wantContent: "你好世", wantContent: "你好世",
wantCurIdx: 6, wantCurIdx: 6,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "half-width runes only, both ends hidden", desc: "half-width runes only, both ends hidden",
@ -964,10 +964,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "half-width runes only, both ends invisible, scrolls to make start visible", desc: "half-width runes only, both ends invisible, scrolls to make start visible",
@ -987,10 +987,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "abc⇨", wantView: "abc⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "half-width runes only, both ends invisible, deletes to make start visible", desc: "half-width runes only, both ends invisible, deletes to make start visible",
@ -1010,10 +1010,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "acd⇨", wantView: "acd⇨",
wantContent: "acde", wantContent: "acde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "half-width runes only, deletion on second page refills the field", desc: "half-width runes only, deletion on second page refills the field",
@ -1033,10 +1033,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "⇦df", wantView: "⇦df",
wantContent: "abcdf", wantContent: "abcdf",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 7, wantOnChangeCalls: 7,
}, },
{ {
desc: "half-width runes only, both ends invisible, scrolls to make end visible", desc: "half-width runes only, both ends invisible, scrolls to make end visible",
@ -1060,10 +1060,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦de", wantView: "⇦de",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "half-width runes only, both ends invisible, deletes to make end visible", desc: "half-width runes only, both ends invisible, deletes to make end visible",
@ -1086,10 +1086,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "⇦de", wantView: "⇦de",
wantContent: "abde", wantContent: "abde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 6, wantOnChangeCalls: 6,
}, },
{ {
desc: "full-width runes only, both ends invisible", desc: "full-width runes only, both ends invisible",
@ -1106,10 +1106,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦⇦世⇨", wantView: "⇦⇦世⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "full-width runes only, both ends invisible, scrolls to make start visible", desc: "full-width runes only, both ends invisible, scrolls to make start visible",
@ -1130,10 +1130,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "你好⇨", wantView: "你好⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "full-width runes only, both ends invisible, deletes to make start visible", desc: "full-width runes only, both ends invisible, deletes to make start visible",
@ -1154,10 +1154,10 @@ func TestFieldEditor(t *testing.T) {
fe.deleteBefore() fe.deleteBefore()
return nil return nil
}, },
wantView: "你世⇨", wantView: "你世⇨",
wantContent: "你世界", wantContent: "你世界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "full-width runes only, both ends invisible, scrolls to make end visible", desc: "full-width runes only, both ends invisible, scrolls to make end visible",
@ -1178,10 +1178,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦⇦界", wantView: "⇦⇦界",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "full-width runes only, both ends invisible, deletes to make end visible", desc: "full-width runes only, both ends invisible, deletes to make end visible",
@ -1202,10 +1202,10 @@ func TestFieldEditor(t *testing.T) {
fe.delete() fe.delete()
return nil return nil
}, },
wantView: "⇦⇦界", wantView: "⇦⇦界",
wantContent: "你好界", wantContent: "你好界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "scrolls to make full-width rune appear at the beginning", desc: "scrolls to make full-width rune appear at the beginning",
@ -1223,10 +1223,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "你b⇨", wantView: "你b⇨",
wantContent: "你bcd", wantContent: "你bcd",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls to make full-width rune appear at the end", desc: "scrolls to make full-width rune appear at the end",
@ -1245,10 +1245,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦你", wantView: "⇦你",
wantContent: "abc你", wantContent: "abc你",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "inserts after last full width rune, first is half-width", desc: "inserts after last full width rune, first is half-width",
@ -1264,10 +1264,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('e') fe.insert('e')
return nil return nil
}, },
wantView: "⇦c你e", wantView: "⇦c你e",
wantContent: "abc你e", wantContent: "abc你e",
wantCurIdx: 5, wantCurIdx: 5,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "inserts after last full width rune, first is half-width", desc: "inserts after last full width rune, first is half-width",
@ -1282,10 +1282,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('d') fe.insert('d')
return nil return nil
}, },
wantView: "⇦你d", wantView: "⇦你d",
wantContent: "世b你d", wantContent: "世b你d",
wantCurIdx: 4, wantCurIdx: 4,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "inserts after last full width rune, hidden rune is full-width", desc: "inserts after last full width rune, hidden rune is full-width",
@ -1300,10 +1300,10 @@ func TestFieldEditor(t *testing.T) {
fe.insert('d') fe.insert('d')
return nil return nil
}, },
wantView: "⇦⇦cd", wantView: "⇦⇦cd",
wantContent: "世你cd", wantContent: "世你cd",
wantCurIdx: 4, wantCurIdx: 4,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls right, first is full-width, last are half-width", desc: "scrolls right, first is full-width, last are half-width",
@ -1327,10 +1327,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦⇦def⇨", wantView: "⇦⇦def⇨",
wantContent: "a你世defgh", wantContent: "a你世defgh",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 8, wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls right, first is half-width, last is full-width", desc: "scrolls right, first is half-width, last is full-width",
@ -1354,10 +1354,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦你世⇨", wantView: "⇦你世⇨",
wantContent: "abc你世fgh", wantContent: "abc你世fgh",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 8, wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls right, first and last are full-width", desc: "scrolls right, first and last are full-width",
@ -1375,10 +1375,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦⇦世⇨", wantView: "⇦⇦世⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls right, first and last are half-width", desc: "scrolls right, first and last are half-width",
@ -1402,10 +1402,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRight() fe.cursorRight()
return nil return nil
}, },
wantView: "⇦cdef⇨", wantView: "⇦cdef⇨",
wantContent: "abcdefg", wantContent: "abcdefg",
wantCurIdx: 4, wantCurIdx: 4,
mutations: 7, wantOnChangeCalls: 7,
}, },
{ {
desc: "scrolls left, first is full-width, last are half-width", desc: "scrolls left, first is full-width, last are half-width",
@ -1429,10 +1429,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦⇦def⇨", wantView: "⇦⇦def⇨",
wantContent: "a你世defgh", wantContent: "a你世defgh",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 8, wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls left, first is half-width, last is full-width", desc: "scrolls left, first is half-width, last is full-width",
@ -1456,10 +1456,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦你世⇨", wantView: "⇦你世⇨",
wantContent: "abc你世fgh", wantContent: "abc你世fgh",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 8, wantOnChangeCalls: 8,
}, },
{ {
desc: "scrolls left, first and last are full-width", desc: "scrolls left, first and last are full-width",
@ -1476,10 +1476,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦⇦世⇨", wantView: "⇦⇦世⇨",
wantContent: "你好世界", wantContent: "你好世界",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 4, wantOnChangeCalls: 4,
}, },
{ {
desc: "scrolls left, first and last are half-width", desc: "scrolls left, first and last are half-width",
@ -1502,10 +1502,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorLeft() fe.cursorLeft()
return nil return nil
}, },
wantView: "⇦cdef⇨", wantView: "⇦cdef⇨",
wantContent: "abcdefg", wantContent: "abcdefg",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 7, wantOnChangeCalls: 7,
}, },
{ {
desc: "resets the field editor", desc: "resets the field editor",
@ -1520,10 +1520,10 @@ func TestFieldEditor(t *testing.T) {
fe.reset() fe.reset()
return nil return nil
}, },
wantView: "", wantView: "",
wantContent: "", wantContent: "",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 3, wantOnChangeCalls: 4,
}, },
{ {
desc: "doesn't insert runes with rune width of zero", desc: "doesn't insert runes with rune width of zero",
@ -1537,10 +1537,10 @@ func TestFieldEditor(t *testing.T) {
} }
return nil return nil
}, },
wantView: "ac", wantView: "ac",
wantContent: "ac", wantContent: "ac",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 2, wantOnChangeCalls: 2,
}, },
{ {
desc: "all text visible, moves cursor to position zero", desc: "all text visible, moves cursor to position zero",
@ -1555,10 +1555,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(0) fe.cursorRelCell(0)
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor to position in the middle", desc: "all text visible, moves cursor to position in the middle",
@ -1573,10 +1573,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(1) fe.cursorRelCell(1)
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor back to the last character", desc: "all text visible, moves cursor back to the last character",
@ -1592,10 +1592,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(2) fe.cursorRelCell(2)
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor to the appending space", desc: "all text visible, moves cursor to the appending space",
@ -1611,10 +1611,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(3) fe.cursorRelCell(3)
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor before the beginning of data", desc: "all text visible, moves cursor before the beginning of data",
@ -1630,10 +1630,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(-1) fe.cursorRelCell(-1)
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 0, wantCurIdx: 0,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "all text visible, moves cursor after the appending space", desc: "all text visible, moves cursor after the appending space",
@ -1649,10 +1649,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(10) fe.cursorRelCell(10)
return nil return nil
}, },
wantView: "abc", wantView: "abc",
wantContent: "abc", wantContent: "abc",
wantCurIdx: 3, wantCurIdx: 3,
mutations: 3, wantOnChangeCalls: 3,
}, },
{ {
desc: "moves cursor when there is no text", desc: "moves cursor when there is no text",
@ -1687,10 +1687,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(0) fe.cursorRelCell(0)
return nil return nil
}, },
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "both ends hidden, moves cursor onto the first character", desc: "both ends hidden, moves cursor onto the first character",
@ -1714,10 +1714,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(1) fe.cursorRelCell(1)
return nil return nil
}, },
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 1, wantCurIdx: 1,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "both ends hidden, moves cursor onto the right arrow", desc: "both ends hidden, moves cursor onto the right arrow",
@ -1740,10 +1740,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(3) fe.cursorRelCell(3)
return nil return nil
}, },
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "both ends hidden, moves cursor onto the last character", desc: "both ends hidden, moves cursor onto the last character",
@ -1766,10 +1766,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(2) fe.cursorRelCell(2)
return nil return nil
}, },
wantView: "⇦cd⇨", wantView: "⇦cd⇨",
wantContent: "abcde", wantContent: "abcde",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor onto the first cell containing a full-width rune", desc: "moves cursor onto the first cell containing a full-width rune",
@ -1792,10 +1792,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(4) fe.cursorRelCell(4)
return nil return nil
}, },
wantView: "⇦⇦世界⇨", wantView: "⇦⇦世界⇨",
wantContent: "你好世界你", wantContent: "你好世界你",
wantCurIdx: 4, wantCurIdx: 4,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor onto the second cell containing a full-width rune", desc: "moves cursor onto the second cell containing a full-width rune",
@ -1818,10 +1818,10 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(5) fe.cursorRelCell(5)
return nil return nil
}, },
wantView: "⇦⇦世界⇨", wantView: "⇦⇦世界⇨",
wantContent: "你好世界你", wantContent: "你好世界你",
wantCurIdx: 4, wantCurIdx: 4,
mutations: 5, wantOnChangeCalls: 5,
}, },
{ {
desc: "moves cursor onto the second right arrow", desc: "moves cursor onto the second right arrow",
@ -1844,20 +1844,19 @@ func TestFieldEditor(t *testing.T) {
fe.cursorRelCell(1) fe.cursorRelCell(1)
return nil return nil
}, },
wantView: "⇦⇦世界⇨", wantView: "⇦⇦世界⇨",
wantContent: "你好世界你", wantContent: "你好世界你",
wantCurIdx: 2, wantCurIdx: 2,
mutations: 5, wantOnChangeCalls: 5,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
fe := newFieldEditor()
var changeCount int var changeCount int
fe.onChange = func(data string) { fe := newFieldEditor(func(data string) {
changeCount++ changeCount++
} })
if tc.ops != nil { if tc.ops != nil {
if err := tc.ops(fe); err != nil { if err := tc.ops(fe); err != nil {
@ -1882,8 +1881,8 @@ func TestFieldEditor(t *testing.T) {
t.Errorf("content -> %q, want %q", gotContent, tc.wantContent) t.Errorf("content -> %q, want %q", gotContent, tc.wantContent)
} }
if tc.mutations != changeCount { if tc.wantOnChangeCalls != changeCount {
t.Errorf("mutation count -> %d, want %d", changeCount, tc.mutations) t.Errorf("unexpected number of onChange calls -> %d, want %d", changeCount, tc.wantOnChangeCalls)
} }
}) })
} }

View File

@ -270,13 +270,14 @@ func OnSubmit(fn SubmitFn) Option {
}) })
} }
// ChangeFn if provided is called when the content of the text input field changes, // The argument to ChangeFn contains all the text in the field after the change.
// the argument data contains all the text in the field.
// //
// The callback function must be thread-safe as the keyboard event that // The callback function must be thread-safe as the keyboard event that
// triggers the submission comes from a separate goroutine. // triggers the submission comes from a separate goroutine.
type ChangeFn func(data string) type ChangeFn func(data string)
// OnChange sets a function that will be called when the content of the text input
// field changes.
func OnChange(fn ChangeFn) Option { func OnChange(fn ChangeFn) Option {
return option(func(opts *options) { return option(func(opts *options) {
opts.onChange = fn opts.onChange = fn

View File

@ -70,10 +70,9 @@ func New(opts ...Option) (*TextInput, error) {
return nil, err return nil, err
} }
ti := &TextInput{ ti := &TextInput{
editor: newFieldEditor(), editor: newFieldEditor(opt.onChange),
opts: opt, opts: opt,
} }
ti.editor.onChange = opt.onChange
for _, r := range ti.opts.defaultText { for _, r := range ti.opts.defaultText {
ti.editor.insert(r) ti.editor.insert(r)
} }