summaryrefslogtreecommitdiff
path: root/src/options.go
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/options.go
parent065b9e6fb2ce3e6e50ff423c3786989afa04ee14 (diff)
downloadfzf-e8405f40fe2eb3675f1cb4f69e825eff5f13f269.tar.gz
Refactor the code so that fzf can be used as a library (#3769)
Diffstat (limited to 'src/options.go')
-rw-r--r--src/options.go965
1 files changed, 632 insertions, 333 deletions
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 criteria
+ return criteria, nil
}
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
@@ -865,7 +873,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), ",") {
@@ -880,7 +889,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 {
@@ -1011,10 +1021,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 {
@@ -1029,13 +1039,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 (
@@ -1079,7 +1089,7 @@ Loop:
break
}
cs := string(action[0])
- ce := ")"
+ var ce string
switch action[0] {
case ':':
masked += strings.Repeat(" ", len(action))
@@ -1120,13 +1130,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
@@ -1303,7 +1313,7 @@ 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)
@@ -1313,7 +1323,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
} 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))
@@ -1332,22 +1342,27 @@ func parseActionList(masked string, original string, prevActions []*action, putA
}
switch t {
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, ",") {
@@ -1356,7 +1371,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 {
@@ -1366,12 +1381,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 {
@@ -1437,43 +1459,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
@@ -1481,66 +1516,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]*)?$`)
@@ -1549,7 +1584,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
}
@@ -1610,14 +1647,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)
}
}
}
@@ -1626,82 +1666,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":
@@ -1715,20 +1792,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":
@@ -1738,26 +1838,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":
@@ -1773,7 +1911,9 @@ func parseOptions(opts *Options, allArgs []string) {
case "+i":
opts.Case = CaseRespect
case "-m", "--multi":
- opts.Multi = optionalNumeric(allArgs, &i, maxMulti)
+ if opts.Multi, err = optionalNumeric(allArgs, &i, maxMulti); err != nil {
+ return err
+ }
case "+m", "--no-multi":
opts.Multi = 0
case "--ansi":
@@ -1795,8 +1935,13 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-bold":
opts.Bold = false
case "--layout":
- opts.Layout = parseLayout(
- nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
+ str, err := nextString(allArgs, &i, "layout required (default / reverse / reverse-list)")
+ if err != nil {
+ return err
+ }
+ if opts.Layout, err = parseLayout(str); err != nil {
+ return err
+ }
case "--reverse":
opts.Layout = layoutReverse
case "--no-reverse":
@@ -1814,16 +1959,25 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-hscroll":
opts.Hscroll = false
case "--hscroll-off":
- opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
+ if opts.HscrollOff, err = nextInt(allArgs, &i, "hscroll offset required"); err != nil {
+ return err
+ }
case "--scroll-off":
- opts.ScrollOff = nextInt(allArgs, &i, "scroll offset required")
+ if opts.ScrollOff, err = nextInt(allArgs, &i, "scroll offset required"); err != nil {
+ return err
+ }
case "--filepath-word":
opts.FileWord = true
case "--no-filepath-word":
opts.FileWord = false
case "--info":
- opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(
- nextString(allArgs, &i, "info style required"))
+ str, err := nextString(allArgs, &i, "info style required")
+ if err != nil {
+ return err
+ }
+ if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {
+ return err
+ }
case "--no-info":
opts.InfoStyle = infoHidden
case "--inline-info":
@@ -1832,7 +1986,10 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-inline-info":
opts.InfoStyle = infoDefault
case "--separator":
- separator := nextString(allArgs, &i, "separator character required")
+ separator, err := nextString(allArgs, &i, "separator character required")
+ if err != nil {
+ return err
+ }
opts.Separator = &separator
case "--no-separator":
nosep := ""
@@ -1848,7 +2005,9 @@ func parseOptions(opts *Options, allArgs []string) {
noBar := ""
opts.Scrollbar = &noBar
case "--jump-labels":
- opts.JumpLabels = nextString(allArgs, &i, "label characters required")
+ if opts.JumpLabels, err = nextString(allArgs, &i, "label characters required"); err != nil {
+ return err
+ }
validateJumpLabels = true
case "-1", "--select-1":
opts.Select1 = true
@@ -1873,11 +2032,22 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-print-query":
opts.PrintQuery = false
case "--prompt":
- opts.Prompt = nextString(allArgs, &i, "prompt string required")
+ opts.Prompt, err = nextString(allArgs, &i, "prompt string required")
+ if err != nil {
+ return err
+ }
case "--pointer":
- opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
+ str, err := nextString(allArgs, &i, "pointer sign string required")
+ if err != nil {
+ return err
+ }
+ opts.Pointer = firstLine(str)
case "--marker":
- opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
+ str, err := nextString(allArgs, &i, "selected sign string required")
+ if err != nil {
+ return err
+ }
+ opts.Marker = firstLine(str)
case "--sync":
opts.Sync = true
case "--no-sync", "--async":
@@ -1885,35 +2055,69 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-history":
opts.History = nil
case "--history":
- setHistory(nextString(allArgs, &i, "history file path required"))
+ str, err := nextString(allArgs, &i, "history file path required")
+ if err != nil {
+ return err
+ }
+ if err := setHistory(str); err != nil {
+ return err
+ }
case "--history-size":
- setHistoryMax(nextInt(allArgs, &i, "history max size required"))
+ n, err := nextInt(allArgs, &i, "history max size required")
+ if err != nil {
+ return err
+ }
+ if err := setHistoryMax(n); err != nil {
+ return err
+ }
case "--no-header":
opts.Header = []string{}
case "--no-header-lines":
opts.HeaderLines = 0
case "--header":
- opts.Header = strLines(nextString(allArgs, &i, "header string required"))
+ str, err := nextString(allArgs, &i, "header string required")
+ if err != nil {
+ return err
+ }
+ opts.Header = strLines(str)
case "--header-lines":
- opts.HeaderLines = atoi(
- nextString(allArgs, &i, "number of header lines required"))
+ if opts.HeaderLines, err = nextInt(allArgs, &i, "number of header lines required"); err != nil {
+ return err
+ }
case "--header-first":
opts.HeaderFirst = true
case "--no-header-first":
opts.HeaderFirst = false
case "--ellipsis":
- opts.Ellipsis = nextString(allArgs, &i, "ellipsis string required")
+ if opts.Ellipsis, err = nextString(allArgs, &i, "ellipsis string required"); err != nil {
+ return err
+ }
case "--preview":
- opts.Preview.command = nextString(allArgs, &i, "preview command required")
+ if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil {
+ return err
+ }
case "--no-preview":
opts.Preview.command = ""
case "--preview-window":
- parsePreviewWindow(&opts.Preview,
- nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
+ str, err := nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]")
+ if err != nil {
+ return err
+ }
+ if err := parsePreviewWindow(&opts.Preview, str); err != nil {
+ return err
+ }
case "--height":
- opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
+ str, err := nextString(allArgs, &i, "height required: [~]HEIGHT[%]")
+ if err != nil {
+ return err
+ }
+ if opts.Height, err = parseHeight(str); err != nil {
+ return err
+ }
case "--min-height":
- opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
+ if opts.MinHeight, err = nextInt(allArgs, &i, "height required: HEIGHT"); err != nil {
+ return err
+ }
case "--no-height":
opts.Height = heightSpec{}
case "--no-margin":
@@ -1924,21 +2128,38 @@ func parseOptions(opts *Options, allArgs []string) {
opts.BorderShape = tui.BorderNone
case "--border":
hasArg, arg := optionalNextString(allArgs, &i)
- opts.BorderShape = parseBorder(arg, !hasArg)
+ if opts.BorderShape, err = parseBorder(arg, !hasArg); err != nil {
+ return err
+ }
case "--no-border-label":
opts.BorderLabel.label = ""
case "--border-label":
- opts.BorderLabel.label = nextString(allArgs, &i, "label required")
+ opts.BorderLabel.label, err = nextString(allArgs, &i, "label required")
+ if err != nil {
+ return err
+ }
case "--border-label-pos":
- pos := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')")
- parseLabelPosition(&opts.BorderLabel, pos)
+ pos, err := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')")
+ if err != nil {
+ return err
+ }
+ if err := parseLabelPosition(&opts.BorderLabel, pos); err != nil {
+ return err
+ }
case "--no-preview-label":
opts.PreviewLabel.label = ""
case "--preview-label":
- opts.PreviewLabel.label = nextString(allArgs, &i, "preview label required")
+ if opts.PreviewLabel.label, err = nextString(allArgs, &i, "preview label required"); err != nil {
+ return err
+ }
case "--preview-label-pos":
- pos := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')")
- parseLabelPosition(&opts.PreviewLabel, pos)
+ pos, err := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')")
+ if err != nil {
+ return err
+ }
+ if err := parseLabelPosition(&opts.PreviewLabel, pos); err != nil {
+ return err
+ }
case "--no-unicode":
opts.Unicode = false
case "--unicode":
@@ -1948,17 +2169,29 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-ambidouble":
opts.Ambidouble = false
case "--margin":
- opts.Margin = parseMargin(
- "margin",
- nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
+ str, err := nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")
+ if err != nil {
+ return err
+ }
+ if opts.Margin, err = parseMargin("margin", str); err != nil {
+ return err
+ }
case "--padding":
- opts.Padding = parseMargin(
- "padding",
- nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
+ str, err := nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)")
+ if err != nil {
+ return err
+ }
+ if opts.Padding, err = parseMargin("padding", str); err != nil {
+ return err
+ }
case "--tabstop":
- opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
+ if opts.Tabstop, err = nextInt(allArgs, &i, "tab stop required"); err != nil {
+ return err
+ }
case "--with-shell":
- opts.WithShell = nextString(allArgs, &i, "shell command and flags required")
+ if opts.WithShell, err = nextString(allArgs, &i, "shell command and flags required"); err != nil {
+ return err
+ }
case "--listen", "--listen-unsafe":
given, str := optionalNextString(allArgs, &i)
addr := defaultListenAddr
@@ -1966,7 +2199,7 @@ func parseOptions(opts *Options, allArgs []string) {
var err error
addr, err = parseListenAddress(str)
if err != nil {
- errorExit(err.Error())
+ return err
}
}
opts.ListenAddr = &addr
@@ -1979,26 +2212,46 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-clear":
opts.ClearOnExit = false
case "--walker":
- opts.WalkerOpts = parseWalkerOpts(nextString(allArgs, &i, "walker options required [file][,dir][,follow][,hidden]"))
+ str, err := nextString(allArgs, &i, "walker options required [file][,dir][,follow][,hidden]")
+ if err != nil {
+ return err
+ }
+ if opts.WalkerOpts, err = parseWalkerOpts(str); err != nil {
+ return err
+ }
case "--walker-root":
- opts.WalkerRoot = nextString(allArgs, &i, "directory required")
+ if opts.WalkerRoot, err = nextString(allArgs, &i, "directory required"); err != nil {
+ return err
+ }
case "--walker-skip":
- opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
- case "--version":
- opts.Version = true
+ str, err := nextString(allArgs, &i, "directory names to ignore required")
+ if err != nil {
+ return err
+ }
+ opts.WalkerSkip = filterNonEmpty(strings.Split(str, ","))
case "--profile-cpu":
- opts.CPUProfile = nextString(allArgs, &i, "file path required: cpu")
+ if opts.CPUProfile, err = nextString(allArgs, &i, "file path required: cpu"); err != nil {
+ return err
+ }
case "--profile-mem":
- opts.MEMProfile = nextString(allArgs, &i, "file path required: mem")
+ if opts.MEMProfile, err = nextString(allArgs, &i, "file path required: mem"); err != nil {
+ return err
+ }
case "--profile-block":
- opts.BlockProfile = nextString(allArgs, &i, "file path required: block")
+ if opts.BlockProfile, err = nextString(allArgs, &i, "file path required: block"); err != nil {
+ return err
+ }
case "--profile-mutex":
- opts.MutexProfile = nextString(allArgs, &i, "file path required: mutex")
+ if opts.MutexProfile, err = nextString(allArgs, &i, "file path required: mutex"); err != nil {
+ return err
+ }
case "--":
// Ignored
default:
if match, value := optString(arg, "--algo="); match {
- opts.FuzzyAlgo = parseAlgo(value)
+ if opts.FuzzyAlgo, err = parseAlgo(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--scheme="); match {
opts.Scheme = strings.ToLower(value)
} else if match, value := optString(arg, "-q", "--query="); match {
@@ -2008,15 +2261,21 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "-d", "--delimiter="); match {
opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--border="); match {
- opts.BorderShape = parseBorder(value, false)
+ if opts.BorderShape, err = parseBorder(value, false); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--border-label="); match {
opts.BorderLabel.label = value
} else if match, value := optString(arg, "--border-label-pos="); match {
- parseLabelPosition(&opts.BorderLabel, value)
+ if err := parseLabelPosition(&opts.BorderLabel, value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--preview-label="); match {
opts.PreviewLabel.label = value
} else if match, value := optString(arg, "--preview-label-pos="); match {
- parseLabelPosition(&opts.PreviewLabel, value)
+ if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match {
@@ -2024,119 +2283,170 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--marker="); match {
opts.Marker = firstLine(value)
} else if match, value := optString(arg, "-n", "--nth="); match {
- opts.Nth = splitNth(value)
+ if opts.Nth, err = splitNth(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--with-nth="); match {
- opts.WithNth = splitNth(value)
+ if opts.WithNth, err = splitNth(value); err != nil {
+ return err
+ }
} else if match, _ := optString(arg, "-s", "--sort="); match {
opts.Sort = 1 // Don't care
} else if match, value := optString(arg, "-m", "--multi="); match {
- opts.Multi = atoi(value)
+ if opts.Multi, err = atoi(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--height="); match {
- opts.Height = parseHeight(value)
+ if opts.Height, err = parseHeight(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--min-height="); match {
- opts.MinHeight = atoi(value)
+ if opts.MinHeight, err = atoi(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--layout="); match {
- opts.Layout = parseLayout(value)
+ if opts.Layout, err = parseLayout(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--info="); match {
- opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(value)
+ if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--separator="); match {
opts.Separator = &value
} else if match, value := optString(arg, "--scrollbar="); match {
opts.Scrollbar = &value
} else if match, value := optString(arg, "--toggle-sort="); match {
- parseToggleSort(opts.Keymap, value)
+ if err := parseToggleSort(opts.Keymap, value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--expect="); match {
- for k, v := range parseKeyChords(value, "key names required") {
+ chords, err := parseKeyChords(value, "key names required")
+ if err != nil {
+ return err
+ }
+ for k, v := range chords {
opts.Expect[k] = v
}
} else if match, value := optString(arg, "--tiebreak="); match {
- opts.Criteria = parseTiebreak(value)
+ if opts.Criteria, err = parseTiebreak(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--color="); match {
- opts.Theme = parseTheme(opts.Theme, value)
+ if opts.Theme, err = parseTheme(opts.Theme, value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--bind="); match {
- parseKeymap(opts.Keymap, value, errorExit)
+ if err := parseKeymap(opts.Keymap, value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--history="); match {
- setHistory(value)
+ if err := setHistory(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--history-size="); match {
- setHistoryMax(atoi(value))
+ n, err := atoi(value)
+ if err != nil {
+ return err
+ }
+ if err := setHistoryMax(n); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--header="); match {
opts.Header = strLines(value)
} else if match, value := optString(arg, "--header-lines="); match {
- opts.HeaderLines = atoi(value)
+ if opts.HeaderLines, err = atoi(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--ellipsis="); match {
opts.Ellipsis = value
} else if match, value := optString(arg, "--preview="); match {
opts.Preview.command = value
} else if match, value := optString(arg, "--preview-window="); match {
- parsePreviewWindow(&opts.Preview, value)
+ if err := parsePreviewWindow(&opts.Preview, value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--margin="); match {
- opts.Margin = parseMargin("margin", value)
+ if opts.Margin, err = parseMargin("margin", value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--padding="); match {
- opts.Padding = parseMargin("padding", value)
+ if opts.Padding, err = parseMargin("padding", value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--tabstop="); match {
- opts.Tabstop = atoi(value)
+ if opts.Tabstop, err = atoi(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--with-shell="); match {
opts.WithShell = value
} else if match, value := optString(arg, "--listen="); match {
addr, err := parseListenAddress(value)
if err != nil {
- errorExit(err.Error())
+ return err
}
opts.ListenAddr = &addr
opts.Unsafe = false
} else if match, value := optString(arg, "--listen-unsafe="); match {
addr, err := parseListenAddress(value)
if err != nil {
- errorExit(err.Error())
+ return err
}
opts.ListenAddr = &addr
opts.Unsafe = true
} else if match, value := optString(arg, "--walker="); match {
- opts.WalkerOpts = parseWalkerOpts(value)
+ if opts.WalkerOpts, err = parseWalkerOpts(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--walker-root="); match {
opts.WalkerRoot = value
} else if match, value := optString(arg, "--walker-skip="); match {
opts.WalkerSkip = filterNonEmpty(strings.Split(value, ","))
} else if match, value := optString(arg, "--hscroll-off="); match {
- opts.HscrollOff = atoi(value)
+ if opts.HscrollOff, err = atoi(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--scroll-off="); match {
- opts.ScrollOff = atoi(value)
+ if opts.ScrollOff, err = atoi(value); err != nil {
+ return err
+ }
} else if match, value := optString(arg, "--jump-labels="); match {
opts.JumpLabels = value
validateJumpLabels = true
} else {
- errorExit("unknown option: " + arg)
+ return errors.New("unknown option: " + arg)
}
}
}
if opts.HeaderLines < 0 {
- errorExit("header lines must be a non-negative integer")
+ return errors.New("header lines must be a non-negative integer")
}
if opts.HscrollOff < 0 {
- errorExit("hscroll offset must be a non-negative integer")
+ return errors.New("hscroll offset must be a non-negative integer")
}
if opts.ScrollOff < 0 {
- errorExit("scroll offset must be a non-negative integer")
+ return errors.New("scroll offset must be a non-negative integer")
}
if opts.Tabstop < 1 {
- errorExit("tab stop must be a positive integer")
+ return errors.New("tab stop must be a positive integer")
}
if len(opts.JumpLabels) == 0 {
- errorExit("empty jump labels")
+ return errors.New("empty jump labels")
}
if validateJumpLabels {
for _, r := range opts.JumpLabels {
if r < 32 || r > 126 {
- errorExit("non-ascii jump labels are not allowed")
+ return errors.New("non-ascii jump labels are not allowed")
}
}
}
+ return err
}
func validateSign(sign string, signOptName string) error {
@@ -2149,31 +2459,33 @@ func validateSign(sign string, signOptName string) error {
return nil
}
-func postProcessOptions(opts *Options) {
+// This function can have side-effects and alter some global states.
+// So we run it on fzf.Run and not on ParseOptions.
+func postProcessOptions(opts *Options) error {
if opts.Ambidouble {
uniseg.EastAsianAmbiguousWidth = 2
}
if err := validateSign(opts.Pointer, "pointer"); err != nil {
- errorExit(err.Error())
+ return err
}
if err := validateSign(opts.Marker, "marker"); err != nil {
- errorExit(err.Error())
+ return err
}
- if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
- errorExit("--height option is currently not supported on this platform")
+ if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
+ return errors.New("--height option is currently not supported on this platform")
}
if opts.Scrollbar != nil {
runes := []rune(*opts.Scrollbar)
if len(runes) > 2 {
- errorExit("--scrollbar should be given one or two characters")
+ return errors.New("--scrollbar should be given one or two characters")
}
for _, r := range runes {
if uniseg.StringWidth(string(r)) != 1 {
- errorExit("scrollbar display width should be 1")
+ return errors.New("scrollbar display width should be 1")
}
}
}
@@ -2227,12 +2539,12 @@ func postProcessOptions(opts *Options) {
if opts.Height.auto {
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {
if s.percent {
- errorExit("adaptive height is not compatible with top/bottom percent margin")
+ return errors.New("adaptive height is not compatible with top/bottom percent margin")
}
}
for _, s := range []sizeSpec{opts.Padding[0], opts.Padding[2]} {
if s.percent {
- errorExit("adaptive height is not compatible with top/bottom percent padding")
+ return errors.New("adaptive height is not compatible with top/bottom percent padding")
}
}
}
@@ -2243,7 +2555,7 @@ func postProcessOptions(opts *Options) {
for _, r := range opts.Nth {
if r.begin == rangeEllipsis && r.end == rangeEllipsis {
opts.Nth = make([]Range, 0)
- return
+ break
}
}
}
@@ -2265,65 +2577,52 @@ func postProcessOptions(opts *Options) {
theme.Spinner = boldify(theme.Spinner)
}
- processScheme(opts)
-}
-
-func expectsArbitraryString(opt string) bool {
- switch opt {
- case "-q", "--query", "-f", "--filter", "--header", "--prompt":
- return true
- }
- return false
+ return processScheme(opts)
}
// ParseOptions parses command-line options
-func ParseOptions() *Options {
+func ParseOptions(useDefaults bool, args []string) (*Options, error) {
opts := defaultOptions()
- for idx, arg := range os.Args[1:] {
- if arg == "--version" && (idx == 0 || idx > 0 && !expectsArbitraryString(os.Args[idx])) {
- opts.Version = true
- return opts
- }
- }
+ if useDefaults {
+ // 1. Options from $FZF_DEFAULT_OPTS_FILE
+ if path := os.Getenv("FZF_DEFAULT_OPTS_FILE"); path != "" {
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error())
+ }
- // 1. Options from $FZF_DEFAULT_OPTS_FILE
- if path := os.Getenv("FZF_DEFAULT_OPTS_FILE"); path != "" {
- bytes, err := os.ReadFile(path)
- if err != nil {
- errorContext = "$FZF_DEFAULT_OPTS_FILE: "
- errorExit(err.Error())
+ words, parseErr := shellwords.Parse(string(bytes))
+ if parseErr != nil {
+ return nil, errors.New(path + ": " + parseErr.Error())
+ }
+ if len(words) > 0 {
+ if err := parseOptions(opts, words); err != nil {
+ return nil, errors.New(path + ": " + err.Error())
+ }
+ }
}
- words, parseErr := shellwords.Parse(string(bytes))
+ // 2. Options from $FZF_DEFAULT_OPTS string
+ words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
if parseErr != nil {
- errorContext = path + ": "
- errorExit(parseErr.Error())
+ return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error())
}
if len(words) > 0 {
- parseOptions(opts, words)
+ if err := parseOptions(opts, words); err != nil {
+ return nil, errors.New("$FZF_DEFAULT_OPTS: " + err.Error())
+ }
}
}
- // 2. Options from $FZF_DEFAULT_OPTS string
- words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
- errorContext = "$FZF_DEFAULT_OPTS: "
- if parseErr != nil {
- errorExit(parseErr.Error())
- }
- if len(words) > 0 {
- parseOptions(opts, words)
- }
-
// 3. Options from command-line arguments
- errorContext = ""
- parseOptions(opts, os.Args[1:])
+ if err := parseOptions(opts, args); err != nil {
+ return nil, err
+ }
if err := opts.initProfiling(); err != nil {
- errorExit("failed to start pprof profiles: " + err.Error())
+ return nil, errors.New("failed to start pprof profiles: " + err.Error())
}
- postProcessOptions(opts)
-
- return opts
+ return opts, nil
}