summaryrefslogtreecommitdiffstats
path: root/src/core.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/core.go')
-rw-r--r--src/core.go118
1 files changed, 76 insertions, 42 deletions
diff --git a/src/core.go b/src/core.go
index 3abda89c..17b7d1df 100644
--- a/src/core.go
+++ b/src/core.go
@@ -2,8 +2,8 @@
package fzf
import (
- "fmt"
"os"
+ "sync"
"time"
"github.com/junegunn/fzf/src/util"
@@ -19,19 +19,27 @@ Matcher -> EvtHeader -> Terminal (update header)
*/
// Run starts fzf
-func Run(opts *Options, version string, revision string) {
- sort := opts.Sort > 0
- sortCriteria = opts.Criteria
+func Run(opts *Options) (int, error) {
+ if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 {
+ return runTmux(os.Args[1:], opts)
+ }
- if opts.Version {
- if len(revision) > 0 {
- fmt.Printf("%s (%s)\n", version, revision)
- } else {
- fmt.Println(version)
+ 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
}
- os.Exit(exitOk)
}
+ sort := opts.Sort > 0
+ sortCriteria = opts.Criteria
+
// Event channel
eventBox := util.NewEventBox()
@@ -45,16 +53,16 @@ func Run(opts *Options, version string, revision string) {
if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState
- trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
+ trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
lineAnsiState = newState
- return util.ToChars([]byte(trimmed)), offsets
+ return util.ToChars(stringBytes(trimmed)), offsets
}
} else {
// When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
- trimmed, _, _ := extractColor(string(data), nil, nil)
- return util.ToChars([]byte(trimmed)), nil
+ trimmed, _, _ := extractColor(byteString(data), nil, nil)
+ return util.ToChars(stringBytes(trimmed)), nil
}
}
}
@@ -66,7 +74,7 @@ func Run(opts *Options, version string, revision string) {
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines {
- header = append(header, string(data))
+ header = append(header, byteString(data))
eventBox.Set(EvtHeader, header)
return false
}
@@ -77,7 +85,7 @@ func Run(opts *Options, version string, revision string) {
})
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
- tokens := Tokenize(string(data), opts.Delimiter)
+ tokens := Tokenize(byteString(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
@@ -101,7 +109,7 @@ func Run(opts *Options, version string, revision string) {
eventBox.Set(EvtHeader, header)
return false
}
- item.text, item.colors = ansiProcessor([]byte(transformed))
+ item.text, item.colors = ansiProcessor(stringBytes(transformed))
item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex
item.origText = &data
@@ -110,14 +118,17 @@ func Run(opts *Options, version string, revision string) {
})
}
+ // Process executor
+ executor := util.NewExecutor(opts.WithShell)
+
// Reader
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, opts.ReadZero, opts.Filter == nil)
- go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
+ }, eventBox, executor, opts.ReadZero, opts.Filter == nil)
+ go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
}
// Matcher
@@ -133,14 +144,16 @@ func Run(opts *Options, version string, revision string) {
forward = true
}
}
+ cache := NewChunkCache()
+ patternCache := make(map[string]*Pattern)
patternBuilder := func(runes []rune) *Pattern {
- return BuildPattern(
+ return BuildPattern(cache, patternCache,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
}
inputRevision := 0
snapshotRevision := 0
- matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
+ matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
// Filtering mode
if opts.Filter != nil {
@@ -154,18 +167,21 @@ func Run(opts *Options, version string, revision string) {
found := false
if streamingFilter {
slab := util.MakeSlab(slab16Size, slab32Size)
+ mutex := sync.Mutex{}
reader := NewReader(
func(runes []byte) bool {
item := Item{}
if chunkList.trans(&item, runes) {
+ mutex.Lock()
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
opts.Printer(item.text.ToString())
found = true
}
+ mutex.Unlock()
}
return false
- }, eventBox, opts.ReadZero, false)
- reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
+ }, eventBox, executor, opts.ReadZero, false)
+ reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
@@ -180,9 +196,9 @@ func Run(opts *Options, version string, revision string) {
}
}
if found {
- os.Exit(exitOk)
+ return ExitOk, nil
}
- os.Exit(exitNoMatch)
+ return ExitNoMatch, nil
}
// Synchronous search
@@ -193,9 +209,13 @@ func Run(opts *Options, version string, revision string) {
// Go interactive
go matcher.Loop()
+ defer matcher.Stop()
// Terminal I/O
- terminal := NewTerminal(opts, eventBox)
+ 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
@@ -212,7 +232,7 @@ func Run(opts *Options, version string, revision string) {
// Event coordination
reading := true
ticks := 0
- var nextCommand *string
+ var nextCommand *commandSpec
var nextEnviron []string
eventBox.Watch(EvtReadNew)
total := 0
@@ -233,7 +253,7 @@ func Run(opts *Options, version string, revision string) {
useSnapshot := false
var snapshot []*Chunk
var count int
- restart := func(command string, environ []string) {
+ restart := func(command commandSpec, environ []string) {
reading = true
chunkList.Clear()
itemIndex = 0
@@ -241,15 +261,15 @@ func Run(opts *Options, version string, revision string) {
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ)
}
+
+ exitCode := ExitOk
+ stop := false
for {
delay := true
ticks++
input := func() []rune {
- reloaded := snapshotRevision != inputRevision
paused, input := terminal.Input()
- if reloaded && paused {
- query = []rune{}
- } else if !paused {
+ if !paused {
query = input
}
return query
@@ -264,7 +284,11 @@ func Run(opts *Options, version string, revision string) {
if reading {
reader.terminate()
}
- os.Exit(value.(int))
+ quitSignal := value.(quitSignal)
+ exitCode = quitSignal.code
+ err = quitSignal.err
+ stop = true
+ return
case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand, nextEnviron)
@@ -278,14 +302,16 @@ func Run(opts *Options, version string, revision string) {
useSnapshot = false
}
if !useSnapshot {
+ if snapshotRevision != inputRevision {
+ query = []rune{}
+ }
snapshot, count = chunkList.Snapshot()
snapshotRevision = inputRevision
}
total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync {
- opts.Sync = false
- terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
+ terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision), false)
}
if heightUnknown && !deferred {
determine(!reading)
@@ -293,7 +319,7 @@ func Run(opts *Options, version string, revision string) {
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
case EvtSearchNew:
- var command *string
+ var command *commandSpec
var environ []string
var changed bool
switch val := value.(type) {
@@ -319,10 +345,13 @@ func Run(opts *Options, version string, revision string) {
break
}
if !useSnapshot {
- newSnapshot, _ := chunkList.Snapshot()
+ newSnapshot, newCount := chunkList.Snapshot()
// 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
- if command == nil || len(newSnapshot) > 0 {
+ if command == nil || newCount > 0 {
+ if snapshotRevision != inputRevision {
+ query = []rune{}
+ }
snapshot = newSnapshot
snapshotRevision = inputRevision
}
@@ -359,20 +388,24 @@ func Run(opts *Options, version string, revision string) {
for i := 0; i < count; i++ {
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
}
- if count > 0 {
- os.Exit(exitOk)
+ if count == 0 {
+ exitCode = ExitNoMatch
}
- os.Exit(exitNoMatch)
+ stop = true
+ return
}
determine(val.final)
}
}
- terminal.UpdateList(val)
+ terminal.UpdateList(val, true)
}
}
}
events.Clear()
})
+ if stop {
+ break
+ }
if delay && reading {
dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep,
@@ -380,4 +413,5 @@ func Run(opts *Options, version string, revision string) {
time.Sleep(dur)
}
}
+ return exitCode, err
}