summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2024-05-07 01:06:42 +0900
committerGitHub <noreply@github.com>2024-05-07 01:06:42 +0900
commite8405f40fe2eb3675f1cb4f69e825eff5f13f269 (patch)
treec917367f1f0098939f9cdf7376a2a135907024fc /src
parent065b9e6fb2ce3e6e50ff423c3786989afa04ee14 (diff)
Refactor the code so that fzf can be used as a library (#3769)
Diffstat (limited to 'src')
-rw-r--r--src/actiontype_string.go167
-rw-r--r--src/algo/algo.go2
-rw-r--r--src/algo/normalize.go2
-rw-r--r--src/ansi.go4
-rw-r--r--src/cache.go10
-rw-r--r--src/constants.go10
-rw-r--r--src/core.go68
-rw-r--r--src/matcher.go20
-rw-r--r--src/options.go965
-rw-r--r--src/options_no_pprof.go4
-rw-r--r--src/options_test.go54
-rw-r--r--src/pattern.go34
-rw-r--r--src/pattern_test.go44
-rw-r--r--src/protector/protector.go4
-rw-r--r--src/reader.go19
-rw-r--r--src/server.go27
-rw-r--r--src/terminal.go210
-rw-r--r--src/tokenizer_test.go9
-rw-r--r--src/tui/dummy.go4
-rw-r--r--src/tui/eventtype_string.go51
-rw-r--r--src/tui/light.go49
-rw-r--r--src/tui/light_unix.go18
-rw-r--r--src/tui/light_windows.go6
-rw-r--r--src/tui/tcell.go18
-rw-r--r--src/tui/tui.go10
-rw-r--r--src/util/atexit.go12
-rw-r--r--src/util/util_unix.go2
-rw-r--r--src/util/util_windows.go6
28 files changed, 1118 insertions, 711 deletions
diff --git a/src/actiontype_string.go b/src/actiontype_string.go
index a9d931d7..6b07134b 100644
--- a/src/actiontype_string.go
+++ b/src/actiontype_string.go
@@ -37,92 +37,93 @@ func _() {
_ = x[actDeleteChar-26]
_ = x[actDeleteCharEof-27]
_ = x[actEndOfLine-28]
- _ = x[actForwardChar-29]
- _ = x[actForwardWord-30]
- _ = x[actKillLine-31]
- _ = x[actKillWord-32]
- _ = x[actUnixLineDiscard-33]
- _ = x[actUnixWordRubout-34]
- _ = x[actYank-35]
- _ = x[actBackwardKillWord-36]
- _ = x[actSelectAll-37]
- _ = x[actDeselectAll-38]
- _ = x[actToggle-39]
- _ = x[actToggleSearch-40]
- _ = x[actToggleAll-41]
- _ = x[actToggleDown-42]
- _ = x[actToggleUp-43]
- _ = x[actToggleIn-44]
- _ = x[actToggleOut-45]
- _ = x[actToggleTrack-46]
- _ = x[actToggleTrackCurrent-47]
- _ = x[actToggleHeader-48]
- _ = x[actTrackCurrent-49]
- _ = x[actUntrackCurrent-50]
- _ = x[actDown-51]
- _ = x[actUp-52]
- _ = x[actPageUp-53]
- _ = x[actPageDown-54]
- _ = x[actPosition-55]
- _ = x[actHalfPageUp-56]
- _ = x[actHalfPageDown-57]
- _ = x[actOffsetUp-58]
- _ = x[actOffsetDown-59]
- _ = x[actJump-60]
- _ = x[actJumpAccept-61]
- _ = x[actPrintQuery-62]
- _ = x[actRefreshPreview-63]
- _ = x[actReplaceQuery-64]
- _ = x[actToggleSort-65]
- _ = x[actShowPreview-66]
- _ = x[actHidePreview-67]
- _ = x[actTogglePreview-68]
- _ = x[actTogglePreviewWrap-69]
- _ = x[actTransform-70]
- _ = x[actTransformBorderLabel-71]
- _ = x[actTransformHeader-72]
- _ = x[actTransformPreviewLabel-73]
- _ = x[actTransformPrompt-74]
- _ = x[actTransformQuery-75]
- _ = x[actPreview-76]
- _ = x[actChangePreview-77]
- _ = x[actChangePreviewWindow-78]
- _ = x[actPreviewTop-79]
- _ = x[actPreviewBottom-80]
- _ = x[actPreviewUp-81]
- _ = x[actPreviewDown-82]
- _ = x[actPreviewPageUp-83]
- _ = x[actPreviewPageDown-84]
- _ = x[actPreviewHalfPageUp-85]
- _ = x[actPreviewHalfPageDown-86]
- _ = x[actPrevHistory-87]
- _ = x[actPrevSelected-88]
- _ = x[actPut-89]
- _ = x[actNextHistory-90]
- _ = x[actNextSelected-91]
- _ = x[actExecute-92]
- _ = x[actExecuteSilent-93]
- _ = x[actExecuteMulti-94]
- _ = x[actSigStop-95]
- _ = x[actFirst-96]
- _ = x[actLast-97]
- _ = x[actReload-98]
- _ = x[actReloadSync-99]
- _ = x[actDisableSearch-100]
- _ = x[actEnableSearch-101]
- _ = x[actSelect-102]
- _ = x[actDeselect-103]
- _ = x[actUnbind-104]
- _ = x[actRebind-105]
- _ = x[actBecome-106]
- _ = x[actResponse-107]
- _ = x[actShowHeader-108]
- _ = x[actHideHeader-109]
+ _ = x[actFatal-29]
+ _ = x[actForwardChar-30]
+ _ = x[actForwardWord-31]
+ _ = x[actKillLine-32]
+ _ = x[actKillWord-33]
+ _ = x[actUnixLineDiscard-34]
+ _ = x[actUnixWordRubout-35]
+ _ = x[actYank-36]
+ _ = x[actBackwardKillWord-37]
+ _ = x[actSelectAll-38]
+ _ = x[actDeselectAll-39]
+ _ = x[actToggle-40]
+ _ = x[actToggleSearch-41]
+ _ = x[actToggleAll-42]
+ _ = x[actToggleDown-43]
+ _ = x[actToggleUp-44]
+ _ = x[actToggleIn-45]
+ _ = x[actToggleOut-46]
+ _ = x[actToggleTrack-47]
+ _ = x[actToggleTrackCurrent-48]
+ _ = x[actToggleHeader-49]
+ _ = x[actTrackCurrent-50]
+ _ = x[actUntrackCurrent-51]
+ _ = x[actDown-52]
+ _ = x[actUp-53]
+ _ = x[actPageUp-54]
+ _ = x[actPageDown-55]
+ _ = x[actPosition-56]
+ _ = x[actHalfPageUp-57]
+ _ = x[actHalfPageDown-58]
+ _ = x[actOffsetUp-59]
+ _ = x[actOffsetDown-60]
+ _ = x[actJump-61]
+ _ = x[actJumpAccept-62]
+ _ = x[actPrintQuery-63]
+ _ = x[actRefreshPreview-64]
+ _ = x[actReplaceQuery-65]
+ _ = x[actToggleSort-66]
+ _ = x[actShowPreview-67]
+ _ = x[actHidePreview-68]
+ _ = x[actTogglePreview-69]
+ _ = x[actTogglePreviewWrap-70]
+ _ = x[actTransform-71]
+ _ = x[actTransformBorderLabel-72]
+ _ = x[actTransformHeader-73]
+ _ = x[actTransformPreviewLabel-74]
+ _ = x[actTransformPrompt-75]
+ _ = x[actTransformQuery-76]
+ _ = x[actPreview-77]
+ _ = x[actChangePreview-78]
+ _ = x[actChangePreviewWindow-79]
+ _ = x[actPreviewTop-80]
+ _ = x[actPreviewBottom-81]
+ _ = x[actPreviewUp-82]
+ _ = x[actPreviewDown-83]
+ _ = x[actPreviewPageUp-84]
+ _ = x[actPreviewPageDown-85]
+ _ = x[actPreviewHalfPageUp-86]
+ _ = x[actPreviewHalfPageDown-87]
+ _ = x[actPrevHistory-88]
+ _ = x[actPrevSelected-89]
+ _ = x[actPut-90]
+ _ = x[actNextHistory-91]
+ _ = x[actNextSelected-92]
+ _ = x[actExecute-93]
+ _ = x[actExecuteSilent-94]
+ _ = x[actExecuteMulti-95]
+ _ = x[actSigStop-96]
+ _ = x[actFirst-97]
+ _ = x[actLast-98]
+ _ = x[actReload-99]
+ _ = x[actReloadSync-100]
+ _ = x[actDisableSearch-101]
+ _ = x[actEnableSearch-102]
+ _ = x[actSelect-103]
+ _ = x[actDeselect-104]
+ _ = x[actUnbind-105]
+ _ = x[actRebind-106]
+ _ = x[actBecome-107]
+ _ = x[actResponse-108]
+ _ = x[actShowHeader-109]
+ _ = x[actHideHeader-110]
}
-const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
+const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
-var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 413, 427, 438, 449, 467, 484, 491, 510, 522, 536, 545, 560, 572, 585, 596, 607, 619, 633, 654, 669, 684, 701, 708, 713, 722, 733, 744, 757, 772, 783, 796, 803, 816, 829, 846, 861, 874, 888, 902, 918, 938, 950, 973, 991, 1015, 1033, 1050, 1060, 1076, 1098, 1111, 1127, 1139, 1153, 1169, 1187, 1207, 1229, 1243, 1258, 1264, 1278, 1293, 1303, 1319, 1334, 1344, 1352, 1359, 1368, 1381, 1397, 1412, 1421, 1432, 1441, 1450, 1459, 1470, 1483, 1496}
+var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 811, 824, 837, 854, 869, 882, 896, 910, 926, 946, 958, 981, 999, 1023, 1041, 1058, 1068, 1084, 1106, 1119, 1135, 1147, 1161, 1177, 1195, 1215, 1237, 1251, 1266, 1272, 1286, 1301, 1311, 1327, 1342, 1352, 1360, 1367, 1376, 1389, 1405, 1420, 1429, 1440, 1449, 1458, 1467, 1478, 1491, 1504}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {
diff --git a/src/algo/algo.go b/src/algo/algo.go
index 3e689746..c85ec82e 100644
--- a/src/algo/algo.go
+++ b/src/algo/algo.go
@@ -152,7 +152,7 @@ var (
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
bonusBoundaryDelimiter int16 = bonusBoundary + 1
- initialCharClass charClass = charWhite
+ initialCharClass = charWhite
// A minor optimization that can give 15%+ performance boost
asciiCharClasses [unicode.MaxASCII + 1]charClass
diff --git a/src/algo/normalize.go b/src/algo/normalize.go
index 93247908..25e92983 100644
--- a/src/algo/normalize.go
+++ b/src/algo/normalize.go
@@ -3,7 +3,7 @@
package algo
-var normalized map[rune]rune = map[rune]rune{
+var normalized = map[rune]rune{
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
diff --git a/src/ansi.go b/src/ansi.go
index 53fae954..dbe4fd66 100644
--- a/src/ansi.go
+++ b/src/ansi.go
@@ -292,7 +292,7 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
var remaining string
- i := -1
+ var i int
if delimiter == 0 {
// Faster than strings.IndexAny(";:")
i = strings.IndexByte(s, ';')
@@ -350,7 +350,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state256 := 0
ptr := &state.fg
- var delimiter byte = 0
+ var delimiter byte
count := 0
for len(ansiCode) != 0 {
var num int
diff --git a/src/cache.go b/src/cache.go
index df1a6ab7..39d4250d 100644
--- a/src/cache.go
+++ b/src/cache.go
@@ -12,8 +12,14 @@ type ChunkCache struct {
}
// NewChunkCache returns a new ChunkCache
-func NewChunkCache() ChunkCache {
- return ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
+func NewChunkCache() *ChunkCache {
+ return &ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
+}
+
+func (cc *ChunkCache) Clear() {
+ cc.mutex.Lock()
+ cc.cache = make(map[*Chunk]*queryCache)
+ cc.mutex.Unlock()
}
// Add adds the list to the cache
diff --git a/src/constants.go b/src/constants.go
index faf6a0e5..dd2e870e 100644
--- a/src/constants.go
+++ b/src/constants.go
@@ -67,9 +67,9 @@ const (
)
const (
- exitCancel = -1
- exitOk = 0
- exitNoMatch = 1
- exitError = 2
- exitInterrupt = 130
+ ExitCancel = -1
+ ExitOk = 0
+ ExitNoMatch = 1
+ ExitError = 2
+ ExitInterrupt = 130
)
diff --git a/src/core.go b/src/core.go
index 14aa781f..dbae6a69 100644
--- a/src/core.go
+++ b/src/core.go
@@ -2,7 +2,6 @@
package fzf
import (
- "fmt"
"sync"
"time"
"unsafe"
@@ -27,22 +26,29 @@ func sbytes(data string) []byte {
return unsafe.Slice(unsafe.StringData(data), len(data))
}
+type quitSignal struct {
+ code int
+ err error
+}
+
// Run starts fzf
-func Run(opts *Options, version string, revision string) {
- defer util.RunAtExitFuncs()
+func Run(opts *Options) (int, error) {
+ if err := postProcessOptions(opts); err != nil {
+ return ExitError, err
+ }
- sort := opts.Sort > 0
- sortCriteria = opts.Criteria
+ defer util.RunAtExitFuncs()
- if opts.Version {
- if len(revision) > 0 {
- fmt.Printf("%s (%s)\n", version, revision)
- } else {
- fmt.Println(version)
+ // 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()
@@ -131,7 +137,7 @@ func Run(opts *Options, version string, revision string) {
reader = NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
- go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
+ go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
}
// Matcher
@@ -147,14 +153,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 {
@@ -182,7 +190,7 @@ func Run(opts *Options, version string, revision string) {
}
return false
}, eventBox, executor, opts.ReadZero, false)
- reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
+ reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
@@ -197,9 +205,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
@@ -210,9 +218,13 @@ func Run(opts *Options, version string, revision string) {
// Go interactive
go matcher.Loop()
+ defer matcher.Stop()
// Terminal I/O
- terminal := NewTerminal(opts, eventBox, executor)
+ 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
@@ -258,6 +270,9 @@ 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++
@@ -278,7 +293,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)
@@ -378,10 +397,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)
}
@@ -392,6 +412,9 @@ func Run(opts *Options, version string, revision string) {
}
events.Clear()
})
+ if stop {
+ break
+ }
if delay && reading {
dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep,
@@ -399,4 +422,5 @@ func Run(opts *Options, version string, revision string) {
time.Sleep(dur)
}
}
+ return exitCode, err
}
diff --git a/src/matcher.go b/src/matcher.go
index b9288bb6..26426e4f 100644
--- a/src/matcher.go
+++ b/src/matcher.go
@@ -21,6 +21,7 @@ type MatchRequest struct {
// Matcher is responsible for performing search
type Matcher struct {
+ cache *ChunkCache
patternBuilder func([]rune) *Pattern
sort bool
tac bool
@@ -38,10 +39,11 @@ const (
)
// NewMatcher returns a new Matcher
-func NewMatcher(patternBuilder func([]rune) *Pattern,
+func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
return &Matcher{
+ cache: cache,
patternBuilder: patternBuilder,
sort: sort,
tac: tac,
@@ -60,8 +62,13 @@ func (m *Matcher) Loop() {
for {
var request MatchRequest
+ stop := false
m.reqBox.Wait(func(events *util.Events) {
- for _, val := range *events {
+ for t, val := range *events {
+ if t == reqQuit {
+ stop = true
+ return
+ }
switch val := val.(type) {
case MatchRequest:
request = val
@@ -71,12 +78,15 @@ func (m *Matcher) Loop() {
}
events.Clear()
})
+ if stop {
+ break
+ }
if request.sort != m.sort || request.revision != m.revision {
m.sort = request.sort
m.revision = request.revision
m.mergerCache = make(map[string]*Merger)
- clearChunkCache()
+ m.cache.Clear()
}
// Restart search
@@ -236,3 +246,7 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
}
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
}
+
+func (m *Matcher) Stop() {
+ m.reqBox.Set(reqQuit, nil)
+}
diff --git a/src/options.go b/src/options.go
index 5098318d..9abecc3b 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1,6 +1,7 @@
package fzf
import (
+ "errors"
"fmt"
"os"
"regexp"
@@ -10,13 +11,12 @@ import (
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
- "github.com/junegunn/fzf/src/util"
"github.com/mattn/go-shellwords"
"github.com/rivo/uniseg"
)
-const usage = `usage: fzf [options]
+const Usage = `usage: fzf [options]
Search
-x, --extended Extended-search mode
@@ -247,9 +247,10 @@ func (o *previewOpts) Toggle() {
o.hidden = !o.hidden
}
-func parseLabelPosition(opts *labelOpts, arg string) {
+func parseLabelPosition(opts *labelOpts, arg string) error {
opts.column = 0
opts.bottom = false
+ var err error
for _, token := range splitRegexp.Split(strings.ToLower(arg), -1) {
switch token {
case "center":
@@ -259,9 +260,10 @@ func parseLabelPosition(opts *labelOpts, arg string) {
case "top":
opts.bottom = false
default:
- opts.column = atoi(token)
+ opts.column, err = atoi(token)
}
}
+ return err
}
func (a previewOpts) aboveOrBelow() bool {
@@ -291,6 +293,8 @@ type walkerOpts struct {
// Options stores the values of command-line options
type Options struct {
+ Input chan string
+ Output chan string
Bash bool
Zsh bool
Fish bool
@@ -365,6 +369,7 @@ type Options struct {
WalkerRoot string
WalkerSkip []string
Version bool
+ Help bool
CPUProfile string
MEMProfile string
BlockProfile string
@@ -455,21 +460,10 @@ func defaultOptions() *Options {
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
WalkerRoot: ".",
WalkerSkip: []string{".git", "node_modules"},
+ Help: false,
Version: false}
}
-func help(code int) {
- os.Stdout.WriteString(usage)
- util.Exit(code)
-}
-
-var errorContext = ""
-
-func errorExit(msg string) {
- os.Stderr.WriteString(errorContext + msg + "\n")
- util.Exit(exitError)
-}
-
func optString(arg string, prefixes ...string) (bool, string) {
for _, prefix := range prefixes {
if strings.HasPrefix(arg, prefix) {
@@ -479,13 +473,13 @@ func optString(arg string, prefixes ...string) (bool, string) {
return false, ""
}
-func nextString(args []string, i *int, message string) string {
+func nextString(args []string, i *int, message string) (string, error) {
if len(args) > *i+1 {
*i++
} else {
- errorExit(message)
+ return "", errors.New(message)
}
- return args[*i]
+ return args[*i], nil
}
func optionalNextString(args []string, i *int) (bool, string) {
@@ -496,44 +490,52 @@ func optionalNextString(args []string, i *int) (bool, string) {
return false, ""
}
-func atoi(str string) int {
+func atoi(str string) (int, error) {
num, err := strconv.Atoi(str)
if err != nil {
- errorExit("not a valid integer: " + str)
+ return 0, errors.New("not a valid integer: " + str)
}
- return num
+ return num, nil
}
-func atof(str string) float64 {
+func atof(str string) (float64, error) {
num, err := strconv.ParseFloat(str, 64)
if err != nil {
- errorExit("not a valid number: " + str)
+ return 0, errors.New("not a valid number: " + str)
}
- return num
+ return num, nil
}
-func nextInt(args []string, i *int, message string) int {
+func nextInt(args []string, i *int, message string) (int, error) {
if len(args) > *i+1 {
*i++
} else {
- errorExit(message)
+ return 0, errors.New(message)
+ }
+ n, err := atoi(args[*i])
+ if err != nil {
+ return 0, errors.New(message)
}
- return atoi(args[*i])
+ return n, nil
}
-func optionalNumeric(args []string, i *int, defaultValue int) int {
+func optionalNumeric(args []string, i *int, defaultValue int) (int, error) {
if len(args) > *i+1 {
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
*i++
- return atoi(args[*i])
+ n, err := atoi(args[*i])
+ if err != nil {
+ return 0, err
+ }
+ return n, nil
}
}
- return defaultValue
+ return defaultValue, nil
}
-func splitNth(str string) []Range {
+func splitNth(str string) ([]Range, error) {
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); !match {
- errorExit("invalid format: " + str)
+ return nil, errors.New("invalid format: " + str)
}
tokens := strings.Split(str, ",")
@@ -541,11 +543,11 @@ func splitNth(str string) []Range {
for idx, s := range tokens {
r, ok := ParseRange(&s)
if !ok {
- errorExit("invalid format: " + str)
+ return nil, errors.New("invalid format: " + str)
}
ranges[idx] = r
}
- return ranges
+ return ranges, nil
}
func delimiterRegexp(str string) Delimiter {
@@ -575,72 +577,68 @@ func isNumeric(char uint8) bool {
return char >= '0' && char <= '9'
}
-func parseAlgo(str string) algo.Algo {
+func parseAlgo(str string) (algo.Algo, error) {
switch str {
case "v1":
- return algo.FuzzyMatchV1
+ return algo.FuzzyMatchV1, nil
case "v2":
- return algo.FuzzyMatchV2
- default:
- errorExit("invalid algorithm (expected: v1 or v2)")
+ return algo.FuzzyMatchV2, nil
}
- return algo.FuzzyMatchV2
+ return nil, errors.New("invalid algorithm (expected: v1 or v2)")
}
-func processScheme(opts *Options) {
+func processScheme(opts *Options) error {
if !algo.Init(opts.Scheme) {
- errorExit("invalid scoring scheme (expected: default|path|history)")
+ return errors.New("invalid scoring scheme (expected: default|path|history)")
}
if opts.Scheme == "history" {
opts.Criteria = []criterion{byScore}
}
+ return nil
}
-func parseBorder(str string, optional bool) tui.BorderShape {
+func parseBorder(str string, optional bool) (tui.BorderShape, error) {
switch str {
case "rounded":
- return tui.BorderRounded
+ return tui.BorderRounded, nil
case "sharp":
- return tui.BorderSharp
+ return tui.BorderSharp, nil
case "bold":
- return tui.BorderBold
+ return tui.BorderBold, nil
case "block":
- return tui.BorderBlock
+ return tui.BorderBlock, nil
case "thinblock":
- return tui.BorderThinBlock
+ return tui.BorderThinBlock, nil
case "double":
- return tui.BorderDouble
+ return tui.BorderDouble, nil
case "horizontal":
- return tui.BorderHorizontal
+ return tui.BorderHorizontal, nil
case "vertical":
- return tui.BorderVertical
+ return tui.BorderVertical, nil
case "top":
- return tui.BorderTop
+ return tui.BorderTop, nil
case "bottom":
- return tui.BorderBottom
+ return tui.BorderBottom, nil
case "left":
- return tui.BorderLeft
+ return tui.BorderLeft, nil
case "right":
- return tui.BorderRight
+ return tui.BorderRight, nil
case "none":
- return tui.BorderNone
- default:
- if optional && str == "" {
- return tui.DefaultBorderShape
- }
- errorExit("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
+ return tui.BorderNone, nil
+ }
+ if optional && str == "" {
+ return tui.DefaultBorderShape, nil
}
- return tui.BorderNone
+ return tui.BorderNone, errors.New("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
}
-func parseKeyChords(str string, message string) map[tui.Event]string {
- return parseKeyChordsImpl(str, message, errorExit)
+func parseKeyChords(str string, message string) (map[tui.Event]string, error) {
+ return parseKeyChordsImpl(str, message)
}
-func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.Event]string {
+func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error) {
if len(str) == 0 {
- exit(message)
- return nil
+ return nil, errors.New(message)
}
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
@@ -810,54 +808,64 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
} else if len(runes) == 1 {
chords[tui.Key(runes[0])] = key
} else {
- exit("unsupported key: " + key)
- return nil
+ return nil, errors.New("unsupported key: " + key)
}
}
}
- return chords
+ return chords, nil
}
-func parseTiebreak(str string) []criterion {
+func parseTiebreak(str string) ([]criterion, error) {
criteria := []criterion{byScore}
hasIndex := false
hasChunk := false
hasLength := false
hasBegin := false
hasEnd := false
- check := func(notExpected *bool, name string) {
+ check := func(notExpected *bool, name string) error {
if *notExpected {
- errorExit("duplicate sort criteria: " + name)
+ return errors.New("duplicate sort criteria: " + name)
}
if hasIndex {
- errorExit("index should be the last criterion")
+ return errors.New("index should be the last criterion")
}
*notExpected = true
+ return nil
}
for _, str := range strings.Split(strings.ToLower(str), ",") {
switch str {
case "index":
- check(&hasIndex, "index")
+ if err := check(&hasIndex, "index"); err != nil {
+ return nil, err
+ }
case "chunk":
- check(&hasChunk, "chunk")
+ if err := check(&hasChunk, "chunk"); err != nil {
+ return nil, err
+ }
criteria = append(criteria, byChunk)
case "length":
- check(&hasLength, "length")
+ if err := check(&hasLength, "length"); err != nil {
+ return nil, err
+ }
criteria = append(criteria, byLength)
case "begin":
- check(&hasBegin, "begin")
+ if err := check(&hasBegin, "begin"); err != nil {
+ return nil, err
+ }
criteria = append(criteria, byBegin)
case "end":
- check(&hasEnd, "end")
+ if err := check(&hasEnd, "end"); err != nil {
+ return nil, err
+ }
criteria = append(criteria, byEnd)
default:
- errorExit("invalid sort criterion: " + str)
+ return nil, errors.New("invalid sort criterion: " + str)
}
}
if len(criteria) > 4 {
- errorExit("at most 3 tiebreaks are allowed: " + str)
+ return nil, errors.New("at most 3 tiebreaks are allowed: " + str)
}
- return cri