Compare commits

...
2 Commits
Author SHA1 Message Date
Junegunn Choi b1be3a8be1 Update issue template
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
2026-09-14 12:16:10 +09:00
Junegunn Choi 961793cf39 Fix --gap-line cutting a grapheme cluster
CodeQL / Analyze (go) (push) Canceled after 0s
build / build (push) Canceled after 0s
Test fzf on macOS / build (push) Canceled after 0s
RepeatToFill filled the remaining width rune by rune, so a cluster
could be split at the right edge. Iterate grapheme clusters instead.

Fix #4920
2026-09-13 19:31:36 +09:00
4 changed files with 48 additions and 12 deletions
+14 -5
View File
@@ -3,10 +3,6 @@ name: Issue Template
description: Report a problem or bug related to fzf to help us improve description: Report a problem or bug related to fzf to help us improve
body: body:
- type: markdown
attributes:
value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Checklist label: Checklist
@@ -32,7 +28,6 @@ body:
- label: Linux - label: Linux
- label: macOS - label: macOS
- label: Windows - label: Windows
- label: Etc.
- type: checkboxes - type: checkboxes
attributes: attributes:
@@ -41,9 +36,23 @@ body:
- label: bash - label: bash
- label: zsh - label: zsh
- label: fish - label: fish
- label: nushell
- label: PowerShell
- type: textarea - type: textarea
attributes: attributes:
label: Problem / Steps to reproduce label: Problem / Steps to reproduce
validations: validations:
required: true required: true
- type: textarea
attributes:
label: How did you run into this?
description: |
What were you actually trying to do? Include the command line or
configuration from your real setup.
If you did not hit this in actual use, say how you found it
(reading the code, automated analysis, etc.).
validations:
required: true
+4
View File
@@ -1,6 +1,10 @@
CHANGELOG CHANGELOG
========= =========
0.74.5
------
- Fixed `--gap-line` cutting a grapheme cluster when filling the last cells of the line (#4920)
0.74.4 0.74.4
------ ------
- Fixed an escape sequence split across reads being parsed as a fragment, which leaked the rest into the query (#4899) - Fixed an escape sequence split across reads being parsed as a fragment, which leaked the rest into the query (#4899)
+9 -7
View File
@@ -105,16 +105,18 @@ func RepeatToFill(str string, length int, limit int) string {
rest := limit % length rest := limit % length
output := strings.Repeat(str, times) output := strings.Repeat(str, times)
if rest > 0 { if rest > 0 {
for _, r := range str { // Iterate over grapheme clusters so that we don't cut a cluster in half
rest -= uniseg.StringWidth(string(r)) end := 0
if rest < 0 { graphemes := uniseg.NewGraphemes(str)
break for rest > 0 && graphemes.Next() {
} width := graphemes.Width()
output += string(r) if width > rest {
if rest == 0 {
break break
} }
rest -= width
_, end = graphemes.Positions()
} }
output += str[:end]
} }
return output return output
} }
+21
View File
@@ -109,6 +109,27 @@ func TestRepeatToFill(t *testing.T) {
if RepeatToFill("abcde", 10, 42) != strings.Repeat("abcde", 4)+"abcde"[:2] { if RepeatToFill("abcde", 10, 42) != strings.Repeat("abcde", 4)+"abcde"[:2] {
t.Error("Expected:", 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) { func TestStringWidth(t *testing.T) {