返回 DeepSeek-Reasonix
glyph_fit.go
根目录 / internal / cli / glyph_fit.go
1 package cli
2
3 import (
4 "strings"
5 "unicode/utf8"
6
7 "github.com/charmbracelet/x/ansi"
8 "golang.org/x/text/width"
9 )
10
11 // glyphFit swaps each rune the console draws wider than the layout counted for
12 // a stand-in both agree on, so a row laid out to the terminal width stays that
13 // wide on screen. The console's own measurement is the only judge of "wider".
14 type glyphFit struct {
15 cells func(rune) int // columns the console advances for r; 0 when unmeasured
16 bestFit func(rune) rune // single-byte best-fit stand-in for r, 0 when none
17 chosen map[rune]rune // per-rune verdict; r maps to itself when it already fits
18 }
19
20 func newGlyphFit(cells func(rune) int, bestFit func(rune) rune) *glyphFit {
21 return &glyphFit{cells: cells, bestFit: bestFit, chosen: map[rune]rune{}}
22 }
23
24 // apply rewrites the printable text of a styled frame; escape sequences,
25 // including OSC payloads such as hyperlink targets, pass through untouched.
26 func (g *glyphFit) apply(s string) string {
27 if g == nil || !g.needed(s) {
28 return s
29 }
30 var b strings.Builder
31 b.Grow(len(s))
32 var state byte
33 for len(s) > 0 {
34 seq, w, n, next := ansi.DecodeSequence(s, state, nil)
35 state = next
36 if w == 0 {
37 b.WriteString(seq)
38 } else {
39 for _, r := range seq {
40 b.WriteRune(g.fit(r))
41 }
42 }
43 s = s[n:]
44 }
45 return b.String()
46 }
47
48 func (g *glyphFit) needed(s string) bool {
49 for _, r := range s {
50 if r >= utf8.RuneSelf && g.fit(r) != r {
51 return true
52 }
53 }
54 return false
55 }
56
57 func (g *glyphFit) fit(r rune) rune {
58 if r < utf8.RuneSelf {
59 return r
60 }
61 if c, ok := g.chosen[r]; ok {
62 return c
63 }
64 c := g.choose(r)
65 g.chosen[r] = c
66 return c
67 }
68
69 // choose prefers Unicode's own halfwidth variant, then the OS best-fit mapping;
70 // a candidate counts only when the console measures it at the counted width.
71 func (g *glyphFit) choose(r rune) rune {
72 counted := ansi.StringWidth(string(r))
73 if counted != 1 || g.cells(r) <= counted {
74 return r
75 }
76 for _, c := range []rune{width.LookupRune(r).Narrow(), g.bestFit(r)} {
77 if c > 0 && c != r && ansi.StringWidth(string(c)) == counted && g.cells(c) == counted {
78 return c
79 }
80 }
81 return r
82 }
83
83 lines GO