From e74b1251c0f579335e03b3e7182cd7a9f88dbe37 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 13 Mar 2024 23:59:34 +0900 Subject: [PATCH] Embed shell integration scripts in fzf binary (`--bash` / `--zsh` / `--fish`) (#3675) This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries. # Set up fzf key bindings and fuzzy completion eval "$(fzf --bash)" # Set up fzf key bindings and fuzzy completion eval "$(fzf --zsh)" # Set up fzf key bindings fzf --fish | source --- CHANGELOG.md | 18 ++++++++- README.md | 106 ++++++++++++++++++++++++++++++++----------------- install | 34 +++++++++------- main.go | 43 +++++++++++++++++++- man/man1/fzf.1 | 23 ++++++++++- src/options.go | 26 ++++++++++++ uninstall | 1 + 7 files changed, 197 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 662f0d87..f902833e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ CHANGELOG 0.48.0 ------ +- Shell integration scripts are now embedded in the fzf binary. This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries. + - bash + ```sh + # Set up fzf key bindings and fuzzy completion + eval "$(fzf --bash)" + ``` + - zsh + ```sh + # Set up fzf key bindings and fuzzy completion + eval "$(fzf --zsh)" + ``` + - fish + ```fish + # Set up fzf key bindings + fzf --fish | source + ``` - Added options for customizing the behavior of the built-in walker | Option | Description | Default | | --- | --- | --- | @@ -28,7 +44,7 @@ CHANGELOG 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. +- Shell integration scripts 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 ------ diff --git a/README.md b/README.md index 3c68962e..e539ea2f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Table of Contents * [Using git](#using-git) * [Using Linux package managers](#using-linux-package-managers) * [Windows](#windows) + * [Setting up shell integration](#setting-up-shell-integration) * [As Vim plugin](#as-vim-plugin) * [Upgrading fzf](#upgrading-fzf) * [Building fzf](#building-fzf) @@ -104,9 +105,9 @@ fzf project consists of the following components: - `fzf` executable - `fzf-tmux` script for launching fzf in a tmux pane -- Shell extensions +- Shell integration - Key bindings (`CTRL-T`, `CTRL-R`, and `ALT-C`) (bash, zsh, fish) - - Fuzzy auto-completion (bash, zsh) + - Fuzzy completion (bash, zsh) - Vim/Neovim plugin You can [download fzf executable][bin] alone if you don't need the extra @@ -121,11 +122,12 @@ to install fzf. ```sh brew install fzf - -# To install useful key bindings and fuzzy completion: -$(brew --prefix)/opt/fzf/install ``` +> [!IMPORTANT] +> To set up shell integration (key bindings and fuzzy completion), +> see [the instructions below](#setting-up-shell-integration). + fzf is also available [via MacPorts][portfile]: `sudo port install fzf` [portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile @@ -140,6 +142,9 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install ``` +The install script will add lines to your shell configuration file to modify +`$PATH` and set up shell integration. + ### Using Linux package managers | Package Manager | Linux Distribution | Command | @@ -158,10 +163,9 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf | XBPS | Void Linux | `sudo xbps-install -S fzf` | | Zypper | openSUSE | `sudo zypper install fzf` | -> :warning: **Key bindings (CTRL-T / CTRL-R / ALT-C) and fuzzy auto-completion -> may not be enabled by default.** -> -> Refer to the package documentation for more information. (e.g. `apt show fzf`) +> [!IMPORTANT] +> To set up shell integration (key bindings and fuzzy completion), +> see [the instructions below](#setting-up-shell-integration). [![Packaging status](https://repology.org/badge/vertical-allrepos/fzf.svg)](https://repology.org/project/fzf/versions) @@ -187,6 +191,31 @@ page][windows-wiki]. [windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows +### Setting up shell integration + +Add the following line to your shell configuration file. + +* bash + ```sh + # Set up fzf key bindings and fuzzy completion + eval "$(fzf --bash)" + ``` +* zsh + ```sh + # Set up fzf key bindings and fuzzy completion + eval "$(fzf --zsh)" + ``` +* fish + ```fish + # Set up fzf key bindings + fzf --fish | source + ``` + +> [!NOTE] +> `--bash`, `--zsh`, and `--fish` options are only available in +> fzf 0.48.0 or above. If you have an older version of fzf, refer to the +> package documentation for more information. (e.g. `apt show fzf`) + ### As Vim plugin If you use @@ -237,18 +266,20 @@ directory to get the list of files. vim $(fzf) ``` +> [!NOTE] > 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* +> [!WARNING] +> A more robust solution would be to use `xargs` but we've presented +> the above as it's easier to grasp > ```sh > fzf --print0 | xargs -0 -o vim > ``` -> -> *:bulb: fzf also has the ability to turn itself into a different process.* +> [!TIP] +> fzf also has the ability to turn itself into a different process. > > ```sh > fzf --bind 'enter:become(vim {})' @@ -322,13 +353,6 @@ or `py`. - `FZF_DEFAULT_COMMAND` - Default command to use when input is tty - e.g. `export FZF_DEFAULT_COMMAND='fd --type f'` - - > :warning: This variable is not used by shell extensions due to the - > slight difference in requirements. - > - > (e.g. `CTRL-T` runs `$FZF_CTRL_T_COMMAND` instead, `vim **` runs - > `_fzf_compgen_path()`, and `cd **` runs `_fzf_compgen_dir()`) - > - > The available options are described later in this document. - `FZF_DEFAULT_OPTS` - Default options - e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"` @@ -337,6 +361,17 @@ or `py`. point to the location of the file - e.g. `export FZF_DEFAULT_OPTS_FILE=~/.fzfrc` +> [!WARNING] +> `FZF_DEFAULT_COMMAND` is not used by shell integration due to the +> slight difference in requirements. +> +> * `CTRL-T` runs `$FZF_CTRL_T_COMMAND` to get a list of files and directories +> * `ALT-C` runs `$FZF_ALT_C_COMMAND` to get a list of directories +> * `vim ~/**` runs `fzf_compgen_path()` with the prefix (`~/`) as the first argument +> * `cd foo**` runs `fzf_compgen_dir()` with the prefix (`foo`) as the first argument +> +> The available options are described later in this document. + ### Options See the man page (`man fzf`) for the full list of options. @@ -749,22 +784,21 @@ See the man page (`man fzf`) for the full list of options. More advanced examples can be found [here](https://github.com/junegunn/fzf/blob/master/ADVANCED.md). ----- - -Since fzf is a general-purpose text filter rather than a file finder, **it is -not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**. - -```sh -# ********************* -# ** DO NOT DO THIS! ** -# ********************* -export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always --line-range :500 {}"' - -# bat doesn't work with any input other than the list of files -ps -ef | fzf -seq 100 | fzf -history | fzf -``` +> [!WARNING] +> Since fzf is a general-purpose text filter rather than a file finder, **it is +> not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**. +> +> ```sh +> # ********************* +> # ** DO NOT DO THIS! ** +> # ********************* +> export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always --line-range :500 {}"' +> +> # bat doesn't work with any input other than the list of files +> ps -ef | fzf +> seq 100 | fzf +> history | fzf +> ``` ### Previewing an image diff --git a/install b/install index e790b749..22a0c0be 100755 --- a/install +++ b/install @@ -262,6 +262,12 @@ if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then PATH="\${PATH:+\${PATH}:}$fzf_base/bin" fi +EOF + + if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then + echo "eval \"\$(fzf --$shell)\"" >> "$src" + else + cat >> "$src" << EOF # Auto-completion # --------------- $fzf_completion @@ -270,6 +276,7 @@ $fzf_completion # ------------ $fzf_key_bindings EOF + fi echo "OK" done @@ -281,18 +288,6 @@ if [[ "$shells" =~ fish ]]; then or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin EOF [ $? -eq 0 ] && echo "OK" || echo "Failed" - - mkdir -p "${fish_dir}/functions" - fish_binding="${fish_dir}/functions/fzf_key_bindings.fish" - if [ $key_bindings -ne 0 ]; then - echo -n "Symlink $fish_binding ... " - ln -sf "$fzf_base/shell/key-bindings.fish" \ - "$fish_binding" && echo "OK" || echo "Failed" - else - echo -n "Removing $fish_binding ... " - rm -f "$fish_binding" - echo "OK" - fi fi append_line() { @@ -355,12 +350,23 @@ done if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then bind_file="${fish_dir}/functions/fish_user_key_bindings.fish" if [ ! -e "$bind_file" ]; then + mkdir -p "${fish_dir}/functions" create_file "$bind_file" \ 'function fish_user_key_bindings' \ - ' fzf_key_bindings' \ + ' fzf --fish | source' \ 'end' else - append_line $update_config "fzf_key_bindings" "$bind_file" + echo "Check $bind_file:" + lno=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ') + if [[ -n $lno ]]; then + echo " ** Found 'fzf_key_bindings' in line #$lno" + echo " ** You have to replace the line to 'fzf --fish | source'" + echo + else + echo " - Clear" + echo + append_line $update_config "fzf --fish | source" "$bind_file" + fi fi fi diff --git a/main.go b/main.go index d0350601..debd2408 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,10 @@ package main import ( + _ "embed" + "fmt" + "strings" + fzf "github.com/junegunn/fzf/src" "github.com/junegunn/fzf/src/protector" ) @@ -8,7 +12,44 @@ import ( var version string = "0.47" var revision string = "devel" +//go:embed shell/key-bindings.bash +var bashKeyBindings []byte + +//go:embed shell/completion.bash +var bashCompletion []byte + +//go:embed shell/key-bindings.zsh +var zshKeyBindings []byte + +//go:embed shell/completion.zsh +var zshCompletion []byte + +//go:embed shell/key-bindings.fish +var fishKeyBindings []byte + +func printScript(label string, content []byte) { + fmt.Println("### " + label + " ###") + fmt.Println(strings.TrimSpace(string(content))) + fmt.Println("### end: " + label + " ###") +} + func main() { protector.Protect() - fzf.Run(fzf.ParseOptions(), version, revision) + options := fzf.ParseOptions() + if options.Bash { + printScript("key-bindings.bash", bashKeyBindings) + printScript("completion.bash", bashCompletion) + return + } + if options.Zsh { + printScript("key-bindings.zsh", zshKeyBindings) + printScript("completion.zsh", zshCompletion) + return + } + if options.Fish { + printScript("key-bindings.fish", fishKeyBindings) + fmt.Println("fzf_key_bindings") + return + } + fzf.Run(options, version, revision) } diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index c29f1e23..56aeeea5 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -33,6 +33,10 @@ fzf [options] fzf is a general-purpose command-line fuzzy finder. .SH OPTIONS +.SS Note +.TP +Most long options have the opposite version with \fB--no-\fR prefix. + .SS Search mode .TP .B "-x, --extended" @@ -879,9 +883,24 @@ The default value is the current working directory. Comma-separated list of directory names to skip during the directory walk. The default value is \fB.git,node_modules\fR. -.SS Note +.SS Shell integration .TP -Most options have the opposite versions with \fB--no-\fR prefix. +.B "--bash" +Print script to set up Bash shell integration + +e.g. \fBeval "$(fzf --bash)"\fR + +.TP +.B "--zsh" +Print script to set up Zsh shell integration + +e.g. \fBeval "$(fzf --zsh)"\fR + +.TP +.B "--fish" +Print script to set up Fish shell integration + +e.g. \fBfzf --fish | source\fR .SH ENVIRONMENT VARIABLES .TP diff --git a/src/options.go b/src/options.go index fdad058d..692ce6b2 100644 --- a/src/options.go +++ b/src/options.go @@ -130,6 +130,11 @@ const usage = `usage: fzf [options] --walker-skip=DIRS Comma-separated list of directory names to skip (default: .git,node_modules) + Shell integration + --bash Print script to set up Bash shell integration + --zsh Print script to set up Zsh shell integration + --fish Print script to set up Fish shell integration + Environment variables FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_OPTS Default options (e.g. '--layout=reverse --info=inline') @@ -289,6 +294,9 @@ type walkerOpts struct { // Options stores the values of command-line options type Options struct { + Bash bool + Zsh bool + Fish bool Fuzzy bool FuzzyAlgo algo.Algo Scheme string @@ -377,6 +385,9 @@ func defaultPreviewOpts(command string) previewOpts { func defaultOptions() *Options { return &Options{ + Bash: false, + Zsh: false, + Fish: false, Fuzzy: true, FuzzyAlgo: algo.FuzzyMatchV2, Scheme: "default", @@ -1655,6 +1666,21 @@ func parseOptions(opts *Options, allArgs []string) { for i := 0; i < len(allArgs); i++ { arg := allArgs[i] switch arg { + case "--bash": + opts.Bash = true + if opts.Zsh || opts.Fish { + errorExit("cannot specify --bash with --zsh or --fish") + } + case "--zsh": + opts.Zsh = true + if opts.Bash || opts.Fish { + errorExit("cannot specify --zsh with --bash or --fish") + } + case "--fish": + opts.Fish = true + if opts.Bash || opts.Zsh { + errorExit("cannot specify --fish with --bash or --zsh") + } case "-h", "--help": help(exitOk) case "-x", "--extended": diff --git a/uninstall b/uninstall index 4e587211..1d338cb4 100755 --- a/uninstall +++ b/uninstall @@ -94,6 +94,7 @@ done bind_file="${fish_dir}/functions/fish_user_key_bindings.fish" if [ -f "$bind_file" ]; then remove_line "$bind_file" "fzf_key_bindings" + remove_line "$bind_file" "fzf --fish | source" fi if [ -d "${fish_dir}/functions" ]; then