@@ -24,10 +24,48 @@ var (
2424 }
2525)
2626
27+ var (
28+ zerowidth table // combining + nonprint merged for faster zero-width lookup
29+ widewidth table // ambiguous + doublewidth merged for EA path
30+ )
31+
2732func init () {
33+ zerowidth = mergeIntervals (combining , nonprint )
34+ widewidth = mergeIntervals (ambiguous , doublewidth )
2835 handleEnv ()
2936}
3037
38+ func mergeIntervals (t1 , t2 table ) table {
39+ merged := make (table , 0 , len (t1 )+ len (t2 ))
40+ i , j := 0 , 0
41+ for i < len (t1 ) && j < len (t2 ) {
42+ if t1 [i ].first <= t2 [j ].first {
43+ merged = append (merged , t1 [i ])
44+ i ++
45+ } else {
46+ merged = append (merged , t2 [j ])
47+ j ++
48+ }
49+ }
50+ merged = append (merged , t1 [i :]... )
51+ merged = append (merged , t2 [j :]... )
52+ if len (merged ) == 0 {
53+ return merged
54+ }
55+ result := merged [:1 ]
56+ for _ , iv := range merged [1 :] {
57+ last := & result [len (result )- 1 ]
58+ if iv .first <= last .last + 1 {
59+ if iv .last > last .last {
60+ last .last = iv .last
61+ }
62+ } else {
63+ result = append (result , iv )
64+ }
65+ }
66+ return result
67+ }
68+
3169func handleEnv () {
3270 env := os .Getenv ("RUNEWIDTH_EASTASIAN" )
3371 if env == "" {
@@ -52,15 +90,6 @@ type interval struct {
5290
5391type table []interval
5492
55- func inTables (r rune , ts ... table ) bool {
56- for _ , t := range ts {
57- if inTable (r , t ) {
58- return true
59- }
60- }
61- return false
62- }
63-
6493func inTable (r rune , t table ) bool {
6594 if r < t [0 ].first {
6695 return false
@@ -131,9 +160,7 @@ func (c *Condition) RuneWidth(r rune) int {
131160 return 0
132161 case r < 0x300 :
133162 return 1
134- case inTable (r , narrow ):
135- return 1
136- case inTables (r , nonprint , combining ):
163+ case inTable (r , zerowidth ):
137164 return 0
138165 case inTable (r , doublewidth ):
139166 return 2
@@ -142,13 +169,13 @@ func (c *Condition) RuneWidth(r rune) int {
142169 }
143170 } else {
144171 switch {
145- case inTables (r , nonprint , combining ):
172+ case inTable (r , zerowidth ):
146173 return 0
147174 case inTable (r , narrow ):
148175 return 1
149- case inTables (r , ambiguous , doublewidth ):
176+ case inTable (r , widewidth ):
150177 return 2
151- case ! c .StrictEmojiNeutral && inTables (r , ambiguous , emoji , narrow ):
178+ case ! c .StrictEmojiNeutral && inTable (r , emoji ):
152179 return 2
153180 default :
154181 return 1
@@ -185,6 +212,16 @@ func (c *Condition) StringWidth(s string) (width int) {
185212 return c .RuneWidth (r )
186213 }
187214 }
215+ // ASCII fast path: no grapheme clustering needed for pure ASCII
216+ if isAllASCII (s ) {
217+ for i := 0 ; i < len (s ); i ++ {
218+ b := s [i ]
219+ if b >= 0x20 && b != 0x7F {
220+ width ++
221+ }
222+ }
223+ return
224+ }
188225 g := graphemes .FromString (s )
189226 for g .Next () {
190227 var chWidth int
@@ -199,6 +236,15 @@ func (c *Condition) StringWidth(s string) (width int) {
199236 return
200237}
201238
239+ func isAllASCII (s string ) bool {
240+ for i := 0 ; i < len (s ); i ++ {
241+ if s [i ] >= 0x80 {
242+ return false
243+ }
244+ }
245+ return true
246+ }
247+
202248// Truncate return string truncated with w cells
203249func (c * Condition ) Truncate (s string , w int , tail string ) string {
204250 if c .StringWidth (s ) <= w {
@@ -264,24 +310,25 @@ func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
264310// Wrap return string wrapped with w cells
265311func (c * Condition ) Wrap (s string , w int ) string {
266312 width := 0
267- out := ""
313+ var out strings.Builder
314+ out .Grow (len (s ) + len (s )/ w + 1 )
268315 for _ , r := range s {
269316 cw := c .RuneWidth (r )
270317 if r == '\n' {
271- out += string (r )
318+ out . WriteRune (r )
272319 width = 0
273320 continue
274321 } else if width + cw > w {
275- out += " \n "
322+ out . WriteByte ( '\n' )
276323 width = 0
277- out += string (r )
324+ out . WriteRune (r )
278325 width += cw
279326 continue
280327 }
281- out += string (r )
328+ out . WriteRune (r )
282329 width += cw
283330 }
284- return out
331+ return out . String ()
285332}
286333
287334// FillLeft return string filled in left by spaces in w cells
@@ -320,7 +367,7 @@ func RuneWidth(r rune) int {
320367
321368// IsAmbiguousWidth returns whether is ambiguous width or not.
322369func IsAmbiguousWidth (r rune ) bool {
323- return inTables (r , private , ambiguous )
370+ return inTable (r , private ) || inTable ( r , ambiguous )
324371}
325372
326373// IsCombiningWidth returns whether is combining width or not.
0 commit comments