From 28205d67305634a4896830ab8bc9f7932af54da3 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Tue, 11 Jun 2024 21:32:03 +0200 Subject: [PATCH] feat: make fzf the same as fzf-lua --- .../completions/fzf_configure_bindings.fish | 8 +++ config/fish/conf.d/fzf.fish | 47 ++++++++++-------- config/fish/conf.d/fzfopts.fish | 24 +++++++++ config/fish/config.fish | 2 +- config/fish/fish_plugins | 1 + config/fish/functions/__fzf_files.fish | 19 ------- config/fish/functions/__fzf_history.fish | 18 ------- config/fish/functions/__fzf_preview_file.fish | 28 ----------- .../functions/__fzf_report_file_type.fish | 6 --- .../_fzf_configure_bindings_help.fish | 43 ++++++++++++++++ .../fish/functions/_fzf_extract_var_info.fish | 15 ++++++ .../functions/_fzf_preview_changed_file.fish | 49 +++++++++++++++++++ config/fish/functions/_fzf_preview_file.fish | 43 ++++++++++++++++ .../fish/functions/_fzf_report_diff_type.fish | 18 +++++++ .../fish/functions/_fzf_report_file_type.fish | 6 +++ .../fish/functions/_fzf_search_directory.fish | 33 +++++++++++++ .../fish/functions/_fzf_search_git_log.fish | 36 ++++++++++++++ .../functions/_fzf_search_git_status.fish | 41 ++++++++++++++++ .../fish/functions/_fzf_search_history.fish | 39 +++++++++++++++ .../fish/functions/_fzf_search_processes.fish | 32 ++++++++++++ .../fish/functions/_fzf_search_variables.fish | 47 ++++++++++++++++++ config/fish/functions/_fzf_wrapper.fish | 21 ++++++++ config/fish/functions/bob.fish | 8 +-- .../functions/fzf_configure_bindings.fish | 46 +++++++++++++++++ nvim/init.lua | 4 +- nvim/lazy-lock.json | 18 +++---- nvim/lua/plugins/telescope.lua | 34 +++++++++++++ 27 files changed, 578 insertions(+), 108 deletions(-) create mode 100644 config/fish/completions/fzf_configure_bindings.fish create mode 100644 config/fish/conf.d/fzfopts.fish delete mode 100644 config/fish/functions/__fzf_files.fish delete mode 100644 config/fish/functions/__fzf_history.fish delete mode 100644 config/fish/functions/__fzf_preview_file.fish delete mode 100644 config/fish/functions/__fzf_report_file_type.fish create mode 100644 config/fish/functions/_fzf_configure_bindings_help.fish create mode 100644 config/fish/functions/_fzf_extract_var_info.fish create mode 100644 config/fish/functions/_fzf_preview_changed_file.fish create mode 100644 config/fish/functions/_fzf_preview_file.fish create mode 100644 config/fish/functions/_fzf_report_diff_type.fish create mode 100644 config/fish/functions/_fzf_report_file_type.fish create mode 100644 config/fish/functions/_fzf_search_directory.fish create mode 100644 config/fish/functions/_fzf_search_git_log.fish create mode 100644 config/fish/functions/_fzf_search_git_status.fish create mode 100644 config/fish/functions/_fzf_search_history.fish create mode 100644 config/fish/functions/_fzf_search_processes.fish create mode 100644 config/fish/functions/_fzf_search_variables.fish create mode 100644 config/fish/functions/_fzf_wrapper.fish create mode 100644 config/fish/functions/fzf_configure_bindings.fish diff --git a/config/fish/completions/fzf_configure_bindings.fish b/config/fish/completions/fzf_configure_bindings.fish new file mode 100644 index 00000000..b38ef927 --- /dev/null +++ b/config/fish/completions/fzf_configure_bindings.fish @@ -0,0 +1,8 @@ +complete fzf_configure_bindings --no-files +complete fzf_configure_bindings --long help --short h --description "Print help" --condition "not __fish_seen_argument --help -h" +complete fzf_configure_bindings --long directory --description "Change the key binding for Search Directory" --condition "not __fish_seen_argument --directory" +complete fzf_configure_bindings --long git_log --description "Change the key binding for Search Git Log" --condition "not __fish_seen_argument --git_log" +complete fzf_configure_bindings --long git_status --description "Change the key binding for Search Git Status" --condition "not __fish_seen_argument --git_status" +complete fzf_configure_bindings --long history --description "Change the key binding for Search History" --condition "not __fish_seen_argument --history" +complete fzf_configure_bindings --long processes --description "Change the key binding for Search Processes" --condition "not __fish_seen_argument --processes" +complete fzf_configure_bindings --long variables --description "Change the key binding for Search Variables" --condition "not __fish_seen_argument --variables" diff --git a/config/fish/conf.d/fzf.fish b/config/fish/conf.d/fzf.fish index f6f8bfc0..8156c11b 100644 --- a/config/fish/conf.d/fzf.fish +++ b/config/fish/conf.d/fzf.fish @@ -1,23 +1,28 @@ -# bind \cr '__fzf_history' -bind -M insert \ch __fzf_tldr -bind -M insert \ct __fzf_files +# fzf.fish is only meant to be used in interactive mode. If not in interactive mode and not in CI, skip the config to speed up shell startup +if not status is-interactive && test "$CI" != true + exit +end -set -l color00 '#292D3E' -set -l color01 '#444267' -set -l color02 '#32374D' -set -l color03 '#676E95' -set -l color04 '#8796B0' -set -l color05 '#959DCB' -set -l color06 '#959DCB' -set -l color07 '#FFFFFF' -set -l color08 '#F07178' -set -l color09 '#F78C6C' -set -l color0A '#FFCB6B' -set -l color0B '#C3E88D' -set -l color0C '#89DDFF' -set -l color0D '#82AAFF' -set -l color0E '#C792EA' -set -l color0F '#FF5370' +# Because of scoping rules, to capture the shell variables exactly as they are, we must read +# them before even executing _fzf_search_variables. We use psub to store the +# variables' info in temporary files and pass in the filenames as arguments. +# This variable is global so that it can be referenced by fzf_configure_bindings and in tests +set --global _fzf_search_vars_command '_fzf_search_variables (set --show | psub) (set --names | psub)' -source ~/projects/tokyonight.nvim/extras/fzf/tokyonight_moon.zsh -set -x FZF_DEFAULT_OPTS "--cycle --layout=reverse --height 40% --preview-window=right:70% $FZF_DEFAULT_OPTS" + +# Install the default bindings, which are mnemonic and minimally conflict with fish's preset bindings +fzf_configure_bindings + +# Doesn't erase autoloaded _fzf_* functions because they are not easily accessible once key bindings are erased +function _fzf_uninstall --on-event fzf_uninstall + _fzf_uninstall_bindings + + set --erase _fzf_search_vars_command + functions --erase _fzf_uninstall _fzf_migration_message _fzf_uninstall_bindings fzf_configure_bindings + complete --erase fzf_configure_bindings + + set_color cyan + echo "fzf.fish uninstalled." + echo "You may need to manually remove fzf_configure_bindings from your config.fish if you were using custom key bindings." + set_color normal +end diff --git a/config/fish/conf.d/fzfopts.fish b/config/fish/conf.d/fzfopts.fish new file mode 100644 index 00000000..63b1276b --- /dev/null +++ b/config/fish/conf.d/fzfopts.fish @@ -0,0 +1,24 @@ +bind -M insert \ch __fzf_tldr + +source ~/projects/tokyonight.nvim/extras/fzf/tokyonight_moon.sh + +set -x FZF_DEFAULT_OPTS "$FZF_DEFAULT_OPTS + --cycle + --layout=reverse + --height 60% + --ansi + --preview-window=right:70% + --bind=ctrl-u:half-page-up,ctrl-d:half-page-down,ctrl-x:jump + --bind=ctrl-f:preview-page-down,ctrl-b:preview-page-up + --bind=ctrl-a:beginning-of-line,ctrl-e:end-of-line + --bind=ctrl-j:down,ctrl-k:up +" + +set fzf_diff_highlighter delta --paging=never --width=20 +fzf_configure_bindings \ + --directory=\ct \ + --git_log=\cg \ + --git_status= \ + --history= \ + --processes=\cp \ + --variables=\c\ev diff --git a/config/fish/config.fish b/config/fish/config.fish index c621dca9..0b566fe0 100644 --- a/config/fish/config.fish +++ b/config/fish/config.fish @@ -17,7 +17,7 @@ fish_add_path /usr/local/opt/sqlite/bin fish_add_path /usr/local/sbin fish_add_path ~/.gem/ruby/2.6.0/bin fish_add_path ~/.local/bin/pnpm -fish_add_path ~/.local/share/bob/active/bin +fish_add_path ~/.local/share/bob-nvim/bin fish_add_path /var/lib/flatpak/exports/bin/ fish_add_path ~/.dotnet/tools fish_add_path ~/.local/share/mise/shims diff --git a/config/fish/fish_plugins b/config/fish/fish_plugins index a295c1e4..02d2479e 100644 --- a/config/fish/fish_plugins +++ b/config/fish/fish_plugins @@ -1,3 +1,4 @@ jorgebucaran/fisher franciscolourenco/done jorgebucaran/autopair.fish +patrickf1/fzf.fish diff --git a/config/fish/functions/__fzf_files.fish b/config/fish/functions/__fzf_files.fish deleted file mode 100644 index 5a4672b3..00000000 --- a/config/fish/functions/__fzf_files.fish +++ /dev/null @@ -1,19 +0,0 @@ -# originally implemented and transposed from https://github.com/patrickf3139/dotfiles/pull/11 -function __fzf_files --description "Search the current directory using fzf and fd. Insert the selected relative file path into the commandline at the cursor." - # Make sure that fzf uses fish to execute __fzf_preview_file. - # See similar comment in __fzf_search_shell_variables.fish. - set --local --export SHELL (command --search fish) - set file_paths_selected ( - fd --hidden --follow --color=always --exclude=.git 2>/dev/null | - fzf --multi --ansi --preview='__fzf_preview_file {}' - ) - - if test $status -eq 0 - for path in $file_paths_selected - set escaped_path (string escape "$path") - commandline --insert "$escaped_path " - end - end - - commandline --function repaint -end diff --git a/config/fish/functions/__fzf_history.fish b/config/fish/functions/__fzf_history.fish deleted file mode 100644 index d680009f..00000000 --- a/config/fish/functions/__fzf_history.fish +++ /dev/null @@ -1,18 +0,0 @@ -# originally implemented and transposed from https://github.com/patrickf3139/dotfiles/pull/11 -function __fzf_history --description "Search command history using fzf. Replace the commandline with the selected command." - # history merge incorporates history changes from other fish sessions - history merge - history --null \ - | string replace -ar "\x0" ";__delim__;" \ - | fish_indent_ansi -i \ - | sed "s/__delim__/\\x0/g" \ - | fzf --tiebreak=index --read0 --ansi --query=(commandline) \ - | read -lz cmd - - if test $status -eq 0 - # trim any surrounding white space - commandline --replace (echo $cmd | sed -zr "s/^\s+|\s+\$//g") - end - - commandline --function repaint -end diff --git a/config/fish/functions/__fzf_preview_file.fish b/config/fish/functions/__fzf_preview_file.fish deleted file mode 100644 index 8923958e..00000000 --- a/config/fish/functions/__fzf_preview_file.fish +++ /dev/null @@ -1,28 +0,0 @@ -# helper function for __fzf_search_current_dir -function __fzf_preview_file --argument-names file_path --description "Prints a preview for the given file based on its file type." - if test -f "$file_path" # regular file - bat --style=numbers --color=always "$file_path" - else if test -d "$file_path" # directory - set --local CLICOLOR_FORCE true - ls -a "$file_path" - else if test -L "$file_path" # symlink - # notify user and recurse on the target of the symlink, which can be any of these file types - set -l target_path (realpath $file_path) - - set_color yellow - echo "'$file_path' is a symlink to '$target_path'." - set_color normal - - __fzf_preview_file "$target_path" - else if test -c "$file_path" - __fzf_report_file_type "$file_path" "character device file" - else if test -b "$file_path" - __fzf_report_file_type "$file_path" "block device file" - else if test -S "$file_path" - __fzf_report_file_type "$file_path" "socket" - else if test -p "$file_path" - __fzf_report_file_type "$file_path" "named pipe" - else - echo "Unknown file type." >&2 - end -end diff --git a/config/fish/functions/__fzf_report_file_type.fish b/config/fish/functions/__fzf_report_file_type.fish deleted file mode 100644 index e92eb028..00000000 --- a/config/fish/functions/__fzf_report_file_type.fish +++ /dev/null @@ -1,6 +0,0 @@ -# helper function for __fzf_preview_file -function __fzf_report_file_type --argument-names file_path file_type --description "Explain the file type for a file." - set_color red - echo "Cannot preview '$file_path': it is a $file_type." - set_color normal -end diff --git a/config/fish/functions/_fzf_configure_bindings_help.fish b/config/fish/functions/_fzf_configure_bindings_help.fish new file mode 100644 index 00000000..ecfe68ec --- /dev/null +++ b/config/fish/functions/_fzf_configure_bindings_help.fish @@ -0,0 +1,43 @@ +function _fzf_configure_bindings_help --description "Prints the help message for fzf_configure_bindings." + echo "\ +USAGE: + fzf_configure_bindings [--COMMAND=[KEY_SEQUENCE]...] + +DESCRIPTION + fzf_configure_bindings installs key bindings for fzf.fish's commands and erases any bindings it + previously installed. It installs bindings for both default and insert modes. fzf.fish executes + it without options on fish startup to install the out-of-the-box key bindings. + + By default, commands are bound to a mnemonic key sequence, shown below. Each command's binding + can be configured using a namesake corresponding option: + COMMAND | DEFAULT KEY SEQUENCE | CORRESPONDING OPTION + Search Directory | Ctrl+Alt+F (F for file) | --directory + Search Git Log | Ctrl+Alt+L (L for log) | --git_log + Search Git Status | Ctrl+Alt+S (S for status) | --git_status + Search History | Ctrl+R (R for reverse) | --history + Search Processes | Ctrl+Alt+P (P for process) | --processes + Search Variables | Ctrl+V (V for variable) | --variables + Override a command's binding by specifying its corresponding option with the desired key + sequence. Disable a command's binding by specifying its corresponding option with no value. + + Because fzf_configure_bindings erases bindings it previously installed, it can be cleanly + executed multiple times. Once the desired fzf_configure_bindings command has been found, add it + to your config.fish in order to persist the customized bindings. + + In terms of validation, fzf_configure_bindings fails if passed unknown options. It expects an + equals sign between an option's name and value. However, it does not validate key sequences. + + Pass -h or --help to print this help message and exit. + +EXAMPLES + Default bindings but bind Search Directory to Ctrl+F and Search Variables to Ctrl+Alt+V + \$ fzf_configure_bindings --directory=\cf --variables=\e\cv + Default bindings but disable Search History + \$ fzf_configure_bindings --history= + An agglomeration of different options + \$ fzf_configure_bindings --git_status=\cg --history=\ch --variables= --processes= + +SEE Also + To learn more about fish key bindings, see bind(1) and fish_key_reader(1). +" +end diff --git a/config/fish/functions/_fzf_extract_var_info.fish b/config/fish/functions/_fzf_extract_var_info.fish new file mode 100644 index 00000000..dd4e9523 --- /dev/null +++ b/config/fish/functions/_fzf_extract_var_info.fish @@ -0,0 +1,15 @@ +# helper function for _fzf_search_variables +function _fzf_extract_var_info --argument-names variable_name set_show_output --description "Extract and reformat lines pertaining to \$variable_name from \$set_show_output." + # Extract only the lines about the variable, all of which begin with either + # $variable_name: ...or... $variable_name[ + string match --regex "^\\\$$variable_name(?::|\[).*" <$set_show_output | + + # Strip the variable name prefix, including ": " for scope info lines + string replace --regex "^\\\$$variable_name(?:: )?" '' | + + # Distill the lines of values, replacing... + # [1]: |value| + # ...with... + # [1] value + string replace --regex ": \|(.*)\|" ' $1' +end diff --git a/config/fish/functions/_fzf_preview_changed_file.fish b/config/fish/functions/_fzf_preview_changed_file.fish new file mode 100644 index 00000000..78dd5611 --- /dev/null +++ b/config/fish/functions/_fzf_preview_changed_file.fish @@ -0,0 +1,49 @@ +# helper for _fzf_search_git_status +# arg should be a line from git status --short, e.g. +# MM functions/_fzf_preview_changed_file.fish +# D README.md +# R LICENSE -> "New License" +function _fzf_preview_changed_file --argument-names path_status --description "Show the git diff of the given file." + # remove quotes because they'll be interpreted literally by git diff + # no need to requote when referencing $path because fish does not perform word splitting + # https://fishshell.com/docs/current/fish_for_bash_users.html + set -f path (string unescape (string sub --start 4 $path_status)) + # first letter of short format shows index, second letter shows working tree + # https://git-scm.com/docs/git-status/2.35.0#_short_format + set -f index_status (string sub --length 1 $path_status) + set -f working_tree_status (string sub --start 2 --length 1 $path_status) + + set -f diff_opts --color=always + + if test $index_status = '?' + _fzf_report_diff_type Untracked + _fzf_preview_file $path + else if contains {$index_status}$working_tree_status DD AU UD UA DU AA UU + # Unmerged statuses taken directly from git status help's short format table + # Unmerged statuses are mutually exclusive with other statuses, so if we see + # these, then safe to assume the path is unmerged + _fzf_report_diff_type Unmerged + git diff $diff_opts -- $path + else + if test $index_status != ' ' + _fzf_report_diff_type Staged + + # renames are only detected in the index, never working tree, so only need to test for it here + # https://stackoverflow.com/questions/73954214 + if test $index_status = R + # diff the post-rename path with the original path, otherwise the diff will show the entire file as being added + set -f orig_and_new_path (string split --max 1 -- ' -> ' $path) + git diff --staged $diff_opts -- $orig_and_new_path[1] $orig_and_new_path[2] + # path currently has the form of "original -> current", so we need to correct it before it's used below + set path $orig_and_new_path[2] + else + git diff --staged $diff_opts -- $path + end + end + + if test $working_tree_status != ' ' + _fzf_report_diff_type Unstaged + git diff $diff_opts -- $path + end + end +end diff --git a/config/fish/functions/_fzf_preview_file.fish b/config/fish/functions/_fzf_preview_file.fish new file mode 100644 index 00000000..c9264756 --- /dev/null +++ b/config/fish/functions/_fzf_preview_file.fish @@ -0,0 +1,43 @@ +# helper function for _fzf_search_directory and _fzf_search_git_status +function _fzf_preview_file --description "Print a preview for the given file based on its file type." + # because there's no way to guarantee that _fzf_search_directory passes the path to _fzf_preview_file + # as one argument, we collect all the arguments into one single variable and treat that as the path + set -f file_path $argv + + if test -L "$file_path" # symlink + # notify user and recurse on the target of the symlink, which can be any of these file types + set -l target_path (realpath "$file_path") + + set_color yellow + echo "'$file_path' is a symlink to '$target_path'." + set_color normal + + _fzf_preview_file "$target_path" + else if test -f "$file_path" # regular file + if set --query fzf_preview_file_cmd + # need to escape quotes to make sure eval receives file_path as a single arg + eval "$fzf_preview_file_cmd '$file_path'" + else + bat --style=numbers --color=always "$file_path" + end + else if test -d "$file_path" # directory + if set --query fzf_preview_dir_cmd + # see above + eval "$fzf_preview_dir_cmd '$file_path'" + else + # -A list hidden files as well, except for . and .. + # -F helps classify files by appending symbols after the file name + command ls -A -F "$file_path" + end + else if test -c "$file_path" + _fzf_report_file_type "$file_path" "character device file" + else if test -b "$file_path" + _fzf_report_file_type "$file_path" "block device file" + else if test -S "$file_path" + _fzf_report_file_type "$file_path" socket + else if test -p "$file_path" + _fzf_report_file_type "$file_path" "named pipe" + else + echo "$file_path doesn't exist." >&2 + end +end diff --git a/config/fish/functions/_fzf_report_diff_type.fish b/config/fish/functions/_fzf_report_diff_type.fish new file mode 100644 index 00000000..cc26fb35 --- /dev/null +++ b/config/fish/functions/_fzf_report_diff_type.fish @@ -0,0 +1,18 @@ +# helper for _fzf_preview_changed_file +# prints out something like +# ╭────────╮ +# │ Staged │ +# ╰────────╯ +function _fzf_report_diff_type --argument-names diff_type --description "Print a distinct colored header meant to preface a git patch." + # number of "-" to draw is the length of the string to box + 2 for padding + set -f repeat_count (math 2 + (string length $diff_type)) + set -f line (string repeat --count $repeat_count ─) + set -f top_border ╭$line╮ + set -f btm_border ╰$line╯ + + set_color yellow + echo $top_border + echo "│ $diff_type │" + echo $btm_border + set_color normal +end diff --git a/config/fish/functions/_fzf_report_file_type.fish b/config/fish/functions/_fzf_report_file_type.fish new file mode 100644 index 00000000..49e02e1c --- /dev/null +++ b/config/fish/functions/_fzf_report_file_type.fish @@ -0,0 +1,6 @@ +# helper function for _fzf_preview_file +function _fzf_report_file_type --argument-names file_path file_type --description "Explain the file type for a file." + set_color red + echo "Cannot preview '$file_path': it is a $file_type." + set_color normal +end diff --git a/config/fish/functions/_fzf_search_directory.fish b/config/fish/functions/_fzf_search_directory.fish new file mode 100644 index 00000000..4541eec9 --- /dev/null +++ b/config/fish/functions/_fzf_search_directory.fish @@ -0,0 +1,33 @@ +function _fzf_search_directory --description "Search the current directory. Replace the current token with the selected file paths." + # Directly use fd binary to avoid output buffering delay caused by a fd alias, if any. + # Debian-based distros install fd as fdfind and the fd package is something else, so + # check for fdfind first. Fall back to "fd" for a clear error message. + set -f fd_cmd (command -v fdfind || command -v fd || echo "fd") + set -f --append fd_cmd --color=always $fzf_fd_opts + + set -f fzf_arguments --multi --ansi $fzf_directory_opts + set -f token (commandline --current-token) + # expand any variables or leading tilde (~) in the token + set -f expanded_token (eval echo -- $token) + # unescape token because it's already quoted so backslashes will mess up the path + set -f unescaped_exp_token (string unescape -- $expanded_token) + + # If the current token is a directory and has a trailing slash, + # then use it as fd's base directory. + if string match --quiet -- "*/" $unescaped_exp_token && test -d "$unescaped_exp_token" + set --append fd_cmd --base-directory=$unescaped_exp_token + # use the directory name as fzf's prompt to indicate the search is limited to that directory + set --prepend fzf_arguments --prompt="Directory $unescaped_exp_token> " --preview="_fzf_preview_file $expanded_token{}" + set -f file_paths_selected $unescaped_exp_token($fd_cmd 2>/dev/null | _fzf_wrapper $fzf_arguments) + else + set --prepend fzf_arguments --prompt="Directory> " --query="$unescaped_exp_token" --preview='_fzf_preview_file {}' + set -f file_paths_selected ($fd_cmd 2>/dev/null | _fzf_wrapper $fzf_arguments) + end + + + if test $status -eq 0 + commandline --current-token --replace -- (string escape -- $file_paths_selected | string join ' ') + end + + commandline --function repaint +end diff --git a/config/fish/functions/_fzf_search_git_log.fish b/config/fish/functions/_fzf_search_git_log.fish new file mode 100644 index 00000000..aa54724d --- /dev/null +++ b/config/fish/functions/_fzf_search_git_log.fish @@ -0,0 +1,36 @@ +function _fzf_search_git_log --description "Search the output of git log and preview commits. Replace the current token with the selected commit hash." + if not git rev-parse --git-dir >/dev/null 2>&1 + echo '_fzf_search_git_log: Not in a git repository.' >&2 + else + if not set --query fzf_git_log_format + # %h gives you the abbreviated commit hash, which is useful for saving screen space, but we will have to expand it later below + set -f fzf_git_log_format '%C(bold blue)%h%C(reset) - %C(cyan)%ad%C(reset) %C(yellow)%d%C(reset) %C(normal)%s%C(reset) %C(dim normal)[%an]%C(reset)' + end + + set -f preview_cmd 'git show --color=always --stat --patch {1}' + if set --query fzf_diff_highlighter + set preview_cmd "$preview_cmd | $fzf_diff_highlighter" + end + + set -f selected_log_lines ( + git log --no-show-signature --color=always --format=format:$fzf_git_log_format --date=short | \ + _fzf_wrapper --ansi \ + --multi \ + --scheme=history \ + --prompt="Git Log> " \ + --preview=$preview_cmd \ + --query=(commandline --current-token) \ + $fzf_git_log_opts + ) + if test $status -eq 0 + for line in $selected_log_lines + set -f abbreviated_commit_hash (string split --field 1 " " $line) + set -f full_commit_hash (git rev-parse $abbreviated_commit_hash) + set -f --append commit_hashes $full_commit_hash + end + commandline --current-token --replace (string join ' ' $commit_hashes) + end + end + + commandline --function repaint +end diff --git a/config/fish/functions/_fzf_search_git_status.fish b/config/fish/functions/_fzf_search_git_status.fish new file mode 100644 index 00000000..358f88c5 --- /dev/null +++ b/config/fish/functions/_fzf_search_git_status.fish @@ -0,0 +1,41 @@ +function _fzf_search_git_status --description "Search the output of git status. Replace the current token with the selected file paths." + if not git rev-parse --git-dir >/dev/null 2>&1 + echo '_fzf_search_git_status: Not in a git repository.' >&2 + else + set -f preview_cmd '_fzf_preview_changed_file {}' + if set --query fzf_diff_highlighter + set preview_cmd "$preview_cmd | $fzf_diff_highlighter" + end + + set -f selected_paths ( + # Pass configuration color.status=always to force status to use colors even though output is sent to a pipe + git -c color.status=always status --short | + _fzf_wrapper --ansi \ + --multi \ + --prompt="Git Status> " \ + --query=(commandline --current-token) \ + --preview=$preview_cmd \ + --nth="2.." \ + $fzf_git_status_opts + ) + if test $status -eq 0 + # git status --short automatically escapes the paths of most files for us so not going to bother trying to handle + # the few edges cases of weird file names that should be extremely rare (e.g. "this;needs;escaping") + set -f cleaned_paths + + for path in $selected_paths + if test (string sub --length 1 $path) = R + # path has been renamed and looks like "R LICENSE -> LICENSE.md" + # extract the path to use from after the arrow + set --append cleaned_paths (string split -- "-> " $path)[-1] + else + set --append cleaned_paths (string sub --start=4 $path) + end + end + + commandline --current-token --replace -- (string join ' ' $cleaned_paths) + end + end + + commandline --function repaint +end diff --git a/config/fish/functions/_fzf_search_history.fish b/config/fish/functions/_fzf_search_history.fish new file mode 100644 index 00000000..cafbce98 --- /dev/null +++ b/config/fish/functions/_fzf_search_history.fish @@ -0,0 +1,39 @@ +function _fzf_search_history --description "Search command history. Replace the command line with the selected command." + # history merge incorporates history changes from other fish sessions + # it errors out if called in private mode + if test -z "$fish_private_mode" + builtin history merge + end + + if not set --query fzf_history_time_format + # Reference https://devhints.io/strftime to understand strftime format symbols + set -f fzf_history_time_format "%m-%d %H:%M:%S" + end + + # Delinate time from command in history entries using the vertical box drawing char (U+2502). + # Then, to get raw command from history entries, delete everything up to it. The ? on regex is + # necessary to make regex non-greedy so it won't match into commands containing the char. + set -f time_prefix_regex '^.*? │ ' + # Delinate commands throughout pipeline using null rather than newlines because commands can be multi-line + set -f commands_selected ( + builtin history --null --show-time="$fzf_history_time_format │ " | + _fzf_wrapper --read0 \ + --print0 \ + --multi \ + --scheme=history \ + --prompt="History> " \ + --query=(commandline) \ + --preview="string replace --regex '$time_prefix_regex' '' -- {} | fish_indent --ansi" \ + --preview-window="bottom:3:wrap" \ + $fzf_history_opts | + string split0 | + # remove timestamps from commands selected + string replace --regex $time_prefix_regex '' + ) + + if test $status -eq 0 + commandline --replace -- $commands_selected + end + + commandline --function repaint +end diff --git a/config/fish/functions/_fzf_search_processes.fish b/config/fish/functions/_fzf_search_processes.fish new file mode 100644 index 00000000..133a8806 --- /dev/null +++ b/config/fish/functions/_fzf_search_processes.fish @@ -0,0 +1,32 @@ +function _fzf_search_processes --description "Search all running processes. Replace the current token with the pid of the selected process." + # Directly use ps command because it is often aliased to a different command entirely + # or with options that dirty the search results and preview output + set -f ps_cmd (command -v ps || echo "ps") + # use all caps to be consistent with ps default format + # snake_case because ps doesn't seem to allow spaces in the field names + set -f ps_preview_fmt (string join ',' 'pid' 'ppid=PARENT' 'user' '%cpu' 'rss=RSS_IN_KB' 'start=START_TIME' 'command') + set -f processes_selected ( + $ps_cmd -A -opid,command | \ + _fzf_wrapper --multi \ + --prompt="Processes> " \ + --query (commandline --current-token) \ + --ansi \ + # first line outputted by ps is a header, so we need to mark it as so + --header-lines=1 \ + # ps uses exit code 1 if the process was not found, in which case show an message explaining so + --preview="$ps_cmd -o '$ps_preview_fmt' -p {1} || echo 'Cannot preview {1} because it exited.'" \ + --preview-window="bottom:4:wrap" \ + $fzf_processes_opts + ) + + if test $status -eq 0 + for process in $processes_selected + set -f --append pids_selected (string split --no-empty --field=1 -- " " $process) + end + + # string join to replace the newlines outputted by string split with spaces + commandline --current-token --replace -- (string join ' ' $pids_selected) + end + + commandline --function repaint +end diff --git a/config/fish/functions/_fzf_search_variables.fish b/config/fish/functions/_fzf_search_variables.fish new file mode 100644 index 00000000..52a7c701 --- /dev/null +++ b/config/fish/functions/_fzf_search_variables.fish @@ -0,0 +1,47 @@ +# This function expects the following two arguments: +# argument 1 = output of (set --show | psub), i.e. a file with the scope info and values of all variables +# argument 2 = output of (set --names | psub), i.e. a file with all variable names +function _fzf_search_variables --argument-names set_show_output set_names_output --description "Search and preview shell variables. Replace the current token with the selected variable." + if test -z "$set_names_output" + printf '%s\n' '_fzf_search_variables requires 2 arguments.' >&2 + + commandline --function repaint + return 22 # 22 means invalid argument in POSIX + end + + # Exclude the history variable from being piped into fzf because + # 1. it's not included in $set_names_output + # 2. it tends to be a very large value => increases computation time + # 3._fzf_search_history is a much better way to examine history anyway + set -f all_variable_names (string match --invert history <$set_names_output) + + set -f current_token (commandline --current-token) + # Use the current token to pre-populate fzf's query. If the current token begins + # with a $, remove it from the query so that it will better match the variable names + set -f cleaned_curr_token (string replace -- '$' '' $current_token) + + set -f variable_names_selected ( + printf '%s\n' $all_variable_names | + _fzf_wrapper --preview "_fzf_extract_var_info {} $set_show_output" \ + --prompt="Variables> " \ + --preview-window="wrap" \ + --multi \ + --query=$cleaned_curr_token \ + $fzf_variables_opts + ) + + if test $status -eq 0 + # If the current token begins with a $, do not overwrite the $ when + # replacing the current token with the selected variable. + # Uses brace expansion to prepend $ to each variable name. + commandline --current-token --replace ( + if string match --quiet -- '$*' $current_token + string join " " \${$variable_names_selected} + else + string join " " $variable_names_selected + end + ) + end + + commandline --function repaint +end diff --git a/config/fish/functions/_fzf_wrapper.fish b/config/fish/functions/_fzf_wrapper.fish new file mode 100644 index 00000000..486e36c3 --- /dev/null +++ b/config/fish/functions/_fzf_wrapper.fish @@ -0,0 +1,21 @@ +function _fzf_wrapper --description "Prepares some environment variables before executing fzf." + # Make sure fzf uses fish to execute preview commands, some of which + # are autoloaded fish functions so don't exist in other shells. + # Use --function so that it doesn't clobber SHELL outside this function. + set -f --export SHELL (command --search fish) + + # If neither FZF_DEFAULT_OPTS nor FZF_DEFAULT_OPTS_FILE are set, then set some sane defaults. + # See https://github.com/junegunn/fzf#environment-variables + set --query FZF_DEFAULT_OPTS FZF_DEFAULT_OPTS_FILE + if test $status -eq 2 + # cycle allows jumping between the first and last results, making scrolling faster + # layout=reverse lists results top to bottom, mimicking the familiar layouts of git log, history, and env + # border shows where the fzf window begins and ends + # height=90% leaves space to see the current command and some scrollback, maintaining context of work + # preview-window=wrap wraps long lines in the preview window, making reading easier + # marker=* makes the multi-select marker more distinguishable from the pointer (since both default to >) + set --export FZF_DEFAULT_OPTS '--cycle --layout=reverse --border --height=90% --preview-window=wrap --marker="*"' + end + + fzf $argv +end diff --git a/config/fish/functions/bob.fish b/config/fish/functions/bob.fish index 25ca32a5..d642c31b 100644 --- a/config/fish/functions/bob.fish +++ b/config/fish/functions/bob.fish @@ -2,7 +2,9 @@ function bob --wraps bob command bob $argv set used (cat ~/.local/share/bob/used) - test -L ~/.local/share/bob/active - and unlink ~/.local/share/bob/active - ln -s ~/.local/share/bob/$used/nvim-linux64 ~/.local/share/bob/active + set src ~/.local/share/bob/$used + set dest ~/.local/share/bob-nvim + test -L $dest + and unlink $dest + ln -s $src/nvim-linux64 $dest end diff --git a/config/fish/functions/fzf_configure_bindings.fish b/config/fish/functions/fzf_configure_bindings.fish new file mode 100644 index 00000000..4b4e7a2b --- /dev/null +++ b/config/fish/functions/fzf_configure_bindings.fish @@ -0,0 +1,46 @@ +# Always installs bindings for insert and default mode for simplicity and b/c it has almost no side-effect +# https://gitter.im/fish-shell/fish-shell?at=60a55915ee77a74d685fa6b1 +function fzf_configure_bindings --description "Installs the default key bindings for fzf.fish with user overrides passed as options." + # no need to install bindings if not in interactive mode or running tests + status is-interactive || test "$CI" = true; or return + + set -f options_spec h/help 'directory=?' 'git_log=?' 'git_status=?' 'history=?' 'processes=?' 'variables=?' + argparse --max-args=0 --ignore-unknown $options_spec -- $argv 2>/dev/null + if test $status -ne 0 + echo "Invalid option or a positional argument was provided." >&2 + _fzf_configure_bindings_help + return 22 + else if set --query _flag_help + _fzf_configure_bindings_help + return + else + # Initialize with default key sequences and then override or disable them based on flags + # index 1 = directory, 2 = git_log, 3 = git_status, 4 = history, 5 = processes, 6 = variables + set -f key_sequences \e\cf \e\cl \e\cs \cr \e\cp \cv # \c = control, \e = escape + set --query _flag_directory && set key_sequences[1] "$_flag_directory" + set --query _flag_git_log && set key_sequences[2] "$_flag_git_log" + set --query _flag_git_status && set key_sequences[3] "$_flag_git_status" + set --query _flag_history && set key_sequences[4] "$_flag_history" + set --query _flag_processes && set key_sequences[5] "$_flag_processes" + set --query _flag_variables && set key_sequences[6] "$_flag_variables" + + # If fzf bindings already exists, uninstall it first for a clean slate + if functions --query _fzf_uninstall_bindings + _fzf_uninstall_bindings + end + + for mode in default insert + test -n $key_sequences[1] && bind --mode $mode $key_sequences[1] _fzf_search_directory + test -n $key_sequences[2] && bind --mode $mode $key_sequences[2] _fzf_search_git_log + test -n $key_sequences[3] && bind --mode $mode $key_sequences[3] _fzf_search_git_status + test -n $key_sequences[4] && bind --mode $mode $key_sequences[4] _fzf_search_history + test -n $key_sequences[5] && bind --mode $mode $key_sequences[5] _fzf_search_processes + test -n $key_sequences[6] && bind --mode $mode $key_sequences[6] "$_fzf_search_vars_command" + end + + function _fzf_uninstall_bindings --inherit-variable key_sequences + bind --erase -- $key_sequences + bind --erase --mode insert -- $key_sequences + end + end +end diff --git a/nvim/init.lua b/nvim/init.lua index f6c33249..382e1fe7 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -20,10 +20,10 @@ vim.print = _G.dd pcall(require, "config.env") require("config.lazy")({ - debug = false, + -- debug = false, profiling = { loader = false, - require = true, + require = false, }, }) diff --git a/nvim/lazy-lock.json b/nvim/lazy-lock.json index fd37b1f5..06bc7187 100644 --- a/nvim/lazy-lock.json +++ b/nvim/lazy-lock.json @@ -1,20 +1,20 @@ { "CopilotChat.nvim": { "branch": "canary", "commit": "82923efe22b604cf9c0cad0bb2a74aa9247755ab" }, - "SchemaStore.nvim": { "branch": "main", "commit": "bc29cb05bb7a9845fbb04ae14ce72434bd271191" }, + "SchemaStore.nvim": { "branch": "main", "commit": "493250022db69edd8afe8e6d0f17105756a3b721" }, "bufferline.nvim": { "branch": "main", "commit": "99337f63f0a3c3ab9519f3d1da7618ca4f91cffe" }, - "catppuccin": { "branch": "main", "commit": "5215ea59df6d0a7e27da9a5cd1165e06d1b04cbe" }, + "catppuccin": { "branch": "main", "commit": "cc8e290d4c0d572171243087f8541e49be2c8764" }, "cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" }, "cmp-git": { "branch": "main", "commit": "8dfbc33fb32c33e5c0be9dcc8176a4f4d395f95e" }, "cmp-nvim-lsp": { "branch": "main", "commit": "39e2eda76828d88b773cc27a3f61d2ad782c922d" }, "cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" }, - "conform.nvim": { "branch": "master", "commit": "cf562dd160c27a7fc5342dfce7e1227746dd3aaa" }, + "conform.nvim": { "branch": "master", "commit": "07d1298739cd7c616cb683bfd848f6b369f93297" }, "copilot-cmp": { "branch": "master", "commit": "72fbaa03695779f8349be3ac54fa8bd77eed3ee3" }, "copilot.lua": { "branch": "master", "commit": "f7612f5af4a7d7615babf43ab1e67a2d790c13a6" }, "dashboard-nvim": { "branch": "master", "commit": "5346d023afc4bfc7ff63d05c70bcdb0784bb657a" }, "dial.nvim": { "branch": "master", "commit": "7af2daaaf933b0617ded0f78b49f4d5fc45f9f64" }, "dressing.nvim": { "branch": "master", "commit": "e3714c8049b2243e792492c4149e4cc395c68eb9" }, "friendly-snippets": { "branch": "main", "commit": "700c4a25caacbb4648c9a27972c2fe203948e0c2" }, - "fzf-lua": { "branch": "main", "commit": "cf0dffd59ab0386f78579ec343091b9c39002061" }, + "fzf-lua": { "branch": "main", "commit": "5d1e32e0cc7d4a23c528c2df61cd32d8b122bb06" }, "headlines.nvim": { "branch": "master", "commit": "618ef1b2502c565c82254ef7d5b04402194d9ce3" }, "inc-rename.nvim": { "branch": "main", "commit": "535b508c0cb14d00c1836ad901b3c531cb1152bb" }, "indent-blankline.nvim": { "branch": "master", "commit": "d98f537c3492e87b6dc6c2e3f66ac517528f406f" }, @@ -44,23 +44,21 @@ "nvim-lspconfig": { "branch": "master", "commit": "4d38bece98300e3e5cd24a9aa0d0ebfea4951c16" }, "nvim-nio": { "branch": "master", "commit": "7969e0a8ffabdf210edd7978ec954a47a737bbcc" }, "nvim-notify": { "branch": "master", "commit": "d333b6f167900f6d9d42a59005d82919830626bf" }, - "nvim-snippets": { "branch": "main", "commit": "cff37a199a92f3c3cd52bb1593a7d01669b35d23" }, + "nvim-snippets": { "branch": "main", "commit": "5c978b3ba0c504dc9f94ca93d17029f26064d229" }, "nvim-spectre": { "branch": "master", "commit": "ec67d4b5370094b923dfcf6b09b39142f2964861" }, - "nvim-treesitter": { "branch": "master", "commit": "b47dde81a0a4b9b62e9a73a71ff0df2202323dd9" }, + "nvim-treesitter": { "branch": "master", "commit": "9a7ad2ff7a7ea81016aca2fc89c9b2c1a5365421" }, "nvim-treesitter-textobjects": { "branch": "master", "commit": "34867c69838078df7d6919b130c0541c0b400c47" }, - "nvim-ts-autotag": { "branch": "main", "commit": "6eb4120a1aadef07ac312f1c4bc6456712220007" }, + "nvim-ts-autotag": { "branch": "main", "commit": "2692808eca8a4ac3311516a1c4a14bb97ecc6482" }, "nvim-web-devicons": { "branch": "master", "commit": "c0cfc1738361b5da1cd0a962dd6f774cc444f856" }, "octo.nvim": { "branch": "master", "commit": "22f34582a4eb1fb221eafd0daa9eb1b2bacfb813" }, "perfanno.nvim": { "branch": "master", "commit": "b138718bf4289b429dc81cadaf80ace8221c647b" }, "plenary.nvim": { "branch": "master", "commit": "a3e3bc82a3f95c5ed0d7201546d5d2c19b20d683" }, "qmk.nvim": { "branch": "main", "commit": "cfa6cecae362d23778cd97317d33ab12671e157c" }, "tailwindcss-colorizer-cmp.nvim": { "branch": "main", "commit": "3d3cd95e4a4135c250faf83dd5ed61b8e5502b86" }, - "telescope-fzf-native.nvim": { "branch": "main", "commit": "9ef21b2e6bb6ebeaf349a0781745549bbb870d27" }, - "telescope.nvim": { "branch": "master", "commit": "f12b15e1b3a33524eb06a1ae7bc852fb1fd92197" }, "tree-sitter-caddy": { "branch": "master", "commit": "65b60437983933d00809c8927e7d8a29ca26dfa3" }, "tree-sitter-just": { "branch": "main", "commit": "fd814fc6c579f68c2a642f5e0268cf69daae92d7" }, "treesj": { "branch": "main", "commit": "f98deb33805485b56a8d44d1a27d16874af00d7f" }, - "venv-selector.nvim": { "branch": "regexp", "commit": "203c9046e1c0787ec00edfd456b30e4381afbfd1" }, + "venv-selector.nvim": { "branch": "regexp", "commit": "d946b1e86212f38ff9c42e3b622a8178bbc93461" }, "vim-dadbod": { "branch": "master", "commit": "7888cb7164d69783d3dce4e0283decd26b82538b" }, "vim-dadbod-completion": { "branch": "master", "commit": "5d5ad196fcde223509d7dabbade0148f7884c5e3" }, "vim-dadbod-ui": { "branch": "master", "commit": "0dc68d9225a70d42f8645049482e090c1a8dce25" }, diff --git a/nvim/lua/plugins/telescope.lua b/nvim/lua/plugins/telescope.lua index e37ed3c8..f7d2832a 100644 --- a/nvim/lua/plugins/telescope.lua +++ b/nvim/lua/plugins/telescope.lua @@ -1,6 +1,40 @@ return { + { + "ibhagwan/fzf-lua", + optional = true, + keys = { + { + "fp", + LazyVim.pick("files", { + cwd = require("lazy.core.config").options.root, + }), + desc = "Find Plugin File", + }, + { + "fl", + function() + local files = {} ---@type table + for _, plugin in pairs(require("lazy.core.config").plugins) do + repeat + if plugin._.module then + local info = vim.loader.find(plugin._.module)[1] + if info then + files[info.modpath] = info.modpath + end + end + plugin = plugin._.super + until not plugin + end + local filespec = table.concat(vim.tbl_values(files), " ") + require("fzf-lua").live_grep({ filespec = "-- " .. filespec, search = "/" }) + end, + desc = "Find Lazy Plugin Spec", + }, + }, + }, { "telescope.nvim", + optional = true, keys = { { "fp",