From bee80a730f945c05ac0167130b9204206371d570 Mon Sep 17 00:00:00 2001 From: msabathier <115698101+msabathier@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:25:30 +0100 Subject: [PATCH] Allow walking multiple root directories (#4109) Co-authored-by: Martin Sabathier Co-authored-by: Junegunn Choi --- man/man1/fzf.1 | 4 ++-- src/options.go | 36 +++++++++++++++++++++++++++++++----- src/reader.go | 12 ++++++++---- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index 7b32817..4ec14ee 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1005,8 +1005,8 @@ Determines the behavior of the built-in directory walker that is used when .br .TP -.B "\-\-walker\-root=DIR" -The root directory from which to start the built-in directory walker. +.B "\-\-walker\-root=DIR [...]" +List of directory names to start the built-in directory walker. The default value is the current working directory. .TP diff --git a/src/options.go b/src/options.go index c42acce..9238ac6 100644 --- a/src/options.go +++ b/src/options.go @@ -145,7 +145,7 @@ Usage: fzf [options] Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set) --walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden) - --walker-root=DIR Root directory from which to start walker (default: .) + --walker-root=DIR [...] List of directories to walk (default: .) --walker-skip=DIRS Comma-separated list of directory names to skip (default: .git,node_modules) @@ -490,7 +490,7 @@ type Options struct { Unsafe bool ClearOnExit bool WalkerOpts walkerOpts - WalkerRoot string + WalkerRoot []string WalkerSkip []string Version bool Help bool @@ -594,7 +594,7 @@ func defaultOptions() *Options { Unsafe: false, ClearOnExit: true, WalkerOpts: walkerOpts{file: true, hidden: true, follow: true}, - WalkerRoot: ".", + WalkerRoot: []string{"."}, WalkerSkip: []string{".git", "node_modules"}, Help: false, Version: false} @@ -626,6 +626,28 @@ func optionalNextString(args []string, i *int) (bool, string) { return false, "" } +func isDir(path string) bool { + stat, err := os.Stat(path) + return err == nil && stat.IsDir() +} + +func nextDirs(args []string, i *int) ([]string, error) { + dirs := []string{} + for *i < len(args)-1 { + arg := args[*i+1] + if isDir(arg) { + dirs = append(dirs, arg) + *i++ + } else { + break + } + } + if len(dirs) == 0 { + return nil, errors.New("no directory specified") + } + return dirs, nil +} + func atoi(str string) (int, error) { num, err := strconv.Atoi(str) if err != nil { @@ -2487,7 +2509,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } case "--walker-root": - if opts.WalkerRoot, err = nextString(allArgs, &i, "directory required"); err != nil { + if opts.WalkerRoot, err = nextDirs(allArgs, &i); err != nil { return err } case "--walker-skip": @@ -2685,7 +2707,11 @@ func parseOptions(index *int, opts *Options, allArgs []string) error { return err } } else if match, value := optString(arg, "--walker-root="); match { - opts.WalkerRoot = value + if !isDir(value) { + return errors.New("not a directory: " + value) + } + dirs, _ := nextDirs(allArgs, &i) + opts.WalkerRoot = append([]string{value}, dirs...) } else if match, value := optString(arg, "--walker-skip="); match { opts.WalkerSkip = filterNonEmpty(strings.Split(value, ",")) } else if match, value := optString(arg, "--hscroll-off="); match { diff --git a/src/reader.go b/src/reader.go index b2531c3..80387ba 100644 --- a/src/reader.go +++ b/src/reader.go @@ -120,7 +120,7 @@ func (r *Reader) readChannel(inputChan chan string) bool { } // ReadSource reads data from the default command or from standard input -func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) { +func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) { r.startEventPoller() var success bool signalReady := func() { @@ -137,7 +137,7 @@ func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, cmd := os.Getenv("FZF_DEFAULT_COMMAND") if len(cmd) == 0 { signalReady() - success = r.readFiles(root, opts, ignores) + success = r.readFiles(roots, opts, ignores) } else { success = r.readFromCommand(cmd, initEnv, signalReady) } @@ -265,7 +265,7 @@ func trimPath(path string) string { return byteString(bytes) } -func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool { +func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool { conf := fastwalk.Config{ Follow: opts.follow, // Use forward slashes when running a Windows binary under WSL or MSYS @@ -301,7 +301,11 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool } return nil } - return fastwalk.Walk(&conf, root, fn) == nil + noerr := true + for _, root := range roots { + noerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil) + } + return noerr } func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {