返回 DeepSeek-Reasonix
grapheme.go
根目录 / internal / textutil / grapheme.go
1 package textutil
2
3 import (
4 "strings"
5
6 "github.com/rivo/uniseg"
7 )
8
9 // FitGraphemeBytes returns the longest prefix that fits maxBytes without
10 // splitting a grapheme cluster. If a single cluster is larger than maxBytes, it
11 // returns that whole cluster so callers never emit malformed user-visible text.
12 func FitGraphemeBytes(text string, maxBytes int) string {
13 if maxBytes <= 0 {
14 return ""
15 }
16 end := 0
17 used := 0
18 graphemes := uniseg.NewGraphemes(text)
19 for graphemes.Next() {
20 size := len(graphemes.Str())
21 if used > 0 && used+size > maxBytes {
22 break
23 }
24 end += size
25 used += size
26 if used >= maxBytes {
27 break
28 }
29 }
30 if end > 0 {
31 return text[:end]
32 }
33 graphemes = uniseg.NewGraphemes(text)
34 if !graphemes.Next() {
35 return ""
36 }
37 return graphemes.Str()
38 }
39
40 // ClipGraphemes truncates s to at most max grapheme clusters, counting suffix
41 // inside the budget when suffix is used.
42 func ClipGraphemes(s string, max int, suffix string) string {
43 if max < 1 {
44 max = 1
45 }
46 clusters := collectGraphemes(s, max+1)
47 if len(clusters) <= max && len(clusters) == countGraphemes(s) {
48 return s
49 }
50 suffixClusters := countGraphemes(suffix)
51 keep := max - suffixClusters
52 if keep < 1 {
53 keep = 1
54 suffix = ""
55 }
56 if keep > len(clusters) {
57 keep = len(clusters)
58 }
59 return strings.Join(clusters[:keep], "") + suffix
60 }
61
62 // TruncateGraphemes truncates s to at most max grapheme clusters, then appends
63 // suffix outside that budget. This preserves legacy preview behavior where the
64 // suffix is an extra truncation marker rather than part of the display width.
65 func TruncateGraphemes(s string, max int, suffix string) string {
66 if max < 0 {
67 max = 0
68 }
69 clusters := collectGraphemes(s, max+1)
70 if len(clusters) <= max && len(clusters) == countGraphemes(s) {
71 return s
72 }
73 if max > len(clusters) {
74 max = len(clusters)
75 }
76 return strings.Join(clusters[:max], "") + suffix
77 }
78
79 func collectGraphemes(s string, limit int) []string {
80 if limit < 1 {
81 return nil
82 }
83 clusters := make([]string, 0, limit)
84 graphemes := uniseg.NewGraphemes(s)
85 for graphemes.Next() {
86 clusters = append(clusters, graphemes.Str())
87 if len(clusters) >= limit {
88 break
89 }
90 }
91 return clusters
92 }
93
94 func countGraphemes(s string) int {
95 count := 0
96 graphemes := uniseg.NewGraphemes(s)
97 for graphemes.Next() {
98 count++
99 }
100 return count
101 }
102
102 lines GO