From 78276a84eec125ea64bd1dcdc349050f251da1b2 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Wed, 28 Aug 2013 07:36:04 -0400 Subject: [PATCH] go.crypto/ssh/terminal: handle ^W, ^K and ^H R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/13207043 --- terminal.go | 132 +++++++++++++++++++++++++++++++++-------------- terminal_test.go | 38 +++++++++++++- 2 files changed, 130 insertions(+), 40 deletions(-) diff --git a/terminal.go b/terminal.go index 411b1f1..bb69c5b 100644 --- a/terminal.go +++ b/terminal.go @@ -118,6 +118,8 @@ const ( keyAltRight keyHome keyEnd + keyDeleteWord + keyDeleteLine ) // bytesToKey tries to parse a key sequence from b. If successful, it returns @@ -127,6 +129,15 @@ func bytesToKey(b []byte) (int, []byte) { return -1, nil } + switch b[0] { + case 8: // ^H + return keyBackspace, b[1:] + case 11: // ^K + return keyDeleteLine, b[1:] + case 23: // ^W + return keyDeleteWord, b[1:] + } + if b[0] != keyEscape { return int(b[0]), b[1:] } @@ -274,6 +285,73 @@ func (t *Terminal) setLine(newLine []byte, newPos int) { t.pos = newPos } +func (t *Terminal) eraseNPreviousChars(n int) { + if n == 0 { + return + } + + if t.pos < n { + n = t.pos + } + t.pos -= n + t.moveCursorToPos(t.pos) + + copy(t.line[t.pos:], t.line[n+t.pos:]) + t.line = t.line[:len(t.line)-n] + if t.echo { + t.writeLine(t.line[t.pos:]) + for i := 0; i < n; i++ { + t.queue(space) + } + t.cursorX += n + t.moveCursorToPos(t.pos) + } +} + +// countToLeftWord returns then number of characters from the cursor to the +// start of the previous word. +func (t *Terminal) countToLeftWord() int { + if t.pos == 0 { + return 0 + } + + pos := t.pos - 1 + for pos > 0 { + if t.line[pos] != ' ' { + break + } + pos-- + } + for pos > 0 { + if t.line[pos] == ' ' { + pos++ + break + } + pos-- + } + + return t.pos - pos +} + +// countToRightWord returns then number of characters from the cursor to the +// start of the next word. +func (t *Terminal) countToRightWord() int { + pos := t.pos + for pos < len(t.line) { + if t.line[pos] == ' ' { + break + } + pos++ + } + for pos < len(t.line) { + if t.line[pos] != ' ' { + break + } + pos++ + } + return pos - t.pos +} + // handleKey processes the given key and, optionally, returns a line of text // that the user has entered. func (t *Terminal) handleKey(key int) (line string, ok bool) { @@ -282,50 +360,14 @@ func (t *Terminal) handleKey(key int) (line string, ok bool) { if t.pos == 0 { return } - t.pos-- - t.moveCursorToPos(t.pos) - - copy(t.line[t.pos:], t.line[1+t.pos:]) - t.line = t.line[:len(t.line)-1] - if t.echo { - t.writeLine(t.line[t.pos:]) - } - t.queue(eraseUnderCursor) - t.moveCursorToPos(t.pos) + t.eraseNPreviousChars(1) case keyAltLeft: // move left by a word. - if t.pos == 0 { - return - } - t.pos-- - for t.pos > 0 { - if t.line[t.pos] != ' ' { - break - } - t.pos-- - } - for t.pos > 0 { - if t.line[t.pos] == ' ' { - t.pos++ - break - } - t.pos-- - } + t.pos -= t.countToLeftWord() t.moveCursorToPos(t.pos) case keyAltRight: // move right by a word. - for t.pos < len(t.line) { - if t.line[t.pos] == ' ' { - break - } - t.pos++ - } - for t.pos < len(t.line) { - if t.line[t.pos] != ' ' { - break - } - t.pos++ - } + t.pos += t.countToRightWord() t.moveCursorToPos(t.pos) case keyLeft: if t.pos == 0 { @@ -385,6 +427,18 @@ func (t *Terminal) handleKey(key int) (line string, ok bool) { t.cursorX = 0 t.cursorY = 0 t.maxLine = 0 + case keyDeleteWord: + // Delete zero or more spaces and then one or more characters. + t.eraseNPreviousChars(t.countToLeftWord()) + case keyDeleteLine: + // Delete everything from the current cursor position to the + // end of line. + for i := t.pos; i < len(t.line); i++ { + t.queue(space) + t.cursorX++ + } + t.line = t.line[:t.pos] + t.moveCursorToPos(t.pos) default: if t.AutoCompleteCallback != nil { t.lock.Unlock() diff --git a/terminal_test.go b/terminal_test.go index 7db3171..75584d3 100644 --- a/terminal_test.go +++ b/terminal_test.go @@ -101,11 +101,47 @@ var keyPressTests = []struct { line: "line1xxx", throwAwayLines: 2, }, + { + in: "\027\r", + line: "", + }, + { + in: "a\027\r", + line: "", + }, + { + in: "a \027\r", + line: "", + }, + { + in: "a b\027\r", + line: "a ", + }, + { + in: "a b \027\r", + line: "a ", + }, + { + in: "one two thr\x1b[D\027\r", + line: "one two r", + }, + { + in: "\013\r", + line: "", + }, + { + in: "a\013\r", + line: "a", + }, + { + in: "ab\x1b[D\013\r", + line: "a", + }, } func TestKeyPresses(t *testing.T) { for i, test := range keyPressTests { - for j := 0; j < len(test.in); j++ { + for j := 1; j < len(test.in); j++ { c := &MockTerminal{ toSend: []byte(test.in), bytesPerRead: j,