aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-04-18 17:34:37 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-04-18 17:34:37 -0700
commitb502299b708395b51a7252826e0da4b72b2787d9 (patch)
treebcf439389afaa9117449e02e52fec30a3c6120a1
parentLet errors scroll off the screen (diff)
downloadccp-b502299b708395b51a7252826e0da4b72b2787d9.tar.zst
Handle wide characters in the renderer
-rw-r--r--go.mod4
-rw-r--r--go.sum2
-rw-r--r--internal/render/render.go25
3 files changed, 18 insertions, 13 deletions
diff --git a/go.mod b/go.mod
index d3d80df..9b7c1fe 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ go 1.24.1
require (
github.com/charmbracelet/bubbles v0.21.0
- github.com/charmbracelet/bubbletea v1.3.4
github.com/charmbracelet/lipgloss v1.1.0
+ github.com/mattn/go-runewidth v0.0.16
github.com/pkg/sftp v1.13.9
github.com/rhogenson/deque v1.1.0
golang.org/x/crypto v0.37.0
@@ -14,6 +14,7 @@ require (
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/charmbracelet/bubbletea v1.3.4 // indirect
github.com/charmbracelet/colorprofile v0.3.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
@@ -24,7 +25,6 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
diff --git a/go.sum b/go.sum
index c7aa66b..02879df 100644
--- a/go.sum
+++ b/go.sum
@@ -42,8 +42,6 @@ github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rhogenson/deque v1.0.1 h1:YWzJiImHBaITBHjQ6eXzc6v1oCTvk9r9vrHqjxB4aOk=
-github.com/rhogenson/deque v1.0.1/go.mod h1:3JVW0+HcBcOClQtYlHhsyE5Q5cr/qG1jLEeC46prdtQ=
github.com/rhogenson/deque v1.1.0 h1:3poepkqJjq3jp+s3eqQ/WTAtiX1hXOmpp+h8l2+PwFI=
github.com/rhogenson/deque v1.1.0/go.mod h1:3JVW0+HcBcOClQtYlHhsyE5Q5cr/qG1jLEeC46prdtQ=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
diff --git a/internal/render/render.go b/internal/render/render.go
index 18708f3..c43d2e8 100644
--- a/internal/render/render.go
+++ b/internal/render/render.go
@@ -6,7 +6,8 @@ import (
"bytes"
"fmt"
"os"
- "unicode/utf8"
+
+ runewidth "github.com/mattn/go-runewidth"
)
// Renderer updates a terminal UI. Typical usage looks like
@@ -46,9 +47,13 @@ func (r *Renderer) Clear(width int) {
}
func truncate(b []byte, width int) ([]byte, int) {
- n := 0
+ str := string(b)
+ currentWidth := 0
for i := 0; i < len(b); {
- if bytes.HasPrefix(b[i:], []byte("\033[")) {
+ chunkBytes := bytes.Index(b[i:], []byte("\033["))
+ if chunkBytes < 0 {
+ chunkBytes = len(b) - i
+ } else if chunkBytes == 0 {
// An escape sequence usually starts with [, then has one or two numbers
// separated by semicolon, and ends with some terminating character. To
// try and munch the whole sequence, skip over any numbers and
@@ -59,14 +64,16 @@ func truncate(b []byte, width int) ([]byte, int) {
i++
continue
}
- if n+1 > width {
- return b[:i], n
+ chunkWidth := runewidth.StringWidth(str[i : i+chunkBytes])
+ if currentWidth+chunkWidth <= width {
+ i += chunkBytes
+ currentWidth += chunkWidth
+ continue
}
- _, runeWidth := utf8.DecodeRune(b[i:])
- i += runeWidth
- n++
+ lastChunk := runewidth.Truncate(str[i:i+chunkBytes], width-currentWidth, "")
+ return b[:i+len(lastChunk)], width
}
- return b, n
+ return b, currentWidth
}
// Write implements io.Writer.