From e64d9b644fb7d85d69cd3e33a92a43ee706c1b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Thu, 17 Dec 2020 22:03:06 +0100 Subject: [PATCH] Fix re-rendering when prompt is exactly the width of the terminal (#321) * Fix re-rendering when prompt is exactly the width of the terminal The code that measures width of rendered lines of content and whether any have overflown in the terminal did not account for the possibility that the content could be _exactly_ the width of the terminal. In that case, it shouldn't be counted as overflow. * Reduce test dependencies by going straight to `pty` This skips the go-expect & vt10x dependencies when stubbing only terminal size and goes straight to the `creack/pty` library, which is already used by go-expect under its old name, `kr/pty`. * Use old name of `creack/pty` since it was already vendored --- go.mod | 2 +- renderer.go | 11 ++++- renderer_posix_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 renderer_posix_test.go diff --git a/go.mod b/go.mod index 88d01fea..81d85215 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/kr/pty v1.1.4 // indirect + github.com/kr/pty v1.1.4 github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-isatty v0.0.8 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b diff --git a/renderer.go b/renderer.go index d436ebc7..54251213 100644 --- a/renderer.go +++ b/renderer.go @@ -148,8 +148,15 @@ func (r *Renderer) countLines(buf bytes.Buffer) int { delim = len(bufBytes) // no new line found, read rest of text } - // account for word wrapping - count += int(utf8.RuneCount(bufBytes[curr:delim]) / w) + if lineWidth := utf8.RuneCount(bufBytes[curr:delim]); lineWidth > w { + // account for word wrapping + count += lineWidth / w + if (lineWidth % w) == 0 { + // content whose width is exactly a multiplier of available width should not + // count as having wrapped on the last line + count -= 1 + } + } curr = delim + 1 } diff --git a/renderer_posix_test.go b/renderer_posix_test.go new file mode 100644 index 00000000..5797cc1e --- /dev/null +++ b/renderer_posix_test.go @@ -0,0 +1,91 @@ +// +build !windows + +package survey + +import ( + "bytes" + "strings" + "testing" + + "github.com/AlecAivazis/survey/v2/terminal" + pseudotty "github.com/kr/pty" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenderer_countLines(t *testing.T) { + t.Parallel() + + termWidth := 72 + pty, tty, err := pseudotty.Open() + require.Nil(t, err) + defer pty.Close() + defer tty.Close() + + err = pseudotty.Setsize(tty, &pseudotty.Winsize{ + Rows: 30, + Cols: uint16(termWidth), + }) + require.Nil(t, err) + + r := Renderer{ + stdio: terminal.Stdio{ + In: tty, + Out: tty, + Err: tty, + }, + } + + tests := []struct { + name string + buf *bytes.Buffer + wants int + }{ + { + name: "empty", + buf: new(bytes.Buffer), + wants: 0, + }, + { + name: "no newline", + buf: bytes.NewBufferString("hello"), + wants: 0, + }, + { + name: "short line", + buf: bytes.NewBufferString("hello\n"), + wants: 1, + }, + { + name: "three short lines", + buf: bytes.NewBufferString("hello\nbeautiful\nworld\n"), + wants: 3, + }, + { + name: "full line", + buf: bytes.NewBufferString(strings.Repeat("A", termWidth) + "\n"), + wants: 1, + }, + { + name: "overflow", + buf: bytes.NewBufferString(strings.Repeat("A", termWidth+1) + "\n"), + wants: 2, + }, + { + name: "overflow fills 2nd line", + buf: bytes.NewBufferString(strings.Repeat("A", termWidth*2) + "\n"), + wants: 2, + }, + { + name: "overflow spills to 3rd line", + buf: bytes.NewBufferString(strings.Repeat("A", termWidth*2+1) + "\n"), + wants: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := r.countLines(*tt.buf) + assert.Equal(t, tt.wants, n) + }) + } +}