diff --git a/CHANGELOG.md b/CHANGELOG.md index 18fcc1be..dbf7615d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +0.74.5 +------ +- Fixed `--gap-line` cutting a grapheme cluster when filling the last cells of the line (#4920) + 0.74.4 ------ - Fixed an escape sequence split across reads being parsed as a fragment, which leaked the rest into the query (#4899) diff --git a/src/util/util.go b/src/util/util.go index fb94fb3c..3e2acd4e 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -105,16 +105,18 @@ func RepeatToFill(str string, length int, limit int) string { rest := limit % length output := strings.Repeat(str, times) if rest > 0 { - for _, r := range str { - rest -= uniseg.StringWidth(string(r)) - if rest < 0 { - break - } - output += string(r) - if rest == 0 { + // Iterate over grapheme clusters so that we don't cut a cluster in half + end := 0 + graphemes := uniseg.NewGraphemes(str) + for rest > 0 && graphemes.Next() { + width := graphemes.Width() + if width > rest { break } + rest -= width + _, end = graphemes.Positions() } + output += str[:end] } return output } diff --git a/src/util/util_test.go b/src/util/util_test.go index 5a609587..440b09dc 100644 --- a/src/util/util_test.go +++ b/src/util/util_test.go @@ -109,6 +109,27 @@ func TestRepeatToFill(t *testing.T) { if RepeatToFill("abcde", 10, 42) != strings.Repeat("abcde", 4)+"abcde"[:2] { t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2]) } + + // Should not cut a grapheme cluster in half + for _, test := range []struct { + str string + limit int + expected string + }{ + {"a\u0301b", 1, "a\u0301"}, + {"a\u0301b", 3, "a\u0301ba\u0301"}, + {"a\u0301b", 4, "a\u0301ba\u0301b"}, + {"a\u4e00", 2, "a"}, + {"a\u4e00", 4, "a\u4e00a"}, + {"-\U0001f468\u200d\U0001f469\u200d\U0001f467", 1, "-"}, + {"-\U0001f468\u200d\U0001f469\u200d\U0001f467", 2, "-"}, + {"-\U0001f468\u200d\U0001f469\u200d\U0001f467", 4, "-\U0001f468\u200d\U0001f469\u200d\U0001f467-"}, + } { + actual := RepeatToFill(test.str, StringWidth(test.str), test.limit) + if actual != test.expected { + t.Errorf("Expected: %q, actual: %q", test.expected, actual) + } + } } func TestStringWidth(t *testing.T) {