mirror of
https://github.com/junegunn/fzf.git
synced 2026-02-05 01:17:53 +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:
110
src/terminal.go
110
src/terminal.go
@@ -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) {
|
||||
width = max(0, width)
|
||||
var trimmed int32
|
||||
// Assume that each rune takes at least one column on screen
|
||||
if len(runes) > width {
|
||||
diff := len(runes) - width
|
||||
trimmed = int32(diff)
|
||||
runes = runes[diff:]
|
||||
|
||||
str := string(runes)
|
||||
runningSum := 0
|
||||
runningSumAdjusted := 0
|
||||
// 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 {
|
||||
runes = runes[1:]
|
||||
trimmed++
|
||||
currentWidth = t.displayWidthWithLimit(runes, ellipsisWidth, width)
|
||||
var w int
|
||||
if len(rs) == 1 && rs[0] == '\t' {
|
||||
w = t.tabstop - runningSum%t.tabstop
|
||||
} 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
|
||||
}
|
||||
|
||||
@@ -3506,9 +3560,16 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
}
|
||||
charOffsets := matchOffsets
|
||||
if pos != nil {
|
||||
runes := item.text.ToRunes()
|
||||
charOffsets = make([]Offset, len(*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
|
||||
}
|
||||
sort.Sort(ByOrder(charOffsets))
|
||||
@@ -3767,24 +3828,16 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
|
||||
if !t.wrap && displayWidth > adjustedMaxWidth {
|
||||
maxe = util.Constrain(maxe+min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
|
||||
transformOffsets := func(diff int32, rightTrim bool) {
|
||||
for idx, offset := range offs {
|
||||
b, e := offset.offset[0], offset.offset[1]
|
||||
el := int32(len(ellipsis))
|
||||
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)
|
||||
transformOffsets := func(diff int32) {
|
||||
for idx := range offs {
|
||||
offs[idx].offset[0] -= diff
|
||||
offs[idx].offset[1] -= diff
|
||||
}
|
||||
}
|
||||
if t.hscroll {
|
||||
if fidx == 1 || fidx == 2 && t.keepRight && pos == nil {
|
||||
trimmed, diff := t.trimLeft(runes, maxWidth, ellipsisWidth)
|
||||
transformOffsets(diff, false)
|
||||
transformOffsets(diff - int32(len(ellipsis)))
|
||||
runes = append(ellipsis, trimmed...)
|
||||
} else if fidx == 0 || !t.overflow(runes[:maxe], maxWidth-ellipsisWidth) {
|
||||
// Stri..
|
||||
@@ -3792,27 +3845,20 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
runes = append(runes, ellipsis...)
|
||||
} else {
|
||||
// Stri..
|
||||
rightTrim := false
|
||||
if t.overflow(runes[maxe:], ellipsisWidth) {
|
||||
runes = append(runes[:maxe], ellipsis...)
|
||||
rightTrim = true
|
||||
}
|
||||
// ..ri..
|
||||
var diff int32
|
||||
runes, diff = t.trimLeft(runes, maxWidth, ellipsisWidth)
|
||||
|
||||
// Transform offsets
|
||||
transformOffsets(diff, rightTrim)
|
||||
transformOffsets(diff - int32(len(ellipsis)))
|
||||
runes = append(ellipsis, runes...)
|
||||
}
|
||||
} else {
|
||||
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,13 @@ func StringWidth(s string) int {
|
||||
|
||||
// RunesWidth returns runes width
|
||||
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
|
||||
gr := uniseg.NewGraphemes(string(runes))
|
||||
gr := uniseg.NewGraphemes(str)
|
||||
idx := 0
|
||||
for gr.Next() {
|
||||
rs := gr.Runes()
|
||||
|
||||
@@ -2175,4 +2175,13 @@ class TestCore < TestInteractive
|
||||
assert_equal 1, it.match_count
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user