diff --git a/AUTHORS b/AUTHORS index c7f2ac2..3ddb6f7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,5 @@ Please keep the contents of this file sorted alphabetically. +Calum MacRae Mateusz Czapliński Rohan Verma diff --git a/up.go b/up.go index 9028d89..be6a877 100644 --- a/up.go +++ b/up.go @@ -36,7 +36,7 @@ import ( "github.com/spf13/pflag" ) -const version = "0.3.1 (2018-10-31)" +const version = "0.3.2 (2018-12-04)" // TODO: in case of error, show it in red (bg?), then below show again initial normal output (see also #4) // TODO: F1 should display help, and it should be multi-line, and scrolling licensing credits @@ -283,9 +283,10 @@ func NewEditor(prompt string) *Editor { type Editor struct { // TODO: make editor multiline. Reuse gocui or something for this? - prompt []rune - value []rune - cursor int + prompt []rune + value []rune + killspace []rune + cursor int // lastw is length of value on last Draw; we need it to know how much to erase after backspace lastw int } @@ -326,14 +327,30 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { e.delete(-1) case key(tcell.KeyDelete): e.delete(0) - case key(tcell.KeyLeft): + case key(tcell.KeyLeft), + key(tcell.KeyCtrlB), + ctrlKey(tcell.KeyCtrlB): if e.cursor > 0 { e.cursor-- } - case key(tcell.KeyRight): + case key(tcell.KeyRight), + key(tcell.KeyCtrlF), + ctrlKey(tcell.KeyCtrlF): if e.cursor < len(e.value) { e.cursor++ } + case key(tcell.KeyCtrlA), + ctrlKey(tcell.KeyCtrlA): + e.cursor = 0 + case key(tcell.KeyCtrlE), + ctrlKey(tcell.KeyCtrlE): + e.cursor = len(e.value) + case key(tcell.KeyCtrlK), + ctrlKey(tcell.KeyCtrlK): + e.kill() + case key(tcell.KeyCtrlY), + ctrlKey(tcell.KeyCtrlY): + e.insert(e.killspace...) default: // Unknown key/combination, not handled return false @@ -341,12 +358,12 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { return true } -func (e *Editor) insert(ch rune) { - // Insert character into value (https://github.com/golang/go/wiki/SliceTricks#insert) - e.value = append(e.value, 0) - copy(e.value[e.cursor+1:], e.value[e.cursor:]) - e.value[e.cursor] = ch - e.cursor++ +func (e *Editor) insert(ch ...rune) { + // Based on https://github.com/golang/go/wiki/SliceTricks#insert + e.value = append(e.value, ch...) // = PREFIX + SUFFIX + (filler) + copy(e.value[e.cursor+len(ch):], e.value[e.cursor:]) // = PREFIX + (filler) + SUFFIX + copy(e.value[e.cursor:], ch) // = PREFIX + ch + SUFFIX + e.cursor += len(ch) } func (e *Editor) delete(dx int) { @@ -358,6 +375,13 @@ func (e *Editor) delete(dx int) { e.cursor = pos } +func (e *Editor) kill() { + if e.cursor != len(e.value) { + e.killspace = append(e.killspace[:0], e.value[e.cursor:]...) + } + e.value = e.value[:e.cursor] +} + type BufView struct { // TODO: Wrap bool Y int // Y of the view in the Buf, for down/up scrolling diff --git a/up_test.go b/up_test.go new file mode 100644 index 0000000..de31c59 --- /dev/null +++ b/up_test.go @@ -0,0 +1,83 @@ +package main + +import "testing" + +func Test_Editor_insert(test *testing.T) { + cases := []struct { + comment string + e Editor + insert []rune + wantValue []rune + }{ + { + comment: "prepend ASCII char", + e: Editor{ + value: []rune(`abc`), + cursor: 0, + }, + insert: []rune{'X'}, + wantValue: []rune(`Xabc`), + }, + { + comment: "prepend UTF char", + e: Editor{ + value: []rune(`abc`), + cursor: 0, + }, + insert: []rune{'☃'}, + wantValue: []rune(`☃abc`), + }, + { + comment: "insert ASCII char", + e: Editor{ + value: []rune(`abc`), + cursor: 1, + }, + insert: []rune{'X'}, + wantValue: []rune(`aXbc`), + }, + { + comment: "insert UTF char", + e: Editor{ + value: []rune(`abc`), + cursor: 1, + }, + insert: []rune{'☃'}, + wantValue: []rune(`a☃bc`), + }, + { + comment: "append ASCII char", + e: Editor{ + value: []rune(`abc`), + cursor: 3, + }, + insert: []rune{'X'}, + wantValue: []rune(`abcX`), + }, + { + comment: "append UTF char", + e: Editor{ + value: []rune(`abc`), + cursor: 3, + }, + insert: []rune{'☃'}, + wantValue: []rune(`abc☃`), + }, + { + comment: "insert 2 ASCII chars", + e: Editor{ + value: []rune(`abc`), + cursor: 1, + }, + insert: []rune{'X', 'Y'}, + wantValue: []rune(`aXYbc`), + }, + } + + for _, c := range cases { + c.e.insert(c.insert...) + if string(c.e.value) != string(c.wantValue) { + test.Errorf("%q: bad value\nwant: %q\nhave: %q", c.comment, c.wantValue, c.e.value) + } + } +}