From 5613df3034d13160acf692154df476b019335295 Mon Sep 17 00:00:00 2001 From: dana Date: Thu, 6 Jul 2017 19:04:14 -0500 Subject: [PATCH] Refactor zsh completion function - Improve documentation - Reorganise into functions - Order options lexicographically - Correct minor wording inconsistencies - Fix --count error - Fix --maxdepth error - Fix --path-separator error - Fix --version error - Adjust exclusivity for --files, -h, -j, -o, -r, -t, -T, -v, -V, &c. - Improve pattern-operand guard behaviour - Partially fix issue with colorspec state - Fix issue with typespec state - Add completion for :include: sequence - Move licence info out of the way --- complete/_rg | 360 ++++++++++++++++++++++++++++----------------------- 1 file changed, 200 insertions(+), 160 deletions(-) diff --git a/complete/_rg b/complete/_rg index a29debae..bd4482aa 100644 --- a/complete/_rg +++ b/complete/_rg @@ -1,4 +1,204 @@ #compdef rg + +## +# zsh completion function for ripgrep +# +# Run ci/test_complete.sh after building to ensure that the options supported by +# this function stay in synch with the `rg` binary. +# +# @see https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide +# +# Based on code from the zsh-users project — see copyright notice below. + +_rg() { + local state_descr ret curcontext="${curcontext}" + local -a context line state + local -A opt_args val_args + local -a rg_args + + # Sort by long option name to match `rg --help` + rg_args=( + '(-A -C --after-context --context)'{-A,--after-context=}'[specify lines to show after each match]:number of lines' + '(-B -C --before-context --context)'{-B,--before-context=}'[specify lines to show before each match]:number of lines' + '(-i -s -S --ignore-case --case-sensitive --smart-case)'{-s,--case-sensitive}'[search case-sensitively]' + '--color=[specify when to use colors in output]:when:( never auto always ansi )' + '*--colors=[specify color settings and styles]: :->colorspec' + '--column[show column numbers]' + '(-A -B -C --after-context --before-context --context)'{-C,--context=}'[specify lines to show before and after each match]:number of lines' + '--context-separator=[specify string used to separate non-continuous context lines in output]:separator' + '(-c --count)'{-c,--count}'[only show count of matches for each file]' + '--debug[show debug messages]' + '--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size' + '(-E --encoding)'{-E,--encoding=}'[specify text encoding of files to search]: :_rg_encodings' + '*'{-f,--file=}'[specify file containing patterns to search for]:file:_files' + "(1)--files[show each file that would be searched (but don't search)]" + '(-l --files-with-matches --files-without-match)'{-l,--files-with-matches}'[only show names of files with matches]' + '(-l --files-with-matches --files-without-match)--files-without-match[only show names of files without matches]' + '(-F --fixed-strings)'{-F,--fixed-strings}'[treat pattern as literal string instead of regular expression]' + '(-L --follow)'{-L,--follow}'[follow symlinks]' + '*'{-g,--glob=}'[include or exclude files for searching that match the specified glob]:glob' + '(: -)'{-h,--help}'[display help information]' + '(-p --no-heading --pretty --vimgrep)--heading[show matches grouped by file name]' + '--hidden[search hidden files and directories]' + '*--iglob=[include or exclude files for searching that match the specified case-insensitive glob]:glob' + '(-i -s -S --case-sensitive --ignore-case --smart-case)'{-i,--ignore-case}'[search case-insensitively]' + '--ignore-file=[specify additional ignore file]:file:_files' + '(-v --invert-match)'{-v,--invert-match}'[invert matching]' + '(-n -N --line-number --no-line-number)'{-n,--line-number}'[show line numbers]' + '(-M --max-columns)'{-M,--max-columns=}'[specify max length of lines to print]:number of bytes' + '(-m --max-count)'{-m,--max-count=}'[specify max number of matches per file]:number of matches' + '--max-filesize=[specify size above which files should be ignored]:file size' + '--maxdepth=[specify max number of directories to descend]:number of directories' + '(--mmap --no-mmap)--mmap[search using memory maps when possible]' + '(-H --with-filename --no-filename)--no-filename[suppress all file names]' + "(-p --heading --pretty --vimgrep)--no-heading[don't group matches by file name]" + "(--no-ignore-parent)--no-ignore[don't respect ignore files]" + "--no-ignore-parent[don't respect ignore files in parent directories]" + "--no-ignore-vcs[don't respect version control ignore files]" + '(-n -N --line-number --no-line-number)'{-N,--no-line-number}'[suppress line numbers]' + '--no-messages[suppress all error messages]' + "(--mmap --no-mmap)--no-mmap[don't search using memory maps]" + '(-0 --null)'{-0,--null}'[print NUL byte after file names]' + '(-o --only-matching -r --replace)'{-o,--only-matching}'[show only matching part of each line]' + '--path-separator=[specify path separator to use when printing file names]:separator' + '(-p --heading --no-heading --pretty --vimgrep)'{-p,--pretty}'[alias for --color=always --heading -n]' + '(-q --quiet)'{-q,--quiet}'[suppress normal output]' + '--regex-size-limit=[specify upper size limit of compiled regex]:regex size' + '(1 -f --file)*'{-e,--regexp=}'[specify pattern]:pattern' + '(-o --only-matching -r --replace)'{-r,--replace=}'[specify string used to replace matches]:replace string' + '(-i -s -S --ignore-case --case-sensitive --smart-case)'{-S,--smart-case}'[search case-insensitively if the pattern is all lowercase]' + '(-j --threads)--sort-files[sort results by file path (disables parallelism)]' + '(-a --text)'{-a,--text}'[search binary files as if they were text]' + '(-j --sort-files --threads)'{-j,--threads=}'[specify approximate number of threads to use]:number of threads' + '*'{-t,--type=}'[only search files matching specified type]: :_rg_types' + '*--type-add=[add new glob for file type]: :->typespec' + '*--type-clear=[clear globs previously defined for specified file type]: :_rg_types' + # This should actually be exclusive with everything but other type options + '(:)--type-list[show all supported file types and their associated globs]' + '*'{-T,--type-not=}"[don't search files matching specified type]: :_rg_types" + '*'{-u,--unrestricted}'[reduce level of "smart" searching]' + '(: -)'{-V,--version}'[display version information]' + '(-p --heading --no-heading --pretty)--vimgrep[show results in vim-compatible format]' + '(-H --no-filename --with-filename)'{-H,--with-filename}'[prefix each match with name of file that contains it]' + '(-w --word-regexp)'{-w,--word-regexp}'[only show matches surrounded by word boundaries]' + '(-e -f --file --files --regexp --type-list)1: :_rg_pattern' + '(--type-list)*:file:_files' + ) + + _arguments -s -S : "${rg_args[@]}" && return 0 + + while (( $#state )); do + case "${state[1]}" in + colorspec) + # @todo I don't like this because it allows you to do weird things like + # `line:line:bg:`. Also, i would like the `compadd -q` behaviour + [[ -prefix *:none: ]] && return 1 + [[ -prefix *:*:*:* ]] && return 1 + + _values -S ':' 'color/style type' \ + 'column[specify coloring for column numbers]: :->attribute' \ + 'line[specify coloring for line numbers]: :->attribute' \ + 'match[specify coloring for match text]: :->attribute' \ + 'path[specify color for file names]: :->attribute' && return 0 + + [[ "${state}" == 'attribute' ]] && + _values -S ':' 'color/style attribute' \ + 'none[clear color/style for type]' \ + 'bg[specify background color]: :->color' \ + 'fg[specify foreground color]: :->color' \ + 'style[specify text style]: :->style' && return 0 + + [[ "${state}" == 'color' ]] && + _values -S ':' 'color value' \ + black blue green red cyan magenta yellow white && return 0 + + [[ "${state}" == 'style' ]] && + _values -S ':' 'style value' \ + bold nobold intense nointense && return 0 + ;; + + typespec) + if compset -P '[^:]##:include:'; then + _sequence -s ',' _rg_types && return 0 + # @todo This bit in particular could be better, but it's a little + # complex, and attempting to solve it seems to run us up against a crash + # bug — zsh # 40362 + elif compset -P '[^:]##:'; then + _message 'glob or include directive' && return 1 + elif [[ ! -prefix *:* ]]; then + _rg_types -qS ':' && return 0 + fi + ;; + esac + shift state + done + + return 1 +} + +# zsh 5.1 refuses to complete options if a 'match-less' operand like our pattern +# could be 'completed' instead. We can use _guard() to avoid this problem, but +# it introduces another one: zsh won't print the message if we try to complete +# the pattern after having passed `--`. To work around *that* problem, we can +# use this function to bypass the _guard() when `--` is on the command line. +# This is inaccurate (it'd get confused by e.g. `rg -e --`), but zsh's handling +# of `--` isn't accurate anyway +_rg_pattern() { + if (( ${words[(I)--]} )); then + _message 'pattern' + else + _guard '^-*' 'pattern' + fi +} + +# Complete encodings +_rg_encodings() { + local -a expl + local -aU _encodings + + # This is impossible to read, but these encodings rarely if ever change, so it + # probably doesn't matter. They are derived from the list given here: + # https://encoding.spec.whatwg.org/#concept-encoding-get + _encodings=( + {{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean} + logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian} + 866 ibm{819,866} csibm866 + big5{,-hkscs} {cn-,cs}big5 x-x-big5 + cp{819,866,125{0..8}} x-cp125{0..8} + csiso2022{jp,kr} csiso8859{6,8}{e,i} + csisolatin{{1..6},9} csisolatin{arabic,cyrillic,greek,hebrew} + ecma-{114,118} asmo-708 elot_928 sun_eu_greek + euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese + {,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312 + iso-2022-{cn,cn-ext,jp,kr} + iso8859{,-}{{1..11},13,14,15} + iso-8859-{{1..11},{6,8}-{e,i},13,14,15,16} iso_8859-{{1..9},15} + iso_8859-{1,2,6,7}:1987 iso_8859-{3,4,5,8}:1988 iso_8859-9:1989 + iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157} + koi{,8,8-r,8-ru,8-u,8_r} cskoi8r + ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987 + latin{1..6} l{{1..6},9} + shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932 + utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8 + windows-{31j,874,949,125{0..8}} dos-874 tis-620 ansi_x3.4-1968 + x-user-defined auto + ) + + _wanted rg-encodings expl 'encoding' compadd -a "${@}" - _encodings +} + +# Complete file types +_rg_types() { + local -a expl + local -aU _types + + _types=( ${${(f)"$( _call_program rg-types rg --type-list )"}%%:*} ) + + _wanted rg-types expl 'file type' compadd -a "${@}" - _types +} + +_rg "${@}" + # ------------------------------------------------------------------------------ # Copyright (c) 2011 Github zsh-users - http://github.com/zsh-users # All rights reserved. @@ -39,166 +239,6 @@ # # ------------------------------------------------------------------------------ -local context state state_descr line -local -A opt_args -local -i ret=1 - -local -a common_options -common_options=( - '(-a --text)'{-a,--text}'[search binary files as if they were text]' - '(-c, --count)'{-c,--count}'[only show count of matches for each file]' - '--color=[specify when to use colors in output]:when:( never auto always ansi )' - '(1)*'{-e,--regexp=}'[specify pattern]:pattern' - '(-E --encoding)'{-E,--encoding=}'[specify text encoding of files to search]: :->encoding' - '(-F --fixed-strings)'{-F,--fixed-strings}'[treat pattern as literal string instead of regular expression]' - '*'{-g,--glob=}'[include or exclude files for searching that match the specified glob]:glob' - '(-h --help)'{-h,--help}'[display help information]' - '(-i -s -S --ignore-case --case-sensitive --smart-case)'{-i,--ignore-case}'[search case-insensitively]' - '(-n -N --line-number --no-line-number)'{-n,--line-number}'[show line numbers]' - '(-n -N --line-number --no-line-number)'{-N,--no-line-number}'[suppress line numbers]' - '(-o --only-matching)'{-o,--only-matching}'[show only matching part of each line]' - '(-q --quiet)'{-q,--quiet}'[suppress normal output]' - '(-T --type-not)*'{-t,--type=}'[only search files matching specified type]: :->type' - '(-t --type)*'{-T,--type-not=}"[don't search files matching specified type]: :->type" - '*'{-u,--unrestricted}'[reduce level of "smart" searching]' - '(-v --invert-match)'{-v,--invert-match}'[invert matching]' - '(-w --word-regexp)'{-w,--word-regexp}'[only show matches surrounded by word boundaries]' -) - -local -a less_common_options -less_common_options=( - '(-A -C --after-context --context)'{-A,--after-context=}'[specify lines to show after each match]:number of lines' - '(-B -C --before-context --context)'{-B,--before-context=}'[specify lines to show before each match]:number of lines' - '(-A -B -C --after-context --before-context --context)'{-C,--context=}'[specify lines to show before and after each match]:number of lines' - '*--colors=[specify color settings and styles]: :->colorspec' - '--column[show column numbers]' - '--context-separator=[specify string used to separate non-continuous context lines in output]:separator string' - '--debug[show debug messages]' - '--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size' - '*'{-f,--file=}'[specify file containing patterns to search for]:file:_files' - "--ignore-file=[specify additional ignore file]:file:_files" - "--files[show each file that would be searched (but don't search)]" - '(-l --files-with-matches --files-without-match)'{-l,--files-with-matches}'[only show names of files with matches]' - '(-l --files-with-matches --files-without-match)--files-without-match[only show names of files without matches]' - '(-H --with-filename --no-filename)'{-H,--with-filename}'[prefix each match with name of file that contains it]' - '(-H --with-filename --no-filename)--no-filename[suppress all file names]' - '(-p --no-heading --pretty --vimgrep)--heading[show matches grouped by file name]' - "(-p --heading --pretty --vimgrep)--no-heading[don't group matches by file name]" - '--hidden[search hidden files and directories]' - '*--iglob=[include or exclude files for searching that match the specified case-insensitive glob]:glob' - '(-L --follow)'{-L,--follow}'[follow symlinks]' - '(-M --max-columns)'{-M,--max-columns=}'[specify max length of lines to print]:number of bytes' - '(-m --max-count)'{-m,--max-count=}'[specify max number of matches per file]:number of matches' - '--max-filesize=[specify size above which files should be ignored]:size' - '--maxdepth[specify max number of directories to descend]:number of directories' - '(--no-mmap)--mmap[search using memory maps when possible]' - '--no-messages[suppress all error messages]' - "(--mmap)--no-mmap[don't search using memory maps]" - "(--no-ignore-parent)--no-ignore[don't respect ignore files]" - "--no-ignore-parent[don't respect ignore files in parent directories]" - "--no-ignore-vcs[don't respect version control ignore files]" - '(-0 --null)'{-0,--null}'[print NUL byte after file names]' - '--path-separator=[specify path separator to use when printing file names]' - '(-p --heading --no-heading --pretty --vimgrep)'{-p,--pretty}'[alias for --color=always --heading -n]' - '--regex-size-limit=[specify upper size limit of compiled regex]:regex size' - '(-r --replace)'{-r,--replace=}'[specify string used to replace matches]:replace string' - '(-i -s -S --ignore-case --case-sensitive --smart-case)'{-s,--case-sensitive}'[search case-sensitively]' - '(-i -s -S --ignore-case --case-sensitive --smart-case)'{-S,--smart-case}'[search case-insensitively if the pattern is all lowercase]' - '--sort-files[sort results by file path (disables parallelism)]' - '(-j --threads)'{-j,--threads=}'[specify approximate number of threads to use]:number of threads' - '(-v --version)'{-V,--version}'[display version information]' - '(-p --heading --no-heading --pretty)--vimgrep[show results in vim-compatible format]' -) - -local -a file_type_management_options -file_type_management_options=( - '--type-list[show all supported file types and their associated globs]' - '*--type-add=[add new glob for file type]: :->typespec' - '*--type-clear=[clear globs previously defined for specified file type]: :->type' -) - -_arguments -S -s : \ - $common_options \ - $less_common_options \ - $file_type_management_options \ - '(-e --regexp)1: :_guard "^--*" pattern' \ - '*:file:_files' \ - && ret=0 - -case "$state" in - colorspec) - _values -S ':' 'color/style type' \ - 'column[specify coloring for column numbers]: :->attribute' \ - 'line[specify coloring for line numbers]: :->attribute' \ - 'match[specify coloring for match text]: :->attribute' \ - 'path[specify color for file names]: :->attribute' && ret=0 - - [[ "$state" == 'attribute' ]] && - _values -S ':' 'color/style attribute' \ - 'none[clear color/style for type]' \ - 'bg[specify background color]: :->color' \ - 'fg[specify foreground color]: :->color' \ - 'style[specify text style]: :->style' && ret=0 - - [[ "$state" == 'color' ]] && - _values -S ':' 'color value' \ - black blue green red cyan magenta yellow white && ret=0 - - [[ "$state" == 'style' ]] && - _values -S ':' 'style value' \ - bold nobold intense nointense && ret=0 - ;; - - encoding) - # This is impossible to read, but these encodings rarely if ever change, so - # it probably doesn't matter. They are derived from the list given here: - # https://encoding.spec.whatwg.org/#concept-encoding-get - local -U encodings - encodings=( - {{,us-}ascii,arabic,chinese,cyrillic,greek{,8},hebrew,korean} - logical visual mac {,cs}macintosh x-mac-{cyrillic,roman,ukrainian} - 866 ibm{819,866} csibm866 - big5{,-hkscs} {cn-,cs}big5 x-x-big5 - cp{819,866,125{0..8}} x-cp125{0..8} - csiso2022{jp,kr} csiso8859{6,8}{e,i} - csisolatin{{1..6},9} csisolatin{arabic,cyrillic,greek,hebrew} - ecma-{114,118} asmo-708 elot_928 sun_eu_greek - euc-{jp,kr} x-euc-jp cseuckr cseucpkdfmtjapanese - {,x-}gbk csiso58gb231280 gb18030 {,cs}gb2312 gb_2312{,-80} hz-gb-2312 - iso-2022-{cn,cn-ext,jp,kr} - iso8859{,-}{{1..11},13,14,15} - iso-8859-{{1..11},{6,8}-{e,i},13,14,15,16} iso_8859-{{1..9},15} - iso_8859-{1,2,6,7}\\:1987 iso_8859-{3,4,5,8}\\:1988 iso_8859-9\\:1989 - iso-ir-{58,100,101,109,110,126,127,138,144,148,149,157} - koi{,8,8-r,8-ru,8-u,8_r} cskoi8r - ks_c_5601-{1987,1989} ksc{,_}5691 csksc56011987 - latin{1..6} l{{1..6},9} - shift{-,_}jis csshiftjis {,x-}sjis ms_kanji ms932 - utf{,-}8 utf-16{,be,le} unicode-1-1-utf-8 - windows-{31j,874,949,125{0..8}} dos-874 tis-620 ansi_x3.4-1968 - x-user-defined auto - ) - - _describe -t encodings 'encoding' encodings && ret=0 - ;; - - type|typespec) - local -U types - types=( ${${(f)"$(_call_program types rg --type-list)"}%%:*} ) - - if [[ "$case" == 'type' ]]; then - _describe -t types "type" types && ret=0 - else - # @todo: Would be nice to complete type names if an include: directive is - # provided here - _values -S ':' 'type spec' \ - ${^types}':glob or include directive' && ret=0 - fi - ;; -esac - -return ret - # Local Variables: # mode: shell-script # coding: utf-8-unix