diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2015-03-13 13:08:42 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2015-03-13 17:41:00 +0900 |
commit | 3935aa84d8cc66c0371926c61afe89832f00df10 (patch) | |
tree | 1c5ae8f1cdf7f6cf850ae296e12b10c1bef9de2e /shell | |
parent | dd6138a6559363871d88c594ab9d170e36959129 (diff) |
Refactor shell extensions
- Use symlinks instead of generating the full content
- Update fish_user_paths and remove ~/.config/fish/functions/fzf.fish
- Create wrapper script for fzf when Ruby version and use it instead of
exported function not to break fzf-tmux
Diffstat (limited to 'shell')
-rw-r--r-- | shell/completion.bash | 242 | ||||
-rw-r--r-- | shell/key-bindings.bash | 75 | ||||
-rw-r--r-- | shell/key-bindings.fish | 79 | ||||
-rw-r--r-- | shell/key-bindings.zsh | 59 |
4 files changed, 455 insertions, 0 deletions
diff --git a/shell/completion.bash b/shell/completion.bash new file mode 100644 index 00000000..e609d2be --- /dev/null +++ b/shell/completion.bash @@ -0,0 +1,242 @@ +#!/bin/bash +# ____ ____ +# / __/___ / __/ +# / /_/_ / / /_ +# / __/ / /_/ __/ +# /_/ /___/_/-completion.bash +# +# - $FZF_COMPLETION_TRIGGER (default: '**') +# - $FZF_COMPLETION_OPTS (default: empty) + +_fzf_orig_completion_filter() { + sed 's/.*-F *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\2=\1;/' | + sed 's/[^a-z0-9_= ;]/_/g' +} + +_fzf_opts_completion() { + local cur opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=" + -x --extended + -e --extended-exact + -i +i + -n --nth + -d --delimiter + +s --no-sort + --tac + -m --multi + --no-mouse + +c --no-color + +2 --no-256 + --black + --reverse + --prompt + -q --query + -1 --select-1 + -0 --exit-0 + -f --filter + --print-query + --sync" + + if [[ ${cur} =~ ^-|\+ ]]; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + return 0 +} + +_fzf_handle_dynamic_completion() { + local cmd orig ret + cmd="$1" + shift + + orig=$(eval "echo \$_fzf_orig_completion_$cmd") + if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then + $orig "$@" + elif [ -n "$_fzf_completion_loader" ]; then + _completion_loader "$@" + ret=$? + eval $(complete | \grep "\-F.* $cmd$" | _fzf_orig_completion_filter) + source $BASH_SOURCE + return $ret + fi +} + +_fzf_path_completion() { + local cur base dir leftover matches trigger cmd + cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') + COMPREPLY=() + trigger=${FZF_COMPLETION_TRIGGER:-**} + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ ${cur} == *"$trigger" ]]; then + base=${cur:0:${#cur}-${#trigger}} + eval base=$base + + dir="$base" + while [ 1 ]; do + if [ -z "$dir" -o -d "$dir" ]; then + leftover=${base/#"$dir"} + leftover=${leftover/#\/} + [ "$dir" = './' ] && dir='' + tput sc + matches=$(find -L "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do + printf "%q$3 " "$item" + done) + matches=${matches% } + if [ -n "$matches" ]; then + COMPREPLY=( "$matches" ) + else + COMPREPLY=( "$cur" ) + fi + tput rc + return 0 + fi + dir=$(dirname "$dir") + [[ "$dir" =~ /$ ]] || dir="$dir"/ + done + else + shift + shift + shift + _fzf_handle_dynamic_completion "$cmd" "$@" + fi +} + +_fzf_list_completion() { + local cur selected trigger cmd src + read -r src + cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') + trigger=${FZF_COMPLETION_TRIGGER:-**} + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ ${cur} == *"$trigger" ]]; then + cur=${cur:0:${#cur}-${#trigger}} + + tput sc + selected=$(eval "$src | fzf $FZF_COMPLETION_OPTS $1 -q '$cur'" | tr '\n' ' ') + selected=${selected% } + tput rc + + if [ -n "$selected" ]; then + COMPREPLY=("$selected") + return 0 + fi + else + shift + _fzf_handle_dynamic_completion "$cmd" "$@" + fi +} + +_fzf_all_completion() { + _fzf_path_completion \ + "-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \ + "-m" "" "$@" +} + +_fzf_file_completion() { + _fzf_path_completion \ + "-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \ + "-m" "" "$@" +} + +_fzf_dir_completion() { + _fzf_path_completion \ + "-name .git -prune -o -name .svn -prune -o -type d -print" \ + "" "/" "$@" +} + +_fzf_kill_completion() { + [ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1 + + local selected + tput sc + selected=$(ps -ef | sed 1d | fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ') + tput rc + + if [ -n "$selected" ]; then + COMPREPLY=( "$selected" ) + return 0 + fi +} + +_fzf_telnet_completion() { + _fzf_list_completion '+m' "$@" << "EOF" + \grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{if (length($2) > 0) {print $2}}' | sort -u +EOF +} + +_fzf_ssh_completion() { + _fzf_list_completion '+m' "$@" << "EOF" + cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i ^host | \grep -v '*') <(\grep -v '^\s*\(#\|$\)' /etc/hosts) | awk '{print $2}' | sort -u +EOF +} + +_fzf_env_var_completion() { + _fzf_list_completion '-m' "$@" << "EOF" + declare -xp | sed 's/=.*//' | sed 's/.* //' +EOF +} + +_fzf_alias_completion() { + _fzf_list_completion '-m' "$@" << "EOF" + alias | sed 's/=.*//' | sed 's/.* //' +EOF +} + +# fzf options +complete -F _fzf_opts_completion fzf + +d_cmds="cd pushd rmdir" +f_cmds=" + awk cat diff diff3 + emacs ex file ftp g++ gcc gvim head hg java + javac ld less more mvim patch perl python ruby + sed sftp sort source tail tee uniq vi view vim wc" +a_cmds=" + basename bunzip2 bzip2 chmod chown curl cp dirname du + find git grep gunzip gzip hg jar + ln ls mv open rm rsync scp + svn tar unzip zip" +x_cmds="kill ssh telnet unset unalias export" + +# Preserve existing completion +if [ "$_fzf_completion_loaded" != '0.8.6-1' ]; then + # Really wish I could use associative array but OSX comes with bash 3.2 :( + eval $(complete | \grep '\-F' | \grep -v _fzf_ | + \grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter) + export _fzf_completion_loaded=0.8.6-1 +fi + +if type _completion_loader > /dev/null 2>&1; then + _fzf_completion_loader=1 +fi + +# Directory +for cmd in $d_cmds; do + complete -F _fzf_dir_completion -o nospace -o plusdirs $cmd +done + +# File +for cmd in $f_cmds; do + complete -F _fzf_file_completion -o default -o bashdefault $cmd +done + +# Anything +for cmd in $a_cmds; do + complete -F _fzf_all_completion -o default -o bashdefault $cmd +done + +# Kill completion +complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill + +# Host completion +complete -F _fzf_ssh_completion -o default -o bashdefault ssh +complete -F _fzf_telnet_completion -o default -o bashdefault telnet + +# Environment variables / Aliases +complete -F _fzf_env_var_completion -o default -o bashdefault unset +complete -F _fzf_env_var_completion -o default -o bashdefault export +complete -F _fzf_alias_completion -o default -o bashdefault unalias + +unset cmd d_cmds f_cmds a_cmds x_cmds diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash new file mode 100644 index 00000000..b7c79463 --- /dev/null +++ b/shell/key-bindings.bash @@ -0,0 +1,75 @@ +# Key bindings +# ------------ +__fsel() { + command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \ + -o -type f -print \ + -o -type d -print \ + -o -type l -print 2> /dev/null | sed 1d | cut -b3- | fzf -m | while read item; do + printf '%q ' "$item" + done + echo +} + +if [[ $- =~ i ]]; then + +__fsel_tmux() { + local height + height=${FZF_TMUX_HEIGHT:-40%} + if [[ $height =~ %$ ]]; then + height="-p ${height%\%}" + else + height="-l $height" + fi + tmux split-window $height "bash -c 'source ~/.fzf.bash; tmux send-keys -t $TMUX_PANE \"\$(__fsel)\"'" +} + +__fcd() { + local dir + dir=$(command find -L ${1:-.} \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \ + -o -type d -print 2> /dev/null | sed 1d | cut -b3- | fzf +m) && printf 'cd %q' "$dir" +} + +__use_tmux=0 +[ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1 + +if [ -z "$(set -o | \grep '^vi.*on')" ]; then + # Required to refresh the prompt after fzf + bind '"\er": redraw-current-line' + + # CTRL-T - Paste the selected file path into the command line + if [ $__use_tmux -eq 1 ]; then + bind '"\C-t": " \C-u \C-a\C-k$(__fsel_tmux)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"' + else + bind '"\C-t": " \C-u \C-a\C-k$(__fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er \C-h"' + fi + + # CTRL-R - Paste the selected command from history into the command line + bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s --tac +m -n2..,.. | sed \"s/ *[0-9]* *//\")\e\C-e\er"' + + # ALT-C - cd into the selected directory + bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"' +else + bind '"\C-x\C-e": shell-expand-line' + bind '"\C-x\C-r": redraw-current-line' + + # CTRL-T - Paste the selected file path into the command line + # - FIXME: Selected items are attached to the end regardless of cursor position + if [ $__use_tmux -eq 1 ]; then + bind '"\C-t": "\e$a \eddi$(__fsel_tmux)\C-x\C-e\e0P$xa"' + else + bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "' + fi + bind -m vi-command '"\C-t": "i\C-t"' + + # CTRL-R - Paste the selected command from history into the command line + bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s --tac +m -n2..,.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"' + bind -m vi-command '"\C-r": "i\C-r"' + + # ALT-C - cd into the selected directory + bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"' + bind -m vi-command '"\ec": "i\ec"' +fi + +unset __use_tmux + +fi diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish new file mode 100644 index 00000000..be39e3d5 --- /dev/null +++ b/shell/key-bindings.fish @@ -0,0 +1,79 @@ +# Key bindings +# ------------ +function fzf_key_bindings + # Due to a bug of fish, we cannot use command substitution, + # so we use temporary file instead + if [ -z "$TMPDIR" ] + set -g TMPDIR /tmp + end + + function __fzf_list + command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \ + -o -type f -print \ + -o -type d -print \ + -o -type l -print 2> /dev/null | sed 1d | cut -b3- + end + + function __fzf_list_dir + command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) \ + -prune -o -type d -print 2> /dev/null | sed 1d | cut -b3- + end + + function __fzf_escape + while read item + echo -n (echo -n "$item" | sed -E 's/([ "$~'\''([{<>})])/\\\\\\1/g')' ' + end + end + + function __fzf_ctrl_t + if [ -n "$TMUX_PANE" -a "$FZF_TMUX" != "0" ] + tmux split-window (__fzf_tmux_height) "fish -c 'fzf_key_bindings; __fzf_ctrl_t_tmux \\$TMUX_PANE'" + else + __fzf_list | fzf -m > $TMPDIR/fzf.result + and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) + commandline -f repaint + rm -f $TMPDIR/fzf.result + end + end + + function __fzf_ctrl_t_tmux + __fzf_list | fzf -m > $TMPDIR/fzf.result + and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result | __fzf_escape) + rm -f $TMPDIR/fzf.result + end + + function __fzf_ctrl_r + history | fzf +s +m > $TMPDIR/fzf.result + and commandline (cat $TMPDIR/fzf.result) + commandline -f repaint + rm -f $TMPDIR/fzf.result + end + + function __fzf_alt_c + # Fish hangs if the command before pipe redirects (2> /dev/null) + __fzf_list_dir | fzf +m > $TMPDIR/fzf.result + [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ] + and cd (cat $TMPDIR/fzf.result) + commandline -f repaint + rm -f $TMPDIR/fzf.result + end + + function __fzf_tmux_height + if set -q FZF_TMUX_HEIGHT + set height $FZF_TMUX_HEIGHT + else + set height 40% + end + if echo $height | \grep -q -E '%$' + echo "-p "(echo $height | sed 's/%$//') + else + echo "-l $height" + end + set -e height + end + + bind \ct '__fzf_ctrl_t' + bind \cr '__fzf_ctrl_r' + bind \ec '__fzf_alt_c' +end + diff --git a/shell/key-bindings.zsh b/shell/key-bindings.zsh new file mode 100644 index 00000000..84ba7302 --- /dev/null +++ b/shell/key-bindings.zsh @@ -0,0 +1,59 @@ +# Key bindings +# ------------ +# CTRL-T - Paste the selected file path(s) into the command line +__fsel() { + command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \ + -o -type f -print \ + -o -type d -print \ + -o -type l -print 2> /dev/null | sed 1d | cut -b3- | fzf -m | while read item; do + printf '%q ' "$item" + done + echo +} + +if [[ $- =~ i ]]; then + +if [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then + fzf-file-widget() { + local height + height=${FZF_TMUX_HEIGHT:-40%} + if [[ $height =~ %$ ]]; then + height="-p ${height%\%}" + else + height="-l $height" + fi + tmux split-window $height "zsh -c 'source ~/.fzf.zsh; tmux send-keys -t $TMUX_PANE \"\$(__fsel)\"'" + } +else + fzf-file-widget() { + LBUFFER="${LBUFFER}$(__fsel)" + zle redisplay + } +fi +zle -N fzf-file-widget +bindkey '^T' fzf-file-widget + +# ALT-C - cd into the selected directory +fzf-cd-widget() { + cd "${$(command find -L . \( -path '*/\.*' -o -fstype 'dev' -o -fstype 'proc' \) -prune \ + -o -type d -print 2> /dev/null | sed 1d | cut -b3- | fzf +m):-.}" + zle reset-prompt +} +zle -N fzf-cd-widget +bindkey '\ec' fzf-cd-widget + +# CTRL-R - Paste the selected command from history into the command line +fzf-history-widget() { + local selected + if selected=$(fc -l 1 | fzf +s --tac +m -n2..,.. -q "$LBUFFER"); then + num=$(echo "$selected" | head -1 | awk '{print $1}' | sed 's/[^0-9]//g') + LBUFFER=!$num + zle expand-history + fi + zle redisplay +} +zle -N fzf-history-widget +bindkey '^R' fzf-history-widget + +fi + |