summaryrefslogtreecommitdiffstats
path: root/src/options.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/options.go')
-rw-r--r--src/options.go1015
1 files changed, 666 insertions, 349 deletions
diff --git a/src/options.go b/src/options.go
index 38a5ba7c..83b0e97f 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1,6 +1,7 @@
package fzf
import (
+ "errors"
"fmt"
"os"
"regexp"
@@ -10,20 +11,19 @@ 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
(enabled by default; +x or --no-extended to disable)
-e, --exact Enable Exact-match
- -i Case-insensitive match (default: smart-case match)
- +i Case-sensitive match
+ -i, --ignore-case Case-insensitive match (default: smart-case match)
+ +i, --no-ignore-case Case-sensitive match
--scheme=SCHEME Scoring scheme [default|path|history]
--literal Do not normalize latin script letters before matching
-n, --nth=N[,..] Comma-separated list of field index expressions
@@ -57,7 +57,7 @@ const usage = `usage: fzf [options]
Layout
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen.
- A negative value is calcalated as the terminal height
+ A negative value is calculated as the terminal height
minus the given value.
If prefixed with '~', fzf will determine the height
according to the input size.
@@ -92,6 +92,7 @@ const usage = `usage: fzf [options]
--ansi Enable processing of ANSI color codes
--tabstop=SPACES Number of spaces for a tab character (default: 8)
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
+ --highlight-line Highlight the whole current line
--no-bold Do not use bold text
History
@@ -120,6 +121,7 @@ const usage = `usage: fzf [options]
--read0 Read input delimited by ASCII NUL characters
--print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering
+ --with-shell=STR Shell command and flags to start child processes with
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
(To allow remote process execution, use --listen-unsafe)
--version Display version information and exit
@@ -246,9 +248,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":
@@ -258,9 +261,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 {
@@ -290,6 +294,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
@@ -317,6 +323,7 @@ type Options struct {
MinHeight int
Layout layoutType
Cycle bool
+ CursorLine bool
KeepRight bool
Hscroll bool
HscrollOff int
@@ -356,6 +363,7 @@ type Options struct {
Unicode bool
Ambidouble bool
Tabstop int
+ WithShell string
ListenAddr *listenAddress
Unsafe bool
ClearOnExit bool
@@ -363,6 +371,7 @@ type Options struct {
WalkerRoot string
WalkerSkip []string
Version bool
+ Help bool
CPUProfile string
MEMProfile string
BlockProfile string
@@ -453,21 +462,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) {
@@ -477,13 +475,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) {
@@ -494,44 +492,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, ",")
@@ -539,11 +545,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 {
@@ -573,72 +579,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}))
@@ -706,6 +708,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
add(tui.Jump)
case "jump-cancel":
add(tui.JumpCancel)
+ case "click-header":
+ add(tui.ClickHeader)
case "alt-enter", "alt-return":
chords[tui.CtrlAltKey('m')] = key
case "alt-space":
@@ -806,54 +810,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 criteria
+ return criteria, nil
}
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
@@ -861,7 +875,8 @@ func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
return &dupe
}
-func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
+func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, error) {
+ var err error
theme := dupeTheme(defaultTheme)
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
for _, str := range strings.Split(strings.ToLower(str), ",") {
@@ -876,7 +891,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme = tui.NoColorTheme()
default:
fail := func() {
- errorExit("invalid color specification: " + str)
+ // Let the code proceed to simplify the error handling
+ err = errors.New("invalid color specification: " + str)
}
// Color is disabled
if theme == nil {
@@ -966,16 +982,22 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
mergeAttr(&theme.PreviewFg)
case "preview-bg":
mergeAttr(&theme.PreviewBg)
- case "fg+":
+ case "current-fg", "fg+":
mergeAttr(&theme.Current)
- case "bg+":
+ case "current-bg", "bg+":
mergeAttr(&theme.DarkBg)
+ case "selected-fg":
+ mergeAttr(&theme.SelectedFg)
+ case "selected-bg":
+ mergeAttr(&theme.SelectedBg)
case "gutter":
mergeAttr(&theme.Gutter)
case "hl":
mergeAttr(&theme.Match)
- case "hl+":
+ case "current-hl", "hl+":
mergeAttr(&theme.CurrentMatch)
+ case "selected-hl":
+ mergeAttr(&theme.SelectedMatch)
case "border":
mergeAttr(&theme.Border)
case "preview-border":
@@ -999,7 +1021,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
case "pointer":
mergeAttr(&theme.Cursor)
case "marker":
- mergeAttr(&theme.Selected)
+ mergeAttr(&theme.Marker)
case "header":
mergeAttr(&theme.Header)
default:
@@ -1007,10 +1029,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
}
}
}
- return theme
+ return theme, err
}
-func parseWalkerOpts(str string) walkerOpts {
+func parseWalkerOpts(str string) (walkerOpts, error) {
opts := walkerOpts{}
for _, str := range strings.Split(strings.ToLower(str), ",") {
switch str {
@@ -1025,13 +1047,13 @@ func parseWalkerOpts(str string) walkerOpts {
case "":
// Ignored
default:
- errorExit("invalid walker option: " + str)
+ return opts, errors.New("invalid walker option: " + str)
}
}
if !opts.file && !opts.dir {
- errorExit("at least one of 'file' or 'dir' should be specified")
+ return opts, errors.New("at least one of 'file' or 'dir' should be specified")
}
- return opts
+ return opts, nil
}
var (
@@ -1055,7 +1077,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
- `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
+ `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1075,7 +1097,7 @@ Loop:
break
}
cs := string(action[0])
- ce := ")"
+ var ce string
switch action[0] {
case ':':
masked += strings.Repeat(" ", len(action))
@@ -1116,13 +1138,13 @@ Loop:
return masked
}
-func parseSingleActionList(str string, exit func(string)) []*action {
+func parseSingleActionList(str string) ([]*action, error) {
// We prepend a colon to satisfy executeRegexp and remove it later
masked := maskActionContents(":" + str)[1:]
- return parseActionList(masked, str, []*action{}, false, exit)
+ return parseActionList(masked, str, []*action{}, false)
}
-func parseActionList(masked string, original string, prevActions []*action, putAllowed bool, exit func(string)) []*action {
+func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) {
maskedStrings := strings.Split(masked, "+")
originalStrings := make([]string, len(maskedStrings))
idx := 0
@@ -1299,15 +1321,17 @@ func parseActionList(masked string, original string, prevActions []*action, putA
if putAllowed {
appendAction(actChar)
} else {
- exit("unable to put non-printable character")
+ return nil, errors.New("unable to put non-printable character")
}
default:
t := isExecuteAction(specLower)
if t == actIgnore {
if specIndex == 0 && specLower == "" {
actions = append(prevActions, actions...)
+ } else if specLower == "change-multi" {
+ appendAction(actChangeMulti)
} else {
- exit("unknown action: " + spec)
+ return nil, errors.New("unknown action: " + spec)
}
} else {
offset := len(actionNameRegexp.FindString(spec))
@@ -1325,27 +1349,28 @@ func parseActionList(masked string, original string, prevActions []*action, putA
actions = append(actions, &action{t: t, a: actionArg})
}
switch t {
- case actBecome:
- if util.IsWindows() {
- exit("become action is not supported on Windows")
- }
case actUnbind, actRebind:
- parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
+ if _, err := parseKeyChordsImpl(actionArg, spec[0:offset]+" target required"); err != nil {
+ return nil, err
+ }
case actChangePreviewWindow:
opts := previewOpts{}
for _, arg := range strings.Split(actionArg, "|") {
// Make sure that each expression is valid
- parsePreviewWindowImpl(&opts, arg, exit)
+ if err := parsePreviewWindowImpl(&opts, arg); err != nil {
+ return nil, err
+ }
}
}
}
}
prevSpec = ""
}
- return actions
+ return actions, nil
}
-func parseKeymap(keymap map[tui.Event][]*action, str string, exit func(string)) {
+func parseKeymap(keymap map[tui.Event][]*action, str string) error {
+ var err error
masked := maskActionContents(str)
idx := 0
for _, pairStr := range strings.Split(masked, ",") {
@@ -1354,7 +1379,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string, exit func(string))
pair := strings.SplitN(pairStr, ":", 2)
if len(pair) < 2 {
- exit("bind action not specified: " + origPairStr)
+ return errors.New("bind action not specified: " + origPairStr)
}
var key tui.Event
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
@@ -1364,12 +1389,19 @@ func parseKeymap(keymap map[tui.Event][]*action, str string, exit func(string))
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
key = tui.Key('+')
} else {
- keys := parseKeyChordsImpl(pair[0], "key name required", exit)
+ keys, err := parseKeyChordsImpl(pair[0], "key name required")
+ if err != nil {
+ return err
+ }
key = firstKey(keys)
}
putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char)
- keymap[key] = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed, exit)
+ keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)
+ if err != nil {
+ return err
+ }
}
+ return nil
}
func isExecuteAction(str string) actionType {
@@ -1407,6 +1439,8 @@ func isExecuteAction(str string) actionType {
return actChangePrompt
case "change-query":
return actChangeQuery
+ case "change-multi":
+ return actChangeMulti
case "pos":
return actPosition
case "execute":
@@ -1433,43 +1467,56 @@ func isExecuteAction(str string) actionType {
return actIgnore
}
-func parseToggleSort(keymap map[tui.Event][]*action, str string) {
- keys := parseKeyChords(str, "key name required")
+func parseToggleSort(keymap map[tui.Event][]*action, str string) error {
+ keys, err := parseKeyChords(str, "key name required")
+ if err != nil {
+ return err
+ }
if len(keys) != 1 {
- errorExit("multiple keys specified")
+ return errors.New("multiple keys specified")
}
keymap[firstKey(keys)] = toActions(actToggleSort)
+ return nil
}
func strLines(str string) []string {
return strings.Split(strings.TrimSuffix(str, "\n"), "\n")
}
-func parseSize(str string, maxPercent float64, label string) sizeSpec {
+func parseSize(str string, maxPercent float64, label string) (sizeSpec, error) {
+ var spec = sizeSpec{}
var val float64
+ var err error
percent := strings.HasSuffix(str, "%")
if percent {
- val = atof(str[:len(str)-1])
+ if val, err = atof(str[:len(str)-1]); err != nil {
+ return spec, err
+ }
+
if val < 0 {
- errorExit(label + " must be non-negative")
+ return spec, errors.New(label + " must be non-negative")
}
if val > maxPercent {
- errorExit(fmt.Sprintf("%s too large (max: %d%%)", label, int(maxPercent)))
+ return spec, fmt.Errorf("%s too large (max: %d%%)", label, int(maxPercent))
}
} else {
if strings.Contains(str, ".") {
- errorExit(label + " (without %) must be a non-negative integer")
+ return spec, errors.New(label + " (without %) must be a non-negative integer")
}
- val = float64(atoi(str))
+ i, err := atoi(str)
+ if err != nil {
+ return spec, err
+ }
+ val = float64(i)
if val < 0 {
- errorExit(label + " must be non-negative")
+ return spec, errors.New(label + " must be non-negative")
}
}
- return sizeSpec{val, percent}
+ return sizeSpec{val, percent}, nil
}
-func parseHeight(str string) heightSpec {
+func parseHeight(str string) (heightSpec, error) {
heightSpec := heightSpec{}
if strings.HasPrefix(str, "~") {
heightSpec.auto = true
@@ -1477,66 +1524,66 @@ func parseHeight(str string) heightSpec {
}
if strings.HasPrefix(str, "-") {
if heightSpec.auto {
- errorExit("negative(-) height is not compatible with adaptive(~) height")
+ return heightSpec, errors.New("negative(-) height is not compatible with adaptive(~) height")
}
heightSpec.inverse = true
str = str[1:]
}
- size := parseSize(str, 100, "height")
+ size, err := parseSize(str, 100, "height")
+ if err != nil {
+ return heightSpec, err
+ }
heightSpec.size = size.size
heightSpec.percent = size.percent
- return heightSpec
+ return heightSpec, nil
}
-func parseLayout(str string) layoutType {
+func parseLayout(str string) (layoutType, error) {
switch str {
case "default":
- return layoutDefault
+ return layoutDefault, nil
case "reverse":
- return layoutReverse
+ return layoutReverse, nil
case "reverse-list":
- return layoutReverseList
- default:
- errorExit("invalid layout (expected: default / reverse / reverse-list)")
+ return layoutReverseList, nil
}
- return layoutDefault
+ return layoutDefault, errors.New("invalid layout (expected: default / reverse / reverse-list)")
}
-func parseInfoStyle(str string) (infoStyle, string) {
+func parseInfoStyle(str string) (infoStyle, string, error) {
switch str {
case "default":
- return infoDefault, ""
+ return infoDefault, "", nil
case "right":
- return infoRight, ""
+ return infoRight, "", nil
case "inline":
- return infoInline, defaultInfoPrefix
+ return infoInline, defaultInfoPrefix, nil
case "inline-right":
- return infoInlineRight, ""
+ return infoInlineRight, "", nil
case "hidden":
- return infoHidden, ""
- default:
- type infoSpec struct {
- name string
- style infoStyle
- }
- for _, spec := range []infoSpec{
- {"inline", infoInline},
- {"inline-right", infoInlineRight}} {
- if strings.HasPrefix(str, spec.name+":") {
- return spec.style, strings.ReplaceAll(str[len(spec.name)+1:], "\n", " ")
- }
+ return infoHidden, "", nil
+ }
+ type infoSpec struct {
+ name string
+ style infoStyle
+ }
+ for _, spec := range []infoSpec{
+ {"inline", infoInline},
+ {"inline-right", infoInlineRight}} {
+ if strings.HasPrefix(str, spec.name+":") {
+ return spec.style, strings.ReplaceAll(str[len(spec.name)+1:], "\n", " "), nil
}
- errorExit("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
}
- return infoDefault, ""
+ return infoDefault, "", errors.New("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
}
-func parsePreviewWindow(opts *previewOpts, input string) {
- parsePreviewWindowImpl(opts, input, errorExit)
+func parsePreviewWindow(opts *previewOpts, input string) error {
+ return parsePreviewWindowImpl(opts, input)
}
-func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string)) {
+func parsePreviewWindowImpl(opts *previewOpts, input string) error {
+ var err error
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
@@ -1545,7 +1592,9 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
var alternative string
for _, match := range tokens {
if len(match[2]) > 0 {
- opts.threshold = atoi(match[2])
+ if opts.threshold, err = atoi(match[2]); err != nil {
+ return err
+ }
alternative = match[3]
continue
}
@@ -1606,14 +1655,17 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
opts.follow = false
default:
if headerRegex.MatchString(token) {
- opts.headerLines = atoi(token[1:])
+ if opts.headerLines, err = atoi(token[1:]); err != nil {
+ return err
+ }
} else if sizeRegex.MatchString(token) {
- opts.size = parseSize(token, 99, "window size")
+ if opts.size, err = parseSize(token, 99, "window size"); err != nil {
+ return err
+ }
} else if offsetRegex.MatchString(token) {
opts.scroll = token
} else {
- exit("invalid preview window option: " + token)
- return
+ return errors.New("invalid preview window option: " + token)
}
}
}
@@ -1622,82 +1674,119 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
opts.alternative = &alternativeOpts
opts.alternative.hidden = false
opts.alternative.alternative = nil
- parsePreviewWindowImpl(opts.alternative, alternative, exit)
+ err = parsePreviewWindowImpl(opts.alternative, alternative)
}
+ return err
}
-func parseMargin(opt string, margin string) [4]sizeSpec {
+func parseMargin(opt string, margin string) ([4]sizeSpec, error) {
margins := strings.Split(margin, ",")
- checked := func(str string) sizeSpec {
+ checked := func(str string) (sizeSpec, error) {
return parseSize(str, 49, opt)
}
switch len(margins) {
case 1:
- m := checked(margins[0])
- return [4]sizeSpec{m, m, m, m}
+ m, e := checked(margins[0])
+ return [4]sizeSpec{m, m, m, m}, e
case 2:
- tb := checked(margins[0])
- rl := checked(margins[1])
- return [4]sizeSpec{tb, rl, tb, rl}
+ tb, e := checked(margins[0])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ rl, e := checked(margins[1])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ return [4]sizeSpec{tb, rl, tb, rl}, nil
case 3:
- t := checked(margins[0])
- rl := checked(margins[1])
- b := checked(margins[2])
- return [4]sizeSpec{t, rl, b, rl}
+ t, e := checked(margins[0])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ rl, e := checked(margins[1])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ b, e := checked(margins[2])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ return [4]sizeSpec{t, rl, b, rl}, nil
case 4:
- return [4]sizeSpec{
- checked(margins[0]), checked(margins[1]),
- checked(margins[2]), checked(margins[3])}
- default:
- errorExit("invalid " + opt + ": " + margin)
+ t, e := checked(margins[0])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ r, e := checked(margins[1])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ b, e := checked(margins[2])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ l, e := checked(margins[3])
+ if e != nil {
+ return defaultMargin(), e
+ }
+ return [4]sizeSpec{t, r, b, l}, nil
}
- return defaultMargin()
+ return [4]sizeSpec{}, errors.New("invalid " + opt + ": " + margin)
}
-func parseOptions(opts *Options, allArgs []string) {
+func parseOptions(opts *Options, allArgs []string) error {
+ var err error
var historyMax int
if opts.History == nil {
historyMax = defaultHistoryMax
} else {
historyMax = opts.History.maxSize
}
- setHistory := func(path string) {
+ setHistory := func(path string) error {
h, e := NewHistory(path, historyMax)
if e != nil {
- errorExit(e.Error())
+ return e
}
opts.History = h
+ return nil
}
- setHistoryMax := func(max int) {
+ setHistoryMax := func(max int) error {
historyMax = max
if historyMax < 1 {
- errorExit("history max must be a positive integer")
+ return errors.New("history max must be a positive integer")
}
if opts.History != nil {
opts.History.maxSize = historyMax
}
+ return nil
}
validateJumpLabels := false
+ clearExitingOpts := func() {
+ // Last-one-wins strategy
+ opts.Bash = false
+ opts.Zsh = false
+ opts.Fish = false
+ opts.Help = false
+ opts.Version = false
+ }
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
switch arg {
case "--bash":
+ clearExitingOpts()
opts.Bash = true
- if opts.Zsh || opts.Fish {
- errorExit("cannot specify --bash with --zsh or --fish")
- }
case "--zsh":
+ clearExitingOpts()
opts.Zsh = true
- if opts.Bash || opts.Fish {
- errorExit("cannot specify --zsh with --bash or --fish")
- }
case "--fish":
+ clearExitingOpts()
opts.Fish = true
- if opts.Bash || opts.Zsh {
- errorExit("cannot specify --fish with --bash or --zsh")
- }
case "-h", "--help":
- help(exitOk)
+ clearExitingOpts()
+ opts.Help = true
+ case "--version":
+ clearExitingOpts()
+ opts.Version = true
case "-x", "--extended":
opts.Extended = true
case "-e", "--exact":
@@ -1711,20 +1800,43 @@ func parseOptions(opts *Options, allArgs []string) {
case "+e", "--no-exact":
opts.Fuzzy = true
case "-q", "--query":
- opts.Query = nextString(allArgs, &i, "query string required")
+ if opts.Query, err = nextString(allArgs, &i, "query string required"); err != nil {
+ return err
+ }
case "-f", "--filter":
- filter := nextString(allArgs, &i, "query string required")
+ filter, err := nextString(allArgs, &i, "query string required")
+ if err != nil {
+ return err
+ }
opts.Filter = &filter
case "--literal":
opts.Normalize = false
case "--no-literal":
opts.Normalize = true
case "--algo":
- opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
+ str, err := nextString(allArgs, &i, "algorithm required (v1|v2)")
+ if err != nil {
+ return err
+ }
+ if opts.FuzzyAlgo, err = parseAlgo(str); err != nil {
+ return err
+ }
case "--scheme":
- opts.Scheme = strings.ToLower(nextString(allArgs, &i, "scoring scheme required (default|path|history)"))
+ str, err := nextString(allArgs, &i, "scoring scheme required (default|path|history)")
+ if err != nil {
+ return err
+ }
+ opts.Scheme = strings.ToLower(str)
case "--expect":
- for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
+ str, err := nextString(allArgs, &i, "key names required")
+ if err != nil {
+ return err
+ }
+ chords, err := parseKeyChords(str, "key names required")
+ if err != nil {
+ return err
+ }
+ for k, v := range chords {
opts.Expect[k] = v
}
case "--no-expect":
@@ -1734,26 +1846,64 @@ func parseOptions(opts *Options, allArgs []string) {
case "--disabled", "--phony":
opts.Phony = true
case "--tiebreak":
- opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
+ str, err := nextString(allArgs, &i, "sort criterion required")
+ if err != nil {
+ return err
+ }
+ if opts.Criteria, err = parseTiebreak(str); err != nil {
+ return err
+ }
case "--bind":
- parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"), errorExit)
+ str, err := nextString(allArgs, &i, "bind expression required")
+ if err != nil {
+ return err
+ }
+ if err := parseKeymap(opts.Keymap, str); err != nil {
+ return err
+ }
case "--color":
_, spec := optionalNextString(allArgs, &i)
if len(spec) == 0 {
opts.Theme = tui.EmptyTheme()
} else {
- opts.Theme = parseTheme(opts.Theme, spec)
+ if opts.Theme, err = parseTheme(opts.Theme, spec); err != nil {
+ return err
+ }
}
case "--toggle-sort":
- parseToggleSort(opts.Keymap, nextString(allArgs, &i, "key name required"))
+ str, err := nextString(allArgs, &i, "key name required")
+ if err != nil {
+ return err
+ }
+ if err := parseToggleSort(opts.Keymap, str); err != nil {
+ return err
+ }
case "-d", "--delimiter":
- opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
+ str, err := nextString(allArgs, &i, "delimiter required")
+ if err != nil {
+ return err
+ }
+ opts.Delimiter = delimiterRegexp(str)
case "-n", "--nth":
- opts.Nth = splitNth(nextString(allArgs, &i, "nth expression required"))
+ str, err := nextString(allArgs, &i, "nth expression required")
+ if err != nil {
+ return err
+ }
+ if opts.Nth, err = splitNth(str); err != nil {
+ return err
+ }
case "--with-nth":
- opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
+ str, err := nextString(allArgs, &i, "nth expression required")
+ if err != nil {
+ return err
+ }
+ if opts.WithNth, err = splitNth(str); err != nil {
+ return err
+ }
case "-s", "--sort":
- opts.Sort = optionalNumeric(allArgs, &i, 1)
+ if opts.Sort, err = optionalNumeric(allArgs, &i, 1); err != nil {
+ return err
+ }
case "+s", "--no-sort":
opts.Sort = 0
case "--track":
@@ -1764,12 +1914,14 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Tac = true
case "--no-tac":
opts.Tac = false
- case "-i":
+ case "-i", "--ignore-case":
opts.Case = CaseIgnore
- case "+i":
+ case "+i", "--no-ignore-case":
opts.Case = CaseRespec