mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-06 23:22:04 -07:00
Option to prioritize file name matches (#4192)
* 'pathname' is a new tiebreak option for prioritizing matches occurring in the file name of the path. * `--scheme=path` will automatically set `--tiebreak=pathname,length`. * fzf will automatically choose `path` scheme when the input is a TTY device, where fzf would start its built-in walker or run `$FZF_DEFAULT_COMMAND` which is usually a command for listing files. Close #4191
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/junegunn/fzf/src/algo"
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
|
||||
"github.com/junegunn/go-shellwords"
|
||||
"github.com/rivo/uniseg"
|
||||
@@ -46,8 +47,8 @@ Usage: fzf [options]
|
||||
--tail=NUM Maximum number of items to keep in memory
|
||||
--disabled Do not perform search
|
||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||
when the scores are tied [length|chunk|begin|end|index]
|
||||
(default: length)
|
||||
when the scores are tied
|
||||
[length|chunk|pathname|begin|end|index] (default: length)
|
||||
|
||||
INPUT/OUTPUT
|
||||
--read0 Read input delimited by ASCII NUL characters
|
||||
@@ -241,6 +242,7 @@ const (
|
||||
byLength
|
||||
byBegin
|
||||
byEnd
|
||||
byPathname
|
||||
)
|
||||
|
||||
type heightSpec struct {
|
||||
@@ -653,7 +655,7 @@ func defaultOptions() *Options {
|
||||
Man: false,
|
||||
Fuzzy: true,
|
||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||
Scheme: "default",
|
||||
Scheme: "", // Unknown
|
||||
Extended: true,
|
||||
Phony: false,
|
||||
Case: CaseSmart,
|
||||
@@ -664,7 +666,7 @@ func defaultOptions() *Options {
|
||||
Sort: 1000,
|
||||
Track: trackDisabled,
|
||||
Tac: false,
|
||||
Criteria: []criterion{byScore, byLength},
|
||||
Criteria: []criterion{}, // Unknown
|
||||
Multi: 0,
|
||||
Ansi: false,
|
||||
Mouse: true,
|
||||
@@ -804,16 +806,6 @@ func parseAlgo(str string) (algo.Algo, error) {
|
||||
return nil, errors.New("invalid algorithm (expected: v1 or v2)")
|
||||
}
|
||||
|
||||
func processScheme(opts *Options) error {
|
||||
if !algo.Init(opts.Scheme) {
|
||||
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, allowLine bool) (tui.BorderShape, error) {
|
||||
switch str {
|
||||
case "line":
|
||||
@@ -1037,6 +1029,19 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
||||
return chords, nil
|
||||
}
|
||||
|
||||
func parseScheme(str string) (string, []criterion, error) {
|
||||
str = strings.ToLower(str)
|
||||
switch str {
|
||||
case "history":
|
||||
return str, []criterion{byScore}, nil
|
||||
case "path":
|
||||
return str, []criterion{byScore, byPathname, byLength}, nil
|
||||
case "default":
|
||||
return str, []criterion{byScore, byLength}, nil
|
||||
}
|
||||
return str, nil, errors.New("invalid scoring scheme: " + str + " (expected: default|path|history)")
|
||||
}
|
||||
|
||||
func parseTiebreak(str string) ([]criterion, error) {
|
||||
criteria := []criterion{byScore}
|
||||
hasIndex := false
|
||||
@@ -1044,6 +1049,7 @@ func parseTiebreak(str string) ([]criterion, error) {
|
||||
hasLength := false
|
||||
hasBegin := false
|
||||
hasEnd := false
|
||||
hasPathname := false
|
||||
check := func(notExpected *bool, name string) error {
|
||||
if *notExpected {
|
||||
return errors.New("duplicate sort criteria: " + name)
|
||||
@@ -1065,6 +1071,11 @@ func parseTiebreak(str string) ([]criterion, error) {
|
||||
return nil, err
|
||||
}
|
||||
criteria = append(criteria, byChunk)
|
||||
case "pathname":
|
||||
if err := check(&hasPathname, "pathname"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
criteria = append(criteria, byPathname)
|
||||
case "length":
|
||||
if err := check(&hasLength, "length"); err != nil {
|
||||
return nil, err
|
||||
@@ -2261,7 +2272,9 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.Scheme = strings.ToLower(str)
|
||||
if opts.Scheme, opts.Criteria, err = parseScheme(str); err != nil {
|
||||
return err
|
||||
}
|
||||
case "--expect":
|
||||
str, err := nextString("key names required")
|
||||
if err != nil {
|
||||
@@ -3173,7 +3186,9 @@ func postProcessOptions(opts *Options) error {
|
||||
return errors.New("failed to start pprof profiles: " + err.Error())
|
||||
}
|
||||
|
||||
return processScheme(opts)
|
||||
algo.Init(opts.Scheme)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseShellWords(str string) ([]string, error) {
|
||||
@@ -3223,7 +3238,26 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. Final validation of merged options
|
||||
// 4. Change default scheme when built-in walker is used
|
||||
if len(opts.Scheme) == 0 {
|
||||
opts.Scheme = "default"
|
||||
if len(opts.Criteria) == 0 {
|
||||
// NOTE: Let's assume $FZF_DEFAULT_COMMAND generates a list of file paths.
|
||||
// But it is possible that it is set to a command that doesn't generate
|
||||
// file paths.
|
||||
//
|
||||
// In that case, you can either
|
||||
// 1. explicitly set --scheme=default,
|
||||
// 2. or replace $FZF_DEFAULT_COMMAND with an equivalent 'start:reload'
|
||||
// binding, which is the new preferred way.
|
||||
if !opts.hasReloadOnStart() && util.IsTty(os.Stdin) {
|
||||
opts.Scheme = "path"
|
||||
}
|
||||
_, opts.Criteria, _ = parseScheme(opts.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Final validation of merged options
|
||||
if err := validateOptions(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3231,6 +3265,17 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func (opts *Options) hasReloadOnStart() bool {
|
||||
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
|
||||
for _, action := range actions {
|
||||
if action.t == actReload || action.t == actReloadSync {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (opts *Options) extractReloadOnStart() string {
|
||||
cmd := ""
|
||||
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
|
||||
|
Reference in New Issue
Block a user