summaryrefslogtreecommitdiffstats
path: root/src/core.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/core.go')
-rw-r--r--src/core.go144
1 files changed, 98 insertions, 46 deletions
diff --git a/src/core.go b/src/core.go
index ec137698..f2563a08 100644
--- a/src/core.go
+++ b/src/core.go
@@ -2,10 +2,9 @@
package fzf
import (
- "fmt"
+ "os"
"sync"
"time"
- "unsafe"
"github.com/junegunn/fzf/src/util"
)
@@ -19,30 +18,54 @@ Matcher -> EvtSearchFin -> Terminal (update list)
Matcher -> EvtHeader -> Terminal (update header)
*/
-func ustring(data []byte) string {
- return unsafe.String(unsafe.SliceData(data), len(data))
+type revision struct {
+ major int
+ minor int
}
-func sbytes(data string) []byte {
- return unsafe.Slice(unsafe.StringData(data), len(data))
+func (r *revision) bumpMajor() {
+ r.major++
+ r.minor = 0
+}
+
+func (r *revision) bumpMinor() {
+ r.minor++
+}
+
+func (r revision) equals(other revision) bool {
+ return r.major == other.major && r.minor == other.minor
+}
+
+func (r revision) compatible(other revision) bool {
+ return r.major == other.major
}
// Run starts fzf
-func Run(opts *Options, version string, revision string) {
- defer util.RunAtExitFuncs()
+func Run(opts *Options) (int, error) {
+ if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
+ return runTmux(os.Args, opts)
+ }
- sort := opts.Sort > 0
- sortCriteria = opts.Criteria
+ if needWinpty(opts) {
+ return runWinpty(os.Args, 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
}
- util.Exit(exitOk)
}
+ sort := opts.Sort > 0
+ sortCriteria = opts.Criteria
+
// Event channel
eventBox := util.NewEventBox()
@@ -56,16 +79,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(ustring(data), lineAnsiState, nil)
+ trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
lineAnsiState = newState
- return util.ToChars(sbytes(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(ustring(data), nil, nil)
- return util.ToChars(sbytes(trimmed)), nil
+ trimmed, _, _ := extractColor(byteString(data), nil, nil)
+ return util.ToChars(stringBytes(trimmed)), nil
}
}
}
@@ -77,7 +100,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, ustring(data))
+ header = append(header, byteString(data))
eventBox.Set(EvtHeader, header)
return false
}
@@ -88,7 +111,7 @@ func Run(opts *Options, version string, revision string) {
})
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
- tokens := Tokenize(ustring(data), opts.Delimiter)
+ tokens := Tokenize(byteString(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
@@ -112,7 +135,7 @@ func Run(opts *Options, version string, revision string) {
eventBox.Set(EvtHeader, header)
return false
}
- item.text, item.colors = ansiProcessor(sbytes(transformed))
+ item.text, item.colors = ansiProcessor(stringBytes(transformed))
item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex
item.origText = &data
@@ -121,14 +144,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
@@ -144,14 +170,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)
+ inputRevision := revision{}
+ snapshotRevision := revision{}
+ matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
// Filtering mode
if opts.Filter != nil {
@@ -178,13 +206,14 @@ func Run(opts *Options, version string, revision string) {
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)
- snapshot, _ := chunkList.Snapshot()
+ // NOTE: Streaming filter is inherently not compatible with --tail
+ snapshot, _, _ := chunkList.Snapshot(opts.Tail)
merger, _ := matcher.scan(MatchRequest{
chunks: snapshot,
pattern: pattern})
@@ -194,9 +223,9 @@ func Run(opts *Options, version string, revision string) {
}
}
if found {
- util.Exit(exitOk)
+ return ExitOk, nil
}
- util.Exit(exitNoMatch)
+ return ExitNoMatch, nil
}
// Synchronous search
@@ -207,9 +236,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
@@ -226,7 +259,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
@@ -247,14 +280,17 @@ 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
- inputRevision++
+ inputRevision.bumpMajor()
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ)
}
+
+ exitCode := ExitOk
+ stop := false
for {
delay := true
ticks++
@@ -275,7 +311,11 @@ func Run(opts *Options, version string, revision string) {
if reading {
reader.terminate()
}
- util.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)
@@ -289,10 +329,14 @@ func Run(opts *Options, version string, revision string) {
useSnapshot = false
}
if !useSnapshot {
- if snapshotRevision != inputRevision {
+ if !snapshotRevision.compatible(inputRevision) {
query = []rune{}
}
- snapshot, count = chunkList.Snapshot()
+ var changed bool
+ snapshot, count, changed = chunkList.Snapshot(opts.Tail)
+ if changed {
+ inputRevision.bumpMinor()
+ }
snapshotRevision = inputRevision
}
total = count
@@ -306,7 +350,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) {
@@ -332,7 +376,10 @@ func Run(opts *Options, version string, revision string) {
break
}
if !useSnapshot {
- newSnapshot, newCount := chunkList.Snapshot()
+ 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
if command == nil || newCount > 0 {
@@ -375,10 +422,11 @@ 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 {
- util.Exit(exitOk)
+ if count == 0 {
+ exitCode = ExitNoMatch
}
- util.Exit(exitNoMatch)
+ stop = true
+ return
}
determine(val.final)
}
@@ -389,6 +437,9 @@ func Run(opts *Options, version string, revision string) {
}
events.Clear()
})
+ if stop {
+ break
+ }
if delay && reading {
dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep,
@@ -396,4 +447,5 @@ func Run(opts *Options, version string, revision string) {
time.Sleep(dur)
}
}
+ return exitCode, err
}