From c7b542782f123a800d247ba2d9a1b3b54b853fec Mon Sep 17 00:00:00 2001 From: Ryan Moore Date: Thu, 6 Jul 2023 20:04:37 +0000 Subject: [PATCH 1/2] support ANSI OSC escapes as well as CSI colors --- util.go | 23 +++++++++++++++++++++-- wrap_test.go | 3 +++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/util.go b/util.go index 6056bee..d9a857f 100644 --- a/util.go +++ b/util.go @@ -15,10 +15,29 @@ import ( "github.com/mattn/go-runewidth" ) -var ansi = regexp.MustCompile("\033\\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[m|K]") +// StripANSIEscapes removes non-print ANSI escape codes from strings so that their width can be determined accurately +// Based on https://en.wikipedia.org/wiki/ANSI_escape_code#Fe_Escape_sequences +func StripANSIEscapes(str string) string { + var regESC = "\x1b" // ASCII escape + var regBEL = "\x07" // ASCII bell + + // String Terminator - ends ANSI sequences + var regST = "(" + regESC + "\\\\" + "|" + regBEL + ")" + + // Control Sequence Introducer - usually color codes + // esc + [ + zero or more 0x30-0x3f + zero or more 0x20-0x2f and a single 0x40-0x7e + var regCSI = regESC + "\\[" + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]" + + // Operating System Command - hyperlinks + // esc + ] + any number of any chars + ST + var regOSC = regESC + "\\]" + ".*?" + regST + + var ansi = regexp.MustCompile("(" + regCSI + "|" + regOSC + ")") + return ansi.ReplaceAllLiteralString(str, "") +} func DisplayWidth(str string) int { - return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, "")) + return runewidth.StringWidth(StripANSIEscapes(str)) } // ConditionString Simple Condition for string diff --git a/wrap_test.go b/wrap_test.go index a03f9fc..eeb5308 100644 --- a/wrap_test.go +++ b/wrap_test.go @@ -55,4 +55,7 @@ func TestDisplayWidth(t *testing.T) { } input = "\033[43;30m" + input + "\033[00m" checkEqual(t, DisplayWidth(input), want) + + input = "\033]8;;https://github.com/olekukonko/tablewriter/pull/220\033\\Github PR\033]8;;\033\\" + checkEqual(t, DisplayWidth(input), 9) } From 007621c25315f668eb973165f73c4f72734fd33f Mon Sep 17 00:00:00 2001 From: Ryan Moore Date: Mon, 5 Feb 2024 18:39:43 +0000 Subject: [PATCH 2/2] don't rebuild regex for every DisplayWidth call --- util.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/util.go b/util.go index d9a857f..5803a1b 100644 --- a/util.go +++ b/util.go @@ -15,9 +15,13 @@ import ( "github.com/mattn/go-runewidth" ) -// StripANSIEscapes removes non-print ANSI escape codes from strings so that their width can be determined accurately +var ansi = generateEscapeFilterRegex() + +// generateEscapeFilterRegex builds a regex to remove non-printing ANSI escape codes from strings so +// that their display width can be determined accurately. The regex is complicated enough that it's +// better to build it programmatically than to write it by hand. // Based on https://en.wikipedia.org/wiki/ANSI_escape_code#Fe_Escape_sequences -func StripANSIEscapes(str string) string { +func generateEscapeFilterRegex() *regexp.Regexp { var regESC = "\x1b" // ASCII escape var regBEL = "\x07" // ASCII bell @@ -32,12 +36,11 @@ func StripANSIEscapes(str string) string { // esc + ] + any number of any chars + ST var regOSC = regESC + "\\]" + ".*?" + regST - var ansi = regexp.MustCompile("(" + regCSI + "|" + regOSC + ")") - return ansi.ReplaceAllLiteralString(str, "") + return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")") } func DisplayWidth(str string) int { - return runewidth.StringWidth(StripANSIEscapes(str)) + return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, "")) } // ConditionString Simple Condition for string