diff options
Diffstat (limited to 'Functions')
27 files changed, 953 insertions, 277 deletions
diff --git a/Functions/MIME/zsh-mime-handler b/Functions/MIME/zsh-mime-handler index 24e5184fc..288a0796d 100644 --- a/Functions/MIME/zsh-mime-handler +++ b/Functions/MIME/zsh-mime-handler @@ -127,7 +127,7 @@ for pattern in $exec_asis; do files=(${dirpref}${~pattern}) if [[ -n ${files[(r)$1]} ]]; then for pattern in $exec_never; do - [[ ${1:A} = ${~pattern} ]] && break 2 + [[ ${1:P} = ${~pattern} ]] && break 2 done if (( list )); then for (( i = 1; i <= $#; i++ )); do diff --git a/Functions/Math/.distfiles b/Functions/Math/.distfiles new file mode 100644 index 000000000..f03668b3a --- /dev/null +++ b/Functions/Math/.distfiles @@ -0,0 +1,2 @@ +DISTFILES_SRC=' +' diff --git a/Functions/Math/zmathfunc b/Functions/Math/zmathfunc new file mode 100644 index 000000000..4ff40700d --- /dev/null +++ b/Functions/Math/zmathfunc @@ -0,0 +1,34 @@ +#autoload + +zsh_math_func_min() { + local result=$1 + shift + local arg + for arg ; do + (( $arg < result )) && result=$arg + done + (( result )) # return +} +functions -M min 1 -1 zsh_math_func_min # at least one argument + +zsh_math_func_max() { + local result=$1 + shift + local arg + for arg ; do + (( $arg > result )) && result=$arg + done + (( result )) # return +} +functions -M max 1 -1 zsh_math_func_max # at least one argument + +zsh_math_func_sum() { + local sum + local arg + for arg ; do + (( sum += $arg )) + done + (( sum )) +} +functions -M sum 0 -1 zsh_math_func_sum + diff --git a/Functions/Misc/add-zle-hook-widget b/Functions/Misc/add-zle-hook-widget new file mode 100644 index 000000000..d8a3950fb --- /dev/null +++ b/Functions/Misc/add-zle-hook-widget @@ -0,0 +1,186 @@ +# Add to HOOK the given WIDGET +# +# HOOK is one of isearch-exit, isearch-update, line-pre-redraw, line-init, +# line-finish, history-line-set, keymap-select (the zle- prefix is allowed +# but not required). If a widget corresponding to HOOK already exists, it +# is preserved and called first in the new set of HOOK widgets. +# +# With -d, remove the WIDGET from the hook instead; deletes the hook +# linkage if it is empty. +# +# -D behaves like -d, but pattern characters are active in WIDGET, so +# any matching widget will be deleted from the hook. +# +# Without -d, if the WIDGET is not already defined, a function having the +# same name is marked for autoload; -U is passed down to autoload if that +# is given, as are -z and -k. (This is harmless if the function is +# already defined.) The WIDGET is then created with zle -N. +# +# The -L option lists the hooks and their associated widgets. + +# This is probably more safeguarding than necessary +zmodload -e zsh/zle || return 1 +{ zmodload zsh/parameter && zmodload zsh/zleparameter } || { + print -u2 "add-zle-hook-widget: Need parameter modules for zle hooks" + return 1 +} + +() { # Preserve caller global option settings + +emulate -L zsh + +# Setup - create the base functions for hook widgets that call the others + +local -a hooktypes=( zle-isearch-exit zle-isearch-update + zle-line-pre-redraw zle-line-init zle-line-finish + zle-history-line-set zle-keymap-select ) +# Stash in zstyle to make it global +zstyle zle-hook types ${hooktypes#zle-} + +# Relying on multifuncdef option here +function azhw:${^hooktypes} { + local -a hook_widgets + local hook + # Values of these styles look like number:name + # and we run them in number order + zstyle -a $WIDGET widgets hook_widgets + for hook in "${(@)${(@on)hook_widgets[@]}#<->:}"; do + if [[ "$hook" = user:* ]]; then + # Preserve $WIDGET within the renamed widget + zle "$hook" -N -- "$@" + else + zle "$hook" -Nw -- "$@" + fi || return + done + return 0 +} + +# Redefine ourself with the setup left out + +function add-zle-hook-widget { + local -a hooktypes + zstyle -a zle-hook types hooktypes + + # This part copied from add-zsh-hook + local usage="Usage: $funcstack[1] hook widgetname\nValid hooks are:\n $hooktypes" + + local opt + local -a autoopts + integer del list help + + while getopts "dDhLUzk" opt; do + case $opt in + (d) + del=1 + ;; + + (D) + del=2 + ;; + + (h) + help=1 + ;; + + (L) + list=1 + ;; + + ([Uzk]) + autoopts+=(-$opt) + ;; + + (*) + return 1 + ;; + esac + done + shift $(( OPTIND - 1 )) + + 1=${1#zle-} # Strip prefix not stored in zle-hook types style + + if (( list )); then + zstyle -L "zle-(${1:-${(@j:|:)hooktypes[@]}})" widgets + return $? + elif (( help || $# != 2 || ${hooktypes[(I)$1]} == 0 )); then + print -u$(( 2 - help )) $usage + return $(( 1 - help )) + fi + + local -aU extant_hooks + local hook="zle-$1" + local fn="$2" + + if (( del )); then + # delete, if hook is set + if zstyle -g extant_hooks "$hook" widgets; then + if (( del == 2 )); then + set -A extant_hooks ${extant_hooks[@]:#(<->:|)${~fn}} + else + set -A extant_hooks ${extant_hooks[@]:#(<->:|)$fn} + fi + # unset if no remaining entries + if (( ${#extant_hooks} )); then + zstyle "$hook" widgets "${extant_hooks[@]}" + else + zstyle -d "$hook" widgets + fi + fi + else + # Check whether attempting to add a widget named for the hook + if [[ "$fn" = "$hook" ]]; then + if [[ -n "${widgets[$fn]}" ]]; then + print -u2 "$funcstack[1]: Cannot hook $fn to itself" + return 1 + fi + # No point in building the array until another is added + autoload "${autoopts[@]}" -- "$fn" + zle -N "$fn" + return 0 + fi + integer i=${#options[ksharrays]}-2 + zstyle -g extant_hooks "$hook" widgets + # Check for an existing widget, add it as the first hook + if [[ ${widgets[$hook]} != "user:azhw:$hook" ]]; then + if [[ -n ${widgets[$hook]} ]]; then + zle -A "$hook" "${widgets[$hook]}" + extant_hooks=(0:"${widgets[$hook]}" "${extant_hooks[@]}") + fi + zle -N "$hook" azhw:"$hook" + fi + # Add new widget only if not already in the hook list + if [[ -z ${(M)extant_hooks[@]:#(<->:|)$fn} ]]; then + # no index and not already hooked + # assign largest existing index plus 1 + i=${${(On@)${(@M)extant_hooks[@]#<->:}%:}[i]}+1 + else + return 0 + fi + extant_hooks+=("${i}:${fn}") + zstyle -- "$hook" widgets "${extant_hooks[@]}" + if [[ -z "${widgets[$fn]}" ]]; then + autoload "${autoopts[@]}" -- "$fn" + zle -N -- "$fn" + fi + if [[ -z "${widgets[$hook]}" ]]; then + zle -N "$hook" azhw:"$hook" + fi + fi +} + +} "$@" # Resume caller global options + +# Handle zsh autoloading conventions: +# - "file" appears last in zsh_eval_context when "source"-ing +# - "evalautofunc" appears with kshautoload set or autoload -k +# - "loadautofunc" appears with kshautoload unset or autoload -z +# - use of autoload +X cannot reliably be detected, use best guess +case "$zsh_eval_context" in +*file) ;; +*evalautofunc) ;; +*loadautofunc) add-zle-hook-widget "$@";; +*) [[ -o kshautoload ]] || add-zle-hook-widget "$@";; +esac +# Note fallback here is equivalent to the usual best-guess used by +# functions written for zsh before $zsh_eval_context was available +# so this case-statement is backward-compatible. diff --git a/Functions/Misc/add-zsh-hook b/Functions/Misc/add-zsh-hook index fc39659ae..3bc952e2f 100644 --- a/Functions/Misc/add-zsh-hook +++ b/Functions/Misc/add-zsh-hook @@ -19,7 +19,7 @@ hooktypes=( chpwd precmd preexec periodic zshaddhistory zshexit zsh_directory_name ) -local usage="Usage: $0 hook function\nValid hooks are:\n $hooktypes" +local usage="Usage: add-zsh-hook hook function\nValid hooks are:\n $hooktypes" local opt local -a autoopts diff --git a/Functions/Misc/run-help-ip b/Functions/Misc/run-help-ip index 740af52b5..8807f9ef1 100644 --- a/Functions/Misc/run-help-ip +++ b/Functions/Misc/run-help-ip @@ -19,8 +19,8 @@ while [[ $# != 0 && $1 == -* ]]; do done case $1 in - (addr*) man ip-address ;; - (addrlabel) man ip-addrlabel ;; + (addrl*) man ip-addrlabel ;; + (a*) man ip-address ;; (l2*) man ip-l2tp ;; (li*) man ip-link ;; (ma*) man ip-maddress ;; diff --git a/Functions/Misc/zcalc b/Functions/Misc/zcalc index 857007a94..480373345 100644 --- a/Functions/Misc/zcalc +++ b/Functions/Misc/zcalc @@ -94,24 +94,53 @@ # sequentially just as if read automatically. emulate -L zsh -setopt extendedglob +setopt extendedglob typesetsilent + +zcalc_show_value() { + if [[ -n $_base ]]; then + print -- $(( $_base $1 )) + elif [[ $1 = *.* ]] || (( _outdigits )); then + # With normal output, ensure trailing "." doesn't get lost. + if [[ -z $_forms[_outform] || ($_outform -eq 1 && $1 = *.) ]]; then + print -- $(( $1 )) + else + printf "$_forms[_outform]\n" $_outdigits $1 + fi + else + printf "%d\n" $1 + fi +} # For testing in ZLE functions. local ZCALC_ACTIVE=1 # TODO: make local variables that shouldn't be visible in expressions # begin with _. -local line ans base defbase forms match mbegin mend psvar optlist opt arg +local _line ans _base _defbase _forms match mbegin mend +local psvar _optlist _opt _arg _tmp local compcontext="-zcalc-line-" -integer num outdigits outform=1 expression_mode -local -a expressions +integer _num _outdigits _outform=1 _expression_mode +integer _rpn_mode _matched _show_stack _i _n +integer _max_stack _push +local -a _expressions stack # We use our own history file with an automatic pop on exit. history -ap "${ZDOTDIR:-$HOME}/.zcalc_history" -forms=( '%2$g' '%.*g' '%.*f' '%.*E' '') +_forms=( '%2$g' '%.*g' '%.*f' '%.*E' '') -zmodload -i zsh/mathfunc 2>/dev/null +local _mathfuncs +if zmodload -i zsh/mathfunc 2>/dev/null; then + zmodload -P _mathfuncs -FL zsh/mathfunc + _mathfuncs="("${(j.|.)${_mathfuncs##f:}}")" +fi +local -A _userfuncs +for _line in ${(f)"$(functions -M)"}; do + match=(${=_line}) + # get minimum number of arguments + _userfuncs[${match[3]}]=${match[4]} +done +_line= autoload -Uz zmathfuncdef if (( ! ${+ZCALCPROMPT} )); then @@ -127,111 +156,119 @@ if [[ -f "${ZDOTDIR:-$HOME}/.zcalcrc" ]]; then fi # Process command line -while [[ -n $1 && $1 = -(|[#-]*|f|e) ]]; do - optlist=${1[2,-1]} +while [[ -n $1 && $1 = -(|[#-]*|f|e|r(<->|)) ]]; do + _optlist=${1[2,-1]} shift - [[ $optlist = (|-) ]] && break - while [[ -n $optlist ]]; do - opt=${optlist[1]} - optlist=${optlist[2,-1]} - case $opt in + [[ $_optlist = (|-) ]] && break + while [[ -n $_optlist ]]; do + _opt=${_optlist[1]} + _optlist=${_optlist[2,-1]} + case $_opt in ('#') # Default base - if [[ -n $optlist ]]; then - arg=$optlist - optlist= + if [[ -n $_optlist ]]; then + _arg=$_optlist + _optlist= elif [[ -n $1 ]]; then - arg=$1 + _arg=$1 shift else print -- "-# requires an argument" >&2 return 1 fi - if [[ $arg != (|\#)[[:digit:]]## ]]; then + if [[ $_arg != (|\#)[[:digit:]]## ]]; then print -- "-# requires a decimal number as an argument" >&2 return 1 fi - defbase="[#${arg}]" + _defbase="[#${_arg}]" ;; (f) # Force floating point operation setopt forcefloat ;; (e) # Arguments are expressions - (( expression_mode = 1 )); + (( _expression_mode = 1 )); + ;; + (r) # RPN mode. + (( _rpn_mode = 1 )) + ZCALC_ACTIVE=rpn + if [[ $_optlist = (#b)(<->)* ]]; then + (( _show_stack = ${match[1]} )) + _optlist=${_optlist[${#match[1]}+1,-2]} + fi ;; esac done done -if (( expression_mode )); then - expressions=("$@") +if (( _expression_mode )); then + _expressions=("$@") argv=() fi -for (( num = 1; num <= $#; num++ )); do +for (( _num = 1; _num <= $#; _num++ )); do # Make sure all arguments have been evaluated. # The `$' before the second argv forces string rather than numeric # substitution. - (( argv[$num] = $argv[$num] )) - print "$num> $argv[$num]" + (( argv[$_num] = $argv[$_num] )) + print "$_num> $argv[$_num]" done -psvar[1]=$num -local prev_line cont_prompt -while (( expression_mode )) || - vared -cehp "${cont_prompt}${ZCALCPROMPT}" line; do - if (( expression_mode )); then - (( ${#expressions} )) || break - line=$expressions[1] - shift expressions +psvar[1]=$_num +local _prev_line _cont_prompt +while (( _expression_mode )) || + vared -cehp "${_cont_prompt}${ZCALCPROMPT}" _line; do + if (( _expression_mode )); then + (( ${#_expressions} )) || break + _line=$_expressions[1] + shift _expressions fi - if [[ $line = (|*[^\\])('\\')#'\' ]]; then - prev_line+=$line[1,-2] - cont_prompt="..." - line= + if [[ $_line = (|*[^\\])('\\')#'\' ]]; then + _prev_line+=$_line[1,-2] + _cont_prompt="..." + _line= continue fi - line="$prev_line$line" - prev_line= - cont_prompt= + _line="$_prev_line$_line" + _prev_line= + _cont_prompt= # Test whether there are as many open as close - # parentheses in the line so far. - if [[ ${#line//[^\(]} -gt ${#line//[^\)]} ]]; then - prev_line+=$line - cont_prompt="..." - line= + # parentheses in the _line so far. + if [[ ${#_line//[^\(]} -gt ${#_line//[^\)]} ]]; then + _prev_line+=$_line + _cont_prompt="..." + _line= continue fi - [[ -z $line ]] && break + [[ -z $_line ]] && break # special cases # Set default base if `[#16]' or `[##16]' etc. on its own. # Unset it if `[#]' or `[##]'. - if [[ $line = (#b)[[:blank:]]#('[#'(\#|)((<->|)(|_|_<->))']')[[:blank:]]#(*) ]]; then + if [[ $_line = (#b)[[:blank:]]#('[#'(\#|)((<->|)(|_|_<->))']')[[:blank:]]#(*) ]]; then if [[ -z $match[6] ]]; then if [[ -z $match[3] ]]; then - defbase= + _defbase= else - defbase=$match[1] + _defbase=$match[1] fi - print -s -- $line - print -- $(( ${defbase} ans )) - line= + print -s -- $_line + print -- $(( ${_defbase} ans )) + _line= continue else - base=$match[1] + _base=$match[1] fi else - base=$defbase + _base=$_defbase fi - print -s -- $line + print -s -- $_line - line="${${line##[[:blank:]]#}%%[[:blank:]]#}" - case "$line" in + _line="${${_line##[[:blank:]]#}%%[[:blank:]]#}" + case "$_line" in # Escapes begin with a colon (:(\\|)\!*) # shell escape: handle completion's habit of quoting the ! - eval ${line##:(\\|)\![[:blank:]]#} - line= + eval ${_line##:(\\|)\![[:blank:]]#} + _line= continue ;; @@ -241,72 +278,196 @@ while (( expression_mode )) || ;; ((:|)norm) # restore output format to default - outform=1 + _outform=1 ;; ((:|)sci[[:blank:]]#(#b)(<->)(#B)) - outdigits=$match[1] - outform=2 + _outdigits=$match[1] + _outform=2 ;; ((:|)fix[[:blank:]]#(#b)(<->)(#B)) - outdigits=$match[1] - outform=3 + _outdigits=$match[1] + _outform=3 ;; ((:|)eng[[:blank:]]#(#b)(<->)(#B)) - outdigits=$match[1] - outform=4 + _outdigits=$match[1] + _outform=4 ;; (:raw) - outform=5 + _outform=5 ;; ((:|)local([[:blank:]]##*|)) - eval $line - line= + eval ${_line##:} + _line= continue ;; ((function|:f(unc(tion|)|))[[:blank:]]##(#b)([^[:blank:]]##)(|[[:blank:]]##([^[:blank:]]*))) zmathfuncdef $match[1] $match[3] - line= + _userfuncs[$match[1]]=${$(functions -Mm $match[1])[4]} + _line= continue ;; (:*) print "Unrecognised escape" - line= + _line= + continue + ;; + + (\$[[:IDENT:]]##) + # Display only, no calculation + _line=${_line##\$} + print -r -- ${(P)_line} + _line= continue ;; (*) - # Latest value is stored as a string, because it might be floating - # point or integer --- we don't know till after the evaluation, and - # arrays always store scalars anyway. - # - # Since it's a string, we'd better make sure we know which - # base it's in, so don't change that until we actually print it. - eval "ans=\$(( $line ))" - # on error $ans is not set; let user re-edit line - [[ -n $ans ]] || continue - argv[num++]=$ans - psvar[1]=$num + _line=${${_line##[[:blank:]]##}%%[[:blank:]]##} + if [[ _rpn_mode -ne 0 && $_line != '' ]]; then + _push=1 + _matched=1 + case $_line in + (\<[[:IDENT:]]##) + ans=${(P)${_line##\<}} + ;; + + (\=|pop|\>[[:IDENT:]]#) + if (( ${#stack} < 1 )); then + print -r -- "${_line}: not enough values on stack" >&2 + _line= + continue + fi + case $_line in + (=) + ans=${stack[1]} + ;; + (pop|\>) + _push=0 + shift stack + ;; + (\>[[:IDENT:]]##) + if [[ ${_line##\>} = (_*|stack|ans|PI|E) ]]; then + print "${_line##\>}: reserved variable" >&2 + _line= + continue + fi + local ${_line##\>} + (( ${_line##\>} = ${stack[1]} )) + _push=0 + shift stack + ;; + (*) + print "BUG in special RPN functions" >&2 + _line= + continue + ;; + esac + ;; + + (+|-|\^|\||\&|\*|/|\*\*|\>\>|\<\</) + # Operators with two arguments + if (( ${#stack} < 2 )); then + print -r -- "${_line}: not enough values on stack" >&2 + _line= + continue + fi + eval "(( ans = \${stack[2]} $_line \${stack[1]} ))" + shift 2 stack + ;; + + (ldexp|jn|yn|scalb|xy|\<\>) + # Functions with two arguments + if (( ${#stack} < 2 )); then + print -r -- "${_line}: not enough values on stack" >&2 + _line= + continue + fi + if [[ $_line = (xy|\<\>) ]]; then + _tmp=${stack[1]} + stack[1]=${stack[2]} + stack[2]=$_tmp + _push=0 + else + eval "(( ans = ${_line}(\${stack[2]},\${stack[1]}) ))" + shift 2 stack + fi + ;; + + (${~_mathfuncs}) + # Functions with a single argument. + # This is actually a superset, but we should have matched + # any that shouldn't be in it in previous cases. + if (( ${#stack} < 1 )); then + print -r -- "${_line}: not enough values on stack" >&2 + _line= + continue + fi + eval "(( ans = ${_line}(\${stack[1]}) ))" + shift stack + ;; + + (${(kj.|.)~_userfuncs}) + # Get minimum number of arguments to user function + _n=${_userfuncs[$_line]} + if (( ${#stack} < n_ )); then + print -r -- "${_line}: not enough values ($_n) on stack" >&2 + _line= + continue + fi + _line+="(" + # least recent elements on stack are earlier arguments + for (( _i = _n; _i > 0; _i-- )); do + _line+=${stack[_i]} + (( _i > 1 )) && _line+="," + done + _line+=")" + shift $_n stack + eval "(( ans = $_line ))" + ;; + + (*) + # Treat as expression evaluating to new value to go on stack. + _matched=0 + ;; + esac + else + _matched=0 + fi + if (( ! _matched )); then + # Latest value is stored` as a string, because it might be floating + # point or integer --- we don't know till after the evaluation, and + # arrays always store scalars anyway. + # + # Since it's a string, we'd better make sure we know which + # base it's in, so don't change that until we actually print it. + if ! eval "ans=\$(( $_line ))"; then + _line= + continue + fi + # on error $ans is not set; let user re-edit _line + [[ -n $ans ]] || continue + fi + argv[_num++]=$ans + psvar[1]=$_num + (( _push )) && stack=($ans $stack) ;; esac - if [[ -n $base ]]; then - print -- $(( $base $ans )) - elif [[ $ans = *.* ]] || (( outdigits )); then - if [[ -z $forms[outform] ]]; then - print -- $(( $ans )) - else - printf "$forms[outform]\n" $outdigits $ans - fi + if (( _show_stack )); then + (( _max_stack = (_show_stack > ${#stack}) ? ${#stack} : _show_stack )) + for (( _i = _max_stack; _i > 0; _i-- )); do + printf "%3d: " $_i + zcalc_show_value ${stack[_i]} + done else - printf "%d\n" $ans + zcalc_show_value $ans fi - line= + _line= done return 0 diff --git a/Functions/Misc/zed b/Functions/Misc/zed index eb8f557ea..77d392bc3 100644 --- a/Functions/Misc/zed +++ b/Functions/Misc/zed @@ -6,6 +6,8 @@ # Use ^X^W to save, ^C to abort. # Option -f: edit shell functions. (Also if called as fned.) +setopt localoptions noksharrays + local var opts zed_file_name # We do not want timeout while we are editing a file integer TMOUT=0 okargs=1 fun bind @@ -74,7 +76,7 @@ if ((fun)) then if [[ $var = *\#\ undefined* ]] then var="$(autoload +X $1; functions -- $1)" elif [[ -z $var ]] then - var="$1() { + var="${(q-)1} () { }" fi vared -M zed -m zed-vicmd var && eval function "$var" diff --git a/Functions/VCS_Info/Backends/VCS_INFO_get_data_git b/Functions/VCS_Info/Backends/VCS_INFO_get_data_git index 472c10d5d..f3dd95dcb 100644 --- a/Functions/VCS_Info/Backends/VCS_INFO_get_data_git +++ b/Functions/VCS_Info/Backends/VCS_INFO_get_data_git @@ -120,41 +120,15 @@ VCS_INFO_git_getbranch () { } VCS_INFO_git_handle_patches () { - local git_applied_s git_unapplied_s gitmsg git_all + local git_applied_s git_unapplied_s gitmsg git_patches_applied=(${(Oa)git_patches_applied}) git_patches_unapplied=(${(Oa)git_patches_unapplied}) - (( git_all = ${#git_patches_applied} + ${#git_patches_unapplied} )) - if VCS_INFO_hook 'gen-applied-string' "${git_patches_applied[@]}"; then - if (( ${#git_patches_applied} )); then - git_applied_s=${git_patches_applied[1]} - else - git_applied_s="" - fi - else - git_applied_s=${hook_com[applied-string]} - fi - hook_com=() - if VCS_INFO_hook 'gen-unapplied-string' "${git_patches_unapplied[@]}"; then - git_patches_unapplied=${#git_patches_unapplied} - else - git_patches_unapplied=${hook_com[unapplied-string]} - fi - - if (( ${#git_patches_applied} )); then - zstyle -s ":vcs_info:${vcs}:${usercontext}:${rrn}" patch-format gitmsg || gitmsg="%p (%n applied)" - else - zstyle -s ":vcs_info:${vcs}:${usercontext}:${rrn}" nopatch-format gitmsg || gitmsg="no patch applied" - fi - hook_com=( applied "${git_applied_s}" unapplied "${git_patches_unapplied}" - applied-n ${#git_patches_applied} unapplied-n ${#git_patches_unapplied} all-n ${git_all} ) - if VCS_INFO_hook 'set-patch-format' "${gitmsg}"; then - zformat -f gitmisc "${gitmsg}" "p:${hook_com[applied]}" "u:${hook_com[unapplied]}" \ - "n:${#git_patches_applied}" "c:${#git_patches_unapplied}" "a:${git_all}" - else - gitmisc=${hook_com[patch-replace]} - fi - hook_com=() + VCS_INFO_set-patch-format 'git_patches_applied' 'git_applied_s' \ + 'git_patches_unapplied' 'git_unapplied_s' \ + ":vcs_info:${vcs}:${usercontext}:${rrn}" gitmsg \ + '' '' + gitmisc=$REPLY } gitdir=${vcs_comm[gitdir]} @@ -182,7 +156,7 @@ if (( querystaged || queryunstaged )) && \ [[ "$(${vcs_comm[cmd]} rev-parse --is-inside-work-tree 2> /dev/null)" == 'true' ]] ; then # Default: off - these are potentially expensive on big repositories if (( queryunstaged )) ; then - ${vcs_comm[cmd]} diff --no-ext-diff --ignore-submodules=dirty --quiet --exit-code || + ${vcs_comm[cmd]} diff --no-ext-diff --ignore-submodules=dirty --quiet --exit-code 2> /dev/null || gitunstaged=1 fi if (( querystaged )) ; then @@ -205,6 +179,7 @@ local patchdir=${gitdir}/patches/${gitbranch} if [[ -d $patchdir ]] && [[ -f $patchdir/applied ]] \ && [[ -f $patchdir/unapplied ]] then + # stgit git_patches_applied=(${(f)"$(< "${patchdir}/applied")"}) git_patches_unapplied=(${(f)"$(< "${patchdir}/unapplied")"}) VCS_INFO_git_handle_patches @@ -213,11 +188,15 @@ elif [[ -d "${gitdir}/rebase-merge" ]]; then local p [[ -f "${patchdir}/done" ]] && for p in ${(f)"$(< "${patchdir}/done")"}; do - # remove action - git_patches_applied+=("${${(s: :)p}[2,-1]}") + # pick/edit/fixup/squash/reword: Add "$hash $subject" to $git_patches_applied. + # exec: Add "exec ${command}" to $git_patches_applied. + # (anything else): As 'exec'. + p=${p/(#s)(p|pick|e|edit|r|reword|f|fixup|s|squash) /} + p=${p/(#s)x /exec } + git_patches_applied+=("$p") done if [[ -f "${patchdir}/git-rebase-todo" ]] ; then - git_patches_unapplied=(${(f)"$(grep -v '^$' "${patchdir}/git-rebase-todo" | grep -v '^#')"}) + git_patches_unapplied=( ${${(f)${"$(<"${patchdir}/git-rebase-todo")"}}:#[#]*} ) fi VCS_INFO_git_handle_patches elif [[ -d "${gitdir}/rebase-apply" ]]; then @@ -228,10 +207,19 @@ elif [[ -d "${gitdir}/rebase-apply" ]]; then local cur=$(< $next) local p subject for ((p = 1; p < cur; p++)); do - git_patches_applied+=("$(printf "%04d" $p) ?") + printf -v "git_patches_applied[$p]" "%04d ?" "$p" done if [[ -f "${patchdir}/msg-clean" ]]; then subject="${$(< "${patchdir}/msg-clean")[(f)1]}" + elif local this_patch_file + printf -v this_patch_file "%s/%04d" "${patchdir}" "${cur}" + [[ -f $this_patch_file ]] + then + () { + local REPLY + VCS_INFO_patch2subject "${this_patch_file}" + subject=$REPLY + } fi if [[ -f "${patchdir}/original-commit" ]]; then if [[ -n $subject ]]; then @@ -257,6 +245,7 @@ elif [[ -f "${gitdir}/MERGE_HEAD" ]]; then # This is 'git merge --no-commit' local -a heads=( ${(@f)"$(<"${gitdir}/MERGE_HEAD")"} ) local subject; + # TODO: maybe read up to the first blank line IFS='' read -r subject < "${gitdir}/MERGE_MSG" # $subject is the subject line of the would-be commit # Maybe we can get the subject lines of MERGE_HEAD's commits cheaply? @@ -282,6 +271,7 @@ elif [[ -f "${gitdir}/CHERRY_PICK_HEAD" ]]; then # ### be "1". The %u/%c tuple will assume the values [(1,2), (1,1), (1,0)], # ### whereas the correct sequence would be [(1,2), (2,1), (3,0)]. local subject + # TODO: maybe read up to the first blank line IFS='' read -r subject < "${gitdir}/MERGE_MSG" git_patches_applied=( "$(<${gitdir}/CHERRY_PICK_HEAD) ${subject}" ) if [[ -f "${gitdir}/sequencer/todo" ]]; then diff --git a/Functions/VCS_Info/Backends/VCS_INFO_get_data_hg b/Functions/VCS_Info/Backends/VCS_INFO_get_data_hg index f35ad5965..d4030125c 100644 --- a/Functions/VCS_Info/Backends/VCS_INFO_get_data_hg +++ b/Functions/VCS_Info/Backends/VCS_INFO_get_data_hg @@ -13,7 +13,7 @@ local hgbase bmfile branchfile rebasefile dirstatefile mqseriesfile \ hgbmstring hgmqstring applied_string unapplied_string guards_string local -a hgid_args defrevformat defbranchformat \ - hgbmarks mqpatches mqseries mqguards mqunapplied hgmisc \ + hgbmarks mqpatches mqguards mqunapplied hgmisc \ i_patchguards i_negguards i_posguards local -A hook_com @@ -40,9 +40,10 @@ VCS_INFO_adjust # Disabled by default anyway, so no harm done. if zstyle -t ":vcs_info:${vcs}:${usercontext}:${rrn}" get-revision ; then if zstyle -t ":vcs_info:${vcs}:${usercontext}:${rrn}" use-simple \ - && ( VCS_INFO_check_com hexdump ) && [[ -r ${dirstatefile} ]] ; then - # Calling hexdump is (much) faster than hg but doesn't get the local rev - r_csetid=$(hexdump -n 20 -e '1/1 "%02x"' ${dirstatefile}) + && VCS_INFO_hexdump ${dirstatefile} 20 ; then + # Calling VCS_INFO_hexdump is (much) faster than hg but doesn't get + # the local rev + r_csetid=$REPLY else # Settling for a short (but unique!) hash because getting the full # 40-char hash in addition to all the other info we want isn't @@ -174,9 +175,6 @@ if zstyle -T ":vcs_info:${vcs}:${usercontext}:${rrn}" get-mq \ # Skip commented lines [[ ${i_patch} == [[:space:]]#"#"* ]] && continue - # Keep list of all patches - mqseries+=( $i_patch ) - # Separate negative and positive guards to more easily find the # intersection of active guards with patch guards i_patchguards=( ${(s: :)i_patchguards} ) @@ -202,50 +200,21 @@ if zstyle -T ":vcs_info:${vcs}:${usercontext}:${rrn}" get-mq \ done < ${mqseriesfile} fi - if VCS_INFO_hook 'gen-applied-string' "${mqpatches[@]}"; then - (( ${#mqpatches} )) && applied_string=${mqpatches[1]} - else - applied_string=${hook_com[applied-string]} - fi - - hook_com=() - - if VCS_INFO_hook 'gen-unapplied-string' "${mqunapplied[@]}"; then - unapplied_string=${#mqunapplied} - else - unapplied_string=${hook_com[unapplied-string]} - fi - - hook_com=() - if VCS_INFO_hook 'gen-mqguards-string' "${mqguards[@]}"; then guards_string=${(j:,:)mqguards} + # TODO: %-escape extra_zformats[g:...] value else guards_string=${hook_com[guards-string]} fi - if (( ${#mqpatches} )); then - zstyle -s ":vcs_info:${vcs}:${usercontext}:${rrn}" patch-format \ - hgmqstring || hgmqstring="%p (%n applied)" - else - zstyle -s ":vcs_info:${vcs}:${usercontext}:${rrn}" nopatch-format \ - hgmqstring || hgmqstring="no patch applied" - fi - - hook_com=( applied "${applied_string}" unapplied "${unapplied_string}" - applied-n ${#mqpatches} unapplied-n ${#mqunapplied} all-n ${#mqseries} - guards "${guards_string}" guards-n ${#mqguards} ) + local -A extra_hook_com=( guards "${guards_string}" guards-n ${#mqguards} ) + local -a extra_zformats=( "g:${extra_hook_com[guards]}" "G:${#mqguards}" ) - if VCS_INFO_hook 'set-patch-format' ${qstring}; then - zformat -f hgmqstring "${hgmqstring}" \ - "p:${hook_com[applied]}" "u:${hook_com[unapplied]}" \ - "n:${#mqpatches}" "c:${#mqunapplied}" "a:${#mqseries}" \ - "g:${hook_com[guards]}" "G:${#mqguards}" - else - hgmqstring=${hook_com[patch-replace]} - fi - - hook_com=() + VCS_INFO_set-patch-format 'mqpatches' 'applied_string' \ + 'mqunapplied' 'unapplied_string' \ + ":vcs_info:${vcs}:${usercontext}:${rrn}" hgmqstring \ + extra_hook_com extra_zformats + hgmqstring=$REPLY fi diff --git a/Functions/VCS_Info/VCS_INFO_hexdump b/Functions/VCS_Info/VCS_INFO_hexdump new file mode 100644 index 000000000..11f1c1a50 --- /dev/null +++ b/Functions/VCS_Info/VCS_INFO_hexdump @@ -0,0 +1,16 @@ +## vim:ft=zsh + +# VCS_INFO_hexdump FILENAME BYTECOUNT +# +# Return in $REPLY a hexadecimal representation (lowercase, no whitespace) +# of the first BYTECOUNT bytes of FILENAME. + +if [[ -r $1 ]]; then + setopt localoptions nomultibyte extendedglob + local val + read -k $2 -u 0 val <$1 + REPLY=${(Lj::)${(l:2::0:)${(@s//)val}//(#m)*/$(( [##16] ##$MATCH ))}} +else + return 1 +fi + diff --git a/Functions/VCS_Info/VCS_INFO_patch2subject b/Functions/VCS_Info/VCS_INFO_patch2subject new file mode 100644 index 000000000..e222e8382 --- /dev/null +++ b/Functions/VCS_Info/VCS_INFO_patch2subject @@ -0,0 +1,67 @@ +# This function takes as an argument a filename of a patch and sets $REPLY to +# a single-line "subject", or unsets it if no subject could be extracted. +{ + setopt localoptions extendedglob + integer i + integer -r LIMIT=10 + local -a lines + local needle + if [[ -f "$1" ]]; then + # Extract the first LIMIT lines, or up to the first empty line or the start of the unidiffs, + # whichever comes first. + while (( i++ < LIMIT )); do + IFS= read -r "lines[$i]" + if [[ -z ${lines[$i]} ]] || [[ ${lines[$i]} == (#b)(---|Index:)* ]]; then + lines[$i]=() + break + fi + done < "$1" + + if needle=${lines[(i)Subject:*]}; (( needle <= $#lines )); then + # "Subject: foo" line, plus rfc822 whitespace unfolding. + # + # Example: 'git format-patch' patches. + REPLY=${lines[needle]} + REPLY=${REPLY#*: } + REPLY=${REPLY#\[PATCH\] } + while [[ ${${lines[++needle]}[1]} == ' ' ]]; do + REPLY+=${lines[needle]} + done + elif needle=${lines[(r)Description:*]}; [[ -n $needle ]]; then + # "Description: foo" line. + # + # Example: DEP-3 patches. + REPLY=${needle#*: } + elif [[ ${lines[1]} == '# HG changeset patch' ]] && { needle=${${lines:#([#]*)}[1]}; [[ -n $needle ]] }; then + # Mercurial patch + REPLY=$needle + elif [[ ${lines[1]} == "commit "[0-9a-f](#c40) ]] && + [[ ${lines[2]} == "Author:"* && ${lines[3]} == "Date:"* ]] && + (( ! ${+lines[4]} )); then + # `git show` output. + # + # The log message is after the first blank line, so open() the file + # again. Also check whether the following line (second line of the + # log message itself) is empty. + { + repeat 4 { IFS= read -r } + IFS= read -r needle; needle=${needle#' '} + if IFS= read -r; REPLY=${REPLY#' '}; [[ -n $REPLY ]]; then + needle+='...' + fi + } < "$1" + REPLY=$needle + elif (( ${+lines[1]} )); then + # The first line of the file is not part of the diff. + REPLY=${lines[1]} + else + # The patch has no subject. + unset REPLY + return 0 + fi + else + # The patch cannot be examined, or invalid arguments. + unset REPLY + return 1 + fi +} diff --git a/Functions/VCS_Info/VCS_INFO_quilt b/Functions/VCS_Info/VCS_INFO_quilt index c3c3d864d..381b58489 100644 --- a/Functions/VCS_Info/VCS_INFO_quilt +++ b/Functions/VCS_Info/VCS_INFO_quilt @@ -80,6 +80,10 @@ function VCS_INFO_quilt-dirfind() { return ${ret} } +function VCS_INFO_quilt-patch2subject() { + VCS_INFO_patch2subject "$@" +} + function VCS_INFO_quilt() { emulate -L zsh setopt extendedglob @@ -87,7 +91,7 @@ function VCS_INFO_quilt() { local patches pc tmp qstring root local -i ret local context - local -a applied unapplied all applied_string unapplied_string quiltcommand quilt_env + local -a applied unapplied applied_string unapplied_string quiltcommand quilt_env local -A hook_com context=":vcs_info:${vcs}.quilt-${mode}:${usercontext}:${rrn}" @@ -119,7 +123,7 @@ function VCS_INFO_quilt() { applied=() fi patches=$(<$pc/.quilt_patches) - patches=`builtin cd -q "${pc:h}" && print -r - ${patches:A}` + patches=`builtin cd -q "${pc:h}" && print -r - ${patches:P}` fi if zstyle -t "${context}" get-unapplied; then # This zstyle call needs to be moved further up if `quilt' needs @@ -147,27 +151,19 @@ function VCS_INFO_quilt() { if [[ -n $patches ]]; then () { - local i line + local i for ((i=1; i<=$#applied; i++)); do - if [[ -f "$patches/$applied[$i]" ]] && - read -r line < "$patches/$applied[$i]" && - [[ $line != (#b)(---|Index:)* ]] && - true - ; + if VCS_INFO_quilt-patch2subject "$patches/$applied[$i]" && (( $+REPLY )) then - applied[$i]+=" $line" + applied[$i]+=" $REPLY" else applied[$i]+=" ?" fi done for ((i=1; i<=$#unapplied; i++)); do - if [[ -f "$patches/$unapplied[$i]" ]] && - read -r line < "$patches/$unapplied[$i]" && - [[ $line != (#b)(---|Index:)* ]] && - true - ; + if VCS_INFO_quilt-patch2subject "$patches/$unapplied[$i]" && (( $+REPLY )) then - unapplied[$i]+=" $line" + unapplied[$i]+=" $REPLY" else unapplied[$i]+=" ?" fi @@ -175,41 +171,15 @@ function VCS_INFO_quilt() { } fi - all=( ${(Oa)applied} ${unapplied} ) - - if VCS_INFO_hook 'gen-applied-string' "${applied[@]}"; then - if (( ${#applied} )); then - applied_string=${applied[1]} - else - applied_string="" - fi - else - applied_string=${hook_com[applied-string]} - fi - hook_com=() - if VCS_INFO_hook 'gen-unapplied-string' "${unapplied[@]}"; then - unapplied_string="${#unapplied}" - else - unapplied_string=${hook_com[unapplied-string]} - fi - - if (( ${#applied} )); then - zstyle -s "${context}" patch-format qstring || qstring="%p (%n applied)" - else - zstyle -s "${context}" nopatch-format qstring || qstring="no patch applied" - fi - hook_com=( applied "${applied_string}" unapplied "${unapplied_string}" - applied-n ${#applied} unapplied-n ${#unapplied} all-n ${#all} ) - if VCS_INFO_hook 'set-patch-format' ${qstring}; then - zformat -f qstring "${qstring}" "p:${hook_com[applied]}" "u:${hook_com[unapplied]}" \ - "n:${#applied}" "c:${#unapplied}" "a:${#all}" - else - qstring=${hook_com[patch-replace]} - fi - hook_com=() + VCS_INFO_set-patch-format 'applied' 'applied_string' \ + 'unapplied' 'unapplied_string' \ + ${context} qstring \ + '' '' + qstring=$REPLY case ${mode} in (standalone) + backend_misc[patches]=${qstring} VCS_INFO_formats '' '' "${root}" '' '' '' "${qstring}" VCS_INFO_set ;; diff --git a/Functions/VCS_Info/VCS_INFO_set-patch-format b/Functions/VCS_Info/VCS_INFO_set-patch-format new file mode 100644 index 000000000..cdf2d303e --- /dev/null +++ b/Functions/VCS_Info/VCS_INFO_set-patch-format @@ -0,0 +1,79 @@ +# This function is the common guts of the gen-applied-string / +# gen-unapplied-string / set-patch-format dance of several backends. +# +# Parameters: +# $1 - name of an array parameter to be the argument to gen-applied-string +# $2 - name of a parameter to store the applied-string in +# $3 - name of an array parameter to be the argument to gen-unapplied-string +# $4 - name of a parameter to store the unapplied-string in +# $5 - context argument for use in zstyle getters +# $6 - name of a parameter to store a patch-format format string in +# $7 - name of an assoc parameter with extra $hook_com key-value pairs for the +# set-patch-format hook invocation, or '' for none +# $8 - name of an array parameter with extra arguments for the patch-format zformat call, or '' for empty +# +# The expanded patch-format string is returned in $REPLY. +# +# Output: +# - $hook_com is overwritten and the keys 'applied', 'applied-n', +# 'unapplied', 'unapplied-n', 'all-n' are set. +{ + local applied_needs_escaping='unknown' + local unapplied_needs_escaping='unknown' + if VCS_INFO_hook 'gen-applied-string' "${(@P)1}"; then + if (( ${(P)#1} )); then + REPLY=${(P)1[1]} + else + REPLY="" + fi + applied_needs_escaping='yes' + else + REPLY=${hook_com[applied-string]} + fi + : ${(P)2::=$REPLY} + hook_com=() + + if VCS_INFO_hook 'gen-unapplied-string' "${(@P)3}"; then + REPLY=${(P)#3} + unapplied_needs_escaping='yes' + else + REPLY=${hook_com[unapplied-string]} + fi + : ${(P)4::=$REPLY} + hook_com=() + + if (( ${(P)#1} )); then + zstyle -s "${5}" patch-format REPLY || REPLY="%p (%n applied)" + else + zstyle -s "${5}" nopatch-format REPLY || REPLY="no patch applied" + fi + : ${(P)6::=$REPLY} + + hook_com=( + applied-n ${(P)#1} + applied "${(P)2}" + unapplied-n ${(P)#3} + unapplied "${(P)4}" + ) + hook_com[all-n]=$(( ${hook_com[applied-n]} + ${hook_com[unapplied-n]} )) + hook_com+=( ${7:+"${(@kvP)7}"} ) + if VCS_INFO_hook 'set-patch-format' "${(P)6}"; then + # Escape the value for use in $PS1 + if [[ $applied_needs_escaping == 'yes' ]]; then + hook_com[applied]=${hook_com[applied]//'%'/%%} + fi + if [[ $unapplied_needs_escaping == 'yes' ]]; then + hook_com[unapplied]=${hook_com[unapplied]//'%'/%%} + fi + + zformat -f REPLY "${(P)6}" "p:${hook_com[applied]}" "u:${hook_com[unapplied]}" \ + "n:${hook_com[applied-n]}" "c:${hook_com[unapplied-n]}" \ + "a:${hook_com[all-n]}" \ + ${8:+"${(@P)8}"} + else + unset applied_needs_escaping unapplied_needs_escaping # the hook deals with escaping + REPLY=${hook_com[patch-replace]} + fi + hook_com=() + +} diff --git a/Functions/VCS_Info/vcs_info b/Functions/VCS_Info/vcs_info index f13f6b501..4e9ac6c6a 100644 --- a/Functions/VCS_Info/vcs_info +++ b/Functions/VCS_Info/vcs_info @@ -19,9 +19,12 @@ static_functions=( VCS_INFO_check_com VCS_INFO_formats VCS_INFO_get_cmd + VCS_INFO_hexdump VCS_INFO_hook + VCS_INFO_set-patch-format VCS_INFO_maxexports VCS_INFO_nvcsformats + VCS_INFO_patch2subject VCS_INFO_quilt VCS_INFO_realpath VCS_INFO_reposub diff --git a/Functions/Zle/bracketed-paste-magic b/Functions/Zle/bracketed-paste-magic index 498cf554d..4baae823e 100644 --- a/Functions/Zle/bracketed-paste-magic +++ b/Functions/Zle/bracketed-paste-magic @@ -20,7 +20,7 @@ # active-widgets # Looked up in the context :bracketed-paste-magic to obtain a list of # patterns that match widget names that should be activated during the -# paste. All other key sequences are processed as self-insert-unmeta. +# paste. All other key sequences are processed as "zle .self-insert". # The default is 'self-*' so any user-defined widgets named with that # prefix are active along with the builtin self-insert. If this style is # not set (note: it must be explicitly deleted after loading this @@ -31,7 +31,7 @@ # # inactive-keys # This is the inverse of active-widgets, it lists key sequences that -# always use self-insert-unmeta even when bound to an active-widget. +# always use "zle .self-insert" even when bound to an active-widget. # Note that this is a list of literal key sequences, not patterns. # This style is in context :bracketed-paste-magic and has no default. # @@ -145,27 +145,26 @@ bracketed-paste-magic() { done fi - # Save context, create a clean slate for the paste - integer bpm_mark=$MARK bpm_cursor=$CURSOR bpm_region=$REGION_ACTIVE - integer bpm_numeric=${NUMERIC:-1} - local bpm_buffer=$BUFFER - fc -p -a /dev/null 0 0 - BUFFER= - zstyle -a :bracketed-paste-magic inactive-keys bpm_inactive if zstyle -s :bracketed-paste-magic active-widgets bpm_active '|'; then - # There are active widgets. Reprocess $PASTED as keystrokes. - NUMERIC=1 - zle -U - $PASTED - + # Save context, create a clean slate for the paste + integer bpm_mark=$MARK bpm_region=$REGION_ACTIVE + integer bpm_numeric=${NUMERIC:-1} + integer bpm_limit=$UNDO_LIMIT_NO bpm_undo=$UNDO_CHANGE_NO + zle .split-undo + UNDO_LIMIT_NO=$UNDO_CHANGE_NO + BUFFER= + CURSOR=1 + fc -p -a /dev/null 0 0 if [[ $bmp_keymap = vicmd ]]; then zle -K viins fi + # There are active widgets. Reprocess $PASTED as keystrokes. + NUMERIC=1 + zle -U - $PASTED + # Just in case there are active undo widgets - zle .split-undo - integer bpm_limit=$UNDO_LIMIT_NO bpm_undo=$UNDO_CHANGE_NO - UNDO_LIMIT_NO=$UNDO_CHANGE_NO while [[ -n $PASTED ]] && zle .read-command; do PASTED=${PASTED#$KEYS} @@ -183,21 +182,16 @@ bracketed-paste-magic() { done PASTED=$BUFFER - # Reset the undo state + # Restore state + zle -K $bpm_keymap + fc -P + MARK=$bpm_mark + REGION_ACTIVE=$bpm_region + NUMERIC=$bpm_numeric zle .undo $bpm_undo UNDO_LIMIT_NO=$bpm_limit - - zle -K $bpm_keymap fi - # Restore state - BUFFER=$bpm_buffer - MARK=$bpm_mark - CURSOR=$bpm_cursor - REGION_ACTIVE=$bpm_region - NUMERIC=$bpm_numeric - fc -P - # PASTED has been updated, run the paste-finish functions if zstyle -a :bracketed-paste-magic paste-finish bpm_hooks; then for bpm_func in $bpm_hooks; do @@ -212,7 +206,7 @@ bracketed-paste-magic() { # Reprocess $PASTED as an actual paste this time zle -U - $PASTED$'\e[201~' # append paste-end marker - zle .bracketed-paste + zle .bracketed-paste -- "$@" zle .split-undo # Arrange to display highlighting if necessary diff --git a/Functions/Zle/bracketed-paste-url-magic b/Functions/Zle/bracketed-paste-url-magic index 06dee2657..b894696bb 100644 --- a/Functions/Zle/bracketed-paste-url-magic +++ b/Functions/Zle/bracketed-paste-url-magic @@ -19,7 +19,7 @@ # The default can be seen just below. local -a schema -zstyle -a :bracketed-paste-url-magic schema schema || schema=(http https ftp ftps file ssh sftp) +zstyle -a :bracketed-paste-url-magic schema schema || schema=(http:// https:// ftp:// ftps:// file:// ssh:// sftp:// magnet:) local wantquote=${NUMERIC:-0} local content @@ -28,7 +28,7 @@ local start=$#LBUFFER zle .$WIDGET -N content if (( $wantquote == 0 )); then - if [[ $content = (${(~j:|:)schema})://* ]]; then + if [[ $content = (${(~j:|:)schema})* ]]; then wantquote=1 fi fi diff --git a/Functions/Zle/delete-whole-word-match b/Functions/Zle/delete-whole-word-match index aece86065..3d52dd3d7 100644 --- a/Functions/Zle/delete-whole-word-match +++ b/Functions/Zle/delete-whole-word-match @@ -12,30 +12,29 @@ emulate -L zsh setopt extendedglob local curcontext=:zle:$WIDGET -local -a matched_words +local -A matched_words # Start and end of range of characters to remove. integer pos1 pos2 autoload -Uz match-words-by-style match-words-by-style -if [[ -n "${matched_words[3]}" ]]; then - # There's whitespace before the cursor, so the word we are deleting - # starts at the cursor position. +if (( ${matched_words[is-word-start]} )); then + # The word we are deleting starts at the cursor position. pos1=$CURSOR else - # No whitespace before us, so delete any wordcharacters there. - pos1="${#matched_words[1]}" + # Not, so delete any wordcharacters before, too + pos1="${#matched_words[start]}" fi -if [[ -n "${matched_words[4]}" ]]; then +if [[ -n "${matched_words[ws-after-cursor]}" ]]; then # There's whitespace at the cursor position, so only delete # up to the cursor position. (( pos2 = CURSOR + 1 )) else # No whitespace at the cursor position, so delete the # current character and any following wordcharacters. - (( pos2 = CURSOR + ${#matched_words[5]} + 1 )) + (( pos2 = CURSOR + ${#matched_words[word-after-cursor]} + 1 )) fi # Move the cursor then delete the block in one go for the diff --git a/Functions/Zle/expand-absolute-path b/Functions/Zle/expand-absolute-path index b85757600..4887f3c60 100644 --- a/Functions/Zle/expand-absolute-path +++ b/Functions/Zle/expand-absolute-path @@ -10,7 +10,7 @@ autoload -Uz modify-current-argument if (( ! ${+functions[glob-expand-absolute-path]} )); then glob-expand-absolute-path() { local -a files - files=(${~1}(N:A)) + files=(${~1}(N:P)) (( ${#files} )) || return REPLY=${(D)files[1]} } diff --git a/Functions/Zle/history-beginning-search-menu b/Functions/Zle/history-beginning-search-menu index 105518102..0e1bbc734 100644 --- a/Functions/Zle/history-beginning-search-menu +++ b/Functions/Zle/history-beginning-search-menu @@ -112,7 +112,7 @@ fi # go to the last one. This allows accept-line-and-down-history etc. # to work. local -a lines -local matchq=${matches[$chars]//(#m)[\][()\\*?#<>~^]/\\$MATCH} +local matchq=${matches[$chars]//(#m)[\][|()\\*?#<>~^]/\\$MATCH} lines=(${(kon)history[(R)$matchq]}) HISTNO=$lines[-1] diff --git a/Functions/Zle/insert-unicode-char b/Functions/Zle/insert-unicode-char index af9aad914..b943fb7f7 100644 --- a/Functions/Zle/insert-unicode-char +++ b/Functions/Zle/insert-unicode-char @@ -12,11 +12,11 @@ then local -i 16 -Z 10 arg=$NUMERIC # ...and use print to turn this into a Unicode character. LBUFFER+="$(print -n "\U${arg##0x}")" - _insert_unicode_ready=0 + integer -g _insert_unicode_ready=0 else # Set the base to 16... zle argument-base 16 # ...wait for user to type hex keys then call this widget again. zle universal-argument - _insert_unicode_ready=1 + integer -g _insert_unicode_ready=1 fi diff --git a/Functions/Zle/match-words-by-style b/Functions/Zle/match-words-by-style index 54e019d23..fc59c2764 100644 --- a/Functions/Zle/match-words-by-style +++ b/Functions/Zle/match-words-by-style @@ -5,8 +5,16 @@ # <whitespace-after-cursor> <word-after-cursor> <whitespace-after-word> # <stuff-at-end> # where the cursor position is always after the third item and `after' -# is to be interpreted as `after or on'. Some -# of the array elements will be empty; this depends on the style. +# is to be interpreted as `after or on'. +# +# matched_words may be an associative array, in which case the +# values above are now given by the elements named start, word-before-cursor, +# ws-before-cursor, ws-after-cursor, word-after-cursor, ws-after-word, +# end. In addition, the element is-word-start is 1 if the cursor +# is on the start of a word; this is non-trivial in the case of subword +# (camel case) matching as there may be no white space to test. +# +# Some of the array elements will be empty; this depends on the style. # For example # foo bar rod stick # ^ @@ -202,7 +210,7 @@ if [[ $wordstyle = *subword* ]]; then # followed by a lower case letter, or an upper case letter at # the start of a group of upper case letters. To make # it easier to be consistent, we just use anything that - # isn't an upper case characer instead of a lower case + # isn't an upper case character instead of a lower case # character. # Here the initial "*" will match greedily, so we get the # last such match, as we want. @@ -224,11 +232,18 @@ charskip=${(l:skip::?:)} eval pat2='${RBUFFER##(#b)('${charskip}${spacepat}')('\ ${wordpat2}')('${spacepat}')}' +if [[ -n $match[2] ]]; then + ws2=$match[1] + word2=$match[2] + ws3=$match[3] +else + # No more words, so anything left is white space after cursor. + ws2=$RBUFFER + pat2= +fi -ws2=$match[1] -word2=$match[2] -ws3=$match[3] - +integer wordstart +[[ ( -n $ws1 || -n $ws2 ) && -n $word2 ]] && wordstart=1 if [[ $wordstyle = *subword* ]]; then # Do we have a group of upper case characters at the start # of word2 (that don't form the entire word)? @@ -237,12 +252,19 @@ if [[ $wordstyle = *subword* ]]; then -n $match[2] ]]; then # Yes, so the last one is new word boundary. (( epos = ${#match[1]} - 1 )) + # Otherwise, are we in the middle of a word? + # In other, er, words, we've got something on the left with no + # white space following and something that doesn't start a word here. + elif [[ -n $word1 && -z $ws1 && -z $ws2 && \ + $word2 = (#b)([^${~subwordrange}]##)* ]]; then + (( epos = ${#match[1]} )) # Otherwise, do we have upper followed by non-upper not # at the start? Ignore the initial character, we already # know it's a word boundary so it can be an upper case character # if it wants. elif [[ $word2 = (#b)(?[^${~subwordrange}]##)[${~subwordrange}]* ]]; then (( epos = ${#match[1]} )) + (( wordstart = 1 )) else (( epos = 0 )) fi @@ -256,4 +278,19 @@ if [[ $wordstyle = *subword* ]]; then fi fi -matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2") +# matched_words should be local to caller. +# Just fix type here. +if [[ ${(t)matched_words} = *association* ]]; then + matched_words=( + start "$pat1" + word-before-cursor "$word1" + ws-before-cursor "$ws1" + ws-after-cursor "$ws2" + word-after-cursor "$word2" + ws-after-word "$ws3" + end "$pat2" + is-word-start $wordstart + ) +else + matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2") +fi diff --git a/Functions/Zle/select-bracketed b/Functions/Zle/select-bracketed index 00f51be2c..d467bb804 100644 --- a/Functions/Zle/select-bracketed +++ b/Functions/Zle/select-bracketed @@ -12,6 +12,8 @@ # done # done +setopt localoptions noksharrays + local style=${${1:-$KEYS}[1]} matching="(){}[]<>bbBB" local -i find=${NUMERIC:-1} idx=${matching[(I)[${${1:-$KEYS}[2]}]]}%9 (( idx )) || return 1 # no corresponding closing bracket diff --git a/Functions/Zle/select-word-match b/Functions/Zle/select-word-match new file mode 100644 index 000000000..8440852ab --- /dev/null +++ b/Functions/Zle/select-word-match @@ -0,0 +1,120 @@ +# Select the entire word around the cursor. Intended for use as +# a vim-style text object in vi mode but with customisable +# word boundaries. +# +# For example: +# autoload -U select-word-match +# zle -N select-in-camel select-word-match +# bindkey -M viopp ic select-in-camel +# zstyle ':zle:*-camel' word-style normal-subword + +emulate -L zsh +setopt extendedglob + +local curcontext=:zle:$WIDGET +local -A matched_words +# Start and end of range of characters +integer pos1 pos2 num=${NUMERIC:-1} +local style word + +# choose between inner word or a word style of widget +for style in $1 ${${WIDGET#*-}[1]} $KEYS[1] "i"; do + [[ $style = [ai] ]] && break +done + +autoload -Uz match-words-by-style + +while (( num-- )); do + if (( MARK > CURSOR )); then + # if cursor is at the start of the selection, just move back a word + match-words-by-style + if [[ $style = i && -n $matched_words[ws-before-cursor] ]]; then + word=$matched_words[ws-before-cursor] + else + word=$matched_words[word-before-cursor]$matched_words[ws-before-cursor] + fi + if [[ -n $word ]]; then + (( CURSOR -= ${#word} )) + else + return 1 + fi + elif (( MARK >= 0 && MARK < CURSOR )); then + # cursor at the end, move forward a word + (( CURSOR+1 == $#BUFFER )) && return 1 + (( CURSOR++ )) + match-words-by-style + if [[ -n $matched_words[ws-after-cursor] ]]; then + if [[ $style = i ]]; then + # just skip the whitespace + word=$matched_words[ws-after-cursor] + else + # skip the whitespace plus word + word=$matched_words[ws-after-cursor]$matched_words[word-after-cursor] + fi + else + if [[ $style = i ]]; then + # skip the word + word=$matched_words[word-after-cursor] + else + # skip word and following whitespace + word=$matched_words[word-after-cursor]$matched_words[ws-after-word] + fi + fi + (( CURSOR += ${#word} - 1 )) + else + match-words-by-style + + if (( ${matched_words[is-word-start]} )); then + # The word we are selecting starts at the cursor position. + pos1=$CURSOR + else + # No whitespace before us, so select any wordcharacters there. + pos1="${#matched_words[start]}" + fi + + if [[ -n "${matched_words[ws-after-cursor]}" ]]; then + if [[ -n "${matched_words[ws-before-cursor]}" ]] || (( CURSOR == 0 )); then + # whitespace either side, select it + (( pos1 = CURSOR - ${#matched_words[ws-before-cursor]} )) + (( pos2 = CURSOR + ${#matched_words[ws-after-cursor]} )) + else + # There's whitespace at the cursor position, so only select + # up to the cursor position. + (( pos2 = CURSOR + 1 )) + fi + else + # No whitespace at the cursor position, so select the + # current character and any following wordcharacters. + (( pos2 = CURSOR + ${#matched_words[word-after-cursor]} )) + fi + + if [[ $style = a ]]; then + if [[ -n "${matched_words[ws-after-cursor]}" && ( -n "${matched_words[ws-before-cursor]}" || CURSOR -eq 0 ) ]]; then + # in the middle of whitespace so grab a word + if [[ -n "${matched_words[word-after-cursor]}" ]]; then + (( pos2 += ${#matched_words[word-after-cursor]} )) # preferably the one after + else + (( pos1 -= ${#matched_words[word-before-cursor]} )) # otherwise the one before + fi + elif [[ -n "${matched_words[ws-after-word]}" ]]; then + (( pos2 += ${#matched_words[ws-after-word]} )) + elif [[ -n "${matched_words[ws-before-cursor]}" ]]; then + # couldn't grab whitespace forwards so try backwards + (( pos1 -= ${#matched_words[ws-before-cursor]} )) + elif (( pos1 > 0 )); then + # There might have been whitespace before the word + (( CURSOR = pos1 )) + match-words-by-style + if [[ -n "${matched_words[ws-before-cursor]}" ]]; then + (( pos1 -= ${#matched_words[ws-before-cursor]} )) + fi + fi + fi + + (( MARK = pos1, CURSOR = pos2-1 )) + fi +done + +if [[ $KEYMAP == vicmd ]] && (( !REGION_ACTIVE )); then + (( CURSOR++ )) # Need to include cursor position for operators +fi diff --git a/Functions/Zle/surround b/Functions/Zle/surround index b7be30b75..b51b77c04 100644 --- a/Functions/Zle/surround +++ b/Functions/Zle/surround @@ -19,6 +19,7 @@ local before after local -A matching matching=( \( \) \{ \} \< \> \[ \] ) +zle -f vichange case $WIDGET in change-*) local MARK="$MARK" CURSOR="$CURSOR" call @@ -69,7 +70,11 @@ case $WIDGET in before="${(k)matching[(r)[$before:q]]}" fi CUTBUFFER="$before$CUTBUFFER$after" - zle .vi-put-after -n 1 + if [[ CURSOR -eq 0 || $BUFFER[CURSOR] = $'\n' ]]; then + zle .vi-put-before -n 1 + else + zle .vi-put-after -n 1 + fi CUTBUFFER="$save_cut" CURSOR="$save_cur" ;; esac diff --git a/Functions/Zle/vi-pipe b/Functions/Zle/vi-pipe new file mode 100644 index 000000000..1729cb6e1 --- /dev/null +++ b/Functions/Zle/vi-pipe @@ -0,0 +1,39 @@ +# Example of a widget that takes a vi motion + +# Filter part of buffer corresponding to a vi motion through an external +# program. + +# To enable with vi compatible bindings use: +# autoload -Uz vi-pipe +# bindkey -a '!' vi-pipe + +setopt localoptions noksharrays + +autoload -Uz read-from-minibuffer +local _save_cut="$CUTBUFFER" REPLY + +# mark this widget as a vi change so it can be repeated as a whole +zle -f vichange + +# force movement to default to line mode +(( REGION_ACTIVE )) || zle -U V +# Use the standard vi-change to accept a vi motion. +zle .vi-change || return +read-from-minibuffer "!" +zle .vi-cmd-mode +local _save_cur=$CURSOR + +# cut buffer contains the deleted text and can be modified +CUTBUFFER=$(eval "$REPLY" <<<"$CUTBUFFER") + +# put the modified text back in position. +if [[ CURSOR -eq 0 || $BUFFER[CURSOR] = $'\n' ]]; then + # at the beginning of a line, vi-delete won't have moved the cursor + # back to a previous line + zle .vi-put-before -n 1 +else + zle .vi-put-after -n 1 +fi + +# restore cut buffer and cursor to the start of the range +CUTBUFFER="$_save_cut" CURSOR="$_save_cur" diff --git a/Functions/Zle/zcalc-auto-insert b/Functions/Zle/zcalc-auto-insert index c9a5c8867..e1affd1c3 100644 --- a/Functions/Zle/zcalc-auto-insert +++ b/Functions/Zle/zcalc-auto-insert @@ -1,6 +1,7 @@ # Bind to a binary operator keystroke for use with zcalc +# Not useful in RPN mode. -if [[ -n $ZCALC_ACTIVE ]]; then +if [[ -n $ZCALC_ACTIVE && $ZCALC_ACTIVE != rpn ]]; then if [[ $CURSOR -eq 0 || $LBUFFER[-1] = "(" ]]; then LBUFFER+=${ZCALC_AUTO_INSERT_PREFIX:-"ans "} fi |