Repository: akavel/up (language: go)

Issue:
Make Ctrl-W work to erase the word before the cursor.
It the terminal (with `readline` for example, `Ctrl-W` erase the command until finding a space, which is very practical and is missing in `up`.


Discussion / hints:

Fix patch:
diff --git a/AUTHORS b/AUTHORS
index 1a7b9a6..4209ea9 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,5 +2,6 @@ Please keep the contents of this file sorted alphabetically.
 
 Александр Крамарев <pochemuto@gmail.com>
 Calum MacRae <calum0macrae@gmail.com>
+Ghislain Rodrigues <git@ghislain-rodrigues.fr>
 Mateusz Czapliński <czapkofan@gmail.com>
 Rohan Verma <hello@rohanverma.net>
diff --git a/up.go b/up.go
index 53ebc09..e83cceb 100644
--- a/up.go
+++ b/up.go
@@ -30,6 +30,7 @@ import (
 	"os"
 	"os/exec"
 	"sync"
+	"unicode"
 
 	"github.com/gdamore/tcell"
 	"github.com/gdamore/tcell/terminfo"
@@ -97,7 +98,7 @@ If a plus '+' is visible in top-left corner, the internal buffer limit
 
 KEYS
 
-- alphanumeric & symbol keys, Left, Right, Ctrl-A/E/B/F/K/Y
+- alphanumeric & symbol keys, Left, Right, Ctrl-A/E/B/F/K/Y/W
                       - navigate and edit the pipeline command
 - Enter   - execute the pipeline command, updating the pipeline output panel
 - Up, Dn, PgUp, PgDn, Ctrl-Left, Ctrl-Right
@@ -419,6 +420,9 @@ func (e *Editor) HandleKey(ev *tcell.EventKey) bool {
 	case key(tcell.KeyCtrlY),
 		ctrlKey(tcell.KeyCtrlY):
 		e.insert(e.killspace...)
+	case key(tcell.KeyCtrlW),
+		ctrlKey(tcell.KeyCtrlW):
+		e.unixWordRubout()
 	default:
 		// Unknown key/combination, not handled
 		return false
@@ -450,6 +454,22 @@ func (e *Editor) kill() {
 	e.value = e.value[:e.cursor]
 }
 
+// unixWordRubout removes the part of the word on the left of the cursor. A word is
+// delimited by whitespaces.
+// The term `unix-word-rubout` comes from `readline` (see `man 3 readline`)
+func (e *Editor) unixWordRubout() {
+	if e.cursor <= 0 {
+		return
+	}
+	pos := e.cursor - 1
+	for pos != 0 && (unicode.IsSpace(e.value[pos]) || !unicode.IsSpace(e.value[pos-1])) {
+		pos--
+	}
+	e.killspace = append(e.killspace[:0], e.value[pos:e.cursor]...)
+	e.value = append(e.value[:pos], e.value[e.cursor:]...)
+	e.cursor = pos
+}
+
 type BufView struct {
 	// TODO: Wrap bool
 	Y   int // Y of the view in the Buf, for down/up scrolling


Test patch:
diff --git a/up_test.go b/up_test.go
index 20e8482..87f5278 100644
--- a/up_test.go
+++ b/up_test.go
@@ -81,3 +81,95 @@ func Test_Editor_insert(t *testing.T) {
 		}
 	}
 }
+
+func Test_Editor_unix_word_rubout(t *testing.T) {
+	tests := []struct {
+		comment       string
+		e             Editor
+		wantValue     []rune
+		wantKillspace []rune
+	}{
+		{
+			comment: "unix-word-rubout at beginning of line",
+			e: Editor{
+				value:  []rune(`abc`),
+				cursor: 0,
+			},
+			wantValue:     []rune(`abc`),
+			wantKillspace: []rune(``),
+		},
+		{
+			comment: "unix-word-rubout at soft beginning of line",
+			e: Editor{
+				value:  []rune(` abc`),
+				cursor: 1,
+			},
+			wantValue:     []rune(`abc`),
+			wantKillspace: []rune(` `),
+		},
+		{
+			comment: "unix-word-rubout until soft beginning of line",
+			e: Editor{
+				value:  []rune(` abc`),
+				cursor: 2,
+			},
+			wantValue:     []rune(` bc`),
+			wantKillspace: []rune(`a`),
+		},
+		{
+			comment: "unix-word-rubout until beginning of line",
+			e: Editor{
+				value:  []rune(`abc`),
+				cursor: 2,
+			},
+			wantValue:     []rune(`c`),
+			wantKillspace: []rune(`ab`),
+		},
+		{
+			comment: "unix-word-rubout in middle of line",
+			e: Editor{
+				value:  []rune(`lorem ipsum dolor`),
+				cursor: 11,
+			},
+			wantValue:     []rune(`lorem  dolor`),
+			wantKillspace: []rune(`ipsum`),
+		},
+		{
+			comment: "unix-word-rubout cursor at beginning of word",
+			e: Editor{
+				value:  []rune(`lorem ipsum dolor`),
+				cursor: 12,
+			},
+			wantValue:     []rune(`lorem dolor`),
+			wantKillspace: []rune(`ipsum `),
+		},
+		{
+			comment: "unix-word-rubout cursor between multiple spaces",
+			e: Editor{
+				value:  []rune(`a b   c`),
+				cursor: 5,
+			},
+			wantValue:     []rune(`a  c`),
+			wantKillspace: []rune(`b  `),
+		},
+		{
+			comment: "unix-word-rubout tab as space char (although is it a realistic case in the context of a command line instruction?)",
+			e: Editor{
+				value: []rune(`a b		c`),
+				cursor: 5,
+			},
+			wantValue: []rune(`a c`),
+			wantKillspace: []rune(`b		`),
+		},
+	}
+
+	for _, tt := range tests {
+		tt.e.unixWordRubout()
+		if string(tt.e.value) != string(tt.wantValue) {
+			t.Errorf("%q: bad value\nwant: %q\nhave: %q", tt.comment, tt.wantValue, tt.e.value)
+		}
+		if string(tt.e.killspace) != string(tt.wantKillspace) {
+			t.Errorf("%q: bad value in killspace\nwant: %q\nhave: %q", tt.comment, tt.wantKillspace, tt.e.value)
+		}
+	}
+}