mirror of
https://github.com/junegunn/fzf.git
synced 2026-05-19 15:00:03 +08:00
Fix coloring of items with zero-width characters
This commit fixes incorrect coloring for items that contain zero-width characters. It also makes ellipsis coloring consistent when text is trimmed from either the left or the right. Fix #4620 Close #4646
This commit is contained in:
+78
-32
@@ -3475,20 +3475,74 @@ func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit in
|
|||||||
func (t *Terminal) trimLeft(runes []rune, width int, ellipsisWidth int) ([]rune, int32) {
|
func (t *Terminal) trimLeft(runes []rune, width int, ellipsisWidth int) ([]rune, int32) {
|
||||||
width = max(0, width)
|
width = max(0, width)
|
||||||
var trimmed int32
|
var trimmed int32
|
||||||
// Assume that each rune takes at least one column on screen
|
|
||||||
if len(runes) > width {
|
str := string(runes)
|
||||||
diff := len(runes) - width
|
runningSum := 0
|
||||||
trimmed = int32(diff)
|
runningSumAdjusted := 0
|
||||||
runes = runes[diff:]
|
// We can't just subtract the width on each segment because there might be
|
||||||
|
// a tab character afterwards. For example, with the tabstop = 8:
|
||||||
|
// 1234____5678
|
||||||
|
// 234_____5678
|
||||||
|
// 34______5678
|
||||||
|
// 4_______5678
|
||||||
|
// ________5678
|
||||||
|
// 5678
|
||||||
|
// 678
|
||||||
|
// 78
|
||||||
|
// 8
|
||||||
|
// We need to look ahead, but not to the end to avoid performance hit.
|
||||||
|
type queuedSegment struct {
|
||||||
|
rs []rune
|
||||||
|
w int
|
||||||
|
}
|
||||||
|
allQueue := []queuedSegment{}
|
||||||
|
queuedWidth := 0
|
||||||
|
limit := width - ellipsisWidth
|
||||||
|
processQueue := func() {
|
||||||
|
for idx, item := range allQueue {
|
||||||
|
if runningSumAdjusted <= limit {
|
||||||
|
allQueue = allQueue[idx:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runningSumAdjusted -= item.w
|
||||||
|
runes = runes[len(item.rs):]
|
||||||
|
trimmed += int32(len(item.rs))
|
||||||
|
}
|
||||||
|
allQueue = []queuedSegment{}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentWidth := t.displayWidth(runes)
|
gr := uniseg.NewGraphemes(str)
|
||||||
|
queue := []queuedSegment{}
|
||||||
|
for gr.Next() {
|
||||||
|
s := gr.Str()
|
||||||
|
rs := gr.Runes()
|
||||||
|
|
||||||
for currentWidth > width-ellipsisWidth && len(runes) > 0 {
|
var w int
|
||||||
runes = runes[1:]
|
if len(rs) == 1 && rs[0] == '\t' {
|
||||||
trimmed++
|
w = t.tabstop - runningSum%t.tabstop
|
||||||
currentWidth = t.displayWidthWithLimit(runes, ellipsisWidth, width)
|
} else {
|
||||||
|
w = util.StringWidth(string(rs))
|
||||||
|
}
|
||||||
|
runningSum += w
|
||||||
|
runningSumAdjusted += w
|
||||||
|
queue = append(queue, queuedSegment{rs: rs, w: w})
|
||||||
|
queuedWidth += w
|
||||||
|
if queuedWidth >= t.tabstop || s == "\t" {
|
||||||
|
queuedWidth = 0
|
||||||
|
|
||||||
|
if s == "\t" {
|
||||||
|
queue[len(queue)-1].w = t.tabstop
|
||||||
|
for idx := range queue[:len(queue)-1] {
|
||||||
|
queue[idx].w = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allQueue = append(allQueue, queue...)
|
||||||
|
queue = []queuedSegment{}
|
||||||
|
processQueue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
allQueue = append(allQueue, queue...)
|
||||||
|
processQueue()
|
||||||
return runes, trimmed
|
return runes, trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3506,9 +3560,16 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
}
|
}
|
||||||
charOffsets := matchOffsets
|
charOffsets := matchOffsets
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
|
runes := item.text.ToRunes()
|
||||||
charOffsets = make([]Offset, len(*pos))
|
charOffsets = make([]Offset, len(*pos))
|
||||||
for idx, p := range *pos {
|
for idx, p := range *pos {
|
||||||
offset := Offset{int32(p), int32(p + 1)}
|
gr := uniseg.NewGraphemes(string(runes[p:]))
|
||||||
|
w := 1
|
||||||
|
for gr.Next() {
|
||||||
|
w = len(gr.Runes())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset := Offset{int32(p), int32(p + w)}
|
||||||
charOffsets[idx] = offset
|
charOffsets[idx] = offset
|
||||||
}
|
}
|
||||||
sort.Sort(ByOrder(charOffsets))
|
sort.Sort(ByOrder(charOffsets))
|
||||||
@@ -3767,24 +3828,16 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
|
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
|
||||||
if !t.wrap && displayWidth > adjustedMaxWidth {
|
if !t.wrap && displayWidth > adjustedMaxWidth {
|
||||||
maxe = util.Constrain(maxe+min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
|
maxe = util.Constrain(maxe+min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
|
||||||
transformOffsets := func(diff int32, rightTrim bool) {
|
transformOffsets := func(diff int32) {
|
||||||
for idx, offset := range offs {
|
for idx := range offs {
|
||||||
b, e := offset.offset[0], offset.offset[1]
|
offs[idx].offset[0] -= diff
|
||||||
el := int32(len(ellipsis))
|
offs[idx].offset[1] -= diff
|
||||||
b += el - diff
|
|
||||||
e += el - diff
|
|
||||||
b = max(b, el)
|
|
||||||
if rightTrim {
|
|
||||||
e = min(e, int32(maxWidth-ellipsisWidth))
|
|
||||||
}
|
|
||||||
offs[idx].offset[0] = b
|
|
||||||
offs[idx].offset[1] = max(b, e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.hscroll {
|
if t.hscroll {
|
||||||
if fidx == 1 || fidx == 2 && t.keepRight && pos == nil {
|
if fidx == 1 || fidx == 2 && t.keepRight && pos == nil {
|
||||||
trimmed, diff := t.trimLeft(runes, maxWidth, ellipsisWidth)
|
trimmed, diff := t.trimLeft(runes, maxWidth, ellipsisWidth)
|
||||||
transformOffsets(diff, false)
|
transformOffsets(diff - int32(len(ellipsis)))
|
||||||
runes = append(ellipsis, trimmed...)
|
runes = append(ellipsis, trimmed...)
|
||||||
} else if fidx == 0 || !t.overflow(runes[:maxe], maxWidth-ellipsisWidth) {
|
} else if fidx == 0 || !t.overflow(runes[:maxe], maxWidth-ellipsisWidth) {
|
||||||
// Stri..
|
// Stri..
|
||||||
@@ -3792,27 +3845,20 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
runes = append(runes, ellipsis...)
|
runes = append(runes, ellipsis...)
|
||||||
} else {
|
} else {
|
||||||
// Stri..
|
// Stri..
|
||||||
rightTrim := false
|
|
||||||
if t.overflow(runes[maxe:], ellipsisWidth) {
|
if t.overflow(runes[maxe:], ellipsisWidth) {
|
||||||
runes = append(runes[:maxe], ellipsis...)
|
runes = append(runes[:maxe], ellipsis...)
|
||||||
rightTrim = true
|
|
||||||
}
|
}
|
||||||
// ..ri..
|
// ..ri..
|
||||||
var diff int32
|
var diff int32
|
||||||
runes, diff = t.trimLeft(runes, maxWidth, ellipsisWidth)
|
runes, diff = t.trimLeft(runes, maxWidth, ellipsisWidth)
|
||||||
|
|
||||||
// Transform offsets
|
// Transform offsets
|
||||||
transformOffsets(diff, rightTrim)
|
transformOffsets(diff - int32(len(ellipsis)))
|
||||||
runes = append(ellipsis, runes...)
|
runes = append(ellipsis, runes...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
|
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
|
||||||
runes = append(runes, ellipsis...)
|
runes = append(runes, ellipsis...)
|
||||||
|
|
||||||
for idx, offset := range offs {
|
|
||||||
offs[idx].offset[0] = min(offset.offset[0], int32(maxWidth-len(ellipsis)))
|
|
||||||
offs[idx].offset[1] = min(offset.offset[1], int32(maxWidth))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -18,8 +18,13 @@ func StringWidth(s string) int {
|
|||||||
|
|
||||||
// RunesWidth returns runes width
|
// RunesWidth returns runes width
|
||||||
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
|
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
|
||||||
|
return StringsWidth(string(runes), prefixWidth, tabstop, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringsWidth returns the width of the string
|
||||||
|
func StringsWidth(str string, prefixWidth int, tabstop int, limit int) (int, int) {
|
||||||
width := 0
|
width := 0
|
||||||
gr := uniseg.NewGraphemes(string(runes))
|
gr := uniseg.NewGraphemes(str)
|
||||||
idx := 0
|
idx := 0
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
rs := gr.Runes()
|
rs := gr.Runes()
|
||||||
|
|||||||
@@ -2175,4 +2175,13 @@ class TestCore < TestInteractive
|
|||||||
assert_equal 1, it.match_count
|
assert_equal 1, it.match_count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_zero_width_characters
|
||||||
|
tmux.send_keys %(for i in {1..1000}; do string+="a̱$i"; printf '\\e[43m%s\\e[0m\\n' "$string"; done | #{FZF} --ansi --query a500 --ellipsis XX), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 981, lines.match_count
|
||||||
|
assert_match(/^> XX.*a̱500/, lines[-3])
|
||||||
|
assert(lines.reverse.drop(5).all? { it.match?(/^ XX.*a̱500.*XX/) })
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user