diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2024-05-07 01:06:42 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-07 01:06:42 +0900 |
commit | e8405f40fe2eb3675f1cb4f69e825eff5f13f269 (patch) | |
tree | c917367f1f0098939f9cdf7376a2a135907024fc /src | |
parent | 065b9e6fb2ce3e6e50ff423c3786989afa04ee14 (diff) |
Refactor the code so that fzf can be used as a library (#3769)
Diffstat (limited to 'src')
-rw-r--r-- | src/actiontype_string.go | 167 | ||||
-rw-r--r-- | src/algo/algo.go | 2 | ||||
-rw-r--r-- | src/algo/normalize.go | 2 | ||||
-rw-r--r-- | src/ansi.go | 4 | ||||
-rw-r--r-- | src/cache.go | 10 | ||||
-rw-r--r-- | src/constants.go | 10 | ||||
-rw-r--r-- | src/core.go | 68 | ||||
-rw-r--r-- | src/matcher.go | 20 | ||||
-rw-r--r-- | src/options.go | 965 | ||||
-rw-r--r-- | src/options_no_pprof.go | 4 | ||||
-rw-r--r-- | src/options_test.go | 54 | ||||
-rw-r--r-- | src/pattern.go | 34 | ||||
-rw-r--r-- | src/pattern_test.go | 44 | ||||
-rw-r--r-- | src/protector/protector.go | 4 | ||||
-rw-r--r-- | src/reader.go | 19 | ||||
-rw-r--r-- | src/server.go | 27 | ||||
-rw-r--r-- | src/terminal.go | 210 | ||||
-rw-r--r-- | src/tokenizer_test.go | 9 | ||||
-rw-r--r-- | src/tui/dummy.go | 4 | ||||
-rw-r--r-- | src/tui/eventtype_string.go | 51 | ||||
-rw-r--r-- | src/tui/light.go | 49 | ||||
-rw-r--r-- | src/tui/light_unix.go | 18 | ||||
-rw-r--r-- | src/tui/light_windows.go | 6 | ||||
-rw-r--r-- | src/tui/tcell.go | 18 | ||||
-rw-r--r-- | src/tui/tui.go | 10 | ||||
-rw-r--r-- | src/util/atexit.go | 12 | ||||
-rw-r--r-- | src/util/util_unix.go | 2 | ||||
-rw-r--r-- | src/util/util_windows.go | 6 |
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 |