diff options
Diffstat (limited to 'etc/_mpv.zsh')
-rw-r--r-- | etc/_mpv.zsh | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/etc/_mpv.zsh b/etc/_mpv.zsh new file mode 100644 index 0000000..c34a381 --- /dev/null +++ b/etc/_mpv.zsh @@ -0,0 +1,265 @@ +#compdef mpv + +# ZSH completion for mpv +# +# For customization, see: +# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization + +# +# This file is part of mpv. +# +# mpv is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# mpv is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with mpv. If not, see <http://www.gnu.org/licenses/>. +# + +local curcontext="$curcontext" state state_descr line +typeset -A opt_args + +local -a match mbegin mend +local MATCH MBEGIN MEND + +# By default, don't complete URLs unless no files match +local -a tag_order +zstyle -a ":completion:*:*:$service:*" tag-order tag_order || + zstyle ":completion:*:*:$service:*" tag-order '!urls' + +typeset -ga _mpv_completion_arguments _mpv_completion_protocols + +function _mpv_generate_arguments { + + _mpv_completion_arguments=() + + local -a option_aliases=() + + local list_options_line + for list_options_line in "${(@f)$($~words[1] --no-config --list-options)}"; do + + [[ $list_options_line =~ $'^[ \t]+--([^ \t]+)[ \t]*(.*)' ]] || continue + + local name=$match[1] desc=$match[2] + + if [[ $desc == Flag* ]]; then + + _mpv_completion_arguments+="$name" + if [[ $name != (\{|\}|v|list-options) ]]; then + # Negated version + _mpv_completion_arguments+="no-$name" + fi + + elif [[ -z $desc ]]; then + + # Sub-option for list option + + if [[ $name == *-(clr|help) ]]; then + # Like a flag + _mpv_completion_arguments+="$name" + else + # Find the parent option and use that with this option's name + _mpv_completion_arguments+="${_mpv_completion_arguments[(R)${name%-*}=*]/*=/$name=}" + fi + + elif [[ $desc == Print* ]]; then + + _mpv_completion_arguments+="$name" + + elif [[ $desc =~ $'^alias for (--)?([^ \t]+)' ]]; then + + # Save this for later; we might not have parsed the target option yet + option_aliases+="$name $match[2]" + + elif [[ $desc =~ $'^removed ' ]]; then + + # skip + + else + + # Option takes argument + + local entry="$name=-:${desc//:/\\:}:" + + if [[ $desc =~ '^Choices: ([^(]*)' ]]; then + + local -a choices=(${(s: :)match[1]}) + entry+="($choices)" + # If "no" is one of the choices, it can also be negated like a flag + # (--no-whatever is equivalent to --whatever=no). + if (( ${+choices[(r)no]} )); then + _mpv_completion_arguments+="no-$name" + fi + + elif [[ $desc == *'[file]'* ]]; then + + entry+='->files' + + elif [[ $name == (ao|vo|af|vf|profile|audio-device|vulkan-device) ]]; then + + entry+="->parse-help-$name" + + elif [[ $name == show-profile ]]; then + + entry+="->parse-help-profile" + + elif [[ $name == h(|elp) ]]; then + + entry+="->help-options" + + fi + + _mpv_completion_arguments+="$entry" + + fi + + done + + # Process aliases + local to_from real_name arg_spec + for to_from in $option_aliases; do + # to_from='alias-name real-name' + real_name=${to_from##* } + for arg_spec in "$real_name" "$real_name=*" "no-$real_name"; do + arg_spec=${_mpv_completion_arguments[(r)$arg_spec]} + [[ -n $arg_spec ]] && + _mpv_completion_arguments+="${arg_spec/$real_name/${to_from%% *}}" + done + done + + # Older versions of zsh have a bug where they won't complete an option listed + # after one that's a prefix of it. To work around this, we can sort the + # options by length, longest first, so that any prefix of an option will be + # listed after it. On newer versions of zsh where the bug is fixed, we skip + # this to avoid slowing down the first tab press any more than we have to. + autoload -Uz is-at-least + if ! is-at-least 5.2; then + # If this were a real language, we wouldn't have to sort by prepending the + # length, sorting the whole thing numerically, and then removing it again. + local -a sort_tmp=() + for arg_spec in $_mpv_completion_arguments; do + sort_tmp+=${#arg_spec%%=*}_$arg_spec + done + _mpv_completion_arguments=(${${(On)sort_tmp}/#*_}) + fi + +} + +function _mpv_generate_protocols { + _mpv_completion_protocols=() + local list_protos_line + for list_protos_line in "${(@f)$($~words[1] --no-config --list-protocols)}"; do + if [[ $list_protos_line =~ $'^[ \t]+(.*)' ]]; then + _mpv_completion_protocols+="$match[1]" + fi + done +} + +function _mpv_generate_if_changed { + # Called with $1 = 'arguments' or 'protocols'. Generates the respective list + # on the first run and re-generates it if the executable being completed for + # is different than the one we used to generate the cached list. + typeset -gA _mpv_completion_binary + local current_binary=${~words[1]:c} + zmodload -F zsh/stat b:zstat + current_binary+=T$(zstat +mtime $current_binary) + if [[ $_mpv_completion_binary[$1] != $current_binary ]]; then + # Use PCRE for regular expression matching if possible. This approximately + # halves the execution time of generate_arguments compared to the default + # POSIX regex, which translates to a more responsive first tab press. + # However, we can't rely on PCRE being available, so we keep all our + # patterns POSIX-compatible. + zmodload -s -F zsh/pcre C:pcre-match && setopt re_match_pcre + _mpv_generate_$1 + _mpv_completion_binary[$1]=$current_binary + fi +} + +# Only consider generating arguments if the argument being completed looks like +# an option. This way, the user should never see a delay when just completing a +# filename. +if [[ $words[$CURRENT] == -* ]]; then + _mpv_generate_if_changed arguments +fi + +local rc=1 + +_arguments -C -S \*--$_mpv_completion_arguments '*:files:->mfiles' && rc=0 + +case $state in + + parse-help-*) + local option_name=${state#parse-help-} + local no_config="--no-config" + # Can't do non-capturing groups without pcre, so we index the ones we want + local pattern name_group=1 desc_group=2 + case $option_name in + audio-device|vulkan-device) + pattern=$'^[ \t]+'\''([^'\'']*)'\'$'[ \t]+''\((.*)\)' + ;; + profile) + # The generic pattern would actually work in most cases for --profile, + # but would break if a profile name contained spaces. This stricter one + # only breaks if a profile name contains tabs. + pattern=$'^\t([^\t]*)\t(.*)' + # We actually want config so we can autocomplete the user's profiles + no_config="" + ;; + *) + pattern=$'^[ \t]+(--'${option_name}$'=)?([^ \t]+)[ \t]*[-:]?[ \t]*(.*)' + name_group=2 desc_group=3 + ;; + esac + local -a values + local current + for current in "${(@f)$($~words[1] ${no_config} --${option_name}=help)}"; do + [[ $current =~ $pattern ]] || continue; + local name=${match[name_group]//:/\\:} desc=${match[desc_group]} + if [[ -n $desc ]]; then + values+="${name}:${desc}" + else + values+="${name}" + fi + done + (( $#values )) && { + compset -P '*,' + compset -S ',*' + _describe "$state_descr" values -r ',=: \t\n\-' && rc=0 + } + ;; + + files) + compset -P '*,' + compset -S ',*' + _files -r ',/ \t\n\-' && rc=0 + ;; + + mfiles) + local expl + _tags files urls + while _tags; do + _requested files expl 'media file' _files && rc=0 + if _requested urls; then + while _next_label urls expl URL; do + _urls "$expl[@]" && rc=0 + _mpv_generate_if_changed protocols + compadd -S '' "$expl[@]" $_mpv_completion_protocols && rc=0 + done + fi + (( rc )) || return 0 + done + ;; + + help-options) + compadd ${${${_mpv_completion_arguments%%=*}:#no-*}:#*-(add|append|clr|pre|set|remove|toggle)} + ;; + +esac + +return rc |