返回 DeepSeek-Reasonix
width.go
根目录 / internal / agent / width.go
1 package agent
2
3 import (
4 "regexp"
5 "strings"
6
7 "github.com/mattn/go-runewidth"
8 )
9
10 // ansiSGR matches ANSI Select-Graphic-Rendition sequences (\e[…m). Width
11 // measurement strips these so styled streamed text still gets counted by its
12 // visible column footprint.
13 var ansiSGR = regexp.MustCompile("\x1b\\[[0-9;]*m")
14
15 // visibleWidth returns the column count of s after stripping ANSI SGR codes.
16 // Delegates to go-runewidth so emoji, fullwidth forms, and ZWJ sequences all
17 // measure correctly — a hand-rolled CJK-only table missed every emoji range
18 // and made the streamed-text row count drift on emoji-heavy answers.
19 func visibleWidth(s string) int {
20 return runewidth.StringWidth(ansiSGR.ReplaceAllString(s, ""))
21 }
22
23 // streamedRows counts how many rows the cursor has descended after raw text
24 // of length s was printed at the given terminal width. Used by the markdown
25 // redraw to know how far up to move before clearing. Each \n descends one
26 // row; lines whose visible width exceeds the terminal width descend an extra
27 // row per wrap. A line exactly the terminal width does not wrap on its own —
28 // terminals "lazy-wrap" only when the next visible character lands.
29 func streamedRows(s string, width int) int {
30 if width <= 0 {
31 width = 80
32 }
33 rows := 0
34 for _, line := range strings.Split(s, "\n") {
35 if w := visibleWidth(line); w > 0 {
36 rows += (w - 1) / width
37 }
38 }
39 rows += strings.Count(s, "\n")
40 return rows
41 }
42
42 lines GO