diff --git a/assets/default/fish/init.fish b/assets/default/fish/init.fish index 69e5efb..a1f7a73 100644 --- a/assets/default/fish/init.fish +++ b/assets/default/fish/init.fish @@ -20,8 +20,9 @@ function _source_synced --description "Sources file from synced dir as well as o end end -if status --is-interactive - _source_synced 'init/alias' -end _source_synced 'init/paths' _source_synced 'init/env' +if status --is-interactive + _source_synced 'init/alias' + _source_synced 'init/fzf' +end diff --git a/assets/default/fish/init/fzf.fish b/assets/default/fish/init/fzf.fish new file mode 100644 index 0000000..11e7408 --- /dev/null +++ b/assets/default/fish/init/fzf.fish @@ -0,0 +1,7 @@ +if fzf --fish 2&> /dev/null + fzf --fish | source +else + # If we can't get completions from fzf, we can get the last built + # completions and functions here + source $fish_synced_dir/init/fzf_integration.fish +end diff --git a/assets/default/fish/init/fzf_integration.fish b/assets/default/fish/init/fzf_integration.fish new file mode 100644 index 0000000..d99fb74 --- /dev/null +++ b/assets/default/fish/init/fzf_integration.fish @@ -0,0 +1,208 @@ +### key-bindings.fish ### +# ____ ____ +# / __/___ / __/ +# / /_/_ / / /_ +# / __/ / /_/ __/ +# /_/ /___/_/ key-bindings.fish +# +# - $FZF_TMUX_OPTS +# - $FZF_CTRL_T_COMMAND +# - $FZF_CTRL_T_OPTS +# - $FZF_CTRL_R_OPTS +# - $FZF_ALT_C_COMMAND +# - $FZF_ALT_C_OPTS + + +# Key bindings +# ------------ +function fzf_key_bindings + + function __fzf_defaults + # $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS + # $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS + test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% + echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1] + test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE + echo $FZF_DEFAULT_OPTS $argv[2] + end + + # Store current token in $dir as root for the 'find' command + function fzf-file-widget -d "List files and folders" + set -l commandline (__fzf_parse_commandline) + set -lx dir $commandline[1] + set -l fzf_query $commandline[2] + set -l prefix $commandline[3] + + test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% + begin + set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_CTRL_T_OPTS") + set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" + set -lx FZF_DEFAULT_OPTS_FILE '' + eval (__fzfcmd) -m --query=$fzf_query | while read -l r; set -a result $r; end + end + if test -z "$result" + commandline -f repaint + return + else + # Remove last token from commandline. + commandline -t "" + end + for i in $result + commandline -it -- $prefix + commandline -it -- (string escape -- $i) + commandline -it -- ' ' + end + commandline -f repaint + end + + function fzf-history-widget -d "Show command history" + test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% + begin + set -l FISH_MAJOR (string split '.' -- $version)[1] + set -l FISH_MINOR (string split '.' -- $version)[2] + + # merge history from other sessions before searching + test -z "$fish_private_mode"; and builtin history merge + + # history's -z flag is needed for multi-line support. + # history's -z flag was added in fish 2.4.0, so don't use it for versions + # before 2.4.0. + if test "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) + set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m") + set -lx FZF_DEFAULT_OPTS_FILE '' + if type -q perl + builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result + and commandline -- $result + else + set -l line 0 + for i in (builtin history -z --reverse | string split0) + set line (math $line + 1) + string escape -n -- $line\t$i + end | string join0 | string replace -a '\n' '\n\t' | string unescape -n | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result + and commandline -- $result + end + else + builtin history | eval (__fzfcmd) -q '(commandline)' | read -l result + and commandline -- $result + end + end + commandline -f repaint + end + + function fzf-cd-widget -d "Change directory" + set -l commandline (__fzf_parse_commandline) + set -lx dir $commandline[1] + set -l fzf_query $commandline[2] + set -l prefix $commandline[3] + + test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% + begin + set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_ALT_C_OPTS") + set -lx FZF_DEFAULT_OPTS_FILE '' + set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" + eval (__fzfcmd) +m --query=$fzf_query | read -l result + + if test -n "$result" + cd -- $result + + # Remove last token from commandline. + commandline -t "" + commandline -it -- $prefix + end + end + + commandline -f repaint + end + + function __fzfcmd + test -n "$FZF_TMUX"; or set FZF_TMUX 0 + test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% + if test -n "$FZF_TMUX_OPTS" + echo "fzf-tmux $FZF_TMUX_OPTS -- " + else if test "$FZF_TMUX" = "1" + echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- " + else + echo "fzf" + end + end + + bind \cr fzf-history-widget + if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" + bind \ct fzf-file-widget + end + if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" + bind \ec fzf-cd-widget + end + + if bind -M insert &> /dev/null + bind -M insert \cr fzf-history-widget + if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" + bind -M insert \ct fzf-file-widget + end + if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" + bind -M insert \ec fzf-cd-widget + end + end + + function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' + set -l commandline (commandline -t) + + # strip -option= from token if present + set -l prefix (string match -r -- '^-[^\s=]+=' $commandline) + set commandline (string replace -- "$prefix" '' $commandline) + + # escape special characters, except for the $ sign of valid variable names, + # so that after eval, the original string is returned, but with the + # variable names replaced by their values. + set commandline (string escape -n -- $commandline) + set commandline (string replace -r -a '\x5c\$(?=[\w])' '\$' -- $commandline) + + # eval is used to do shell expansion on paths + eval set commandline $commandline + + # Combine multiple consecutive slashes into one + set commandline (string replace -r -a '/+' '/' -- $commandline) + + if test -z "$commandline" + # Default to current directory with no --query + set dir '.' + set fzf_query '' + else + set dir (__fzf_get_dir $commandline) + + # BUG: on combined expressions, if a left argument is a single `!`, the + # builtin test command of fish will treat it as the ! operator. To + # overcome this, have the variable parts on the right. + if test "." = "$dir" -a "." != (string sub -l 1 -- $commandline) + # if $dir is "." but commandline is not a relative path, this means no file path found + set fzf_query $commandline + else + # Also remove trailing slash after dir, to "split" input properly + set fzf_query (string replace -r "^$dir/?" '' -- $commandline) + end + end + + echo (string escape -- $dir) + echo (string escape -- $fzf_query) + echo $prefix + end + + function __fzf_get_dir -d 'Find the longest existing filepath from input string' + set dir $argv + + # Strip trailing slash, unless $dir is root dir (/) + set dir (string replace -r '(?