mirror of
https://github.com/junegunn/fzf.git
synced 2026-03-07 23:52:32 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9249ea1739 | ||
|
|
92bfe68c74 | ||
|
|
92dc40ea82 | ||
|
|
12a280ba14 | ||
|
|
0c6ead6e98 | ||
|
|
280a011f02 | ||
|
|
d324580840 | ||
|
|
f9830c5a3d | ||
|
|
95bc5b8f0c | ||
|
|
0b08f0dea0 | ||
|
|
e7300fe300 | ||
|
|
260d160973 | ||
|
|
d57ed157ad | ||
|
|
9226bc605d |
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
@@ -12,6 +12,6 @@ jobs:
|
|||||||
label:
|
label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v5
|
- uses: actions/labeler@v6
|
||||||
with:
|
with:
|
||||||
configuration-path: .github/labeler.yml
|
configuration-path: .github/labeler.yml
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
builtin printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
builtin printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_exec_awk() {
|
__fzf_exec_awk() {
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ if [[ -o interactive ]]; then
|
|||||||
# the changes. See code comments in "common.sh" for the implementation details.
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
builtin printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
builtin printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_exec_awk() {
|
__fzf_exec_awk() {
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ if [[ $- =~ i ]]; then
|
|||||||
# the changes. See code comments in "common.sh" for the implementation details.
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
builtin printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
builtin printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_exec_awk() {
|
__fzf_exec_awk() {
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ if [[ -o interactive ]]; then
|
|||||||
# the changes. See code comments in "common.sh" for the implementation details.
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
builtin printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
builtin printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_exec_awk() {
|
__fzf_exec_awk() {
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ const (
|
|||||||
maxBgProcessesPerAction = 3
|
maxBgProcessesPerAction = 3
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
numPartitionsMultiplier = 8
|
|
||||||
maxPartitions = 32
|
|
||||||
progressMinDuration = 200 * time.Millisecond
|
progressMinDuration = 200 * time.Millisecond
|
||||||
|
|
||||||
// Capacity of each chunk
|
// Capacity of each chunk
|
||||||
|
|||||||
@@ -195,11 +195,13 @@ func Run(opts *Options) (int, error) {
|
|||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync && opts.Bench == 0
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync && opts.Bench == 0
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
|
var ingestionStart time.Time
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
|
|
||||||
|
ingestionStart = time.Now()
|
||||||
readyChan := make(chan bool)
|
readyChan := make(chan bool)
|
||||||
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
|
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
|
||||||
<-readyChan
|
<-readyChan
|
||||||
@@ -283,6 +285,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
|
ingestionTime := time.Since(ingestionStart)
|
||||||
|
|
||||||
// NOTE: Streaming filter is inherently not compatible with --tail
|
// NOTE: Streaming filter is inherently not compatible with --tail
|
||||||
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
|
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
|
||||||
@@ -316,13 +319,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
avg := total / time.Duration(len(times))
|
avg := total / time.Duration(len(times))
|
||||||
selectivity := float64(matchCount) / float64(totalItems) * 100
|
selectivity := float64(matchCount) / float64(totalItems) * 100
|
||||||
fmt.Printf(" %d iterations avg: %.2fms min: %.2fms max: %.2fms total: %.2fs items: %d matches: %d (%.2f%%)\n",
|
fmt.Printf(" %d iterations avg: %.2fms min: %.2fms max: %.2fms total: %.2fs items: %d matches: %d (%.2f%%) ingestion: %.2fms\n",
|
||||||
len(times),
|
len(times),
|
||||||
float64(avg.Microseconds())/1000,
|
float64(avg.Microseconds())/1000,
|
||||||
float64(minD.Microseconds())/1000,
|
float64(minD.Microseconds())/1000,
|
||||||
float64(maxD.Microseconds())/1000,
|
float64(maxD.Microseconds())/1000,
|
||||||
total.Seconds(),
|
total.Seconds(),
|
||||||
totalItems, matchCount, selectivity)
|
totalItems, matchCount, selectivity,
|
||||||
|
float64(ingestionTime.Microseconds())/1000)
|
||||||
return ExitOk, nil
|
return ExitOk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
@@ -57,7 +58,7 @@ const (
|
|||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox, revision revision, threads int) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision revision, threads int) *Matcher {
|
||||||
partitions := min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := runtime.NumCPU()
|
||||||
if threads > 0 {
|
if threads > 0 {
|
||||||
partitions = threads
|
partitions = threads
|
||||||
}
|
}
|
||||||
@@ -148,27 +149,6 @@ func (m *Matcher) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
|
|
||||||
partitions := m.partitions
|
|
||||||
perSlice := len(chunks) / partitions
|
|
||||||
|
|
||||||
if perSlice == 0 {
|
|
||||||
partitions = len(chunks)
|
|
||||||
perSlice = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
slices := make([][]*Chunk, partitions)
|
|
||||||
for i := 0; i < partitions; i++ {
|
|
||||||
start := i * perSlice
|
|
||||||
end := start + perSlice
|
|
||||||
if i == partitions-1 {
|
|
||||||
end = len(chunks)
|
|
||||||
}
|
|
||||||
slices[i] = chunks[start:end]
|
|
||||||
}
|
|
||||||
return slices
|
|
||||||
}
|
|
||||||
|
|
||||||
type partialResult struct {
|
type partialResult struct {
|
||||||
index int
|
index int
|
||||||
matches []Result
|
matches []Result
|
||||||
@@ -192,39 +172,37 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
|
|||||||
maxIndex := request.chunks[numChunks-1].lastIndex(minIndex)
|
maxIndex := request.chunks[numChunks-1].lastIndex(minIndex)
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
|
|
||||||
slices := m.sliceChunks(request.chunks)
|
numWorkers := min(m.partitions, numChunks)
|
||||||
numSlices := len(slices)
|
var nextChunk atomic.Int32
|
||||||
resultChan := make(chan partialResult, numSlices)
|
resultChan := make(chan partialResult, numWorkers)
|
||||||
countChan := make(chan int, numChunks)
|
countChan := make(chan int, numChunks)
|
||||||
waitGroup := sync.WaitGroup{}
|
waitGroup := sync.WaitGroup{}
|
||||||
|
|
||||||
for idx, chunks := range slices {
|
for idx := range numWorkers {
|
||||||
waitGroup.Add(1)
|
waitGroup.Add(1)
|
||||||
if m.slab[idx] == nil {
|
if m.slab[idx] == nil {
|
||||||
m.slab[idx] = util.MakeSlab(slab16Size, slab32Size)
|
m.slab[idx] = util.MakeSlab(slab16Size, slab32Size)
|
||||||
}
|
}
|
||||||
go func(idx int, slab *util.Slab, chunks []*Chunk) {
|
go func(idx int, slab *util.Slab) {
|
||||||
defer func() { waitGroup.Done() }()
|
defer waitGroup.Done()
|
||||||
count := 0
|
var matches []Result
|
||||||
allMatches := make([][]Result, len(chunks))
|
for {
|
||||||
for idx, chunk := range chunks {
|
ci := int(nextChunk.Add(1)) - 1
|
||||||
matches := request.pattern.Match(chunk, slab)
|
if ci >= numChunks {
|
||||||
allMatches[idx] = matches
|
break
|
||||||
count += len(matches)
|
}
|
||||||
|
chunkMatches := request.pattern.Match(request.chunks[ci], slab)
|
||||||
|
matches = append(matches, chunkMatches...)
|
||||||
if cancelled.Get() {
|
if cancelled.Get() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
countChan <- len(matches)
|
countChan <- len(chunkMatches)
|
||||||
}
|
|
||||||
sliceMatches := make([]Result, 0, count)
|
|
||||||
for _, matches := range allMatches {
|
|
||||||
sliceMatches = append(sliceMatches, matches...)
|
|
||||||
}
|
}
|
||||||
if m.sort && request.pattern.sortable {
|
if m.sort && request.pattern.sortable {
|
||||||
m.sortBuf[idx] = radixSortResults(sliceMatches, m.tac, m.sortBuf[idx])
|
m.sortBuf[idx] = radixSortResults(matches, m.tac, m.sortBuf[idx])
|
||||||
}
|
}
|
||||||
resultChan <- partialResult{idx, sliceMatches}
|
resultChan <- partialResult{idx, matches}
|
||||||
}(idx, m.slab[idx], chunks)
|
}(idx, m.slab[idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
wait := func() bool {
|
wait := func() bool {
|
||||||
@@ -252,8 +230,8 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partialResults := make([][]Result, numSlices)
|
partialResults := make([][]Result, numWorkers)
|
||||||
for range slices {
|
for range numWorkers {
|
||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ type Pattern struct {
|
|||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
revision revision
|
revision revision
|
||||||
procFun map[termType]algo.Algo
|
procFun [6]algo.Algo
|
||||||
cache *ChunkCache
|
cache *ChunkCache
|
||||||
denylist map[int32]struct{}
|
denylist map[int32]struct{}
|
||||||
startIndex int32
|
startIndex int32
|
||||||
@@ -150,7 +150,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
cache: cache,
|
cache: cache,
|
||||||
denylist: denylist,
|
denylist: denylist,
|
||||||
startIndex: startIndex,
|
startIndex: startIndex,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
ptr.directAlgo, ptr.directTerm = ptr.buildDirectAlgo(fuzzyAlgo)
|
ptr.directAlgo, ptr.directTerm = ptr.buildDirectAlgo(fuzzyAlgo)
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ import "golang.org/x/sys/unix"
|
|||||||
|
|
||||||
// Protect calls OS specific protections like pledge on OpenBSD
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
func Protect() {
|
func Protect() {
|
||||||
unix.PledgePromises("stdio dpath wpath rpath tty proc exec inet tmppath")
|
unix.PledgePromises("stdio cpath dpath wpath rpath tty proc exec inet")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3905,6 +3905,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
frozenRight = line[splitOffsetRight:]
|
frozenRight = line[splitOffsetRight:]
|
||||||
}
|
}
|
||||||
displayWidthSum := 0
|
displayWidthSum := 0
|
||||||
|
displayWidthLeft := 0
|
||||||
todo := [3]func(){}
|
todo := [3]func(){}
|
||||||
for fidx, runes := range [][]rune{frozenLeft, frozenRight, middle} {
|
for fidx, runes := range [][]rune{frozenLeft, frozenRight, middle} {
|
||||||
if len(runes) == 0 {
|
if len(runes) == 0 {
|
||||||
@@ -3930,7 +3931,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
// For frozen parts, reserve space for the ellipsis in the middle part
|
// For frozen parts, reserve space for the ellipsis in the middle part
|
||||||
adjustedMaxWidth -= ellipsisWidth
|
adjustedMaxWidth -= ellipsisWidth
|
||||||
}
|
}
|
||||||
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
|
var prefixWidth int
|
||||||
|
if fidx == 2 {
|
||||||
|
prefixWidth = displayWidthLeft
|
||||||
|
}
|
||||||
|
displayWidth = t.displayWidthWithLimit(runes, prefixWidth, 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) {
|
transformOffsets := func(diff int32) {
|
||||||
@@ -3968,6 +3973,9 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
||||||
}
|
}
|
||||||
displayWidthSum += displayWidth
|
displayWidthSum += displayWidth
|
||||||
|
if fidx == 0 {
|
||||||
|
displayWidthLeft = displayWidth
|
||||||
|
}
|
||||||
|
|
||||||
if maxWidth > 0 {
|
if maxWidth > 0 {
|
||||||
color := colBase
|
color := colBase
|
||||||
@@ -3975,7 +3983,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
color = color.WithFg(t.theme.Nomatch)
|
color = color.WithFg(t.theme.Nomatch)
|
||||||
}
|
}
|
||||||
todo[fidx] = func() {
|
todo[fidx] = func() {
|
||||||
t.printColoredString(t.window, runes, offs, color)
|
t.printColoredString(t.window, runes, offs, color, prefixWidth)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -4002,10 +4010,13 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
return finalLineNum
|
return finalLineNum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
|
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair, initialPrefixWidth ...int) {
|
||||||
var index int32
|
var index int32
|
||||||
var substr string
|
var substr string
|
||||||
var prefixWidth int
|
var prefixWidth int
|
||||||
|
if len(initialPrefixWidth) > 0 {
|
||||||
|
prefixWidth = initialPrefixWidth[0]
|
||||||
|
}
|
||||||
maxOffset := int32(len(text))
|
maxOffset := int32(len(text))
|
||||||
var url *url
|
var url *url
|
||||||
for _, offset := range offsets {
|
for _, offset := range offsets {
|
||||||
@@ -4212,7 +4223,7 @@ func (t *Terminal) followOffset() int {
|
|||||||
for i := len(body) - 1; i >= 0; i-- {
|
for i := len(body) - 1; i >= 0; i-- {
|
||||||
h := t.previewLineHeight(body[i], maxWidth)
|
h := t.previewLineHeight(body[i], maxWidth)
|
||||||
if visualLines+h > height {
|
if visualLines+h > height {
|
||||||
return headerLines + i + 1
|
return min(len(lines)-1, headerLines+i+1)
|
||||||
}
|
}
|
||||||
visualLines += h
|
visualLines += h
|
||||||
}
|
}
|
||||||
@@ -4510,7 +4521,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width() || t.previewed.filled
|
||||||
if fillRet == tui.FillNextLine {
|
if fillRet == tui.FillNextLine {
|
||||||
continue
|
continue
|
||||||
} else if fillRet == tui.FillSuspend {
|
} else if fillRet == tui.FillSuspend {
|
||||||
@@ -4533,7 +4544,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
}
|
}
|
||||||
t.previewer.scrollable = t.previewer.scrollable || index < len(lines)-1
|
t.previewer.scrollable = t.previewer.scrollable || t.previewed.filled || index < len(lines)-1
|
||||||
t.previewed.image = image
|
t.previewed.image = image
|
||||||
t.previewed.wireframe = wireframe
|
t.previewed.wireframe = wireframe
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ func awkTokenizer(input string) ([]string, int) {
|
|||||||
end := 0
|
end := 0
|
||||||
for idx := 0; idx < len(input); idx++ {
|
for idx := 0; idx < len(input); idx++ {
|
||||||
r := input[idx]
|
r := input[idx]
|
||||||
white := r == 9 || r == 32
|
white := r == 9 || r == 32 || r == 10
|
||||||
switch state {
|
switch state {
|
||||||
case awkNil:
|
case awkNil:
|
||||||
if white {
|
if white {
|
||||||
@@ -218,11 +218,12 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
return withPrefixLengths(tokens, 0)
|
return withPrefixLengths(tokens, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StripLastDelimiter removes the trailing delimiter and whitespaces
|
// StripLastDelimiter removes the trailing delimiter
|
||||||
func StripLastDelimiter(str string, delimiter Delimiter) string {
|
func StripLastDelimiter(str string, delimiter Delimiter) string {
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
str = strings.TrimSuffix(str, *delimiter.str)
|
return strings.TrimSuffix(str, *delimiter.str)
|
||||||
} else if delimiter.regex != nil {
|
}
|
||||||
|
if delimiter.regex != nil {
|
||||||
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
if len(locs) > 0 {
|
if len(locs) > 0 {
|
||||||
lastLoc := locs[len(locs)-1]
|
lastLoc := locs[len(locs)-1]
|
||||||
@@ -230,6 +231,7 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
|
|||||||
str = str[:lastLoc[0]]
|
str = str[:lastLoc[0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
return strings.TrimRightFunc(str, unicode.IsSpace)
|
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ func TestParseRange(t *testing.T) {
|
|||||||
|
|
||||||
func TestTokenize(t *testing.T) {
|
func TestTokenize(t *testing.T) {
|
||||||
// AWK-style
|
// AWK-style
|
||||||
input := " abc: def: ghi "
|
input := " abc: \n\t def: ghi "
|
||||||
tokens := Tokenize(input, Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 {
|
if tokens[0].text.ToString() != "abc: \n\t " || tokens[0].prefixLength != 2 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +71,9 @@ func TestTokenize(t *testing.T) {
|
|||||||
// With delimiter regex
|
// With delimiter regex
|
||||||
tokens = Tokenize(input, delimiterRegexp("\\s+"))
|
tokens = Tokenize(input, delimiterRegexp("\\s+"))
|
||||||
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 ||
|
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 ||
|
||||||
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 ||
|
tokens[1].text.ToString() != "abc: \n\t " || tokens[1].prefixLength != 2 ||
|
||||||
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 ||
|
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 10 ||
|
||||||
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 {
|
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 16 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1190,6 +1190,16 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_freeze_left_tabstop
|
||||||
|
writelines(%W[1\t2\t3])
|
||||||
|
# With --freeze-left 1 and --tabstop=2:
|
||||||
|
# Frozen left: "1" (width 1)
|
||||||
|
# Middle starts with "\t" at prefix width 1, tabstop 2 → 1 space
|
||||||
|
# Then "2" at column 2, next "\t" at column 3 → 1 space, then "3"
|
||||||
|
tmux.send_keys %(cat #{tempname} | #{FZF} --tabstop=2 --freeze-left 1), :Enter
|
||||||
|
tmux.until { |lines| assert_equal '> 1 2 3', lines[-3] }
|
||||||
|
end
|
||||||
|
|
||||||
def test_freeze_left_keep_right
|
def test_freeze_left_keep_right
|
||||||
tmux.send_keys %(seq 10000 | #{FZF} --read0 --delimiter "\n" --freeze-left 3 --keep-right --ellipsis XX --no-multi-line --bind space:toggle-multi-line), :Enter
|
tmux.send_keys %(seq 10000 | #{FZF} --read0 --delimiter "\n" --freeze-left 3 --keep-right --ellipsis XX --no-multi-line --bind space:toggle-multi-line), :Enter
|
||||||
tmux.until { |lines| assert_match(/^> 1␊2␊3XX.*10000␊$/, lines[-3]) }
|
tmux.until { |lines| assert_match(/^> 1␊2␊3XX.*10000␊$/, lines[-3]) }
|
||||||
@@ -2085,13 +2095,13 @@ class TestCore < TestInteractive
|
|||||||
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter
|
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists tempname
|
assert_path_exists tempname
|
||||||
# Last delimiter and the whitespaces are removed
|
# Last delimiter is removed
|
||||||
assert_equal ['bar,bar,foo ,bazfoo'], File.readlines(tempname, chomp: true)
|
assert_equal ['bar,bar,foo ,bazfoo '], File.readlines(tempname, chomp: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_nth_regex_delimiter
|
def test_accept_nth_regex_delimiter
|
||||||
tmux.send_keys %(echo "foo :,:bar,baz" | #{FZF} --delimiter='[:,]+' --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter
|
tmux.send_keys %(echo "foo :,:bar,baz" | #{FZF} --delimiter=' *[:,]+ *' --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists tempname
|
assert_path_exists tempname
|
||||||
# Last delimiter and the whitespaces are removed
|
# Last delimiter and the whitespaces are removed
|
||||||
@@ -2109,7 +2119,7 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_nth_template
|
def test_accept_nth_template
|
||||||
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d " *, *" --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists tempname
|
assert_path_exists tempname
|
||||||
# Last delimiter and the whitespaces are removed
|
# Last delimiter and the whitespaces are removed
|
||||||
|
|||||||
@@ -393,6 +393,20 @@ class TestPreview < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_follow_wrap_long_line
|
||||||
|
tmux.send_keys %(seq 1 | #{FZF} --preview "seq 2; yes yes | head -10000 | tr '\n' ' '" --preview-window follow,wrap --bind up:preview-up,down:preview-down), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert lines.any_include?('3/3 │')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert lines.any_include?('2/3 │') }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert lines.any_include?('1/3 │') }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert lines.any_include?('2/3 │') }
|
||||||
|
end
|
||||||
|
|
||||||
def test_close
|
def test_close
|
||||||
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
|
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
@@ -593,7 +607,7 @@ class TestPreview < TestInteractive
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_wrap_sign_between_ansi_fragments_overflow
|
def test_preview_wrap_sign_between_ansi_fragments_overflow
|
||||||
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m1234567890 \\x1b[mhello"; echo -e "\\x1b[33m1234567890 \\x1b[mhello"' --preview-window 2,wrap-word), :Enter
|
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m123 \\x1b[mhi"; echo -e "\\x1b[33m123 \\x1b[mhi"' --preview-window 2,wrap-word,noinfo), :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 1, lines.match_count
|
assert_equal 1, lines.match_count
|
||||||
assert_equal(2, lines.count { |line| line.include?('│ 12 │') })
|
assert_equal(2, lines.count { |line| line.include?('│ 12 │') })
|
||||||
@@ -602,7 +616,7 @@ class TestPreview < TestInteractive
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_wrap_sign_between_ansi_fragments_overflow2
|
def test_preview_wrap_sign_between_ansi_fragments_overflow2
|
||||||
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m1234567890 \\x1b[mhello"; echo -e "\\x1b[33m1234567890 \\x1b[mhello"' --preview-window 1,wrap-word), :Enter
|
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m123 \\x1b[mhi"; echo -e "\\x1b[33m123 \\x1b[mhi"' --preview-window 1,wrap-word,noinfo), :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 1, lines.match_count
|
assert_equal 1, lines.match_count
|
||||||
assert_equal(2, lines.count { |line| line.include?('│ 1 │') })
|
assert_equal(2, lines.count { |line| line.include?('│ 1 │') })
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ tabe = "tabe"
|
|||||||
Iterm = "Iterm"
|
Iterm = "Iterm"
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
extend-exclude = ["README.md"]
|
extend-exclude = ["README.md", "*.s"]
|
||||||
|
|||||||
Reference in New Issue
Block a user