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) + }) + } +}