From a760a13a0eb9f72097a302be19772b5b2a6142af Mon Sep 17 00:00:00 2001 From: cmacrae Date: Tue, 20 Nov 2018 14:47:53 +0000 Subject: keys: Implement basic readline movement keys - C-b: left - C-f: right - C-a: beginning-of-line - C-e: end-of-line --- up.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/up.go b/up.go index 9028d89..3236ec8 100644 --- a/up.go +++ b/up.go @@ -326,14 +326,20 @@ 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), + ctrlKey(tcell.KeyCtrlB): if e.cursor > 0 { e.cursor-- } - case key(tcell.KeyRight): + case key(tcell.KeyRight), + ctrlKey(tcell.KeyCtrlF): if e.cursor < len(e.value) { e.cursor++ } + case ctrlKey(tcell.KeyCtrlA): + e.cursor = 0 + case ctrlKey(tcell.KeyCtrlE): + e.cursor = e.lastw default: // Unknown key/combination, not handled return false -- cgit v1.2.3 From edeaabebd3212d0fb1075d9bbedd063eb77446d2 Mon Sep 17 00:00:00 2001 From: cmacrae Date: Tue, 20 Nov 2018 17:22:57 +0000 Subject: keys: Implement `kill-to-end-of-line (C-k)` --- up.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/up.go b/up.go index 3236ec8..3900df8 100644 --- a/up.go +++ b/up.go @@ -340,6 +340,8 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { e.cursor = 0 case ctrlKey(tcell.KeyCtrlE): e.cursor = e.lastw + case ctrlKey(tcell.KeyCtrlK): + e.value = e.value[:e.cursor] default: // Unknown key/combination, not handled return false -- cgit v1.2.3 From e5cc9317dc7019d4f2a211891331e2cc2ff2d9c0 Mon Sep 17 00:00:00 2001 From: cmacrae Date: Tue, 20 Nov 2018 17:59:08 +0000 Subject: keys: Implement yank (C-y) --- up.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/up.go b/up.go index 3900df8..89e7fb5 100644 --- a/up.go +++ b/up.go @@ -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 } @@ -341,7 +342,9 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { case ctrlKey(tcell.KeyCtrlE): e.cursor = e.lastw case ctrlKey(tcell.KeyCtrlK): - e.value = e.value[:e.cursor] + e.kill() + case ctrlKey(tcell.KeyCtrlY): + e.yank() default: // Unknown key/combination, not handled return false @@ -366,6 +369,20 @@ func (e *Editor) delete(dx int) { e.cursor = pos } +func (e *Editor) kill() { + e.killspace = e.value[e.cursor:] + e.value = e.value[:e.cursor] +} + +func (e *Editor) yank() { + if len(e.killspace) == 0 { + return + } + for _, r := range e.killspace { + e.insert(r) + } +} + type BufView struct { // TODO: Wrap bool Y int // Y of the view in the Buf, for down/up scrolling -- cgit v1.2.3 From 1d0e6fb76d2a89e1e70155650061c078060cfb17 Mon Sep 17 00:00:00 2001 From: cmacrae Date: Wed, 21 Nov 2018 10:15:32 +0000 Subject: keys: Use len(e.value) for `C-e` --- up.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/up.go b/up.go index 89e7fb5..c9affa0 100644 --- a/up.go +++ b/up.go @@ -340,7 +340,7 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { case ctrlKey(tcell.KeyCtrlA): e.cursor = 0 case ctrlKey(tcell.KeyCtrlE): - e.cursor = e.lastw + e.cursor = len(e.value) case ctrlKey(tcell.KeyCtrlK): e.kill() case ctrlKey(tcell.KeyCtrlY): -- cgit v1.2.3 From 7ce69017311b3199e7edbdcf7120476c936c0a0a Mon Sep 17 00:00:00 2001 From: cmacrae Date: Wed, 21 Nov 2018 11:24:59 +0000 Subject: keys: Protect against killspace consuming an empty buffer upon kill --- up.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/up.go b/up.go index c9affa0..a3ae807 100644 --- a/up.go +++ b/up.go @@ -370,14 +370,13 @@ func (e *Editor) delete(dx int) { } func (e *Editor) kill() { - e.killspace = e.value[e.cursor:] + if e.cursor != len(e.value) { + e.killspace = append(e.killspace[:0], e.value[e.cursor:]...) + } e.value = e.value[:e.cursor] } func (e *Editor) yank() { - if len(e.killspace) == 0 { - return - } for _, r := range e.killspace { e.insert(r) } -- cgit v1.2.3 From 768439418ecc58dbf740893fbee754e6a19fa90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Czapli=C5=84ski?= Date: Tue, 4 Dec 2018 20:58:37 +0100 Subject: authors: add Calum MacRae --- AUTHORS | 1 + 1 file changed, 1 insertion(+) 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 -- cgit v1.2.3 From 748ed0c619d2e74e39580965820a05acf4e538c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Czapli=C5=84ski?= Date: Tue, 4 Dec 2018 21:04:19 +0100 Subject: keys: supplement ctrlKey() with key() I'm not really sure why KeyCtrlB needs to be wrapped with ctrlKey() on my machine, if it already has Ctrl in name; I hoped key() would be enough here. But it seems to only work in ctrlKey() for me. However, given the confusion, I currently prefer to err on the "beter safe than sorry" side, and slap both variants everywhere. To be fixed some day in future maybe. --- up.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/up.go b/up.go index a3ae807..cc06d1a 100644 --- a/up.go +++ b/up.go @@ -328,22 +328,28 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { case key(tcell.KeyDelete): e.delete(0) case key(tcell.KeyLeft), + key(tcell.KeyCtrlB), ctrlKey(tcell.KeyCtrlB): if e.cursor > 0 { e.cursor-- } case key(tcell.KeyRight), + key(tcell.KeyCtrlF), ctrlKey(tcell.KeyCtrlF): if e.cursor < len(e.value) { e.cursor++ } - case ctrlKey(tcell.KeyCtrlA): + case key(tcell.KeyCtrlA), + ctrlKey(tcell.KeyCtrlA): e.cursor = 0 - case ctrlKey(tcell.KeyCtrlE): + case key(tcell.KeyCtrlE), + ctrlKey(tcell.KeyCtrlE): e.cursor = len(e.value) - case ctrlKey(tcell.KeyCtrlK): + case key(tcell.KeyCtrlK), + ctrlKey(tcell.KeyCtrlK): e.kill() - case ctrlKey(tcell.KeyCtrlY): + case key(tcell.KeyCtrlY), + ctrlKey(tcell.KeyCtrlY): e.yank() default: // Unknown key/combination, not handled -- cgit v1.2.3 From 6f8d6d1da1c11c21aa7472309ce8dd9f9009ac51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Czapli=C5=84ski?= Date: Tue, 4 Dec 2018 21:38:58 +0100 Subject: replace yank with better insert Previous prototype of yank was O(n*m). The new code is more effective O(n+m), and also removes the need for yank function, by extending the insert function to accept more than 1 rune at once. --- up.go | 20 ++++++--------- up_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 up_test.go diff --git a/up.go b/up.go index cc06d1a..0e815f3 100644 --- a/up.go +++ b/up.go @@ -350,7 +350,7 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool { e.kill() case key(tcell.KeyCtrlY), ctrlKey(tcell.KeyCtrlY): - e.yank() + e.insert(e.killspace...) default: // Unknown key/combination, not handled return false @@ -358,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) { @@ -382,12 +382,6 @@ func (e *Editor) kill() { e.value = e.value[:e.cursor] } -func (e *Editor) yank() { - for _, r := range e.killspace { - e.insert(r) - } -} - 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) + } + } +} -- cgit v1.2.3 From 8127fd512737ba84406f8ee4255b69d7ff202058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Czapli=C5=84ski?= Date: Tue, 4 Dec 2018 21:54:44 +0100 Subject: upgrade version to 0.3.2 --- up.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/up.go b/up.go index 0e815f3..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 -- cgit v1.2.3