diff --git a/shell/completion.bash b/shell/completion.bash index ded3bf25..7bba62c9 100644 --- a/shell/completion.bash +++ b/shell/completion.bash @@ -2,10 +2,10 @@ # / __/___ / __/ # / /_/_ / / /_ # / __/ / /_/ __/ -# /_/ /___/_/-completion.bash +# /_/ /___/_/ completion.bash # # - $SKIM_TMUX (default: 0) -# - $SKIM_TMUX_HEIGHT (default: '40%') +# - $SKIM_TMUX_OPTS (default: empty) # - $SKIM_COMPLETION_TRIGGER (default: '**') # - $SKIM_COMPLETION_OPTS (default: empty) # copied and modified from https://github.com/junegunn/fzf/blob/master/shell/completion.bash @@ -38,9 +38,9 @@ bind '"\e[0n": redraw-current-line' __skim_comprun() { if [ "$(type -t _skim_comprun 2>&1)" = function ]; then _skim_comprun "$@" - elif [ -n "$TMUX_PANE" ] && [ "${SKIM_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then + elif [ -n "$TMUX_PANE" ] && { [ "${SKIM_TMUX:-0}" != 0 ] || [ -n "$SKIM_TMUX_OPTS" ]; }; then shift - sk-tmux -d "${SKIM_TMUX_HEIGHT:-40%}" "$@" + sk-tmux ${SKIM_TMUX_OPTS:--d${SKIM_TMUX_HEIGHT:-40%}} -- "$@" else shift sk "$@" @@ -191,6 +191,27 @@ __skim_generic_path_completion() { } _skim_complete() { + # Split arguments around -- + local args rest str_arg i sep + args=("$@") + sep= + for i in "${!args[@]}"; do + if [[ "${args[$i]}" = -- ]]; then + sep=$i + break + fi + done + if [[ -n "$sep" ]]; then + str_arg= + rest=("${args[@]:$((sep + 1)):${#args[@]}}") + args=("${args[@]:0:$sep}") + else + str_arg=$1 + args=() + shift + rest=("$@") + fi + local cur selected trigger cmd post post="$(caller 0 | awk '{print $2}')_post" type -t "$post" > /dev/null 2>&1 || post=cat @@ -201,7 +222,7 @@ _skim_complete() { if [[ "$cur" == *"$trigger" ]]; then cur=${cur:0:${#cur}-${#trigger}} - selected=$(cat | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_COMPLETION_OPTS $1" __skim_comprun "$2" -q "$cur" | $post | tr '\n' ' ') + selected=$(SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_COMPLETION_OPTS $str_arg" __skim_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ') selected=${selected% } # Strip trailing space not to repeat "-o nospace" if [ -n "$selected" ]; then COMPREPLY=("$selected") @@ -211,8 +232,7 @@ _skim_complete() { printf '\e[5n' return 0 else - shift - _skim_handle_dynamic_completion "$cmd" "$@" + _skim_handle_dynamic_completion "$cmd" "${rest[@]}" fi } @@ -230,22 +250,30 @@ _skim_dir_completion() { } _skim_complete_kill() { - [ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1 - - local selected - selected=$(command ps -ef | sed 1d | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-50%} --min-height 15 --reverse $SKIM_DEFAULT_OPTIONS $SKIM_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __skim_comprun "kill" -m | awk '{print $2}' | tr '\n' ' ') - selected=${selected% } - printf '\e[5n' - - if [ -n "$selected" ]; then - COMPREPLY=( "$selected" ) - return 0 + local trigger=${SKIM_COMPLETION_TRIGGER-'**'} + local cur="${COMP_WORDS[COMP_CWORD]}" + if [[ -z "$cur" ]]; then + COMP_WORDS[$COMP_CWORD]=$trigger + elif [[ "$cur" != *"$trigger" ]]; then + return 1 fi + + _skim_proc_completion "$@" +} + +_skim_proc_completion() { + _skim_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <( + command ps -ef | sed 1d + ) +} + +_skim_proc_completion_post() { + awk '{print $2}' } _skim_host_completion() { - _skim_complete '--no-multi' "$@" < <( - cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ + _skim_complete --no-multi -- "$@" < <( + command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | awk '{if (length($2) > 0) {print $2}}' | sort -u @@ -253,13 +281,13 @@ _skim_host_completion() { } _skim_var_completion() { - _skim_complete '-m' "$@" < <( + _skim_complete -m -- "$@" < <( declare -xp | sed 's/=.*//' | sed 's/.* //' ) } _skim_alias_completion() { - _skim_complete '-m' "$@" < <( + _skim_complete -m -- "$@" < <( alias | sed 's/=.*//' | sed 's/.* //' ) } @@ -277,11 +305,10 @@ a_cmds=" find git grep gunzip gzip hg jar ln ls mv open rm rsync scp svn tar unzip zip" -x_cmds="kill" # Preserve existing completion eval "$(complete | - sed -E '/-F/!d; / _skim/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' | + sed -E '/-F/!d; / _skim/d; '"/ ($(echo $d_cmds $a_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' | __skim_orig_completion_filter)" if type _completion_loader > /dev/null 2>&1; then @@ -313,17 +340,17 @@ for cmd in $d_cmds; do __skim_defc "$cmd" _skim_dir_completion "-o nospace -o dirnames" done -# Kill completion +# Kill completion (supports empty completion trigger) complete -F _skim_complete_kill -o default -o bashdefault kill -unset cmd d_cmds a_cmds x_cmds +unset cmd d_cmds a_cmds _skim_setup_completion() { local kind fn cmd kind=$1 fn=_skim_${1}_completion if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then - echo "usage: ${FUNCNAME[0]} path|dir|var|alias|host COMMANDS..." + echo "usage: ${FUNCNAME[0]} path|dir|var|alias|host|proc COMMANDS..." return 1 fi shift diff --git a/shell/completion.zsh b/shell/completion.zsh index fe7ded87..6a6b2f4f 100644 --- a/shell/completion.zsh +++ b/shell/completion.zsh @@ -2,14 +2,79 @@ # / __/___ / __/ # / /_/_ / / /_ # / __/ / /_/ __/ -# /_/ /___/_/-completion.zsh +# /_/ /___/_/ completion.zsh # # - $SKIM_TMUX (default: 0) -# - $SKIM_TMUX_HEIGHT (default: '40%') +# - $SKIM_TMUX_OPTS (default: '-d 40%') # - $SKIM_COMPLETION_TRIGGER (default: '**') # - $SKIM_COMPLETION_OPTS (default: empty) -if [[ $- =~ i ]]; then +# Both branches of the following `if` do the same thing -- define +# __skim_completion_options such that `eval $__skim_completion_options` sets +# all options to the same values they currently have. We'll do just that at +# the bottom of the file after changing options to what we prefer. +# +# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted +# *must* be quoted in order to prevent alias expansion. In addition, code must +# be written in a way works with any set of zsh options. This is very tricky, so +# careful when you change it. +# +# Start by loading the builtin zsh/parameter module. It provides `options` +# associative array that stores current shell options. +if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then + # This is the fast branch and it gets taken on virtually all Zsh installations. + # + # ${(kv)options[@]} expands to array of keys (option names) and values ("on" + # or "off"). The subsequent expansion# with (j: :) flag joins all elements + # together separated by spaces. __skim_completion_options ends up with a value + # like this: "options=(shwordsplit off aliases on ...)". + __skim_completion_options="options=(${(j: :)${(kv)options[@]}})" +else + # This branch is much slower because it forks to get the names of all + # zsh options. It's possible to eliminate this fork but it's not worth the + # trouble because this branch gets taken only on very ancient or broken + # zsh installations. + () { + # That `()` above defines an anonymous function. This is essentially a scope + # for local parameters. We use it to avoid polluting global scope. + 'local' '__skim_opt' + __skim_completion_options="setopt" + # `set -o` prints one line for every zsh option. Each line contains option + # name, some spaces, and then either "on" or "off". We just want option names. + # Expansion with (@f) flag splits a string into lines. The outer expansion + # removes spaces and everything that follow them on every line. __skim_opt + # ends up iterating over option names: shwordsplit, aliases, etc. + for __skim_opt in "${(@)${(@f)$(set -o)}%% *}"; do + if [[ -o "$__skim_opt" ]]; then + # Option $__skim_opt is currently on, so remember to set it back on. + __skim_completion_options+=" -o $__skim_opt" + else + # Option $__skim_opt is currently off, so remember to set it back off. + __skim_completion_options+=" +o $__skim_opt" + fi + done + # The value of __skim_completion_options here looks like this: + # "setopt +o shwordsplit -o aliases ..." + } +fi + +# Enable the default zsh options (those marked with in `man zshoptions`) +# but without `aliases`. Aliases in functions are expanded when functions are +# defined, so if we disable aliases here, we'll be sure to have no pesky +# aliases in any of our functions. This way we won't need prefix every +# command with `command` or to quote every word to defend against global +# aliases. Note that `aliases` is not the only option that's important to +# control. There are several others that could wreck havoc if they are set +# to values we don't expect. With the following `emulate` command we +# sidestep this issue entirely. +'emulate' 'zsh' '-o' 'no_aliases' + +# This brace is the start of try-always block. The `always` part is like +# `finally` in lesser languages. We use it to *always* restore user options. +{ + +# Bail out if not interactive shell. +[[ -o interactive ]] || return 0 # To use custom commands instead of find, override _skim_compgen_{path,dir} if ! declare -f _skim_compgen_path > /dev/null; then @@ -34,9 +99,13 @@ fi __skim_comprun() { if [[ "$(type _skim_comprun 2>&1)" =~ function ]]; then _skim_comprun "$@" - elif [ -n "$TMUX_PANE" ] && [ "${SKIM_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then + elif [ -n "$TMUX_PANE" ] && { [ "${SKIM_TMUX:-0}" != 0 ] || [ -n "$SKIM_TMUX_OPTS" ]; }; then shift - sk-tmux -d "${SKIM_TMUX_HEIGHT:-40%}" "$@" + if [ -n "$SKIM_TMUX_OPTS" ]; then + sk-tmux ${(Q)${(Z+n+)SKIM_TMUX_OPTS}} -- "$@" + else + sk-tmux -d ${SKIM_TMUX_HEIGHT:-40%} -- "$@" + fi else shift sk "$@" @@ -48,6 +117,7 @@ __skim_extract_command() { local token tokens tokens=(${(z)1}) for token in $tokens; do + token=${(Q)token} if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then echo "$token" return @@ -107,16 +177,37 @@ _skim_feed_fifo() ( ) _skim_complete() { - local fifo skim_opts lbuf cmd matches post + setopt localoptions ksh_arrays + # Split arguments around -- + local args rest str_arg i sep + args=("$@") + sep= + for i in {0..${#args[@]}}; do + if [[ "${args[$i]}" = -- ]]; then + sep=$i + break + fi + done + if [[ -n "$sep" ]]; then + str_arg= + rest=("${args[@]:$((sep + 1)):${#args[@]}}") + args=("${args[@]:0:$sep}") + else + str_arg=$1 + args=() + shift + rest=("$@") + fi + + local fifo lbuf cmd matches post fifo="${TMPDIR:-/tmp}/skim-complete-fifo-$$" - skim_opts=$1 - lbuf=$2 + lbuf=${rest[0]} cmd=$(__skim_extract_command "$lbuf") - post="${funcstack[2]}_post" + post="${funcstack[1]}_post" type $post > /dev/null 2>&1 || post=cat _skim_feed_fifo "$fifo" - matches=$(cat "$fifo" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_COMPLETION_OPTS" __skim_comprun "$cmd" ${(Q)${(Z+n+)skim_opts}} -q "${(Q)prefix}" | $post | tr '\n' ' ') + matches=$(SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_COMPLETION_OPTS $str_arg" __skim_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') if [ -n "$matches" ]; then LBUFFER="$lbuf$matches" fi @@ -125,16 +216,16 @@ _skim_complete() { } _skim_complete_telnet() { - _skim_complete '-m' "$@" < <( + _skim_complete --no-multi -- "$@" < <( command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' | awk '{if (length($2) > 0) {print $2}}' | sort -u ) } _skim_complete_ssh() { - _skim_complete '-m' "$@" < <( + _skim_complete --no-multi -- "$@" < <( setopt localoptions nonomatch - command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ + command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | awk '{if (length($2) > 0) {print $2}}' | sort -u @@ -142,23 +233,33 @@ _skim_complete_ssh() { } _skim_complete_export() { - _skim_complete '-m' "$@" < <( + _skim_complete -m -- "$@" < <( declare -xp | sed 's/=.*//' | sed 's/.* //' ) } _skim_complete_unset() { - _skim_complete '-m' "$@" < <( + _skim_complete -m -- "$@" < <( declare -xp | sed 's/=.*//' | sed 's/.* //' ) } _skim_complete_unalias() { - _skim_complete '-m' "$@" < <( + _skim_complete --no-multi -- "$@" < <( alias | sed 's/=.*//' ) } +_skim_complete_kill() { + _skim_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <( + command ps -ef | sed 1d + ) +} + +_skim_complete_kill_post() { + awk '{print $2}' +} + skim-completion() { local tokens cmd prefix trigger tail matches lbuf d_cmds setopt localoptions noshwordsplit noksh_arrays noposixbuiltins @@ -183,20 +284,21 @@ skim-completion() { tokens=(${tokens[0,-2]}) fi + lbuf=$LBUFFER tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))} # Kill completion (do not require trigger sequence) - if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then - matches=$(command ps -ef | sed 1d | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-50%} --min-height 15 --reverse $SKIM_DEFAULT_OPTIONS $SKIM_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __skim_comprun "$cmd" -m | awk '{print $2}' | tr '\n' ' ') - if [ -n "$matches" ]; then - LBUFFER="$LBUFFER$matches" - fi - zle reset-prompt + if [ "$cmd" = kill -a ${LBUFFER[-1]} = ' ' ]; then + tail=$trigger + tokens+=$trigger + lbuf="$lbuf$trigger" + fi + # Trigger sequence given - elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then + if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then d_cmds=(${=SKIM_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}) [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}} - [ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}} + [ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}} if eval "type _skim_complete_${cmd} > /dev/null"; then prefix="$prefix" eval _skim_complete_${cmd} ${(q)lbuf} @@ -220,4 +322,8 @@ skim-completion() { zle -N skim-completion bindkey '^I' skim-completion -fi +} always { + # Restore the original options. + eval $__skim_completion_options + 'unset' '__skim_completion_options' +} diff --git a/shell/key-bindings.bash b/shell/key-bindings.bash index 15fab638..c5705184 100644 --- a/shell/key-bindings.bash +++ b/shell/key-bindings.bash @@ -1,3 +1,16 @@ +# ____ ____ +# / __/___ / __/ +# / /_/_ / / /_ +# / __/ / /_/ __/ +# /_/ /___/_/ key-bindings.bash +# +# - $SKIM_TMUX_OPTS +# - $SKIM_CTRL_T_COMMAND +# - $SKIM_CTRL_T_OPTS +# - $SKIM_CTRL_R_OPTS +# - $SKIM_ALT_C_COMMAND +# - $SKIM_ALT_C_OPTS + # Key bindings # ------------ # copied and modified from https://github.com/junegunn/fzf/blob/master/shell/key-bindings.bash @@ -6,7 +19,7 @@ __skim_select__() { -o -type f -print \ -o -type d -print \ -o -type l -print 2> /dev/null | cut -b3-"}" - eval "$cmd" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_CTRL_T_OPTS" sk -m "$@" | while read -r item; do + eval "$cmd" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_CTRL_T_OPTS" $(__skimcmd) -m "$@" | while read -r item; do printf '%q ' "$item" done echo @@ -14,85 +27,70 @@ __skim_select__() { if [[ $- =~ i ]]; then -__skim_use_tmux__() { - [ -n "$TMUX_PANE" ] && [ "${SKIM_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] -} - __skimcmd() { - __skim_use_tmux__ && - echo "sk-tmux -d${SKIM_TMUX_HEIGHT:-40%}" || echo "sk" -} - -__skim_select_tmux__() { - local height - height=${SKIM_TMUX_HEIGHT:-40%} - if [[ $height =~ %$ ]]; then - height="-p ${height%\%}" - else - height="-l $height" - fi - - tmux split-window $height "cd $(printf %q "$PWD"); SKIM_DEFAULT_OPTIONS=$(printf %q "$SKIM_DEFAULT_OPTIONS") PATH=$(printf %q "$PATH") SKIM_CTRL_T_COMMAND=$(printf %q "$SKIM_CTRL_T_COMMAND") SKIM_CTRL_T_OPTS=$(printf %q "$SKIM_CTRL_T_OPTS") bash -c 'source \"${BASH_SOURCE[0]}\"; RESULT=\"\$(__skim_select__ --no-height)\"; tmux setb -b skim \"\$RESULT\" \\; pasteb -b skim -t $TMUX_PANE \\; deleteb -b skim || tmux send-keys -t $TMUX_PANE \"\$RESULT\"'" + [ -n "$TMUX_PANE" ] && { [ "${SKIM_TMUX:-0}" != 0 ] || [ -n "$SKIM_TMUX_OPTS" ]; } && + echo "sk-tmux ${SKIM_TMUX_OPTS:--d${SKIM_TMUX_HEIGHT:-40%}} -- " || echo "sk" } skim-file-widget() { - if __skim_use_tmux__; then - __skim_select_tmux__ - else - local selected="$(__skim_select__)" - READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}" - READLINE_POINT=$(( READLINE_POINT + ${#selected} )) - fi + local selected="$(__skim_select__)" + READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}" + READLINE_POINT=$(( READLINE_POINT + ${#selected} )) } __skim_cd__() { local cmd dir cmd="${SKIM_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-"}" - dir=$(eval "$cmd" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_ALT_C_OPTS" $(__skimcmd) -m) && printf 'cd %q' "$dir" + dir=$(eval "$cmd" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_ALT_C_OPTS" $(__skimcmd) --no-multi) && printf 'cd %q' "$dir" } -__skim_history__() ( - local line - line=$( +__skim_history__() { + local output + output=$( builtin fc -lnr -2147483648 | - perl -p -l0 -e 'BEGIN { getc; $/ = "\n\t" } s/^[ *]//; $_ = '"$1"' - $. . "\t$_"' | - SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} $SKIM_DEFAULT_OPTIONS --tiebreak=score,index $SKIM_CTRL_R_OPTS -m --read0" $(__skimcmd) - ) - echo "${line#*$'\t'}" -) + last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' | + SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} $SKIM_DEFAULT_OPTIONS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $SKIM_CTRL_R_OPTS --no-multi --read0" $(__skimcmd) --query "$READLINE_LINE" + ) || return + READLINE_LINE=${output#*$'\t'} + if [ -z "$READLINE_POINT" ]; then + echo "$READLINE_LINE" + else + READLINE_POINT=0x7fffffff + fi +} # Required to refresh the prompt after skim bind -m emacs-standard '"\er": redraw-current-line' -# CTRL-T - Paste the selected file path into the command line -if [ $BASH_VERSINFO -gt 3 ]; then - bind -m emacs-standard -x '"\C-t": "skim-file-widget"' -elif __skim_use_tmux__; then - bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__skim_select_tmux__`\e\C-e\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"' -else - bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__skim_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"' -fi - -# CTRL-R - Paste the selected command from history into the command line -bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u__skim_history__ $HISTCMD\e\C-e`"\C-a"`\C-e\e\C-e\er"' - -# ALT-C - cd into the selected directory -bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__skim_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"' - bind -m vi-command '"\C-z": emacs-editing-mode' bind -m vi-insert '"\C-z": emacs-editing-mode' bind -m emacs-standard '"\C-z": vi-editing-mode' -# CTRL-T - Paste the selected file path into the command line -bind -m vi-command '"\C-t": "\C-z\C-t\C-z"' -bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"' +if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then + # CTRL-T - Paste the selected file path into the command line + bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__skim_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"' + bind -m vi-command '"\C-t": "\C-z\C-t\C-z"' + bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"' -# CTRL-R - Paste the selected command from history into the command line -bind -m vi-command '"\C-r": "\C-z\C-r\C-z"' -bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"' + # CTRL-R - Paste the selected command from history into the command line + bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__skim_history__)"\e\C-e\er"' + bind -m vi-command '"\C-r": "\C-z\C-r\C-z"' + bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"' +else + # CTRL-T - Paste the selected file path into the command line + bind -m emacs-standard -x '"\C-t": skim-file-widget' + bind -m vi-command -x '"\C-t": skim-file-widget' + bind -m vi-insert -x '"\C-t": skim-file-widget' + + # CTRL-R - Paste the selected command from history into the command line + bind -m emacs-standard -x '"\C-r": __skim_history__' + bind -m vi-command -x '"\C-r": __skim_history__' + bind -m vi-insert -x '"\C-r": __skim_history__' +fi # ALT-C - cd into the selected directory +bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__skim_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"' bind -m vi-command '"\ec": "\C-z\ec\C-z"' bind -m vi-insert '"\ec": "\C-z\ec\C-z"' diff --git a/shell/key-bindings.fish b/shell/key-bindings.fish index 2901e5bc..661c3a50 100644 --- a/shell/key-bindings.fish +++ b/shell/key-bindings.fish @@ -1,6 +1,21 @@ #!/bin/fish # completion.fish # copied and modified from https://github.com/junegunn/fzf/blob/master/shell/completion.fish +# ____ ____ +# / __/___ / __/ +# / /_/_ / / /_ +# / __/ / /_/ __/ +# /_/ /___/_/ key-bindings.fish +# +# - $SKIM_TMUX_OPTS +# - $SKIM_CTRL_T_COMMAND +# - $SKIM_CTRL_T_OPTS +# - $SKIM_CTRL_R_OPTS +# - $SKIM_ALT_C_COMMAND +# - $SKIM_ALT_C_OPTS + +# Key bindings +# ------------ function skim_key_bindings # Store current token in $dir as root for the 'find' command @@ -11,13 +26,13 @@ function skim_key_bindings # "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not # $dir itself, even if hidden. - set -q SKIM_CTRL_T_COMMAND; or set -l SKIM_CTRL_T_COMMAND " + test -n "$SKIM_CTRL_T_COMMAND"; or set -l SKIM_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@^\./@@'" - set -q SKIM_TMUX_HEIGHT; or set SKIM_TMUX_HEIGHT 40% + test -n "$SKIM_TMUX_HEIGHT"; or set SKIM_TMUX_HEIGHT 40% begin set -lx SKIM_DEFAULT_OPTIONS "--height $SKIM_TMUX_HEIGHT --reverse $SKIM_DEFAULT_OPTIONS $SKIM_CTRL_T_OPTS" eval "$SKIM_CTRL_T_COMMAND | "(__skimcmd)' -m --query "'$skim_query'"' | while read -l r; set result $result $r; end @@ -37,9 +52,9 @@ function skim_key_bindings end function skim-history-widget -d "Show command history" - set -q SKIM_TMUX_HEIGHT; or set SKIM_TMUX_HEIGHT 40% + test -n "$SKIM_TMUX_HEIGHT"; or set SKIM_TMUX_HEIGHT 40% begin - set -lx SKIM_DEFAULT_OPTIONS "--height $SKIM_TMUX_HEIGHT $SKIM_DEFAULT_OPTIONS --tiebreak=score,index --bind=ctrl-r:toggle-sort $SKIM_CTRL_R_OPTS -m" + set -lx SKIM_DEFAULT_OPTIONS "--height $SKIM_TMUX_HEIGHT $SKIM_DEFAULT_OPTIONS --tiebreak=index --bind=ctrl-r:toggle-sort $SKIM_CTRL_R_OPTS --no-multi" set -l FISH_MAJOR (echo $version | cut -f1 -d.) set -l FISH_MINOR (echo $version | cut -f2 -d.) @@ -63,13 +78,13 @@ function skim_key_bindings set -l dir $commandline[1] set -l skim_query $commandline[2] - set -q SKIM_ALT_C_COMMAND; or set -l SKIM_ALT_C_COMMAND " + test -n "$SKIM_ALT_C_COMMAND"; or set -l SKIM_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@^\./@@'" - set -q SKIM_TMUX_HEIGHT; or set SKIM_TMUX_HEIGHT 40% + test -n "$SKIM_TMUX_HEIGHT"; or set SKIM_TMUX_HEIGHT 40% begin set -lx SKIM_DEFAULT_OPTIONS "--height $SKIM_TMUX_HEIGHT --reverse $SKIM_DEFAULT_OPTIONS $SKIM_ALT_C_OPTS" - eval "$SKIM_ALT_C_COMMAND | "(__skimcmd)' -m --query "'$skim_query'"' | read -l result + eval "$SKIM_ALT_C_COMMAND | "(__skimcmd)' --no-multi --query "'$skim_query'"' | read -l result if [ -n "$result" ] cd $result @@ -83,10 +98,12 @@ function skim_key_bindings end function __skimcmd - set -q SKIM_TMUX; or set SKIM_TMUX 0 - set -q SKIM_TMUX_HEIGHT; or set SKIM_TMUX_HEIGHT 40% - if [ $SKIM_TMUX -eq 1 ] - echo "sk-tmux -d$SKIM_TMUX_HEIGHT" + test -n "$SKIM_TMUX"; or set SKIM_TMUX 0 + test -n "$SKIM_TMUX_HEIGHT"; or set SKIM_TMUX_HEIGHT 40% + if [ -n "$SKIM_TMUX_OPTS" ] + echo "sk-tmux $SKIM_TMUX_OPTS -- " + else if [ $SKIM_TMUX -eq 1 ] + echo "sk-tmux -d$SKIM_TMUX_HEIGHT -- " else echo "sk" end @@ -113,12 +130,12 @@ function skim_key_bindings else set dir (__skim_get_dir $commandline) - if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ] + if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ] # if $dir is "." but commandline is not a relative path, this means no file path found set skim_query $commandline else # Also remove trailing slash after dir, to "split" input properly - set skim_query (string replace -r "^$dir/?" '' "$commandline") + set skim_query (string replace -r "^$dir/?" -- '' "$commandline") end end @@ -130,15 +147,15 @@ function skim_key_bindings set dir $argv # Strip all trailing slashes. Ignore if $dir is root dir (/) - if [ (string length $dir) -gt 1 ] - set dir (string replace -r '/*$' '' $dir) + if [ (string length -- $dir) -gt 1 ] + set dir (string replace -r '/*$' -- '' $dir) end # Iteratively check if dir exists and strip tail end of path while [ ! -d "$dir" ] # If path is absolute, this can keep going until ends up at / # If path is relative, this can keep going until entire input is consumed, dirname returns "." - set dir (dirname "$dir") + set dir (dirname -- "$dir") end echo $dir diff --git a/shell/key-bindings.zsh b/shell/key-bindings.zsh index eb3e8a64..4ed3fdc3 100644 --- a/shell/key-bindings.zsh +++ b/shell/key-bindings.zsh @@ -1,7 +1,42 @@ +# ____ ____ +# / __/___ / __/ +# / /_/_ / / /_ +# / __/ / /_/ __/ +# /_/ /___/_/ key-bindings.zsh +# +# - $SKIM_TMUX_OPTS +# - $SKIM_CTRL_T_COMMAND +# - $SKIM_CTRL_T_OPTS +# - $SKIM_CTRL_R_OPTS +# - $SKIM_ALT_C_COMMAND +# - $SKIM_ALT_C_OPTS + # Key bindings # ------------ -# copied and modified from https://github.com/junegunn/fzf/blob/master/shell/key-bindings.zsh -if [[ $- == *i* ]]; then + +# The code at the top and the bottom of this file is the same as in completion.zsh. +# Refer to that file for explanation. +if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then + __skim_key_bindings_options="options=(${(j: :)${(kv)options[@]}})" +else + () { + __skim_key_bindings_options="setopt" + 'local' '__skim_opt' + for __skim_opt in "${(@)${(@f)$(set -o)}%% *}"; do + if [[ -o "$__skim_opt" ]]; then + __skim_key_bindings_options+=" -o $__skim_opt" + else + __skim_key_bindings_options+=" +o $__skim_opt" + fi + done + } +fi + +'emulate' 'zsh' '-o' 'no_aliases' + +{ + +[[ -o interactive ]] || return 0 # CTRL-T - Paste the selected file path(s) into the command line __fsel() { @@ -22,13 +57,9 @@ __fsel() { return $ret } -__skim_use_tmux__() { - [ -n "$TMUX_PANE" ] && [ "${SKIM_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] -} - __skimcmd() { - __skim_use_tmux__ && - echo "sk-tmux -d${SKIM_TMUX_HEIGHT:-40%}" || echo "sk" + [ -n "$TMUX_PANE" ] && { [ "${SKIM_TMUX:-0}" != 0 ] || [ -n "$SKIM_TMUX_OPTS" ]; } && + echo "sk-tmux ${SKIM_TMUX_OPTS:--d${SKIM_TMUX_HEIGHT:-40%}} -- " || echo "sk" } skim-file-widget() { @@ -57,16 +88,22 @@ skim-cd-widget() { setopt localoptions pipefail no_aliases 2> /dev/null REPORTTIME_=$REPORTTIME unset REPORTTIME - local dir="$(eval "$cmd" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_ALT_C_OPTS" $(__skimcmd) -m)" + local dir="$(eval "$cmd" | SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} --reverse $SKIM_DEFAULT_OPTIONS $SKIM_ALT_C_OPTS" $(__skimcmd) --no-multi)" REPORTTIME=$REPORTTIME_ unset REPORTTIME_ if [[ -z "$dir" ]]; then zle redisplay return 0 fi - cd "$dir" - unset dir # ensure this doesn't end up appearing in prompt expansion + if [ -z "$BUFFER" ]; then + BUFFER="cd ${(q)dir}" + zle accept-line + else + print -sr "cd ${(q)dir}" + cd "$dir" + fi local ret=$? + unset dir # ensure this doesn't end up appearing in prompt expansion zle skim-redraw-prompt return $ret } @@ -77,8 +114,8 @@ bindkey '\ec' skim-cd-widget skim-history-widget() { local selected num setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null - selected=( $(fc -rl 1 | - SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} $SKIM_DEFAULT_OPTIONS -n2..,.. --tiebreak=score,index $SKIM_CTRL_R_OPTS --query=${(qqq)LBUFFER} -m" $(__skimcmd)) ) + selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' | + SKIM_DEFAULT_OPTIONS="--height ${SKIM_TMUX_HEIGHT:-40%} $SKIM_DEFAULT_OPTIONS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $SKIM_CTRL_R_OPTS --query=${(qqq)LBUFFER} --no-multi" $(__skimcmd)) ) local ret=$? if [ -n "$selected" ]; then num=$selected[1] @@ -92,4 +129,7 @@ skim-history-widget() { zle -N skim-history-widget bindkey '^R' skim-history-widget -fi +} always { + eval $__skim_key_bindings_options + 'unset' '__skim_key_bindings_options' +}