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