返回 DeepSeek-Reasonix
glyph_fit_test.go
根目录 / internal / cli / glyph_fit_test.go
1 package cli
2
3 import (
4 "strings"
5 "testing"
6
7 tea "charm.land/bubbletea/v2"
8 "github.com/charmbracelet/x/ansi"
9
10 "reasonix/internal/control"
11 "reasonix/internal/event"
12 "reasonix/internal/i18n"
13 )
14
15 // conhostCJKCells is what a legacy conhost window with the default Chinese
16 // console font measured through WriteConsoleW + cursor read-back (#10540).
17 var conhostCJKCells = map[rune]int{
18 '·': 2, '…': 2, '“': 2, '”': 2, '‘': 2, '’': 2, '—': 2, '–': 2,
19 '●': 2, '○': 2, '←': 2, '↑': 2, '→': 2, '↓': 2, '※': 2, '★': 2,
20 '■': 2, '◆': 2, 'α': 2, 'β': 2, 'Ω': 2, 'Ж': 2, 'ж': 2, '°': 2,
21 '±': 2, '×': 2, '€': 2,
22 '─': 1, '│': 1, '┌': 1, '•': 1, '█': 1, 'ü': 1, 'é': 1, '™': 1,
23 '←': 1, '↑': 1, '→': 1, '↓': 1, '■': 1, '○': 1, '│': 1,
24 }
25
26 // conhostBestFit is the same machine's WideCharToMultiByte(20127) answer.
27 var conhostBestFit = map[rune]rune{
28 '·': '.', '…': '.', '“': '"', '”': '"', '‘': '\'', '’': '\'', '—': '-', '–': '-',
29 '•': '.', 'ü': 'u', 'é': 'e', '™': 'T',
30 }
31
32 func conhostTableFit() *glyphFit {
33 return newGlyphFit(conhostCellsOf, func(r rune) rune {
34 if c, ok := conhostBestFit[r]; ok {
35 return c
36 }
37 return '?'
38 })
39 }
40
41 func conhostCellsOf(r rune) int {
42 if w, ok := conhostCJKCells[r]; ok {
43 return w
44 }
45 if r < 0x80 {
46 return 1
47 }
48 return ansi.StringWidth(string(r))
49 }
50
51 func conhostRowCells(row string) int {
52 n := 0
53 for _, r := range ansi.Strip(row) {
54 n += conhostCellsOf(r)
55 }
56 return n
57 }
58
59 func TestChatFrameFitsConhostMeasuredWidths(t *testing.T) {
60 i18n.DetectLanguage("en")
61 const cols = 120
62 ctrl := newOwnedTestController(t, control.Options{})
63 m := newChatTUI(ctrl, "", make(chan event.Event, 1), cols)
64 m.glyphs = conhostTableFit()
65 m.notice(strings.Repeat("“引号”…—→·", 12))
66 next, _ := m.Update(tea.WindowSizeMsg{Width: cols, Height: 30})
67 m = next.(chatTUI)
68
69 frame := m.View().Content
70 for i, row := range strings.Split(frame, "\n") {
71 if got := conhostRowCells(row); got > cols {
72 t.Fatalf("row %d draws %d cells on conhost, terminal has %d:\n%s", i, got, cols, ansi.Strip(row))
73 }
74 }
75 if !strings.Contains(ansi.Strip(frame), `"引号".-→.`) {
76 t.Fatalf("notice should carry the measured stand-ins:\n%s", ansi.Strip(frame))
77 }
78 }
79
80 func TestGlyphFitLeavesEscapePayloadsAlone(t *testing.T) {
81 link := ansi.SetHyperlink("https://example.com/·") + "a·b" + ansi.ResetHyperlink()
82 got := conhostTableFit().apply(link)
83 want := ansi.SetHyperlink("https://example.com/·") + "a.b" + ansi.ResetHyperlink()
84 if got != want {
85 t.Fatalf("apply(%q) = %q, want %q", link, got, want)
86 }
87 }
88
89 func TestGlyphFitKeepsRunesTheConsoleAgreesOn(t *testing.T) {
90 in := "│─┌ 中文 • ü"
91 if got := conhostTableFit().apply(in); got != in {
92 t.Fatalf("apply(%q) = %q, want it unchanged", in, got)
93 }
94 }
95
95 lines GO