diff --git a/meson.build b/meson.build index 3ce12dc89..afe2e38de 100644 --- a/meson.build +++ b/meson.build @@ -655,6 +655,28 @@ if build_tools install_dir: bash_completion_path) endif + if get_option('enable-zsh-completion') + zsh_completion_path = get_option('zsh-completion-path') + if zsh_completion_path == '' + zsh_completion_path = get_option('datadir') / 'zsh/site-functions' + endif + install_data('tools/xkbcli-zsh-completion.zsh', + rename: '_xkbcli', + install_dir: zsh_completion_path) + install_data('tools/xkb-model-zsh-completion.zsh', + rename: '_xkb_model', + install_dir: zsh_completion_path) + install_data('tools/xkb-layout-zsh-completion.zsh', + rename: '_xkb_layout', + install_dir: zsh_completion_path) + install_data('tools/xkb-variant-zsh-completion.zsh', + rename: '_xkb_variant', + install_dir: zsh_completion_path) + install_data('tools/xkb-options-zsh-completion.zsh', + rename: '_xkb_options', + install_dir: zsh_completion_path) + endif + # Tool: compile-keymap xkbcli_compile_keymap = executable('xkbcli-compile-keymap', 'tools/compile-keymap.c', diff --git a/meson_options.txt b/meson_options.txt index 54132104d..b84200292 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -28,6 +28,11 @@ option( type: 'string', description: 'Directory for bash completion scripts' ) +option( + 'zsh-completion-path', + type: 'string', + description: 'Directory for zsh completion scripts' +) option( 'default-rules', type: 'string', @@ -100,3 +105,9 @@ option( value: true, description: 'Enable installing bash completion scripts', ) +option( + 'enable-zsh-completion', + type: 'boolean', + value: true, + description: 'Enable installing zsh completion scripts', +) diff --git a/tools/xkb-layout-zsh-completion.zsh b/tools/xkb-layout-zsh-completion.zsh new file mode 100644 index 000000000..a8840d875 --- /dev/null +++ b/tools/xkb-layout-zsh-completion.zsh @@ -0,0 +1,31 @@ +#compdef -value-,XKB_DEFAULT_LAYOUT,-default- + +local layouts_yaml +layouts_yaml="$(_call_program -l xkb-yaml xkbcli list)" + +case $status in + 127 ) _message "xkb layout completion requires xkbcli" && return 127 ;; + <1->) _message "error listing xkb layouts" && return 1 ;; +esac + +local yq_prog='.layouts[] | select(.variant == "") | "\(.layout):\(.description)"' +local -a xkb_layouts +xkb_layouts=( ${(@f)"$(printf '%s\n' $layouts_yaml | _call_program -l xkb-parse-yaml yq ${(q)yq_prog})"} ) + +case $status in + 127) + local sed_layouts='/^layouts:/,/^$/p' + local sed_names="s/- layout: '([^']+)'/\\1/p" sed_variant="s/ variant: '([^']*)'/v\1/p" sed_descs="s/ description: //p" + local layouts_sed="$(_call_program -l sed sed -n ${(q)sed_layouts} <<< $layouts_yaml)" + local -a layout_names=( ${(@f)"$(_call_program -l sed-names sed -En ${(q)sed_names} <<< $layouts_sed)"} ) + local -a layout_variants=( ${(@f)"$(_call_program -l sed-variant sed -En ${(q)sed_variant} <<< $layouts_sed)"} ) + local -a layout_descs=( ${(@f)"$(_call_program -l sed-descs sed -n ${(q)sed_descs} <<< $layouts_sed)"} ) + local -a xkb_layouts1 xkb_layouts2 + printf -v xkb_layouts1 '%s:%s' ${layout_names:^layout_variants} + printf -v xkb_layouts2 '%s:%s' ${xkb_layouts1:^layout_descs} + xkb_layouts=( ${${(M)xkb_layouts2:#*:v:*}:s/:v:/:} ) + ;; + <1->) _message "error completing xkb layouts" && return 1 ;; +esac + +_describe -t xkb-layout 'xkb layout' xkb_layouts "$@" diff --git a/tools/xkb-model-zsh-completion.zsh b/tools/xkb-model-zsh-completion.zsh new file mode 100644 index 000000000..8f1e3f947 --- /dev/null +++ b/tools/xkb-model-zsh-completion.zsh @@ -0,0 +1,26 @@ +#compdef -value-,XKB_DEFAULT_MODEL,-default- + +local models_yaml +models_yaml="$(_call_program -l xkb-yaml xkbcli list)" + +case $status in + 127 ) _message "xkb model completion requires xkbcli" && return 127 ;; + <1->) _message "error listing xkb models" && return 1 ;; +esac + +local yq_prog='.models[] | "\(.name):\(.description)"' +local -a models +models=( ${(@f)"$(printf '%s\n' $models_yaml | _call_program -l xkb-parse-yaml yq ${(q)yq_prog})"} ) +case $status in + 127) + local sed_models='/^models:/,/^$/p' sed_names='s/- name: //p' sed_descriptions='s/ description: //p' + local models_sed="$(_call_program -l sed sed -n ${(q)sed_models} <<< $models_yaml)" + local -a model_names=( ${(@f)"$(_call_program -l sed-names sed -n ${(q)sed_names} <<< $models_sed)"}) + local -a model_descriptions=( ${(@f)"$(_call_program -l sed-models sed -n ${(q)sed_descriptions} <<< $models_sed)"}) + printf -v models '%s:%s' ${model_names:^model_descriptions} + ;; + + <1->) _message "error completing xkb models" && return 1 ;; +esac + +_describe -t xkb-model 'xkb model' models "$@" diff --git a/tools/xkb-options-zsh-completion.zsh b/tools/xkb-options-zsh-completion.zsh new file mode 100644 index 000000000..c5b071c66 --- /dev/null +++ b/tools/xkb-options-zsh-completion.zsh @@ -0,0 +1,41 @@ +#compdef -value-,XKB_DEFAULT_OPTIONS,-default- + +local options_yaml +options_yaml="$(_call_program -l xkb-yaml xkbcli list)" + +case $status in + 127 ) _message "xkb option completion requires xkbcli" && return 127 ;; + <1->) _message "error listing xkb options" && return 1 ;; +esac + +local yq_prog='.option_groups[] as $grp | $grp.options[].name | sub(":.*", "") as $name | "*\($name)[\($grp.description)]: :->option-\($name)"' +local -Ua option_groups +option_groups=( ${(@f)"$(printf '%s\n' $options_yaml | _call_program -l xkb-parse-yaml yq ${(q)yq_prog})"} ) + +case $status in + 127 ) + # fallback with sed + local sed_options='/^option_groups:/,/^$/p' + local sed_names="s/ - name: '([^']+)'/\\1/p" sed_descs="s/ description: '([^']+)'/\\1/p" + local options_sed="$(printf '%s\n' $options_yaml | _call_program -l sed sed -n ${(q)sed_options})" + local -a options_names=( ${(@f)"$(_call_program -l sed-names sed -En ${(q)sed_names} <<< $options_sed)"}) + local -a options_descs=( ${(@f)"$(_call_program -l sed-desc sed -En ${(q)sed_descs} <<< $options_sed)"}) + local -a xkb_options + printf -v xkb_options '%s:%s' ${${options_names:s/:/\\:}:^options_descs} + _describe -t xkb-option 'xkb option' xkb_options + return + ;; + <1->) _message "error completing xkb options" && return 1 ;; +esac + +local context state state_descr line +typeset -A val_args + +_values -s, -S: 'xkb option' ${option_groups} +if [[ $state == option-* ]]; then + local optname=${state#option-} + local -a xkb_options + yq_prog='.option_groups[].options[] | select(.name | test("'$optname'" + ":")) | "\(.name):\(.description)"' + xkb_options=( ${${(@f)"$(printf '%s\n' $options_yaml | _call_program -l xkb-parse-yaml yq ${(q)yq_prog})"}#*:} ) + _describe -t xkb-option 'xkb option' xkb_options -qS, +fi diff --git a/tools/xkb-variant-zsh-completion.zsh b/tools/xkb-variant-zsh-completion.zsh new file mode 100644 index 000000000..012c7270d --- /dev/null +++ b/tools/xkb-variant-zsh-completion.zsh @@ -0,0 +1,31 @@ +#compdef -value-,XKB_DEFAULT_VARIANT,-default- + +local variants_yaml +variants_yaml="$(_call_program -l xkb-yaml xkbcli list)" + +case $status in + 127 ) _message "xkb model completion requires xkbcli" && return 127 ;; + <1->) _message "error listing xkb variants" && return 1 ;; +esac + +local yq_prog1 yq_prog2 layout=${(v)opt_args[(I)*layout]} +if [[ ${(t)opt_args} == *association* && -n $layout ]]; then + # If we're called from _arguments this will attempt to match the layout + # given in any --layout option previously seen on the command line + yq_prog1='.layouts[] | select(.variant != "" and .layout == '${(qqq)${(Q)layout}}') | "\(.layout)(\(.variant)):\(.description)"' + yq_prog2='.layouts[] | select(.variant != "" and .layout == '${(qqq)${(Q)layout}}') | "\(.variant):\(.description)"' +else + yq_prog1='.layouts[] | select(.variant != "") | "\(.layout)(\(.variant)):\(.description)"' + yq_prog2='.layouts[] | select(.variant != "") | "\(.variant):\(.description)"' +fi + +local -a variants1 variants2 +variants1=( ${(@f)"$(printf '%s\n' $variants_yaml | _call_program -l xkb-parse-yaml yq ${(q)yq_prog1})"} ) +variants2=( ${(@f)"$(printf '%s\n' $variants_yaml | _call_program -l xkb-parse-yaml yq ${(q)yq_prog2})"} ) + +case $status in + 127 ) _message "xkb model completion requires yq" && return 127 ;; + <1->) _message "error completing xkb variants" && return 1 ;; +esac + +_describe -t xkb-model 'xkb model' variants1 variants2 "$@" diff --git a/tools/xkbcli-zsh-completion.zsh b/tools/xkbcli-zsh-completion.zsh new file mode 100644 index 000000000..ae33c6933 --- /dev/null +++ b/tools/xkbcli-zsh-completion.zsh @@ -0,0 +1,200 @@ +#compdef xkbcli -P xkbcli-* + +local context state state_descr line +typeset -A opt_args +local expl + +_xkbcli_commands() { + local -a commands=( + 'list:list available rules, models, layouts, variants and options' + 'interactive:interactive debugger for XKB keymaps' + 'interactive-wayland:interactive debugger for XKB keymaps for Wayland' + 'interactive-x11:interactive debugger for XKB keymaps for X11' + 'interactive-evdev:interactive debugger for XKB keymaps for evdev' + 'dump-keymap:dump a XKB keymap from a Wayland or X11 compositor' + 'dump-keymap-wayland:dump a XKB keymap from a Wayland compositor' + 'dump-keymap-x11:dump a XKB keymap from an X server' + 'compile-keymap:compile an XKB keymap' + 'compile-compose:compile a Compose file' + 'how-to-type:print key sequences to type a Unicode codepoint' + ) + + _describe -t xkbcli-command 'xkbcli command' commands +} + +local -a rmlvo_opts_common=( + '--rules=[the XKB ruleset]:rules:(base evdev)' + '--model=[the XKB model]:model:_xkb_model' + '--layout=[the XKB layout]:layout:_xkb_layout' + '--variant=[the XKB variant]:variant:_xkb_variant' + '--options=[the XKB options]:options:_xkb_options' + '--enable-environment-names[set the default RMLVO values from the environment]' +) + +_xkbcli-list() { + _arguments -S : \ + '(-v --verbose)'{-v,--verbose}'[increase verbosity]' \ + '--help[print a help message and exit]' \ + '--ruleset=[load a ruleset]' \ + '--skip-default-paths[do not load the default XKB paths]' \ + '--load-exotic[load the exotic (extra) rulesets]' \ + '*:xkb base directory:_files -/' +} + +_xkbcli-interactive() { + _arguments -S : \ + '--verbose[enable verbose debugging output]' \ + '--help[print a help message and exit]' \ + '(-1 --uniline --multiline)--multiline[enable multiline event output]' \ + '(-1 --uniline --multiline)'{-1,--uniline}'[enable uniline event output]' \ + '(--local-state)--keymap=[use the given keymap instead of the compositor keymap]:keymap:_files' \ + '--local-state[enable local state handling and ignore modifiers/layouts]' \ + '--enable-compose[enable Compose]' \ + '--format=[use the given keymap format]:xkb format:(v1 v2)' +} + +_xkbcli-interactive-evdev() { + _arguments -S : \ + '--help[print a help message and exit]' \ + '--format=[use the given keymap format]:xkb format:(v1 v2)' \ + '--verbose[enable verbose debugging output]' \ + '(-1 --uniline --multiline)'{-1,--uniline}'[enable uniline event output]' \ + '(-1 --uniline --multiline)--multiline[enable multiline event output]' \ + '--short[shorter event output]' \ + '--report-state-changes[report changes to the state]' \ + '--enable-compose[enable Compose]' \ + '--consumed-mode=[select the consumed modifiers mode]:mode:(xkb|gtk)' \ + '--without-x11-offset[do not add X11 keycode offset]' \ + "$rmlvo_opts_common[@]" +} + +_xkbcli-dump-keymap() { + _arguments -S : \ + '--verbose[enable verbose debugging output]' \ + '--help[print a help message and exit]' \ + '(--format)--input-format=[use the given input keymap format]:xkb format:(v1 v2)' \ + '(--format)--output-format=[use the given output keymap format]:xkb format:(v1 v2)' \ + '(--input-format --output-format)--format=[use the given keymap format for input and output]:xkb format:(v1 v2)' \ + '--no-pretty[do not pretty preint when serializing a keymap]' \ + '--drop-unused[disable unused bits serialization]' \ + '--raw[dump raw keymap without parsing it]' +} + +_xkbcli-compile-keymap() { + _arguments -S : \ + '--help[print a help message and exit]' \ + '--verbose[enable verbose debugging output]' \ + '--test[test compilation but do not print the keymap]' \ + + input \ + '*--include[add the given path to the include path list]' \ + '--include-defaults[add the default set of include directories]' \ + '(--format)--input-format=[the keymap format to use for parsing]:xkb format:(v1 v2)' \ + '(--format)--output-format=[the keymap format to use for serializing]:xkb format:(v1 v2)' \ + '(--input-format --output-format)--format=[the keymap format to use for parsing and serializing]:xkb format:(v1 v2)' \ + '--no-pretty[do not pretty preint when serializing a keymap]' \ + '--drop-unused[disable unused bits serialization]' \ + '(--enable-environment-names)--keymap=[use the given keymap file]:keymap:_files' \ + "$rmlvo_opts_common[@]" \ + + '(output)' \ + '--kccgst[print a keymap in KcCGST format]' \ + '--kccgst-yaml[print a KcCGST keymap in YAML format]' \ + '--rmlvo[print the full RMLVO in YAML format]' \ + '--modmaps[print the real and virtual key modmaps in YAML format]' +} + +_xkbcli-compile-compose() { + _arguments -S : \ + '--help[print a help message and exit]' \ + '--verbose[enable verbose debugging output]' \ + '--test[test compilation but do not print the Compose file]' \ + '--locale=[use the specified locale]:locale:_locales' +} + +_xkbcli-keysyms() { + local include="/usr/include/xkbcommon/xkbcommon-keysyms.h" + local -Ua keysyms=(${(@f)"$(_call_program -l keysym grep -Pwo 'XKB_KEY_\\K\\w+' $include)"}) + _wanted keysym expl 'keysym' compadd -a "$@" - keysyms +} + +_xkbcli-how-to-type() { + local context state state_descr line + typeset -A opt_args + + local ret=1 + _arguments -S : \ + '--help[print a help message and exit]' \ + '--verbose[enable verbose debugging output]' \ + '--keysym[treat the argument only as a keysym]' \ + '--disable-compose[disable Compose support]' \ + + xkb \ + '--format=[the keymap format to use]' \ + '--keymap=[the keymap file to load]:xkb keymap:_files' \ + "$rmlvo_opts_common[@]" \ + ': :->key' && ret=0 + + case $state in + key) + if (( $+opt_args[--keysym] )); then + _xkbcli-keysyms && ret=0 + else + _alternative \ + 'character:character:()' \ + 'codepoint:codepoint:()' \ + 'keysym:keysym:_xkbcli-keysyms' && ret=0 + fi + ;; + esac + return ret +} + +local ret=1 +case $service in + xkbcli-interactive-(wayland|x11)) + _xkbcli-interactive + return + ;; + xkbcli-dump-keymap-(wayland|x11)) + _xkbcli-dump-keymap + return + ;; + xkbcli-*) + _call_function ret "_$service" + return ret + ;; + xkbcli) + _arguments -S : \ + '(-)'{-h,--help}'[show a help message and exit]' \ + '(-)'{-V,--version}'[show version info and exit]' \ + '(-): :->command' \ + '(-)*:: :->option-or-argument' && ret=0 + ;; +esac + +case $state in + command) + _xkbcli_commands && ret=0 + ;; + option-or-argument) + local curcontext=${curcontext%:*:*}:xkbcli-$words[1]: + case $words[1] in + list) + _xkbcli-list && ret=0 ;; + interactive(|-wayland|-x11)) + _xkbcli-interactive && ret=0 ;; + interactive-evdev) + _xkbcli-interactive-evdev && ret=0 ;; + dump-keymap(|-wayland|-x11)) + _xkbcli-dump-keymap && ret=0 ;; + compile-keymap) + _xkbcli-compile-keymap && ret=0 ;; + compile-compose) + _xkbcli-compile-compose && ret=0 ;; + how-to-type) + _xkbcli-how-to-type && ret=0 ;; + *) + _message "unknown $service command ${(qq)words[1]}" + ;; + esac + ;; +esac +return ret