toumorokoshi / tome

convert any directory of scripts into your own fully-featured command.
MIT License
38 stars 2 forks source link

Tab completing --switch options in zsh prints compgen:14: bad option -- #43

Closed indrat closed 1 year ago

indrat commented 1 year ago

System/Version debian:12, zsh: 5.9, tome v0.9.0 macOS 12.6, zsh: 5.8.1, tome v0.9.0

Example command (modified example/dir_example/bar:

#!/usr/bin/env bash
# COMPLETE

if [[ "${@:-1}" == "--complete" ]]; then
    echo "--bar1 --bar2 --not-bar"
    exit
fi
echo "bar $*"

Behaviour: after typing --<TAB> the tab complete options are displayed correctly however after typing the next character and typing <TAB> a compgen warning is printed. Continuing to type results in additional compgen warnings being printed however, the argument is completed.

# no compgen warning
7aa02469b61d# tool bar --<TAB>
--bar1     --bar2     --not-bar

# compgen warning after <TAB>
7aa02469b61d# tool bar --bcompgen:14: bad option: --
--bar1     --bar2     --noarbar

# Multiple <TAB>s ends up completing the option however prints the compgen warning with each <TAB>
0df544f0a4b9# tool bar --b<TAB>compgen:14: bad option: --
ar<TAB>compgen:14: bad option: --
              tool bar --bar<TAB>compgen:14: bad option: --
1-bar1  --bar2
bar --bar1
toumorokoshi commented 1 year ago

hi! I'm having trouble reproducing this:

tsutsumi:/home/tsutsumi/.ytlaces/cookbook/t (0)
$ s t bar --bar1   
--bar1     --bar2     --not-bar

(it just cycles through the three options for me). This is with tome 0.9.0, zsh 5.9, arch linux. I feel like the debian / arch difference is likely not the culprit.

The call to compgen is here: https://github.com/toumorokoshi/tome/blob/master/src/commands/init.rs#L48.

And the relevant part is:

    all_options=`{tome_executable} command-complete {script_root} -s {shell} -- $tome_args`
    valid_options=$(compgen -W "$all_options" "$token_to_complete")

which makes me wonder what that token_to_complete is for you? it should be quoted, but perhaps it's being interpreted as an actual flag.

Or your compgen varies from mind for some reason (which it shouldn't be, zsh provides it).

can you try:

tome command-complete {your_script_root} -s zsh -- bar --

to see what it outputs?

indrat commented 1 year ago

Sure, the output is below:

$ cat $PWD/example/dir_example/bar 
#!/usr/bin/env bash
# COMPLETE

if [[ "$1" == "--complete" ]]; then
    echo "--bar1 --bar2 --not-bar"
    exit
fi
echo "COMMAND: bar $*"

$ tome command-complete $PWD/example/dir_example -s zsh -- bar --

--bar1 --bar2 --not-bar

Calling compgen directly based on the linked rust code:

$ compgen -W "--bar1 --bar2 --not-bar" "--"                       
--bar1
--bar2
--not-bar

$ compgen -W "--bar1 --bar2 --not-bar" "--b"
compgen:14: bad option: --
--bar1
--bar2
--not-bar
unset
rehash
popd
....

In an attempt to remove as much of my environment as possible I also built a container with:

FROM debian:bookworm

RUN /bin/true \
    && apt update \
    && apt install -y \
        zsh \
        curl \
    && curl -L -o /usr/local/bin/tome https://github.com/toumorokoshi/tome/releases/download/v0.9.0/tome-linux_amd64 \
    && chmod ugo+x /usr/local/bin/tome \
    && echo 'eval "$(tome init tool /usr/local/scripts zsh)"' > /root/.zshrc
$ podman build -t tome-test .
$ podman run --rm -it -v ${PWD}/example/dir_example:/usr/local/scripts localhost/tome-test:latest /bin/zsh
599e767dd146# tool bar --b<TAB>compgen:14: bad option: --
--bar1     --bar2     --noarbar                                  
815f45867787# compgen -W "--bar1 --bar2 --not-bar" "--b"
compgen:14: bad option: --
--bar1
--bar2
--not-bar
toumorokoshi commented 1 year ago

awesome, thank you for the container repro! I'll take a look.

toumorokoshi commented 1 year ago

so I was able to repro. compgen function in question:

compgen () {
        local opts prefix suffix job OPTARG OPTIND ret=1
        local -a name res results jids
        local -A shortopts
        emulate -L sh
        setopt kshglob noshglob braceexpand nokshautoload
        shortopts=(a alias b builtin c command d directory e export f file g group j job k keyword u user v variable)
        while getopts "o:A:G:C:F:P:S:W:X:abcdefgjkuv" name
        do
                case $name in
                        ([abcdefgjkuv]) OPTARG="${shortopts[$name]}"  ;&
                        (A) case $OPTARG in
                                        (alias) results+=("${(k)aliases[@]}")  ;;
                                        (arrayvar) results+=("${(k@)parameters[(R)array*]}")  ;;
                                        (binding) results+=("${(k)widgets[@]}")  ;;
                                        (builtin) results+=("${(k)builtins[@]}" "${(k)dis_builtins[@]}")  ;;
                                        (command) results+=("${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}")  ;;
                                        (directory) setopt bareglobqual
                                                results+=(${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/))
                                                setopt nobareglobqual ;;
                                        (disabled) results+=("${(k)dis_builtins[@]}")  ;;
                                        (enabled) results+=("${(k)builtins[@]}")  ;;
                                        (export) results+=("${(k)parameters[(R)*export*]}")  ;;
                                        (file) setopt bareglobqual
                                                results+=(${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N))
                                                setopt nobareglobqual ;;
                                        (function) results+=("${(k)functions[@]}")  ;;
                                        (group) emulate zsh
                                                _groups -U -O res
                                                emulate sh
                                                setopt kshglob noshglob braceexpand
                                                results+=("${res[@]}")  ;;
                                        (hostname) emulate zsh
                                                _hosts -U -O res
                                                emulate sh
                                                setopt kshglob noshglob braceexpand
                                                results+=("${res[@]}")  ;;
                                        (job) results+=("${savejobtexts[@]%% *}")  ;;
                                        (keyword) results+=("${(k)reswords[@]}")  ;;
                                        (running) jids=("${(@k)savejobstates[(R)running*]}")
                                                for job in "${jids[@]}"
                                                do
                                                        results+=(${savejobtexts[$job]%% *})
                                                done ;;
                                        (stopped) jids=("${(@k)savejobstates[(R)suspended*]}")
                                                for job in "${jids[@]}"
                                                do
                                                        results+=(${savejobtexts[$job]%% *})
                                                done ;;
                                        (setopt | shopt) results+=("${(k)options[@]}")  ;;
                                        (signal) results+=("SIG${^signals[@]}")  ;;
                                        (user) results+=("${(k)userdirs[@]}")  ;;
                                        (variable) results+=("${(k)parameters[@]}")  ;;
                                        (helptopic)  ;;
                                esac ;;
                        (F) COMPREPLY=()
                                local -a args
                                args=("${words[0]}" "${@[-1]}" "${words[CURRENT-2]}")
                                () {
                                        typeset -h words
                                        $OPTARG "${args[@]}"
                                }
                                results+=("${COMPREPLY[@]}")  ;;
                        (G) setopt nullglob
                                results+=(${~OPTARG})
                                unsetopt nullglob ;;
                        (W) results+=(${(Q)~=OPTARG})  ;;
                        (C) results+=($(eval $OPTARG))  ;;
                        (P) prefix="$OPTARG"  ;;
                        (S) suffix="$OPTARG"  ;;
                        (X) if [[ ${OPTARG[0]} = '!' ]]
                                then
                                        results=("${(M)results[@]:#${OPTARG#?}}")
                                else
                                        results=("${results[@]:#$OPTARG}")
                                fi ;;
                esac
        done
        print -l -r -- "$prefix${^results[@]}$suffix"
}

and my compgen which worked:

compgen () {
        local opts prefix suffix job OPTARG OPTIND ret=1
        local -a name res results jids
        local -A shortopts
        emulate -L sh
        setopt kshglob noshglob braceexpand nokshautoload
        shortopts=(a alias b builtin c command d directory e export f file g group j job k keyword u user v variable)
        while getopts "o:A:G:C:F:P:S:W:X:abcdefgjkuv" name
        do
                case $name in
                        ([abcdefgjkuv]) OPTARG="${shortopts[$name]}"  ;&
                        (A) case $OPTARG in
                                        (alias) results+=("${(k)aliases[@]}")  ;;
                                        (arrayvar) results+=("${(k@)parameters[(R)array*]}")  ;;
                                        (binding) results+=("${(k)widgets[@]}")  ;;
                                        (builtin) results+=("${(k)builtins[@]}" "${(k)dis_builtins[@]}")  ;;
                                        (command) results+=("${(k)commands[@]}" "${(k)aliases[@]}" "${(k)builtins[@]}" "${(k)functions[@]}" "${(k)reswords[@]}")  ;;
                                        (directory) setopt bareglobqual
                                                results+=(${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N-/))
                                                setopt nobareglobqual ;;
                                        (disabled) results+=("${(k)dis_builtins[@]}")  ;;
                                        (enabled) results+=("${(k)builtins[@]}")  ;;
                                        (export) results+=("${(k)parameters[(R)*export*]}")  ;;
                                        (file) setopt bareglobqual
                                                results+=(${IPREFIX}${PREFIX}*${SUFFIX}${ISUFFIX}(N))
                                                setopt nobareglobqual ;;
                                        (function) results+=("${(k)functions[@]}")  ;;
                                        (group) emulate zsh
                                                _groups -U -O res
                                                emulate sh
                                                setopt kshglob noshglob braceexpand
                                                results+=("${res[@]}")  ;;
                                        (hostname) emulate zsh
                                                _hosts -U -O res
                                                emulate sh
                                                setopt kshglob noshglob braceexpand
                                                results+=("${res[@]}")  ;;
                                        (job) results+=("${savejobtexts[@]%% *}")  ;;
                                        (keyword) results+=("${(k)reswords[@]}")  ;;
                                        (running) jids=("${(@k)savejobstates[(R)running*]}")
                                                for job in "${jids[@]}"
                                                do
                                                        results+=(${savejobtexts[$job]%% *})
                                                done ;;
                                        (stopped) jids=("${(@k)savejobstates[(R)suspended*]}")
                                                for job in "${jids[@]}"
                                                do
                                                        results+=(${savejobtexts[$job]%% *})
                                                done ;;
                                        (setopt | shopt) results+=("${(k)options[@]}")  ;;
                                        (signal) results+=("SIG${^signals[@]}")  ;;
                                        (user) results+=("${(k)userdirs[@]}")  ;;
                                        (variable) results+=("${(k)parameters[@]}")  ;;
                                        (helptopic)  ;;
                                esac ;;
                        (F) COMPREPLY=()
                                local -a args
                                args=("${words[0]}" "${@[-1]}" "${words[CURRENT-2]}")
                                () {
                                        typeset -h words
                                        $OPTARG "${args[@]}"
                                }
                                results+=("${COMPREPLY[@]}")  ;;
                        (G) setopt nullglob
                                results+=(${~OPTARG})
                                unsetopt nullglob ;;
                        (W) results+=(${(Q)~=OPTARG})  ;;
                        (C) results+=($(eval $OPTARG))  ;;
                        (P) prefix="$OPTARG"  ;;
                        (S) suffix="$OPTARG"  ;;
                        (X) if [[ ${OPTARG[0]} = '!' ]]
                                then
                                        results=("${(M)results[@]:#${OPTARG#?}}")
                                else
                                        results=("${results[@]:#$OPTARG}")
                                fi ;;
                esac
        done
        print -l -r -- "$prefix${^results[@]}$suffix"
}
toumorokoshi commented 1 year ago

I was able to repro and produce a fix (#47), shipped in 0.10.0 (just released). Please try it! and feel free to re-open if it doesn't fix your issue.

indrat commented 1 year ago

Works great! Thanks for the fix 👍