diff options
Diffstat (limited to 'src/core.go')
-rw-r--r-- | src/core.go | 118 |
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 } |