Skip to content

Commit ed8c10e

Browse files
authored
Merge pull request #276 from olekukonko/runewidth
Improve EastAsianWidth Handling with Custom Condition
2 parents 24f5104 + 7846e62 commit ed8c10e

File tree

16 files changed

+840
-261
lines changed

16 files changed

+840
-261
lines changed

MIGRATION.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,7 +1914,7 @@ func main() {
19141914
- **Direct ANSI Codes**: Embed codes (e.g., `\033[32m` for green) in strings for manual control (`zoo.go:convertCellsToStrings`).
19151915
- **tw.Formatter**: Implement `Format() string` on custom types to control cell output, including colors (`tw/types.go:Formatter`).
19161916
- **tw.CellFilter**: Use `Config.<Section>.Filter.Global` or `PerColumn` to apply transformations like coloring dynamically (`tw/cell.go:CellFilter`).
1917-
- **Width Handling**: `tw.DisplayWidth()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
1917+
- **Width Handling**: `twdw.Width()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
19181918
- **No Built-In Color Presets**: Unlike v0.0.5’s potential `tablewriter.Colors`, v1.0.x requires manual ANSI code management or external libraries for color constants.
19191919

19201920
**Migration Tips**:
@@ -1928,7 +1928,7 @@ func main() {
19281928
**Potential Pitfalls**:
19291929
- **Terminal Support**: Some terminals may not support ANSI codes, causing artifacts; test in your environment or provide a non-colored fallback.
19301930
- **Filter Overlap**: Combining `tw.CellFilter` with `AutoFormat` or other transformations can lead to unexpected results; prioritize filters for coloring (`zoo.go`).
1931-
- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `tw.DisplayWidth` (`tw/fn.go`).
1931+
- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `twdw.Width` (`tw/fn.go`).
19321932
- **Streaming Consistency**: In streaming mode, ensure color codes are applied consistently across rows to avoid visual discrepancies (`stream.go`).
19331933
- **Performance**: Applying filters to large datasets may add overhead; optimize filter logic for efficiency (`zoo.go`).
19341934

@@ -2818,7 +2818,7 @@ func main() {
28182818
**Notes**:
28192819
- **Configuration**: Uses `tw.CellFilter` for per-column coloring, embedding ANSI codes (`tw/cell.go`).
28202820
- **Migration from v0.0.5**: Replaces `SetColumnColor` with dynamic filters (`tablewriter.go`).
2821-
- **Key Features**: Flexible color application; `tw.DisplayWidth` handles ANSI codes correctly (`tw/fn.go`).
2821+
- **Key Features**: Flexible color application; `twdw.Width` handles ANSI codes correctly (`tw/fn.go`).
28222822
- **Best Practices**: Test in ANSI-compatible terminals; use constants for code clarity.
28232823
- **Potential Issues**: Non-ANSI terminals may show artifacts; provide fallbacks (`tw/fn.go`).
28242824

@@ -3005,7 +3005,7 @@ This section addresses common migration issues with detailed solutions, covering
30053005
| Merging not working | **Cause**: Streaming mode or mismatched data. **Solution**: Use batch mode for vertical/hierarchical merging; ensure identical content (`zoo.go`). |
30063006
| Alignment ignored | **Cause**: `PerColumn` overrides `Global`. **Solution**: Check `Config.Section.Alignment.PerColumn` settings or `ConfigBuilder` calls (`tw/cell.go`). |
30073007
| Padding affects widths | **Cause**: Padding included in `Config.Widths`. **Solution**: Adjust `Config.Widths` to account for `tw.CellPadding` (`zoo.go`). |
3008-
| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `tw.DisplayWidth` (`tw/fn.go`). |
3008+
| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `twdw.Width` (`tw/fn.go`). |
30093009
| Caption missing | **Cause**: `Close()` not called in streaming or incorrect `Spot`. **Solution**: Ensure `Close()`; verify `tw.Caption.Spot` (`tablewriter.go`). |
30103010
| Filters not applied | **Cause**: Incorrect `PerColumn` indexing or nil filters. **Solution**: Set filters correctly; test with sample data (`tw/cell.go`). |
30113011
| Stringer cache overhead | **Cause**: Large datasets with diverse types. **Solution**: Disable `WithStringerCache` for small tables if not using `WithStringer` or if types vary greatly (`tablewriter.go`). |

deprecated.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package tablewriter
22

3-
import "github.com/olekukonko/tablewriter/tw"
3+
import (
4+
"github.com/olekukonko/tablewriter/tw"
5+
)
46

57
// WithBorders configures the table's border settings by updating the renderer's border configuration.
68
// This function is deprecated and will be removed in a future version.

option.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package tablewriter
22

33
import (
4+
"github.com/mattn/go-runewidth"
45
"github.com/olekukonko/ll"
6+
"github.com/olekukonko/tablewriter/pkg/twwidth"
57
"github.com/olekukonko/tablewriter/tw"
68
"reflect"
79
)
@@ -613,6 +615,31 @@ func WithRendition(rendition tw.Rendition) Option {
613615
}
614616
}
615617

618+
// WithEastAsian configures the global East Asian width calculation setting.
619+
// - enable=true: Enables East Asian width calculations. CJK and ambiguous characters
620+
// are typically measured as double width.
621+
// - enable=false: Disables East Asian width calculations. Characters are generally
622+
// measured as single width, subject to Unicode standards.
623+
//
624+
// This setting affects all subsequent display width calculations using the twdw package.
625+
func WithEastAsian(enable bool) Option {
626+
return func(target *Table) {
627+
twwidth.SetEastAsian(enable)
628+
}
629+
}
630+
631+
// WithCondition provides a way to set a custom global runewidth.Condition
632+
// that will be used for all subsequent display width calculations by the twwidth (twdw) package.
633+
//
634+
// The runewidth.Condition object allows for more fine-grained control over how rune widths
635+
// are determined, beyond just toggling EastAsianWidth. This could include settings for
636+
// ambiguous width characters or other future properties of runewidth.Condition.
637+
func WithCondition(condition *runewidth.Condition) Option {
638+
return func(target *Table) {
639+
twwidth.SetCondition(condition)
640+
}
641+
}
642+
616643
// WithSymbols sets the symbols used for drawing table borders and separators.
617644
// The symbols are applied to the table's renderer configuration, if a renderer is set.
618645
// If no renderer is set (target.renderer is nil), this option has no effect. .

pkg/twwarp/wrap.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
package twwarp
99

1010
import (
11-
"github.com/rivo/uniseg"
1211
"math"
1312
"strings"
1413
"unicode"
1514

16-
"github.com/mattn/go-runewidth"
15+
"github.com/olekukonko/tablewriter/pkg/twwidth" // IMPORT YOUR NEW PACKAGE
16+
"github.com/rivo/uniseg"
17+
// "github.com/mattn/go-runewidth" // This can be removed if all direct uses are gone
1718
)
1819

1920
const (
@@ -59,7 +60,8 @@ func WrapString(s string, lim int) ([]string, int) {
5960
var lines []string
6061
max := 0
6162
for _, v := range words {
62-
max = runewidth.StringWidth(v)
63+
// max = runewidth.StringWidth(v) // OLD
64+
max = twwidth.Width(v) // NEW: Use twdw.Width
6365
if max > lim {
6466
lim = max
6567
}
@@ -82,12 +84,13 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
8284
return []string{""}, lim
8385
}
8486
if strings.TrimSpace(s) == "" { // All spaces
85-
if runewidth.StringWidth(s) <= lim {
86-
return []string{s}, runewidth.StringWidth(s)
87+
// if runewidth.StringWidth(s) <= lim { // OLD
88+
if twwidth.Width(s) <= lim { // NEW: Use twdw.Width
89+
// return []string{s}, runewidth.StringWidth(s) // OLD
90+
return []string{s}, twwidth.Width(s) // NEW: Use twdw.Width
8791
}
8892
// For very long all-space strings, "wrap" by truncating to the limit.
8993
if lim > 0 {
90-
// Use our new helper function to get a substring of the correct display width
9194
substring, _ := stringToDisplayWidth(s, lim)
9295
return []string{substring}, lim
9396
}
@@ -96,7 +99,6 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
9699

97100
var leadingSpaces, trailingSpaces, coreContent string
98101
firstNonSpace := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
99-
// firstNonSpace will not be -1 due to TrimSpace check above.
100102
leadingSpaces = s[:firstNonSpace]
101103
lastNonSpace := strings.LastIndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
102104
trailingSpaces = s[lastNonSpace+1:]
@@ -116,7 +118,8 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
116118

117119
maxCoreWordWidth := 0
118120
for _, v := range words {
119-
w := runewidth.StringWidth(v)
121+
// w := runewidth.StringWidth(v) // OLD
122+
w := twwidth.Width(v) // NEW: Use twdw.Width
120123
if w > maxCoreWordWidth {
121124
maxCoreWordWidth = w
122125
}
@@ -153,15 +156,14 @@ func stringToDisplayWidth(s string, targetWidth int) (substring string, actualWi
153156
g := uniseg.NewGraphemes(s)
154157
for g.Next() {
155158
grapheme := g.Str()
156-
graphemeWidth := runewidth.StringWidth(grapheme) // Get width of the current grapheme cluster
159+
// graphemeWidth := runewidth.StringWidth(grapheme) // OLD
160+
graphemeWidth := twwidth.Width(grapheme) // NEW: Use twdw.Width
157161

158162
if currentWidth+graphemeWidth > targetWidth {
159-
// Adding this grapheme would exceed the target width
160163
break
161164
}
162165

163166
currentWidth += graphemeWidth
164-
// Get the end byte position of the current grapheme cluster
165167
_, e := g.Positions()
166168
endIndex = e
167169
}
@@ -186,14 +188,15 @@ func WrapWords(words []string, spc, lim, pen int) [][]string {
186188
}
187189
lengths := make([]int, n)
188190
for i := 0; i < n; i++ {
189-
lengths[i] = runewidth.StringWidth(words[i])
191+
// lengths[i] = runewidth.StringWidth(words[i]) // OLD
192+
lengths[i] = twwidth.Width(words[i]) // NEW: Use twdw.Width
190193
}
191194
nbrk := make([]int, n)
192195
cost := make([]int, n)
193196
for i := range cost {
194197
cost[i] = math.MaxInt32
195198
}
196-
remainderLen := lengths[n-1]
199+
remainderLen := lengths[n-1] // Uses updated lengths
197200
for i := n - 1; i >= 0; i-- {
198201
if i < n-1 {
199202
remainderLen += spc + lengths[i]

pkg/twwarp/wrap_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package twwarp
1010
import (
1111
"bytes"
1212
"fmt"
13+
"github.com/olekukonko/tablewriter/pkg/twwidth"
1314
"github.com/olekukonko/tablewriter/tw"
1415
"os"
1516
"reflect"
@@ -72,14 +73,14 @@ func TestDisplayWidth(t *testing.T) {
7273
if runewidth.IsEastAsian() {
7374
want = 14
7475
}
75-
if n := tw.DisplayWidth(input); n != want {
76+
if n := twwidth.Width(input); n != want {
7677
t.Errorf("Wants: %d Got: %d", want, n)
7778
}
7879
input = "\033[43;30m" + input + "\033[00m"
79-
checkEqual(t, tw.DisplayWidth(input), want)
80+
checkEqual(t, twwidth.Width(input), want)
8081

8182
input = "\033]8;;idea://open/?file=/path/somefile.php&line=12\033\\some URL\033]8;;\033\\"
82-
checkEqual(t, tw.DisplayWidth(input), 8)
83+
checkEqual(t, twwidth.Width(input), 8)
8384

8485
}
8586

0 commit comments

Comments
 (0)