From f052c83bf93255baaa30fa4a8cf060b240f10944 Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Thu, 15 Sep 2022 18:08:36 +0300 Subject: [PATCH 1/7] trim prefix --- runewidth.go | 34 +++++++++++++++++++++++ runewidth_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/runewidth.go b/runewidth.go index ffdcc50..c08c366 100644 --- a/runewidth.go +++ b/runewidth.go @@ -214,6 +214,35 @@ func (c *Condition) Truncate(s string, w int, tail string) string { return s[:pos] + tail } +// TrimPrefix cuts w cells from the beginning of the `s`. +func (c *Condition) TrimPrefix(s string, w int, tail string) string { + if c.StringWidth(s) <= w { + return "" + tail + } + + var width int + pos := len(s) + + g := uniseg.NewGraphemes(s) + for g.Next() { + var chWidth int + for _, r := range g.Runes() { + chWidth = c.RuneWidth(r) + if chWidth > 0 { + break // See StringWidth() for details. + } + } + if width+chWidth > w { + pos, _ = g.Positions() + + break + } + width += chWidth + } + + return s[pos:] + tail +} + // Wrap return string wrapped with w cells func (c *Condition) Wrap(s string, w int) string { width := 0 @@ -291,6 +320,11 @@ func Truncate(s string, w int, tail string) string { return DefaultCondition.Truncate(s, w, tail) } +// TrimPrefix cuts w cells from the beginning of the `s`. +func TrimPrefix(s string, w int, tail string) string { + return DefaultCondition.TrimPrefix(s, w, tail) +} + // Wrap return string wrapped with w cells func Wrap(s string, w int) string { return DefaultCondition.Wrap(s, w) diff --git a/runewidth_test.go b/runewidth_test.go index 8a1317b..20fc9d5 100644 --- a/runewidth_test.go +++ b/runewidth_test.go @@ -380,6 +380,76 @@ func TestTruncateNoNeeded(t *testing.T) { } } +func Test_TrimPrefix(t *testing.T) { + t.Parallel() + + t.Run("ascii", func(t *testing.T) { + t.Parallel() + s := "source" + expected := "ce" + + out := TrimPrefix(s, 4, "") + if out != expected { + t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + } + }) + + t.Run("ascii: with tail", func(t *testing.T) { + t.Parallel() + s := "source" + expected := "ce..." + + out := TrimPrefix(s, 4, "...") + if out != expected { + t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + } + }) + + t.Run("non ascii", func(t *testing.T) { + t.Parallel() + s := "あいうえお" + expected := "えお" + + out := TrimPrefix(s, 6, "") + if out != expected { + t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + } + }) + + t.Run("non ascii: with tail", func(t *testing.T) { + t.Parallel() + s := "あいうえお" + expected := "えお..." + + out := TrimPrefix(s, 6, "...") + if out != expected { + t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + } + }) + + t.Run("trim all", func(t *testing.T) { + t.Parallel() + s := "あいうえお" + expected := "" + + out := TrimPrefix(s, 10, "") + if out != expected { + t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + } + }) + + t.Run("trim all: with tail", func(t *testing.T) { + t.Parallel() + s := "あいうえお" + expected := "..." + + out := TrimPrefix(s, 10, "...") + if out != expected { + t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + } + }) +} + var isneutralwidthtests = []struct { in rune out bool From 01f382afeca388d41baff4ab673ca7d4924c9659 Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Thu, 15 Sep 2022 20:04:04 +0300 Subject: [PATCH 2/7] change tail to prefix --- runewidth.go | 10 +++++----- runewidth_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/runewidth.go b/runewidth.go index c08c366..b3e6ed7 100644 --- a/runewidth.go +++ b/runewidth.go @@ -215,9 +215,9 @@ func (c *Condition) Truncate(s string, w int, tail string) string { } // TrimPrefix cuts w cells from the beginning of the `s`. -func (c *Condition) TrimPrefix(s string, w int, tail string) string { +func (c *Condition) TrimPrefix(s string, w int, prefix string) string { if c.StringWidth(s) <= w { - return "" + tail + return prefix } var width int @@ -240,7 +240,7 @@ func (c *Condition) TrimPrefix(s string, w int, tail string) string { width += chWidth } - return s[pos:] + tail + return prefix + s[pos:] } // Wrap return string wrapped with w cells @@ -321,8 +321,8 @@ func Truncate(s string, w int, tail string) string { } // TrimPrefix cuts w cells from the beginning of the `s`. -func TrimPrefix(s string, w int, tail string) string { - return DefaultCondition.TrimPrefix(s, w, tail) +func TrimPrefix(s string, w int, prefix string) string { + return DefaultCondition.TrimPrefix(s, w, prefix) } // Wrap return string wrapped with w cells diff --git a/runewidth_test.go b/runewidth_test.go index 20fc9d5..d69fafa 100644 --- a/runewidth_test.go +++ b/runewidth_test.go @@ -397,7 +397,7 @@ func Test_TrimPrefix(t *testing.T) { t.Run("ascii: with tail", func(t *testing.T) { t.Parallel() s := "source" - expected := "ce..." + expected := "...ce" out := TrimPrefix(s, 4, "...") if out != expected { @@ -419,7 +419,7 @@ func Test_TrimPrefix(t *testing.T) { t.Run("non ascii: with tail", func(t *testing.T) { t.Parallel() s := "あいうえお" - expected := "えお..." + expected := "...えお" out := TrimPrefix(s, 6, "...") if out != expected { From 8f4e6bca58a8899257f38ea1b8fc4d27e532753f Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Thu, 15 Sep 2022 20:13:25 +0300 Subject: [PATCH 3/7] change t.Run names --- runewidth_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runewidth_test.go b/runewidth_test.go index d69fafa..30fac13 100644 --- a/runewidth_test.go +++ b/runewidth_test.go @@ -394,7 +394,7 @@ func Test_TrimPrefix(t *testing.T) { } }) - t.Run("ascii: with tail", func(t *testing.T) { + t.Run("ascii: with prefix", func(t *testing.T) { t.Parallel() s := "source" expected := "...ce" @@ -416,7 +416,7 @@ func Test_TrimPrefix(t *testing.T) { } }) - t.Run("non ascii: with tail", func(t *testing.T) { + t.Run("non ascii: with prefix", func(t *testing.T) { t.Parallel() s := "あいうえお" expected := "...えお" @@ -438,7 +438,7 @@ func Test_TrimPrefix(t *testing.T) { } }) - t.Run("trim all: with tail", func(t *testing.T) { + t.Run("trim all: with prefix", func(t *testing.T) { t.Parallel() s := "あいうえお" expected := "..." From 669734a145d04dfdf8d0268ebef43a0a9f9c3e91 Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Fri, 16 Sep 2022 16:44:19 +0300 Subject: [PATCH 4/7] change tests to table-driven approach --- runewidth_test.go | 84 +++++++++++------------------------------------ 1 file changed, 19 insertions(+), 65 deletions(-) diff --git a/runewidth_test.go b/runewidth_test.go index 30fac13..4a31380 100644 --- a/runewidth_test.go +++ b/runewidth_test.go @@ -380,74 +380,28 @@ func TestTruncateNoNeeded(t *testing.T) { } } -func Test_TrimPrefix(t *testing.T) { - t.Parallel() - - t.Run("ascii", func(t *testing.T) { - t.Parallel() - s := "source" - expected := "ce" - - out := TrimPrefix(s, 4, "") - if out != expected { - t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) - } - }) - - t.Run("ascii: with prefix", func(t *testing.T) { - t.Parallel() - s := "source" - expected := "...ce" - - out := TrimPrefix(s, 4, "...") - if out != expected { - t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) - } - }) - - t.Run("non ascii", func(t *testing.T) { - t.Parallel() - s := "あいうえお" - expected := "えお" - - out := TrimPrefix(s, 6, "") - if out != expected { - t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) - } - }) - - t.Run("non ascii: with prefix", func(t *testing.T) { - t.Parallel() - s := "あいうえお" - expected := "...えお" - - out := TrimPrefix(s, 6, "...") - if out != expected { - t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) - } - }) - - t.Run("trim all", func(t *testing.T) { - t.Parallel() - s := "あいうえお" - expected := "" - - out := TrimPrefix(s, 10, "") - if out != expected { - t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) - } - }) +var trimprefixtests = []struct { + s string + w int + prefix string + out string +}{ + {"source", 4, "", "ce"}, + {"source", 4, "...", "...ce"}, + {"あいうえお", 6, "", "えお"}, + {"あいうえお", 6, "...", "...えお"}, + {"あいうえお", 10, "", ""}, + {"あいうえお", 10, "...", "..."}, +} - t.Run("trim all: with prefix", func(t *testing.T) { - t.Parallel() - s := "あいうえお" - expected := "..." +func TestTrimPrefix(t *testing.T) { + t.Parallel() - out := TrimPrefix(s, 10, "...") - if out != expected { - t.Errorf("TrimPrefix(%q) = %q, want %q", s, out, expected) + for _, tt := range trimprefixtests { + if out := TrimPrefix(tt.s, tt.w, tt.prefix); out != tt.out { + t.Errorf("TrimPrefix(%q) = %q, want %q", tt.s, out, tt.out) } - }) + } } var isneutralwidthtests = []struct { From b18c354ec17ffdbc5210fc822dfbfb7ad1151495 Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Fri, 16 Sep 2022 22:49:38 +0300 Subject: [PATCH 5/7] if pos < w: add empty chars --- runewidth.go | 8 +++++++- runewidth_test.go | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/runewidth.go b/runewidth.go index b3e6ed7..bbc3bc4 100644 --- a/runewidth.go +++ b/runewidth.go @@ -2,6 +2,7 @@ package runewidth import ( "os" + "strings" "github.com/rivo/uniseg" ) @@ -233,7 +234,12 @@ func (c *Condition) TrimPrefix(s string, w int, prefix string) string { } } if width+chWidth > w { - pos, _ = g.Positions() + if width < w { + _, pos = g.Positions() + prefix += strings.Repeat(" ", w-width) + } else { + pos, _ = g.Positions() + } break } diff --git a/runewidth_test.go b/runewidth_test.go index 4a31380..ef9984c 100644 --- a/runewidth_test.go +++ b/runewidth_test.go @@ -392,6 +392,8 @@ var trimprefixtests = []struct { {"あいうえお", 6, "...", "...えお"}, {"あいうえお", 10, "", ""}, {"あいうえお", 10, "...", "..."}, + {"あいうえお", 5, "", " えお"}, + {"Aあいうえお", 5, "", "うえお"}, } func TestTrimPrefix(t *testing.T) { From 6dff02ea199c87863b9862eeac527819fcd15a43 Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Sat, 17 Sep 2022 13:12:14 +0300 Subject: [PATCH 6/7] change function name to TruncateLeft --- runewidth.go | 32 ++++++++++++++++++-------------- runewidth_test.go | 10 +++++----- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/runewidth.go b/runewidth.go index bbc3bc4..36b6931 100644 --- a/runewidth.go +++ b/runewidth.go @@ -215,8 +215,8 @@ func (c *Condition) Truncate(s string, w int, tail string) string { return s[:pos] + tail } -// TrimPrefix cuts w cells from the beginning of the `s`. -func (c *Condition) TrimPrefix(s string, w int, prefix string) string { +// TruncateLeft cuts w cells from the beginning of the `s`. +func (c *Condition) TruncateLeft(s string, w int, prefix string) string { if c.StringWidth(s) <= w { return prefix } @@ -233,17 +233,21 @@ func (c *Condition) TrimPrefix(s string, w int, prefix string) string { break // See StringWidth() for details. } } - if width+chWidth > w { - if width < w { - _, pos = g.Positions() - prefix += strings.Repeat(" ", w-width) - } else { - pos, _ = g.Positions() - } - break + if width+chWidth <= w { + width += chWidth + + continue } - width += chWidth + + if width < w { + _, pos = g.Positions() + prefix += strings.Repeat(" ", w-width) + } else { + pos, _ = g.Positions() + } + + break } return prefix + s[pos:] @@ -326,9 +330,9 @@ func Truncate(s string, w int, tail string) string { return DefaultCondition.Truncate(s, w, tail) } -// TrimPrefix cuts w cells from the beginning of the `s`. -func TrimPrefix(s string, w int, prefix string) string { - return DefaultCondition.TrimPrefix(s, w, prefix) +// TruncateLeft cuts w cells from the beginning of the `s`. +func TruncateLeft(s string, w int, prefix string) string { + return DefaultCondition.TruncateLeft(s, w, prefix) } // Wrap return string wrapped with w cells diff --git a/runewidth_test.go b/runewidth_test.go index ef9984c..c3d0fc9 100644 --- a/runewidth_test.go +++ b/runewidth_test.go @@ -380,7 +380,7 @@ func TestTruncateNoNeeded(t *testing.T) { } } -var trimprefixtests = []struct { +var truncatelefttests = []struct { s string w int prefix string @@ -396,12 +396,12 @@ var trimprefixtests = []struct { {"Aあいうえお", 5, "", "うえお"}, } -func TestTrimPrefix(t *testing.T) { +func TestTruncateLeft(t *testing.T) { t.Parallel() - for _, tt := range trimprefixtests { - if out := TrimPrefix(tt.s, tt.w, tt.prefix); out != tt.out { - t.Errorf("TrimPrefix(%q) = %q, want %q", tt.s, out, tt.out) + for _, tt := range truncatelefttests { + if out := TruncateLeft(tt.s, tt.w, tt.prefix); out != tt.out { + t.Errorf("TruncateLeft(%q) = %q, want %q", tt.s, out, tt.out) } } } From 10a3ce9fb00415f12c046ad069bbe53190dd8665 Mon Sep 17 00:00:00 2001 From: Roman Suvorov Date: Sat, 17 Sep 2022 13:25:24 +0300 Subject: [PATCH 7/7] add spaces == width+chWidth-w --- runewidth.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/runewidth.go b/runewidth.go index 36b6931..7dfbb3b 100644 --- a/runewidth.go +++ b/runewidth.go @@ -234,20 +234,18 @@ func (c *Condition) TruncateLeft(s string, w int, prefix string) string { } } - if width+chWidth <= w { - width += chWidth - - continue - } + if width+chWidth > w { + if width < w { + _, pos = g.Positions() + prefix += strings.Repeat(" ", width+chWidth-w) + } else { + pos, _ = g.Positions() + } - if width < w { - _, pos = g.Positions() - prefix += strings.Repeat(" ", w-width) - } else { - pos, _ = g.Positions() + break } - break + width += chWidth } return prefix + s[pos:]