Repository: junegunn/fzf (language: go)

Issue:
Exclude macOS containers from default find commands
Specifically, exclude any file with the com.apple.containermanager.uuid extended attribute from the default Ctrl-T and Alt-C commands.

See additional discussion at #2705 


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bbddf86a3cc..662f0d87995 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +1,46 @@
 CHANGELOG
 =========
 
+0.48.0
+------
+- Added options for customizing the behavior of the built-in walker
+    | Option               | Description                                       | Default              |
+    | ---                  | ---                                               | ---                  |
+    | `--walker=OPTS`      | Walker options (`[file][,dir][,follow][,hidden]`) | `file,follow,hidden` |
+    | `--walker-root=DIR`  | Root directory from which to start walker         | `.`                  |
+    | `--walker-skip=DIRS` | Comma-separated list of directory names to skip   | `.git,node_modules`  |
+    - Examples
+        ```sh
+        # Built-in walker is only used by standalone fzf when $FZF_DEFAULT_COMMAND is not set
+        unset FZF_DEFAULT_COMMAND
+
+        fzf # default: --walker=file,follow,hidden --walker-root=. --walker-skip=.git,node_modules
+        fzf --walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target
+
+        # Walker options in $FZF_DEFAULT_OPTS
+        export FZF_DEFAULT_OPTS="--walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target"
+        fzf
+
+        # Reading from STDIN; --walker is ignored
+        seq 100 | fzf --walker=dir
+
+        # Reading from $FZF_DEFAULT_COMMAND; --walker is ignored
+        export FZF_DEFAULT_COMMAND='seq 100'
+        fzf --walker=dir
+        ```
+- The shell extensions (key bindings and fuzzy completion) have been updated to use the built-in walker with these new options and they are now much faster out of the box.
+
 0.47.0
 ------
-- Replaced ["the default find command"][find] with a built-in directory traversal to simplify the code and to achieve better performance and consistent behavior across platforms.
+- Replaced ["the default find command"][find] with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms.
   This doesn't affect you if you have `$FZF_DEFAULT_COMMAND` set.
     - Breaking changes:
         - Unlike [the previous "find" command][find], the new traversal code will list hidden files, but hidden directories will still be ignored
         - No filtering of `devtmpfs` or `proc` types
         - Traversal is parallelized, so the order of the entries will be different each time
-    - You would wonder why fzf implements directory traversal anyway when it's a filter program following the Unix philosophy.
-      But fzf has had [the traversal code for years][walker] to tackle the performance problem on Windows. And I decided to use the same approach on different platforms as well for the benefits listed above.
-    - Built-in traversal is now done using the excellent [charlievieth/fastwalk][fastwalk] library, which easily outperforms its competitors and supports safely following symlinks.
+    - You may wonder why fzf implements directory walker anyway when it's a filter program following the [Unix philosophy][unix].
+      But fzf has had [the walker code for years][walker] to tackle the performance problem on Windows. And I decided to use the same approach on different platforms as well for the benefits listed above.
+    - Built-in walker is using the excellent [charlievieth/fastwalk][fastwalk] library, which easily outperforms its competitors and supports safely following symlinks.
 - Added `$FZF_DEFAULT_OPTS_FILE` to allow managing default options in a file
     - See [#3618](https://github.com/junegunn/fzf/pull/3618)
     - Option precedence from lower to higher
@@ -23,6 +52,7 @@ CHANGELOG
 [find]: https://github.com/junegunn/fzf/blob/0.46.1/src/constants.go#L60-L64
 [walker]: https://github.com/junegunn/fzf/pull/1847
 [fastwalk]: https://github.com/charlievieth/fastwalk
+[unix]: https://en.wikipedia.org/wiki/Unix_philosophy
 
 0.46.1
 ------
diff --git a/README.md b/README.md
index 657655bca1a..3c68962ef0b 100644
--- a/README.md
+++ b/README.md
@@ -231,13 +231,16 @@ find * -type f | fzf > selected
 ```
 
 Without STDIN pipe, fzf will traverse the file system under the current
-directory to get the list of files, skipping hidden directories. (You can
-override the default behavior with `FZF_DEFAULT_COMMAND`)
+directory to get the list of files.
 
 ```sh
 vim $(fzf)
 ```
 
+> You can override the default behavior
+> * Either by setting `$FZF_DEFAULT_COMMAND` to a command that generates the desired list
+> * Or by setting `--walker`, `--walker-root`, and `--walker-skip` options in `$FZF_DEFAULT_OPTS`
+
 > *:bulb: A more robust solution would be to use `xargs` but we've presented
 > the above as it's easier to grasp*
 > ```sh
@@ -388,11 +391,14 @@ The install script will setup the following key bindings for bash, zsh, and
 fish.
 
 - `CTRL-T` - Paste the selected files and directories onto the command-line
-    - Set `FZF_CTRL_T_COMMAND` to override the default command
+    - The list is generated using `--walker file,dir,follow,hidden` option
+        - You can override the behavior by setting `FZF_CTRL_T_COMMAND` to a custom command that generates the desired list
+        - Or you can set `--walker*` options in `FZF_CTRL_T_OPTS`
     - Set `FZF_CTRL_T_OPTS` to pass additional options to fzf
       ```sh
       # Preview file content using bat (https://github.com/sharkdp/bat)
       export FZF_CTRL_T_OPTS="
+        --walker-skip .git,node_modules,target
         --preview 'bat -n --color=always {}'
         --bind 'ctrl-/:change-preview-window(down|hidden|)'"
       ```
@@ -411,11 +417,15 @@ fish.
         --header 'Press CTRL-Y to copy command into clipboard'"
       ```
 - `ALT-C` - cd into the selected directory
+    - The list is generated using `--walker dir,follow,hidden` option
     - Set `FZF_ALT_C_COMMAND` to override the default command
+        - Or you can set `--walker-*` options in `FZF_ALT_C_OPTS`
     - Set `FZF_ALT_C_OPTS` to pass additional options to fzf
       ```sh
       # Print tree structure in the preview window
-      export FZF_ALT_C_OPTS="--preview 'tree -C {}'"
+      export FZF_ALT_C_OPTS="
+        --walker-skip .git,node_modules,target
+        --preview 'tree -C {}'"
       ```
 
 If you're on a tmux session, you can start fzf in a tmux split-pane or in
@@ -787,7 +797,7 @@ fd --type f --strip-cwd-prefix | fzf
 # Setting fd as the default source for fzf
 export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix'
 
-# Now fzf (w/o pipe) will use fd instead of find
+# Now fzf (w/o pipe) will use the fd command to generate the list
 fzf
 
 # To apply the command to CTRL-T as well
diff --git a/bin/fzf-tmux b/bin/fzf-tmux
index c41e755e31a..1c3c38ccf70 100755
--- a/bin/fzf-tmux
+++ b/bin/fzf-tmux
@@ -196,8 +196,9 @@ if [[ "$opt" =~ "-E" ]]; then
     exit 2
   fi
 fi
-[[ -n "$FZF_DEFAULT_OPTS"    ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
-[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
+envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
+envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
+envs="$envs FZF_DEFAULT_OPTS_FILE=$(printf %q "$FZF_DEFAULT_OPTS_FILE")"
 [[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
 [[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
 echo "$envs;" > "$argsf"
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index f524b8f6f5e..c29f1e23c6d 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -620,7 +620,7 @@ The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-p
 script to render an image using either of the protocols inside the preview window.
 
 e.g.
-      \fBfzf --preview='fzf-preview.sh {}'
+      \fBfzf --preview='fzf-preview.sh {}'\fR
 
 .RE
 
@@ -854,8 +854,34 @@ e.g.
 .B "--version"
 Display version information and exit
 
+.SS Directory traversal
 .TP
-Note that most options have the opposite versions with \fB--no-\fR prefix.
+.B "--walker=[file][,dir][,follow][,hidden]"
+Determines the behavior of the built-in directory walker that is used when
+\fB$FZF_DEFAULT_COMMAND\fR is not set. The default value is \fBfile,follow,hidden\fR.
+
+* \fBfile\fR: Include files in the search result
+.br
+* \fBdir\fR: Include directories in the search result
+.br
+* \fBhidden\fR: Include and follow hidden directories
+.br
+* \fBfollow\fR: Follow symbolic links
+.br
+
+.TP
+.B "--walker-root=DIR"
+The root directory from which to start the built-in directory walker.
+The default value is the current working directory.
+
+.TP
+.B "--walker-skip=DIRS"
+Comma-separated list of directory names to skip during the directory walk.
+The default value is \fB.git,node_modules\fR.
+
+.SS Note
+.TP
+Most options have the opposite versions with \fB--no-\fR prefix.
 
 .SH ENVIRONMENT VARIABLES
 .TP
diff --git a/shell/completion.bash b/shell/completion.bash
index 6f60ecf0db1..7d34af2c84a 100644
--- a/shell/completion.bash
+++ b/shell/completion.bash
@@ -13,22 +13,19 @@
 
 
 # To use custom commands instead of find, override _fzf_compgen_{path,dir}
-if ! declare -F _fzf_compgen_path > /dev/null; then
-  _fzf_compgen_path() {
-    echo "$1"
-    command find -L "$1" \
-      -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-      -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
-  }
-fi
-
-if ! declare -F _fzf_compgen_dir > /dev/null; then
-  _fzf_compgen_dir() {
-    command find -L "$1" \
-      -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-      -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
-  }
-fi
+#
+#   _fzf_compgen_path() {
+#     echo "$1"
+#     command find -L "$1" \
+#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
+#       -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
+#   }
+#
+#   _fzf_compgen_dir() {
+#     command find -L "$1" \
+#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
+#       -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
+#   }
 
 ###########################################################
 
@@ -336,9 +333,18 @@ __fzf_generic_path_completion() {
         leftover=${leftover/#\/}
         [[ -z "$dir" ]] && dir='.'
         [[ "$dir" != "/" ]] && dir="${dir/%\//}"
-        matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
-          printf "%q " "${item%$3}$3"
-        done)
+        matches=$(
+          unset FZF_DEFAULT_COMMAND
+          export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2"
+          if declare -F "$1" > /dev/null; then
+            eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
+          else
+            [[ $1 =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
+            __fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir"
+          fi | while read -r item; do
+            printf "%q " "${item%$3}$3"
+          done
+        )
         matches=${matches% }
         [[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
         if [[ -n "$matches" ]]; then
diff --git a/shell/completion.zsh b/shell/completion.zsh
index c4ba2c758b3..163b02996ae 100644
--- a/shell/completion.zsh
+++ b/shell/completion.zsh
@@ -77,22 +77,19 @@ fi
 {
 
 # To use custom commands instead of find, override _fzf_compgen_{path,dir}
-if ! declare -f _fzf_compgen_path > /dev/null; then
-  _fzf_compgen_path() {
-    echo "$1"
-    command find -L "$1" \
-      -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-      -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
-  }
-fi
-
-if ! declare -f _fzf_compgen_dir > /dev/null; then
-  _fzf_compgen_dir() {
-    command find -L "$1" \
-      -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-      -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
-  }
-fi
+#
+#   _fzf_compgen_path() {
+#     echo "$1"
+#     command find -L "$1" \
+#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
+#       -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+#   }
+#
+#   _fzf_compgen_dir() {
+#     command find -L "$1" \
+#       -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
+#       -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
+#   }
 
 ###########################################################
 
@@ -148,10 +145,19 @@ __fzf_generic_path_completion() {
       leftover=${leftover/#\/}
       [ -z "$dir" ] && dir='.'
       [ "$dir" != "/" ] && dir="${dir/%\//}"
-      matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
-        item="${item%$suffix}$suffix"
-        echo -n "${(q)item} "
-      done)
+      matches=$(
+        unset FZF_DEFAULT_COMMAND
+        export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
+        if declare -f "$compgen" > /dev/null; then
+          eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
+        else
+          [[ $compgen =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
+          __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" < /dev/tty
+        fi | while read item; do
+          item="${item%$suffix}$suffix"
+          echo -n "${(q)item} "
+        done
+      )
       matches=${matches% }
       if [ -n "$matches" ]; then
         LBUFFER="$lbuf$matches$tail"
diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash
index c4dce3ba5bf..f6cd48f10ab 100644
--- a/shell/key-bindings.bash
+++ b/shell/key-bindings.bash
@@ -17,14 +17,9 @@
 # Key bindings
 # ------------
 __fzf_select__() {
-  local cmd opts
-  cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-    -o -type f -print \
-    -o -type d -print \
-    -o -type l -print 2> /dev/null | command cut -b3-"}"
-  opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
-  eval "$cmd" |
-    FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
+  local opts
+  opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=file,dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
+  FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
     while read -r item; do
       printf '%q ' "$item"  # escape special chars
     done
@@ -42,11 +37,11 @@ fzf-file-widget() {
 }
 
 __fzf_cd__() {
-  local cmd opts dir
-  cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-    -o -type d -print 2> /dev/null | command cut -b3-"}"
-  opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
-  dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
+  local opts dir
+  opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
+  dir=$(
+    FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
+  ) && printf 'builtin cd -- %q' "$dir"
 }
 
 if command -v perl > /dev/null; then
diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish
index 69f769703a2..51ddcc0d603 100644
--- a/shell/key-bindings.fish
+++ b/shell/key-bindings.fish
@@ -25,18 +25,11 @@ function fzf_key_bindings
     set -l fzf_query $commandline[2]
     set -l prefix $commandline[3]
 
-    # "-path \$dir'*/.*'" matches hidden files/folders inside $dir but not
-    # $dir itself, even if hidden.
-    test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
-    command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-    -o -type f -print \
-    -o -type d -print \
-    -o -type l -print 2> /dev/null | sed 's@^\./@@'"
-
     test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
     begin
-      set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
-      eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
+      set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
+      set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
+      eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
     end
     if [ -z "$result" ]
       commandline -f repaint
@@ -81,13 +74,11 @@ function fzf_key_bindings
     set -l fzf_query $commandline[2]
     set -l prefix $commandline[3]
 
-    test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
-    command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-    -o -type d -print 2> /dev/null | sed 's@^\./@@'"
     test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
     begin
-      set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
-      eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
+      set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
+      set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
+      eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
 
       if [ -n "$result" ]
         cd -- $result
diff --git a/shell/key-bindings.zsh b/shell/key-bindings.zsh
index 7d0a14d6fef..a3699add49f 100644
--- a/shell/key-bindings.zsh
+++ b/shell/key-bindings.zsh
@@ -41,13 +41,9 @@ fi
 
 # CTRL-T - Paste the selected file path(s) into the command line
 __fsel() {
-  local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-    -o -type f -print \
-    -o -type d -print \
-    -o -type l -print 2> /dev/null | cut -b3-"}"
   setopt localoptions pipefail no_aliases 2> /dev/null
   local item
-  eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
+  FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" < /dev/tty | while read item; do
     echo -n "${(q)item} "
   done
   local ret=$?
@@ -73,10 +69,8 @@ bindkey -M viins '^T' fzf-file-widget
 
 # ALT-C - cd into the selected directory
 fzf-cd-widget() {
-  local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-    -o -type d -print 2> /dev/null | cut -b3-"}"
   setopt localoptions pipefail no_aliases 2> /dev/null
-  local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
+  local dir="$(FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m < /dev/tty)"
   if [[ -z "$dir" ]]; then
     zle redisplay
     return 0
diff --git a/src/core.go b/src/core.go
index 56a2198e96b..3abda89c788 100644
--- a/src/core.go
+++ b/src/core.go
@@ -117,7 +117,7 @@ func Run(opts *Options, version string, revision string) {
 		reader = NewReader(func(data []byte) bool {
 			return chunkList.Push(data)
 		}, eventBox, opts.ReadZero, opts.Filter == nil)
-		go reader.ReadSource()
+		go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
 	}
 
 	// Matcher
@@ -165,7 +165,7 @@ func Run(opts *Options, version string, revision string) {
 					}
 					return false
 				}, eventBox, opts.ReadZero, false)
-			reader.ReadSource()
+			reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
 		} else {
 			eventBox.Unwatch(EvtReadNew)
 			eventBox.WaitFor(EvtReadFin)
diff --git a/src/options.go b/src/options.go
index 84e1c888a87..fdad058d96a 100644
--- a/src/options.go
+++ b/src/options.go
@@ -124,6 +124,12 @@ const usage = `usage: fzf [options]
                            (To allow remote process execution, use --listen-unsafe)
     --version              Display version information and exit
 
+  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-skip=DIRS     Comma-separated list of directory names to skip
+                           (default: .git,node_modules)
+
   Environment variables
     FZF_DEFAULT_COMMAND    Default command to use when input is tty
     FZF_DEFAULT_OPTS       Default options (e.g. '--layout=reverse --info=inline')
@@ -274,6 +280,13 @@ func firstLine(s string) string {
 	return strings.SplitN(s, "\n", 2)[0]
 }
 
+type walkerOpts struct {
+	file   bool
+	dir    bool
+	hidden bool
+	follow bool
+}
+
 // Options stores the values of command-line options
 type Options struct {
 	Fuzzy        bool
@@ -342,9 +355,22 @@ type Options struct {
 	ListenAddr   *listenAddress
 	Unsafe       bool
 	ClearOnExit  bool
+	WalkerOpts   walkerOpts
+	WalkerRoot   string
+	WalkerSkip   []string
 	Version      bool
 }
 
+func filterNonEmpty(input []string) []string {
+	output := make([]string, 0, len(input))
+	for _, str := range input {
+		if len(str) > 0 {
+			output = append(output, str)
+		}
+	}
+	return output
+}
+
 func defaultPreviewOpts(command string) previewOpts {
 	return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
 }
@@ -413,6 +439,9 @@ func defaultOptions() *Options {
 		PreviewLabel: labelOpts{},
 		Unsafe:       false,
 		ClearOnExit:  true,
+		WalkerOpts:   walkerOpts{file: true, hidden: true, follow: true},
+		WalkerRoot:   ".",
+		WalkerSkip:   []string{".git", "node_modules"},
 		Version:      false}
 }
 
@@ -966,6 +995,30 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
 	return theme
 }
 
+func parseWalkerOpts(str string) walkerOpts {
+	opts := walkerOpts{}
+	for _, str := range strings.Split(strings.ToLower(str), ",") {
+		switch str {
+		case "file":
+			opts.file = true
+		case "dir":
+			opts.dir = true
+		case "hidden":
+			opts.hidden = true
+		case "follow":
+			opts.follow = true
+		case "":
+			// Ignored
+		default:
+			errorExit("invalid walker option: " + str)
+		}
+	}
+	if !opts.file && !opts.dir {
+		errorExit("at least one of 'file' or 'dir' should be specified")
+	}
+	return opts
+}
+
 var (
 	executeRegexp    *regexp.Regexp
 	splitRegexp      *regexp.Regexp
@@ -1880,6 +1933,12 @@ func parseOptions(opts *Options, allArgs []string) {
 			opts.ClearOnExit = true
 		case "--no-clear":
 			opts.ClearOnExit = false
+		case "--walker":
+			opts.WalkerOpts = parseWalkerOpts(nextString(allArgs, &i, "walker options required [file][,dir][,follow][,hidden]"))
+		case "--walker-root":
+			opts.WalkerRoot = nextString(allArgs, &i, "directory required")
+		case "--walker-skip":
+			opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
 		case "--version":
 			opts.Version = true
 		case "--":
@@ -1977,6 +2036,12 @@ func parseOptions(opts *Options, allArgs []string) {
 				}
 				opts.ListenAddr = &addr
 				opts.Unsafe = true
+			} else if match, value := optString(arg, "--walker="); match {
+				opts.WalkerOpts = parseWalkerOpts(value)
+			} 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)
 			} else if match, value := optString(arg, "--scroll-off="); match {
diff --git a/src/reader.go b/src/reader.go
index fb45c7b9b46..47102ec122f 100644
--- a/src/reader.go
+++ b/src/reader.go
@@ -93,13 +93,13 @@ func (r *Reader) restart(command string, environ []string) {
 }
 
 // ReadSource reads data from the default command or from standard input
-func (r *Reader) ReadSource() {
+func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
 	r.startEventPoller()
 	var success bool
 	if util.IsTty() {
 		cmd := os.Getenv("FZF_DEFAULT_COMMAND")
 		if len(cmd) == 0 {
-			success = r.readFiles()
+			success = r.readFiles(root, opts, ignores)
 		} else {
 			// We can't export FZF_* environment variables to the default command
 			success = r.readFromCommand(cmd, nil)
@@ -145,9 +145,9 @@ func (r *Reader) readFromStdin() bool {
 	return true
 }
 
-func (r *Reader) readFiles() bool {
+func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
 	r.killed = false
-	conf := fastwalk.Config{Follow: true}
+	conf := fastwalk.Config{Follow: opts.follow}
 	fn := func(path string, de os.DirEntry, err error) error {
 		if err != nil {
 			return nil
@@ -155,10 +155,18 @@ func (r *Reader) readFiles() bool {
 		path = filepath.Clean(path)
 		if path != "." {
 			isDir := de.IsDir()
-			if isDir && filepath.Base(path)[0] == '.' {
-				return filepath.SkipDir
+			if isDir {
+				base := filepath.Base(path)
+				if !opts.hidden && base[0] == '.' {
+					return filepath.SkipDir
+				}
+				for _, ignore := range ignores {
+					if ignore == base {
+						return filepath.SkipDir
+					}
+				}
 			}
-			if !isDir && r.pusher([]byte(path)) {
+			if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher([]byte(path)) {
 				atomic.StoreInt32(&r.event, int32(EvtReadNew))
 			}
 		}
@@ -169,7 +177,7 @@ func (r *Reader) readFiles() bool {
 		}
 		return nil
 	}
-	return fastwalk.Walk(&conf, ".", fn) == nil
+	return fastwalk.Walk(&conf, root, fn) == nil
 }
 
 func (r *Reader) readFromCommand(command string, environ []string) bool {


Test patch: