2015-01-11 02:59:57 -05:00
|
|
|
/*
|
|
|
|
Package fzf implements fzf, a command-line fuzzy finder.
|
|
|
|
|
|
|
|
The MIT License (MIT)
|
|
|
|
|
|
|
|
Copyright (c) 2015 Junegunn Choi
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
|
|
all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
THE SOFTWARE.
|
|
|
|
*/
|
2015-01-01 14:49:30 -05:00
|
|
|
package fzf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"runtime"
|
|
|
|
"time"
|
2015-01-11 22:56:17 -05:00
|
|
|
|
|
|
|
"github.com/junegunn/fzf/src/util"
|
2015-01-01 14:49:30 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
func initProcs() {
|
|
|
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2015-01-11 13:01:24 -05:00
|
|
|
Reader -> EvtReadFin
|
|
|
|
Reader -> EvtReadNew -> Matcher (restart)
|
2015-03-31 09:05:02 -04:00
|
|
|
Terminal -> EvtSearchNew:bool -> Matcher (restart)
|
2015-01-11 13:01:24 -05:00
|
|
|
Matcher -> EvtSearchProgress -> Terminal (update info)
|
|
|
|
Matcher -> EvtSearchFin -> Terminal (update list)
|
2015-07-21 14:21:20 -04:00
|
|
|
Matcher -> EvtHeader -> Terminal (update header)
|
2015-01-01 14:49:30 -05:00
|
|
|
*/
|
|
|
|
|
2015-01-11 13:01:24 -05:00
|
|
|
// Run starts fzf
|
2015-05-20 07:42:45 -04:00
|
|
|
func Run(opts *Options) {
|
2015-01-01 14:49:30 -05:00
|
|
|
initProcs()
|
|
|
|
|
2015-03-31 09:05:02 -04:00
|
|
|
sort := opts.Sort > 0
|
2015-04-16 01:19:28 -04:00
|
|
|
rankTiebreak = opts.Tiebreak
|
2015-01-01 14:49:30 -05:00
|
|
|
|
|
|
|
if opts.Version {
|
2015-08-02 00:06:15 -04:00
|
|
|
fmt.Println(version)
|
2015-01-01 14:49:30 -05:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Event channel
|
2015-01-11 22:56:17 -05:00
|
|
|
eventBox := util.NewEventBox()
|
2015-01-01 14:49:30 -05:00
|
|
|
|
2015-03-18 12:59:14 -04:00
|
|
|
// ANSI code processor
|
2015-08-02 01:25:57 -04:00
|
|
|
ansiProcessor := func(data []byte) ([]rune, []ansiOffset) {
|
|
|
|
return util.BytesToRunes(data), nil
|
|
|
|
}
|
|
|
|
ansiProcessorRunes := func(data []rune) ([]rune, []ansiOffset) {
|
|
|
|
return data, nil
|
2015-03-18 12:59:14 -04:00
|
|
|
}
|
|
|
|
if opts.Ansi {
|
2015-04-17 13:52:30 -04:00
|
|
|
if opts.Theme != nil {
|
2015-07-22 01:19:45 -04:00
|
|
|
var state *ansiState
|
2015-08-02 01:25:57 -04:00
|
|
|
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
|
|
|
|
trimmed, offsets, newState := extractColor(string(data), state)
|
2015-07-22 01:19:45 -04:00
|
|
|
state = newState
|
2015-08-02 01:00:18 -04:00
|
|
|
return []rune(trimmed), offsets
|
2015-03-18 12:59:14 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// When color is disabled but ansi option is given,
|
|
|
|
// we simply strip out ANSI codes from the input
|
2015-08-02 01:25:57 -04:00
|
|
|
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
|
|
|
|
trimmed, _, _ := extractColor(string(data), nil)
|
2015-08-02 01:00:18 -04:00
|
|
|
return []rune(trimmed), nil
|
2015-03-18 12:59:14 -04:00
|
|
|
}
|
|
|
|
}
|
2015-08-02 01:25:57 -04:00
|
|
|
ansiProcessorRunes = func(data []rune) ([]rune, []ansiOffset) {
|
|
|
|
return ansiProcessor([]byte(string(data)))
|
|
|
|
}
|
2015-03-18 12:59:14 -04:00
|
|
|
}
|
|
|
|
|
2015-01-01 14:49:30 -05:00
|
|
|
// Chunk list
|
|
|
|
var chunkList *ChunkList
|
2015-07-21 14:21:20 -04:00
|
|
|
header := make([]string, 0, opts.HeaderLines)
|
2015-01-01 14:49:30 -05:00
|
|
|
if len(opts.WithNth) == 0 {
|
2015-08-02 01:25:57 -04:00
|
|
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
2015-07-21 14:21:20 -04:00
|
|
|
if len(header) < opts.HeaderLines {
|
2015-08-02 01:00:18 -04:00
|
|
|
header = append(header, string(data))
|
2015-07-21 14:21:20 -04:00
|
|
|
eventBox.Set(EvtHeader, header)
|
|
|
|
return nil
|
|
|
|
}
|
2015-08-02 01:25:57 -04:00
|
|
|
runes, colors := ansiProcessor(data)
|
2015-01-11 09:49:12 -05:00
|
|
|
return &Item{
|
2015-08-02 01:25:57 -04:00
|
|
|
text: runes,
|
2015-03-18 12:59:14 -04:00
|
|
|
index: uint32(index),
|
|
|
|
colors: colors,
|
|
|
|
rank: Rank{0, 0, uint32(index)}}
|
2015-01-01 14:49:30 -05:00
|
|
|
})
|
|
|
|
} else {
|
2015-08-02 01:25:57 -04:00
|
|
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
|
|
|
runes := util.BytesToRunes(data)
|
|
|
|
tokens := Tokenize(runes, opts.Delimiter)
|
2015-07-22 08:24:02 -04:00
|
|
|
trans := Transform(tokens, opts.WithNth)
|
2015-07-21 14:21:20 -04:00
|
|
|
if len(header) < opts.HeaderLines {
|
2015-08-02 01:00:18 -04:00
|
|
|
header = append(header, string(joinTokens(trans)))
|
2015-07-21 14:21:20 -04:00
|
|
|
eventBox.Set(EvtHeader, header)
|
|
|
|
return nil
|
|
|
|
}
|
2015-01-08 12:37:08 -05:00
|
|
|
item := Item{
|
2015-04-17 09:23:52 -04:00
|
|
|
text: joinTokens(trans),
|
2015-08-02 01:25:57 -04:00
|
|
|
origText: &runes,
|
2015-01-11 09:49:12 -05:00
|
|
|
index: uint32(index),
|
2015-03-18 12:59:14 -04:00
|
|
|
colors: nil,
|
2015-01-08 12:37:08 -05:00
|
|
|
rank: Rank{0, 0, uint32(index)}}
|
2015-03-18 12:59:14 -04:00
|
|
|
|
2015-08-02 01:25:57 -04:00
|
|
|
trimmed, colors := ansiProcessorRunes(item.text)
|
2015-03-18 12:59:14 -04:00
|
|
|
item.text = trimmed
|
|
|
|
item.colors = colors
|
2015-01-01 14:49:30 -05:00
|
|
|
return &item
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reader
|
2015-03-31 09:05:02 -04:00
|
|
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
2015-02-28 21:16:38 -05:00
|
|
|
if !streamingFilter {
|
2015-08-02 01:25:57 -04:00
|
|
|
reader := Reader{func(data []byte) bool {
|
2015-08-02 01:00:18 -04:00
|
|
|
return chunkList.Push(data)
|
2015-07-21 14:21:20 -04:00
|
|
|
}, eventBox, opts.ReadZero}
|
2015-02-28 21:16:38 -05:00
|
|
|
go reader.ReadSource()
|
|
|
|
}
|
2015-01-01 14:49:30 -05:00
|
|
|
|
|
|
|
// Matcher
|
|
|
|
patternBuilder := func(runes []rune) *Pattern {
|
|
|
|
return BuildPattern(
|
|
|
|
opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes)
|
|
|
|
}
|
2015-03-31 09:05:02 -04:00
|
|
|
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
2015-01-01 14:49:30 -05:00
|
|
|
|
2015-02-17 10:08:17 -05:00
|
|
|
// Filtering mode
|
|
|
|
if opts.Filter != nil {
|
|
|
|
if opts.PrintQuery {
|
|
|
|
fmt.Println(*opts.Filter)
|
2015-01-01 14:49:30 -05:00
|
|
|
}
|
2015-02-28 21:16:38 -05:00
|
|
|
|
|
|
|
pattern := patternBuilder([]rune(*opts.Filter))
|
|
|
|
|
|
|
|
if streamingFilter {
|
|
|
|
reader := Reader{
|
2015-08-02 01:25:57 -04:00
|
|
|
func(runes []byte) bool {
|
2015-08-02 01:00:18 -04:00
|
|
|
item := chunkList.trans(runes, 0)
|
2015-07-21 14:21:20 -04:00
|
|
|
if item != nil && pattern.MatchItem(item) {
|
2015-08-02 01:00:18 -04:00
|
|
|
fmt.Println(string(item.text))
|
2015-02-28 21:16:38 -05:00
|
|
|
}
|
2015-07-21 14:21:20 -04:00
|
|
|
return false
|
2015-06-08 02:36:21 -04:00
|
|
|
}, eventBox, opts.ReadZero}
|
2015-02-28 21:16:38 -05:00
|
|
|
reader.ReadSource()
|
|
|
|
} else {
|
|
|
|
eventBox.Unwatch(EvtReadNew)
|
|
|
|
eventBox.WaitFor(EvtReadFin)
|
|
|
|
|
|
|
|
snapshot, _ := chunkList.Snapshot()
|
|
|
|
merger, _ := matcher.scan(MatchRequest{
|
|
|
|
chunks: snapshot,
|
|
|
|
pattern: pattern})
|
|
|
|
for i := 0; i < merger.Length(); i++ {
|
|
|
|
fmt.Println(merger.Get(i).AsString())
|
|
|
|
}
|
2015-02-17 10:08:17 -05:00
|
|
|
}
|
|
|
|
os.Exit(0)
|
2015-01-01 14:49:30 -05:00
|
|
|
}
|
|
|
|
|
2015-02-12 22:25:19 -05:00
|
|
|
// Synchronous search
|
|
|
|
if opts.Sync {
|
|
|
|
eventBox.Unwatch(EvtReadNew)
|
|
|
|
eventBox.WaitFor(EvtReadFin)
|
|
|
|
}
|
|
|
|
|
2015-01-01 14:49:30 -05:00
|
|
|
// Go interactive
|
|
|
|
go matcher.Loop()
|
|
|
|
|
|
|
|
// Terminal I/O
|
|
|
|
terminal := NewTerminal(opts, eventBox)
|
2015-02-17 10:08:17 -05:00
|
|
|
deferred := opts.Select1 || opts.Exit0
|
2015-01-01 14:49:30 -05:00
|
|
|
go terminal.Loop()
|
2015-02-17 10:08:17 -05:00
|
|
|
if !deferred {
|
|
|
|
terminal.startChan <- true
|
|
|
|
}
|
2015-01-01 14:49:30 -05:00
|
|
|
|
|
|
|
// Event coordination
|
|
|
|
reading := true
|
|
|
|
ticks := 0
|
2015-01-11 13:01:24 -05:00
|
|
|
eventBox.Watch(EvtReadNew)
|
2015-01-01 14:49:30 -05:00
|
|
|
for {
|
|
|
|
delay := true
|
2015-01-11 13:01:24 -05:00
|
|
|
ticks++
|
2015-01-11 22:56:17 -05:00
|
|
|
eventBox.Wait(func(events *util.Events) {
|
2015-01-01 14:49:30 -05:00
|
|
|
defer events.Clear()
|
|
|
|
for evt, value := range *events {
|
|
|
|
switch evt {
|
|
|
|
|
2015-01-11 13:01:24 -05:00
|
|
|
case EvtReadNew, EvtReadFin:
|
|
|
|
reading = reading && evt == EvtReadNew
|
2015-01-03 15:01:13 -05:00
|
|
|
snapshot, count := chunkList.Snapshot()
|
|
|
|
terminal.UpdateCount(count, !reading)
|
2015-03-31 09:05:02 -04:00
|
|
|
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
2015-01-01 14:49:30 -05:00
|
|
|
|
2015-01-11 13:01:24 -05:00
|
|
|
case EvtSearchNew:
|
2015-04-16 01:44:41 -04:00
|
|
|
switch val := value.(type) {
|
|
|
|
case bool:
|
|
|
|
sort = val
|
2015-03-31 09:05:02 -04:00
|
|
|
}
|
2015-01-03 15:01:13 -05:00
|
|
|
snapshot, _ := chunkList.Snapshot()
|
2015-03-31 09:05:02 -04:00
|
|
|
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
|
2015-01-01 14:49:30 -05:00
|
|
|
delay = false
|
|
|
|
|
2015-01-11 13:01:24 -05:00
|
|
|
case EvtSearchProgress:
|
2015-01-01 14:49:30 -05:00
|
|
|
switch val := value.(type) {
|
|
|
|
case float32:
|
|
|
|
terminal.UpdateProgress(val)
|
|
|
|
}
|
|
|
|
|
2015-07-21 14:21:20 -04:00
|
|
|
case EvtHeader:
|
|
|
|
terminal.UpdateHeader(value.([]string), opts.HeaderLines)
|
|
|
|
|
2015-01-11 13:01:24 -05:00
|
|
|
case EvtSearchFin:
|
2015-01-01 14:49:30 -05:00
|
|
|
switch val := value.(type) {
|
2015-01-09 11:06:08 -05:00
|
|
|
case *Merger:
|
2015-02-17 10:08:17 -05:00
|
|
|
if deferred {
|
|
|
|
count := val.Length()
|
|
|
|
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
|
|
|
deferred = false
|
|
|
|
terminal.startChan <- true
|
2015-02-17 10:51:44 -05:00
|
|
|
} else if val.final {
|
2015-02-17 10:08:17 -05:00
|
|
|
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
|
|
|
if opts.PrintQuery {
|
|
|
|
fmt.Println(opts.Query)
|
|
|
|
}
|
2015-03-31 07:52:16 -04:00
|
|
|
if len(opts.Expect) > 0 {
|
|
|
|
fmt.Println()
|
|
|
|
}
|
2015-02-17 10:08:17 -05:00
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
fmt.Println(val.Get(i).AsString())
|
|
|
|
}
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
deferred = false
|
|
|
|
terminal.startChan <- true
|
|
|
|
}
|
|
|
|
}
|
2015-01-01 14:49:30 -05:00
|
|
|
terminal.UpdateList(val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2015-01-08 08:07:04 -05:00
|
|
|
if delay && reading {
|
2015-01-11 22:56:17 -05:00
|
|
|
dur := util.DurWithin(
|
2015-01-11 13:01:24 -05:00
|
|
|
time.Duration(ticks)*coordinatorDelayStep,
|
|
|
|
0, coordinatorDelayMax)
|
2015-01-08 08:07:04 -05:00
|
|
|
time.Sleep(dur)
|
2015-01-01 14:49:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|