Skip to content

Commit 66db774

Browse files
authored
text: fix alignment issues with box/block chars (#389)
1 parent a020032 commit 66db774

File tree

2 files changed

+52
-23
lines changed

2 files changed

+52
-23
lines changed

text/string.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,20 @@ func LongestLineLen(str string) int {
7676
return maxLength
7777
}
7878

79-
// OverrideRuneWidthEastAsianWidth can *probably* help with alignment, and
80-
// length calculation issues when dealing with Unicode character-set and a
81-
// non-English language set in the LANG variable.
79+
// OverrideRuneWidthEastAsianWidth overrides the East Asian width detection in
80+
// the runewidth library. This is primarily for advanced use cases.
8281
//
83-
// Set this to 'false' to force the "runewidth" library to pretend to deal with
84-
// English character-set. Be warned that if the text/content you are dealing
85-
// with contains East Asian character-set, this may result in unexpected
86-
// behavior.
82+
// Box drawing (U+2500-U+257F) and block element (U+2580-U+259F) characters
83+
// are automatically handled and always reported as width 1, regardless of
84+
// this setting, fixing alignment issues that previously required setting this
85+
// to false.
8786
//
88-
// References:
89-
// * https://github.com/mattn/go-runewidth/issues/64#issuecomment-1221642154
87+
// Setting this to false forces runewidth to treat all characters as if in an
88+
// English locale. Warning: this may cause East Asian characters (Chinese,
89+
// Japanese, Korean) to be incorrectly reported as width 1 instead of 2.
90+
//
91+
// See:
92+
// * https://github.com/mattn/go-runewidth/issues/64
9093
// * https://github.com/jedib0t/go-pretty/issues/220
9194
// * https://github.com/jedib0t/go-pretty/issues/204
9295
func OverrideRuneWidthEastAsianWidth(val bool) {
@@ -184,16 +187,28 @@ func RuneCount(str string) int {
184187
return StringWidthWithoutEscSequences(str)
185188
}
186189

187-
// RuneWidth returns the mostly accurate character-width of the rune. This is
188-
// not 100% accurate as the character width is usually dependent on the
189-
// typeface (font) used in the console/terminal. For ex.:
190+
// RuneWidth returns the display width of a rune. Width accuracy depends on
191+
// the terminal font, as character width is font-dependent. Examples:
190192
//
191193
// RuneWidth('A') == 1
192194
// RuneWidth('ツ') == 2
193195
// RuneWidth('⊙') == 1
194196
// RuneWidth('︿') == 2
195197
// RuneWidth(0x27) == 0
198+
//
199+
// Box drawing (U+2500-U+257F) and block element (U+2580-U+259F) characters
200+
// are always treated as width 1, regardless of locale, to ensure proper
201+
// alignment in tables and progress indicators. This fixes incorrect width 2
202+
// reporting in East Asian locales (e.g., LANG=zh_CN.UTF-8).
203+
//
204+
// See:
205+
// * https://github.com/mattn/go-runewidth/issues/64
206+
// * https://github.com/jedib0t/go-pretty/issues/220
207+
// * https://github.com/jedib0t/go-pretty/issues/204
196208
func RuneWidth(r rune) int {
209+
if (r >= 0x2500 && r <= 0x257F) || (r >= 0x2580 && r <= 0x259F) {
210+
return 1
211+
}
197212
return rwCondition.RuneWidth(r)
198213
}
199214

text/string_test.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,33 @@ func TestOverrideRuneWidthEastAsianWidth(t *testing.T) {
9494
rwCondition.EastAsianWidth = originalValue
9595
}()
9696

97+
// Box drawing characters (U+2500-U+257F) are now always reported as width 1,
98+
// regardless of the EastAsianWidth setting. This fixes alignment issues
99+
// that previously occurred when LANG was set to something like 'zh_CN.UTF-8'.
100+
// Previously, '╋' would be reported as width 2 when EastAsianWidth was true.
97101
OverrideRuneWidthEastAsianWidth(true)
98-
assert.Equal(t, 2, StringWidthWithoutEscSequences("╋"))
102+
assert.Equal(t, 1, StringWidthWithoutEscSequences("╋"), "Box drawing character should always be width 1, even when EastAsianWidth is true")
99103
OverrideRuneWidthEastAsianWidth(false)
100-
assert.Equal(t, 1, StringWidthWithoutEscSequences("╋"))
101-
102-
// Note for posterity. We want the length of the box drawing character to
103-
// be reported as 1. However, with an environment where LANG is set to
104-
// something like 'zh_CN.UTF-8', the value being returned is 2, which breaks
105-
// text alignment/padding logic in this library.
106-
//
107-
// If a future version of runewidth is able to address this internally and
108-
// return 1 for the above, the function being tested can be marked for
109-
// deprecation.
104+
assert.Equal(t, 1, StringWidthWithoutEscSequences("╋"), "Box drawing character should always be width 1, even when EastAsianWidth is false")
105+
106+
// Verify that block elements (U+2580-U+259F) are also handled correctly.
107+
// These are used in progress indicators and should always be width 1.
108+
OverrideRuneWidthEastAsianWidth(true)
109+
assert.Equal(t, 1, StringWidthWithoutEscSequences("█"), "Block element should always be width 1, even when EastAsianWidth is true")
110+
assert.Equal(t, 1, StringWidthWithoutEscSequences("▒"), "Block element should always be width 1, even when EastAsianWidth is true")
111+
assert.Equal(t, 1, StringWidthWithoutEscSequences("▓"), "Block element should always be width 1, even when EastAsianWidth is true")
112+
OverrideRuneWidthEastAsianWidth(false)
113+
assert.Equal(t, 1, StringWidthWithoutEscSequences("█"), "Block element should always be width 1, even when EastAsianWidth is false")
114+
assert.Equal(t, 1, StringWidthWithoutEscSequences("▒"), "Block element should always be width 1, even when EastAsianWidth is false")
115+
assert.Equal(t, 1, StringWidthWithoutEscSequences("▓"), "Block element should always be width 1, even when EastAsianWidth is false")
116+
117+
// Verify that actual East Asian characters are still handled correctly.
118+
// Note: The runewidth library reports 'ツ' as width 2 regardless of the
119+
// EastAsianWidth setting, as it's inherently a full-width character.
120+
OverrideRuneWidthEastAsianWidth(true)
121+
assert.Equal(t, 2, StringWidthWithoutEscSequences("ツ"), "East Asian character should be width 2")
122+
OverrideRuneWidthEastAsianWidth(false)
123+
assert.Equal(t, 2, StringWidthWithoutEscSequences("ツ"), "East Asian character should be width 2")
110124
}
111125

112126
func ExamplePad() {

0 commit comments

Comments
 (0)