@ 261102.1

260913.1 · 961793cf39a1d406d6982586d14e8ba4445a6054

Fix --gap-line cutting a grapheme cluster

RepeatToFill filled the remaining width rune by rune, so a cluster could be split at the right edge. Iterate grapheme clusters instead.

Fix #4920

reading declarations

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)

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 }

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) {