fix(cli): make stream width handling rune-safe
Some checks are pending
Security Scan / security (push) Waiting to run
Some checks are pending
Security Scan / security (push) Waiting to run
This commit is contained in:
parent
cf9c068650
commit
e29b6e4889
2 changed files with 27 additions and 6 deletions
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unicode/utf8"
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StreamOption configures a Stream.
|
// StreamOption configures a Stream.
|
||||||
|
|
@ -60,11 +61,11 @@ func (s *Stream) Write(text string) {
|
||||||
|
|
||||||
if s.wrap <= 0 {
|
if s.wrap <= 0 {
|
||||||
fmt.Fprint(s.out, text)
|
fmt.Fprint(s.out, text)
|
||||||
// Track column across newlines for Done() trailing-newline logic.
|
// Track visible width across newlines for Done() trailing-newline logic.
|
||||||
if idx := strings.LastIndex(text, "\n"); idx >= 0 {
|
if idx := strings.LastIndex(text, "\n"); idx >= 0 {
|
||||||
s.col = utf8.RuneCountInString(text[idx+1:])
|
s.col = runewidth.StringWidth(text[idx+1:])
|
||||||
} else {
|
} else {
|
||||||
s.col += utf8.RuneCountInString(text)
|
s.col += runewidth.StringWidth(text)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -76,13 +77,14 @@ func (s *Stream) Write(text string) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.col >= s.wrap {
|
rw := runewidth.RuneWidth(r)
|
||||||
|
if rw > 0 && s.col > 0 && s.col+rw > s.wrap {
|
||||||
fmt.Fprintln(s.out)
|
fmt.Fprintln(s.out)
|
||||||
s.col = 0
|
s.col = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprint(s.out, string(r))
|
fmt.Fprint(s.out, string(r))
|
||||||
s.col++
|
s.col += rw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,14 @@ func TestStream_Good(t *testing.T) {
|
||||||
assert.Equal(t, 11, s.Column())
|
assert.Equal(t, 11, s.Column())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("column tracking uses visible width", func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
s := NewStream(WithStreamOutput(&buf))
|
||||||
|
|
||||||
|
s.Write("東京")
|
||||||
|
assert.Equal(t, 4, s.Column())
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("WriteFrom io.Reader", func(t *testing.T) {
|
t.Run("WriteFrom io.Reader", func(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
s := NewStream(WithStreamOutput(&buf))
|
s := NewStream(WithStreamOutput(&buf))
|
||||||
|
|
@ -144,6 +152,17 @@ func TestStream_Good(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, "text\n", buf.String()) // no double newline
|
assert.Equal(t, "text\n", buf.String()) // no double newline
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("word wrap uses visible width", func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
s := NewStream(WithWordWrap(4), WithStreamOutput(&buf))
|
||||||
|
|
||||||
|
s.Write("東京A")
|
||||||
|
s.Done()
|
||||||
|
s.Wait()
|
||||||
|
|
||||||
|
assert.Equal(t, "東京\nA\n", buf.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStream_Bad(t *testing.T) {
|
func TestStream_Bad(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue