Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh/terminal: add Terminal.SetEnterClear(bool) #84

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions ssh/terminal/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ type Terminal struct {
// the incomplete, initial line. That value is stored in
// historyPending.
historyPending string

// enterClear will clear the input line on enter, instead of printing a
// new line after the input. It's useful for replacing the input with
// something else without echoing it.
enterClear bool
}

// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
Expand Down Expand Up @@ -517,14 +522,27 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
}
}
case keyEnter:
t.moveCursorToPos(len(t.line))
t.queue([]rune("\r\n"))
line = string(t.line)
if t.enterClear {
// Clear line on enter instead of starting a new line.
reset := []rune{
// Clear whole line
keyEscape, '[', '2', 'K',
// Clear screen below (for any wrapped lines)
keyEscape, '[', 'J',
}
t.queue(reset)
} else {
// Pushing the line up resets the cursor to 0,0 and we render a
// fresh prompt.
t.moveCursorToPos(len(t.line))
t.queue([]rune("\r\n"))
t.cursorX = 0
t.cursorY = 0
}
ok = true
t.line = t.line[:0]
t.pos = 0
t.cursorX = 0
t.cursorY = 0
t.maxLine = 0
case keyDeleteWord:
// Delete zero or more spaces and then one or more characters.
Expand Down Expand Up @@ -871,6 +889,17 @@ func (t *Terminal) SetSize(width, height int) error {
return err
}

// SetEnterClear controls whether the input line should be cleared upon
// pressing Enter. When false (the default), the input line is pushed up with a
// new-line and the prompt is repainted. When true, the input line is cleared
// without any new lines.
func (t *Terminal) SetEnterClear(on bool) {
t.lock.Lock()
defer t.lock.Unlock()

t.enterClear = on
}

type pasteIndicatorError struct{}

func (pasteIndicatorError) Error() string {
Expand Down
35 changes: 32 additions & 3 deletions ssh/terminal/terminal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,10 @@ func TestKeyPresses(t *testing.T) {
}

var renderTests = []struct {
in string
received string
err error
in string
received string
err error
setEnterClear bool
}{
{
// Cursor move after keyHome (left 4) then enter (right 4, newline)
Expand All @@ -257,6 +258,17 @@ var renderTests = []struct {
"\x1b[4D" + // Put cursor back in position to insert again
"\x1b[4C\r\n", // Put cursor at the end of the line and newline.
},
{
// Write a plain line, enter
in: "abcd\r\n",
received: "> abcd\r\n",
},
{
// Write a plain line, enter with setEnterClear enabled
in: "abcd\r\n",
received: "> abcd\x1b[2K\x1b[J",
setEnterClear: true,
},
}

func TestRender(t *testing.T) {
Expand All @@ -267,6 +279,9 @@ func TestRender(t *testing.T) {
bytesPerRead: j,
}
ss := NewTerminal(c, "> ")
if test.setEnterClear {
ss.SetEnterClear(true)
}
_, err := ss.ReadLine()
if err != test.err {
t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err)
Expand Down Expand Up @@ -405,3 +420,17 @@ func TestOutputNewlines(t *testing.T) {
t.Errorf("incorrect output: was %q, expected %q", output, expected)
}
}

func TestCursorStateCrash(t *testing.T) {
// If the cursor math is off, reading a bunch of times will inevitably
// panic with a slice bounds out of range.
buf := bytes.NewBufferString("foo")
ss := NewTerminal(buf, "> ")
ss.SetEnterClear(true)
for i := 0; i < 100; i++ {
_, err := ss.ReadLine()
if err != nil {
t.Errorf("unexpected error: %q", err)
}
}
}