@@ -6,11 +6,12 @@ import (
66 "strings"
77 "sync"
88
9+ "github.com/clipperhouse/displaywidth"
910 "github.com/mattn/go-runewidth"
1011)
1112
12- // condition holds the global runewidth configuration, including East Asian width settings.
13- var condition * runewidth. Condition
13+ // globalOptions holds the global displaywidth configuration, including East Asian width settings.
14+ var globalOptions displaywidth. Options
1415
1516// mu protects access to condition and widthCache for thread safety.
1617var mu sync.Mutex
@@ -19,10 +20,21 @@ var mu sync.Mutex
1920var ansi = Filter ()
2021
2122func init () {
22- condition = runewidth . NewCondition ()
23+ globalOptions = newOptions ()
2324 widthCache = make (map [cacheKey ]int )
2425}
2526
27+ func newOptions () displaywidth.Options {
28+ // go-runewidth has default logic based on env variables and locale,
29+ // we want to keep that compatibility
30+ cond := runewidth .NewCondition ()
31+ options := displaywidth.Options {
32+ EastAsianWidth : cond .EastAsianWidth ,
33+ StrictEmojiNeutral : cond .StrictEmojiNeutral ,
34+ }
35+ return options
36+ }
37+
2638// cacheKey is used as a key for memoizing string width results in widthCache.
2739type cacheKey struct {
2840 str string // Input string
@@ -60,26 +72,42 @@ func Filter() *regexp.Regexp {
6072func SetEastAsian (enable bool ) {
6173 mu .Lock ()
6274 defer mu .Unlock ()
63- if condition .EastAsianWidth != enable {
64- condition .EastAsianWidth = enable
75+ if globalOptions .EastAsianWidth != enable {
76+ globalOptions .EastAsianWidth = enable
6577 widthCache = make (map [cacheKey ]int ) // Clear cache on setting change
6678 }
6779}
6880
69- // SetCondition updates the global runewidth.Condition used for width calculations.
70- // When the condition is changed, the width cache is cleared.
81+ // IsEastAsian returns the current East Asian width setting.
7182// This function is thread-safe.
7283//
7384// Example:
7485//
75- // newCond := runewidth.NewCondition()
76- // newCond.EastAsianWidth = true
77- // twdw.SetCondition(newCond)
78- func SetCondition (newCond * runewidth.Condition ) {
86+ // if twdw.IsEastAsian() {
87+ // // Handle East Asian width characters
88+ // }
89+ func IsEastAsian () bool {
90+ mu .Lock ()
91+ defer mu .Unlock ()
92+ return globalOptions .EastAsianWidth
93+ }
94+
95+ // SetCondition updates the global runewidth.Condition used for width calculations.
96+ // This method is kept for backward compatibility. The condition is converted to
97+ // displaywidth.Options internally for better performance.
98+ func SetCondition (cond * runewidth.Condition ) {
7999 mu .Lock ()
80100 defer mu .Unlock ()
81- condition = newCond
82101 widthCache = make (map [cacheKey ]int ) // Clear cache on setting change
102+ globalOptions = conditionToOptions (cond )
103+ }
104+
105+ // Convert runewidth.Condition to displaywidth.Options
106+ func conditionToOptions (cond * runewidth.Condition ) displaywidth.Options {
107+ return displaywidth.Options {
108+ EastAsianWidth : cond .EastAsianWidth ,
109+ StrictEmojiNeutral : cond .StrictEmojiNeutral ,
110+ }
83111}
84112
85113// Width calculates the visual width of a string, excluding ANSI escape sequences,
@@ -92,19 +120,18 @@ func SetCondition(newCond *runewidth.Condition) {
92120// width := twdw.Width("Hello\x1b[31mWorld") // Returns 10
93121func Width (str string ) int {
94122 mu .Lock ()
95- key := cacheKey {str : str , eastAsianWidth : condition .EastAsianWidth }
123+ key := cacheKey {str : str , eastAsianWidth : globalOptions .EastAsianWidth }
96124 if w , found := widthCache [key ]; found {
97125 mu .Unlock ()
98126 return w
99127 }
100128 mu .Unlock ()
101129
102- // Use a temporary condition to avoid holding the lock during calculation
103- tempCond := runewidth .NewCondition ()
104- tempCond .EastAsianWidth = key .eastAsianWidth
130+ options := newOptions ()
131+ options .EastAsianWidth = key .eastAsianWidth
105132
106133 stripped := ansi .ReplaceAllLiteralString (str , "" )
107- calculatedWidth := tempCond . StringWidth (stripped )
134+ calculatedWidth := options . String (stripped )
108135
109136 mu .Lock ()
110137 widthCache [key ] = calculatedWidth
@@ -122,14 +149,14 @@ func Width(str string) int {
122149// width := twdw.WidthNoCache("Hello\x1b[31mWorld") // Returns 10
123150func WidthNoCache (str string ) int {
124151 mu .Lock ()
125- currentEA := condition .EastAsianWidth
152+ currentEA := globalOptions .EastAsianWidth
126153 mu .Unlock ()
127154
128- tempCond := runewidth . NewCondition ()
129- tempCond .EastAsianWidth = currentEA
155+ options := newOptions ()
156+ options .EastAsianWidth = currentEA
130157
131158 stripped := ansi .ReplaceAllLiteralString (str , "" )
132- return tempCond . StringWidth (stripped )
159+ return options . String (stripped )
133160}
134161
135162// Display calculates the visual width of a string, excluding ANSI escape sequences,
@@ -142,7 +169,8 @@ func WidthNoCache(str string) int {
142169// cond := runewidth.NewCondition()
143170// width := twdw.Display(cond, "Hello\x1b[31mWorld") // Returns 10
144171func Display (cond * runewidth.Condition , str string ) int {
145- return cond .StringWidth (ansi .ReplaceAllLiteralString (str , "" ))
172+ options := conditionToOptions (cond )
173+ return options .String (ansi .ReplaceAllLiteralString (str , "" ))
146174}
147175
148176// Truncate shortens a string to fit within a specified visual width, optionally
@@ -205,7 +233,7 @@ func Truncate(s string, maxWidth int, suffix ...string) string {
205233
206234 // Capture the global EastAsianWidth setting once for consistent use
207235 mu .Lock ()
208- currentGlobalEastAsianWidth := condition .EastAsianWidth
236+ currentGlobalEastAsianWidth := globalOptions .EastAsianWidth
209237 mu .Unlock ()
210238
211239 // Special case for EastAsian true: if only suffix fits, return suffix.
@@ -243,8 +271,8 @@ func Truncate(s string, maxWidth int, suffix ...string) string {
243271 inAnsiSequence := false
244272 ansiWrittenToContent := false
245273
246- localRunewidthCond := runewidth . NewCondition ()
247- localRunewidthCond .EastAsianWidth = currentGlobalEastAsianWidth
274+ options := newOptions ()
275+ options .EastAsianWidth = currentGlobalEastAsianWidth
248276
249277 for _ , r := range s {
250278 if r == '\x1b' {
@@ -278,7 +306,7 @@ func Truncate(s string, maxWidth int, suffix ...string) string {
278306 ansiSeqBuf .Reset ()
279307 }
280308 } else { // Normal character
281- runeDisplayWidth := localRunewidthCond . RuneWidth (r )
309+ runeDisplayWidth := options . Rune (r )
282310 if targetContentForIteration == 0 { // No budget for content at all
283311 break
284312 }
0 commit comments