Files
fzf/src/core.go
T

446 lines
11 KiB
Go
Raw Normal View History

2022-08-12 22:11:15 +09:00
// Package fzf implements fzf, a command-line fuzzy finder.
2015-01-02 04:49:30 +09:00
package fzf
import (
"os"
"sync"
2015-01-02 04:49:30 +09:00
"time"
2015-01-12 12:56:17 +09:00
"github.com/junegunn/fzf/src/util"
2015-01-02 04:49:30 +09:00
)
/*
2015-01-12 03:01:24 +09:00
Reader -> EvtReadFin
Reader -> EvtReadNew -> Matcher (restart)
2015-03-31 22:05:02 +09:00
Terminal -> EvtSearchNew:bool -> Matcher (restart)
2015-01-12 03:01:24 +09:00
Matcher -> EvtSearchProgress -> Terminal (update info)
Matcher -> EvtSearchFin -> Terminal (update list)
2015-07-22 03:21:20 +09:00
Matcher -> EvtHeader -> Terminal (update header)
2015-01-02 04:49:30 +09:00
*/
type revision struct {
major int
minor int
}
func (r *revision) bumpMajor() {
r.major++
r.minor = 0
}
func (r *revision) bumpMinor() {
r.minor++
}
func (r revision) compatible(other revision) bool {
return r.major == other.major
}
2015-01-12 03:01:24 +09:00
// Run starts fzf
func Run(opts *Options) (int, error) {
2024-06-12 21:02:48 +09:00
if opts.Filter == nil {
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
return runTmux(os.Args, opts)
}
2024-06-12 21:02:48 +09:00
if needWinpty(opts) {
return runWinpty(os.Args, opts)
}
}
if err := postProcessOptions(opts); err != nil {
return ExitError, err
}
defer util.RunAtExitFuncs()
// Output channel given
if opts.Output != nil {
opts.Printer = func(str string) {
opts.Output <- str
}
}
2015-03-31 22:05:02 +09:00
sort := opts.Sort > 0
sortCriteria = opts.Criteria
2015-01-02 04:49:30 +09:00
// Event channel
2015-01-12 12:56:17 +09:00
eventBox := util.NewEventBox()
2015-01-02 04:49:30 +09:00
2015-03-19 01:59:14 +09:00
// ANSI code processor
2016-08-19 02:39:32 +09:00
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
return util.ToChars(data), nil
}
var lineAnsiState, prevLineAnsiState *ansiState
2015-03-19 01:59:14 +09:00
if opts.Ansi {
2020-10-25 19:29:37 +09:00
if opts.Theme.Colored {
2016-08-19 02:39:32 +09:00
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
lineAnsiState = newState
return util.ToChars(stringBytes(trimmed)), offsets
2015-03-19 01:59:14 +09:00
}
} else {
// When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input
2016-08-19 02:39:32 +09:00
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, _, _ := extractColor(byteString(data), nil, nil)
return util.ToChars(stringBytes(trimmed)), nil
2015-03-19 01:59:14 +09:00
}
}
}
2015-01-02 04:49:30 +09:00
// Chunk list
cache := NewChunkCache()
2015-01-02 04:49:30 +09:00
var chunkList *ChunkList
2017-08-16 12:26:06 +09:00
var itemIndex int32
2015-07-22 03:21:20 +09:00
header := make([]string, 0, opts.HeaderLines)
2015-01-02 04:49:30 +09:00
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
2015-07-22 03:21:20 +09:00
if len(header) < opts.HeaderLines {
header = append(header, byteString(data))
2015-07-22 03:21:20 +09:00
eventBox.Set(EvtHeader, header)
2017-08-15 01:10:41 +09:00
return false
2015-07-22 03:21:20 +09:00
}
2017-08-15 01:10:41 +09:00
item.text, item.colors = ansiProcessor(data)
2017-08-16 12:26:06 +09:00
item.text.Index = itemIndex
itemIndex++
2017-08-15 01:10:41 +09:00
return true
2015-01-02 04:49:30 +09:00
})
} else {
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
tokens := Tokenize(byteString(data), opts.Delimiter)
2020-10-25 19:29:37 +09:00
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState
ansiState = &ansiStateDup
}
for _, token := range tokens {
prevAnsiState := ansiState
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
if prevAnsiState != nil {
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
} else {
token.text.Prepend("\x1b[m")
}
}
}
2015-07-22 21:24:02 +09:00
trans := Transform(tokens, opts.WithNth)
transformed := joinTokens(trans)
2015-07-22 03:21:20 +09:00
if len(header) < opts.HeaderLines {
header = append(header, transformed)
2015-07-22 03:21:20 +09:00
eventBox.Set(EvtHeader, header)
2017-08-15 01:10:41 +09:00
return false
2015-07-22 03:21:20 +09:00
}
item.text, item.colors = ansiProcessor(stringBytes(transformed))
item.text.TrimTrailingWhitespaces()
2017-08-16 12:26:06 +09:00
item.text.Index = itemIndex
2017-08-15 01:10:41 +09:00
item.origText = &data
2017-08-16 12:26:06 +09:00
itemIndex++
2017-08-15 01:10:41 +09:00
return true
2015-01-02 04:49:30 +09:00
})
}
// Process executor
executor := util.NewExecutor(opts.WithShell)
2015-01-02 04:49:30 +09:00
// Reader
2015-03-31 22:05:02 +09:00
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
var reader *Reader
if !streamingFilter {
reader = NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
}
2015-01-02 04:49:30 +09:00
// Matcher
forward := true
2022-08-03 22:18:26 +09:00
withPos := false
for idx := len(opts.Criteria) - 1; idx > 0; idx-- {
switch opts.Criteria[idx] {
case byChunk:
withPos = true
case byEnd:
forward = false
2022-08-03 22:18:26 +09:00
case byBegin:
forward = true
}
}
patternCache := make(map[string]*Pattern)
2015-01-02 04:49:30 +09:00
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(cache, patternCache,
2022-08-03 22:18:26 +09:00
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
2016-09-07 09:58:18 +09:00
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
2015-01-02 04:49:30 +09:00
}
inputRevision := revision{}
snapshotRevision := revision{}
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
2015-01-02 04:49:30 +09:00
2015-02-18 00:08:17 +09:00
// Filtering mode
if opts.Filter != nil {
if opts.PrintQuery {
2016-09-18 04:52:47 +09:00
opts.Printer(*opts.Filter)
2015-01-02 04:49:30 +09:00
}
pattern := patternBuilder([]rune(*opts.Filter))
matcher.sort = pattern.sortable
found := false
if streamingFilter {
2016-09-07 09:58:18 +09:00
slab := util.MakeSlab(slab16Size, slab32Size)
mutex := sync.Mutex{}
2017-08-16 03:24:23 +09:00
reader := NewReader(
func(runes []byte) bool {
2017-08-15 01:10:41 +09:00
item := Item{}
2017-08-16 12:26:06 +09:00
if chunkList.trans(&item, runes) {
mutex.Lock()
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
2016-09-18 04:52:47 +09:00
opts.Printer(item.text.ToString())
2016-08-20 01:46:54 +09:00
found = true
}
mutex.Unlock()
}
2015-07-22 03:21:20 +09:00
return false
}, eventBox, executor, opts.ReadZero, false)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
// NOTE: Streaming filter is inherently not compatible with --tail
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
merger, _ := matcher.scan(MatchRequest{
chunks: snapshot,
pattern: pattern})
for i := 0; i < merger.Length(); i++ {
2016-09-18 04:52:47 +09:00
opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
found = true
}
2015-02-18 00:08:17 +09:00
}
if found {
return ExitOk, nil
}
return ExitNoMatch, nil
2015-01-02 04:49:30 +09:00
}
2015-02-13 12:25:19 +09:00
// Synchronous search
if opts.Sync {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
}
2015-01-02 04:49:30 +09:00
// Go interactive
go matcher.Loop()
defer matcher.Stop()
2015-01-02 04:49:30 +09:00
// Terminal I/O
terminal, err := NewTerminal(opts, eventBox, executor)
if err != nil {
return ExitError, err
}
maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0
heightUnknown := opts.Height.auto
if heightUnknown {
2023-12-21 18:41:01 +09:00
maxFit, padHeight = terminal.MaxFitAndPad()
}
deferred := opts.Select1 || opts.Exit0 || opts.Sync
2015-01-02 04:49:30 +09:00
go terminal.Loop()
if !deferred && !heightUnknown {
// Start right away
terminal.startChan <- fitpad{-1, -1}
2015-02-18 00:08:17 +09:00
}
2015-01-02 04:49:30 +09:00
// Event coordination
reading := true
ticks := 0
var nextCommand *commandSpec
var nextEnviron []string
2015-01-12 03:01:24 +09:00
eventBox.Watch(EvtReadNew)
total := 0
2021-01-03 00:00:40 +09:00
query := []rune{}
determine := func(final bool) {
if heightUnknown {
if total >= maxFit || final {
deferred = false
heightUnknown = false
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
}
} else if deferred {
deferred = false
terminal.startChan <- fitpad{-1, -1}
}
}
2022-12-29 20:03:51 +09:00
useSnapshot := false
var snapshot []*Chunk
var count int
restart := func(command commandSpec, environ []string) {
2022-12-29 20:03:51 +09:00
reading = true
chunkList.Clear()
itemIndex = 0
inputRevision.bumpMajor()
2022-12-29 20:03:51 +09:00
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ)
2022-12-29 20:03:51 +09:00
}
exitCode := ExitOk
stop := false
2015-01-02 04:49:30 +09:00
for {
delay := true
2015-01-12 03:01:24 +09:00
ticks++
input := func() []rune {
2021-01-03 00:00:40 +09:00
paused, input := terminal.Input()
2024-03-27 17:25:56 +09:00
if !paused {
2021-01-03 00:00:40 +09:00
query = input
2019-11-09 22:54:48 +09:00
}
2021-01-03 00:00:40 +09:00
return query
2019-11-09 22:54:48 +09:00
}
2015-01-12 12:56:17 +09:00
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {
delete(*events, EvtReadNew)
}
2015-01-02 04:49:30 +09:00
for evt, value := range *events {
switch evt {
2021-03-07 11:30:26 +09:00
case EvtQuit:
if reading {
reader.terminate()
}
quitSignal := value.(quitSignal)
exitCode = quitSignal.code
err = quitSignal.err
stop = true
return
2015-01-12 03:01:24 +09:00
case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand, nextEnviron)
nextCommand = nil
nextEnviron = nil
break
} else {
reading = reading && evt == EvtReadNew
}
2022-12-29 20:03:51 +09:00
if useSnapshot && evt == EvtReadFin {
useSnapshot = false
}
if !useSnapshot {
if !snapshotRevision.compatible(inputRevision) {
2024-03-27 17:25:56 +09:00
query = []rune{}
}
var changed bool
snapshot, count, changed = chunkList.Snapshot(opts.Tail)
if changed {
inputRevision.bumpMinor()
}
snapshotRevision = inputRevision
2022-12-29 20:03:51 +09:00
}
total = count
terminal.UpdateCount(total, !reading, value.(*string))
if heightUnknown && !deferred {
determine(!reading)
}
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
2015-01-02 04:49:30 +09:00
2015-01-12 03:01:24 +09:00
case EvtSearchNew:
var command *commandSpec
var environ []string
var changed bool
2015-04-16 14:44:41 +09:00
switch val := value.(type) {
case searchRequest:
sort = val.sort
command = val.command
environ = val.environ
changed = val.changed
2022-12-29 20:03:51 +09:00
if command != nil {
useSnapshot = val.sync
}
}
if command != nil {
if reading {
reader.terminate()
nextCommand = command
nextEnviron = environ
} else {
restart(*command, environ)
}
}
if !changed {
break
2015-03-31 22:05:02 +09:00
}
2022-12-29 20:03:51 +09:00
if !useSnapshot {
newSnapshot, newCount, changed := chunkList.Snapshot(opts.Tail)
if changed {
inputRevision.bumpMinor()
}
// We want to avoid showing empty list when reload is triggered
// and the query string is changed at the same time i.e. command != nil && changed
2024-03-27 17:25:56 +09:00
if command == nil || newCount > 0 {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot = newSnapshot
snapshotRevision = inputRevision
}
2022-12-29 20:03:51 +09:00
}
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
2015-01-02 04:49:30 +09:00
delay = false
2015-01-12 03:01:24 +09:00
case EvtSearchProgress:
2015-01-02 04:49:30 +09:00
switch val := value.(type) {
case float32:
terminal.UpdateProgress(val)
}
2015-07-22 03:21:20 +09:00
case EvtHeader:
2019-12-16 18:47:05 +09:00
headerPadded := make([]string, opts.HeaderLines)
copy(headerPadded, value.([]string))
terminal.UpdateHeader(headerPadded)
2015-07-22 03:21:20 +09:00
2015-01-12 03:01:24 +09:00
case EvtSearchFin:
2015-01-02 04:49:30 +09:00
switch val := value.(type) {
case *Merger:
2015-02-18 00:08:17 +09:00
if deferred {
count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
determine(val.final)
} else if val.final {
2015-02-18 00:08:17 +09:00
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery {
2016-09-18 04:52:47 +09:00
opts.Printer(opts.Query)
2015-02-18 00:08:17 +09:00
}
if len(opts.Expect) > 0 {
2016-09-18 04:52:47 +09:00
opts.Printer("")
}
2015-02-18 00:08:17 +09:00
for i := 0; i < count; i++ {
2016-09-18 04:52:47 +09:00
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
2015-02-18 00:08:17 +09:00
}
if count == 0 {
exitCode = ExitNoMatch
}
stop = true
return
2015-02-18 00:08:17 +09:00
}
determine(val.final)
2015-02-18 00:08:17 +09:00
}
}
2024-06-17 00:07:27 +09:00
terminal.UpdateList(val)
2015-01-02 04:49:30 +09:00
}
}
}
events.Clear()
2015-01-02 04:49:30 +09:00
})
if stop {
break
}
2015-01-08 22:07:04 +09:00
if delay && reading {
2015-01-12 12:56:17 +09:00
dur := util.DurWithin(
2015-01-12 03:01:24 +09:00
time.Duration(ticks)*coordinatorDelayStep,
0, coordinatorDelayMax)
2015-01-08 22:07:04 +09:00
time.Sleep(dur)
2015-01-02 04:49:30 +09:00
}
}
return exitCode, err
2015-01-02 04:49:30 +09:00
}