You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fzf/src/options.go

318 lines
8.0 KiB
Go

10 years ago
package fzf
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/junegunn/go-shellwords"
10 years ago
)
10 years ago
const usage = `usage: fzf [options]
10 years ago
Search mode
10 years ago
-x, --extended Extended-search mode
-e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END])
--with-nth=N[,..] Transform the item using index expressions for search
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
(e.g. 'history | fzf --tac --no-sort')
10 years ago
Interface
-m, --multi Enable multi-select with tab/shift-tab
--ansi Enable processing of ANSI color codes
10 years ago
--no-mouse Disable mouse
+c, --no-color Disable colors
+2, --no-256 Disable 256-color
--black Use black background
--reverse Reverse orientation
--prompt=STR Input prompt (default: '> ')
Scripting
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
--sync Synchronous search for multi-staged filtering
(e.g. 'fzf --multi | fzf --sync')
10 years ago
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. '-x -m')
10 years ago
`
10 years ago
// Mode denotes the current search mode
10 years ago
type Mode int
10 years ago
// Search modes
10 years ago
const (
10 years ago
ModeFuzzy Mode = iota
ModeExtended
ModeExtendedExact
10 years ago
)
10 years ago
// Case denotes case-sensitivity of search
10 years ago
type Case int
10 years ago
// Case-sensitivities
10 years ago
const (
10 years ago
CaseSmart Case = iota
CaseIgnore
CaseRespect
10 years ago
)
10 years ago
// Options stores the values of command-line options
10 years ago
type Options struct {
Mode Mode
Case Case
Nth []Range
WithNth []Range
Delimiter *regexp.Regexp
Sort int
Tac bool
10 years ago
Multi bool
Ansi bool
10 years ago
Mouse bool
Color bool
Color256 bool
Black bool
Reverse bool
Prompt string
Query string
Select1 bool
Exit0 bool
Filter *string
PrintQuery bool
Sync bool
10 years ago
Version bool
}
10 years ago
func defaultOptions() *Options {
10 years ago
return &Options{
10 years ago
Mode: ModeFuzzy,
Case: CaseSmart,
10 years ago
Nth: make([]Range, 0),
WithNth: make([]Range, 0),
Delimiter: nil,
Sort: 1000,
Tac: false,
10 years ago
Multi: false,
Ansi: false,
10 years ago
Mouse: true,
Color: true,
Color256: strings.Contains(os.Getenv("TERM"), "256"),
Black: false,
Reverse: false,
Prompt: "> ",
Query: "",
Select1: false,
Exit0: false,
Filter: nil,
PrintQuery: false,
Sync: false,
10 years ago
Version: false}
}
func help(ok int) {
10 years ago
os.Stderr.WriteString(usage)
10 years ago
os.Exit(ok)
}
func errorExit(msg string) {
os.Stderr.WriteString(msg + "\n")
help(1)
}
func optString(arg string, prefix string) (bool, string) {
rx, _ := regexp.Compile(fmt.Sprintf("^(?:%s)(.*)$", prefix))
matches := rx.FindStringSubmatch(arg)
if len(matches) > 1 {
return true, matches[1]
}
10 years ago
return false, ""
10 years ago
}
func nextString(args []string, i *int, message string) string {
if len(args) > *i+1 {
*i++
} else {
errorExit(message)
}
return args[*i]
}
func optionalNumeric(args []string, i *int) int {
if len(args) > *i+1 {
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
*i++
}
}
return 1 // Don't care
}
func splitNth(str string) []Range {
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); !match {
errorExit("invalid format: " + str)
}
tokens := strings.Split(str, ",")
ranges := make([]Range, len(tokens))
for idx, s := range tokens {
r, ok := ParseRange(&s)
if !ok {
errorExit("invalid format: " + str)
}
ranges[idx] = r
}
return ranges
}
func delimiterRegexp(str string) *regexp.Regexp {
rx, e := regexp.Compile(str)
if e != nil {
str = regexp.QuoteMeta(str)
}
rx, e = regexp.Compile(fmt.Sprintf("(?:.*?%s)|(?:.+?$)", str))
if e != nil {
errorExit("invalid regular expression: " + e.Error())
}
return rx
}
func parseOptions(opts *Options, allArgs []string) {
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
switch arg {
case "-h", "--help":
help(0)
case "-x", "--extended":
10 years ago
opts.Mode = ModeExtended
10 years ago
case "-e", "--extended-exact":
10 years ago
opts.Mode = ModeExtendedExact
10 years ago
case "+x", "--no-extended", "+e", "--no-extended-exact":
10 years ago
opts.Mode = ModeFuzzy
10 years ago
case "-q", "--query":
opts.Query = nextString(allArgs, &i, "query string required")
case "-f", "--filter":
filter := nextString(allArgs, &i, "query string required")
opts.Filter = &filter
case "-d", "--delimiter":
opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
case "-n", "--nth":
opts.Nth = splitNth(nextString(allArgs, &i, "nth expression required"))
case "--with-nth":
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
case "-s", "--sort":
opts.Sort = optionalNumeric(allArgs, &i)
case "+s", "--no-sort":
opts.Sort = 0
case "--tac":
opts.Tac = true
case "--no-tac":
opts.Tac = false
10 years ago
case "-i":
10 years ago
opts.Case = CaseIgnore
10 years ago
case "+i":
10 years ago
opts.Case = CaseRespect
10 years ago
case "-m", "--multi":
opts.Multi = true
case "+m", "--no-multi":
opts.Multi = false
case "--ansi":
opts.Ansi = true
case "--no-ansi":
opts.Ansi = false
10 years ago
case "--no-mouse":
opts.Mouse = false
case "+c", "--no-color":
opts.Color = false
case "+2", "--no-256":
opts.Color256 = false
case "--black":
opts.Black = true
case "--no-black":
opts.Black = false
case "--reverse":
opts.Reverse = true
case "--no-reverse":
opts.Reverse = false
case "-1", "--select-1":
opts.Select1 = true
case "+1", "--no-select-1":
opts.Select1 = false
case "-0", "--exit-0":
opts.Exit0 = true
case "+0", "--no-exit-0":
opts.Exit0 = false
case "--print-query":
opts.PrintQuery = true
case "--no-print-query":
opts.PrintQuery = false
case "--prompt":
opts.Prompt = nextString(allArgs, &i, "prompt string required")
case "--sync":
opts.Sync = true
case "--no-sync":
opts.Sync = false
case "--async":
opts.Sync = false
10 years ago
case "--version":
opts.Version = true
default:
if match, value := optString(arg, "-q|--query="); match {
opts.Query = value
} else if match, value := optString(arg, "-f|--filter="); match {
opts.Filter = &value
} else if match, value := optString(arg, "-d|--delimiter="); match {
opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value
} else if match, value := optString(arg, "-n|--nth="); match {
opts.Nth = splitNth(value)
} else if match, value := optString(arg, "--with-nth="); match {
opts.WithNth = splitNth(value)
} else if match, _ := optString(arg, "-s|--sort="); match {
opts.Sort = 1 // Don't care
} else {
errorExit("unknown option: " + arg)
}
}
}
// If we're not using extended search mode, --nth option becomes irrelevant
// if it contains the whole range
if opts.Mode == ModeFuzzy || len(opts.Nth) == 1 {
for _, r := range opts.Nth {
if r.begin == rangeEllipsis && r.end == rangeEllipsis {
opts.Nth = make([]Range, 0)
return
}
}
}
10 years ago
}
10 years ago
// ParseOptions parses command-line options
10 years ago
func ParseOptions() *Options {
10 years ago
opts := defaultOptions()
10 years ago
// Options from Env var
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
parseOptions(opts, words)
// Options from command-line arguments
parseOptions(opts, os.Args[1:])
return opts
}