Skip to content

Commit 7fa08e5

Browse files
committed
fix(readline): handle wrapped render rows
1 parent 5b4d445 commit 7fa08e5

4 files changed

Lines changed: 49 additions & 7 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.22
44

55
require (
66
github.com/gookit/color v1.6.2-0.20260604125953-289d54c4470a
7-
github.com/gookit/goutil v0.7.6
7+
github.com/gookit/goutil v0.8.0
88
golang.org/x/term v0.29.0
99
)
1010

go.sum

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
22
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
3-
github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
4-
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
53
github.com/gookit/color v1.6.2-0.20260604125953-289d54c4470a h1:utlwkRKoYYXGEGyQBQSy7p/gYxOV9SQXvF9JhaRoLmA=
64
github.com/gookit/color v1.6.2-0.20260604125953-289d54c4470a/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
7-
github.com/gookit/goutil v0.7.6 h1:700ZP6QPWhw5ms7X13JH9fUs4LTyYMmncFFMGpK73ns=
8-
github.com/gookit/goutil v0.7.6/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
5+
github.com/gookit/goutil v0.8.0 h1:efZWxfesXw8+5tQfTfRMSIC6A0ax527/H+A/aIiaSrw=
6+
github.com/gookit/goutil v0.8.0/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
97
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
108
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
119
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=

interact/backend/readline/readline.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/gookit/cliui/interact/backend"
1313
"github.com/gookit/cliui/interact/backend/plain"
1414
"github.com/gookit/color"
15+
"github.com/gookit/goutil/strutil"
1516
"golang.org/x/term"
1617
)
1718

@@ -97,6 +98,7 @@ func (s *Session) Render(view backend.View) error {
9798
}
9899
}
99100

101+
currentRows := viewRows(view)
100102
renderedLines := len(view.Lines)
101103
if s.rendered > renderedLines {
102104
renderedLines = s.rendered
@@ -113,7 +115,7 @@ func (s *Session) Render(view backend.View) error {
113115
}
114116

115117
if view.CursorRow >= 0 && view.CursorRow < len(view.Lines) {
116-
moveUp := renderedLines - 1 - view.CursorRow
118+
moveUp := currentRows - 1 - cursorScreenRow(view)
117119
if moveUp > 0 {
118120
fmt.Fprintf(s.out, "\x1B[%dA", moveUp)
119121
}
@@ -123,10 +125,36 @@ func (s *Session) Render(view backend.View) error {
123125
}
124126
}
125127

126-
s.rendered = len(view.Lines)
128+
s.rendered = currentRows
127129
return nil
128130
}
129131

132+
func viewRows(view backend.View) int {
133+
if view.Width <= 0 {
134+
return len(view.Lines)
135+
}
136+
137+
rows := 0
138+
for _, line := range view.Lines {
139+
width := strutil.TextWidth(color.ClearTag(line))
140+
rows += 1 + (width-1)/view.Width
141+
}
142+
return rows
143+
}
144+
145+
func cursorScreenRow(view backend.View) int {
146+
if view.Width <= 0 {
147+
return view.CursorRow
148+
}
149+
150+
row := 0
151+
for _, line := range view.Lines[:view.CursorRow] {
152+
width := strutil.TextWidth(color.ClearTag(line))
153+
row += 1 + (width-1)/view.Width
154+
}
155+
return row + view.CursorColumn/view.Width
156+
}
157+
130158
// ReadEvent reads and normalizes one terminal event.
131159
func (s *Session) ReadEvent(ctx context.Context) (backend.Event, error) {
132160
select {

interact/backend/readline/readline_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ func TestSession_RenderClearsStaleLinesWhenViewShrinks(t *testing.T) {
7373
is.Eq(2, s.rendered)
7474
}
7575

76+
func TestSession_RenderTracksWrappedRows(t *testing.T) {
77+
is := assert.New(t)
78+
79+
buf := new(bytes.Buffer)
80+
s := &Session{out: buf}
81+
82+
err := s.Render(backend.View{Lines: []string{"one", strings.Repeat("x", 11)}, CursorRow: 1, Width: 10})
83+
is.Nil(err)
84+
is.Eq(3, s.rendered)
85+
86+
buf.Reset()
87+
err = s.Render(backend.View{Lines: []string{"one", "two"}, CursorRow: 1, Width: 10})
88+
is.Nil(err)
89+
is.Contains(buf.String(), "\x1B[2A")
90+
}
91+
7692
func TestSession_RenderUsesCarriageReturnAfterNewline(t *testing.T) {
7793
is := assert.New(t)
7894

0 commit comments

Comments
 (0)