This is not a complete topic but I'd like to present the problem and my approach to see if it's a good way to go. We have started recently to rely on parse-options to help complete options. One of the leftover items is allowing completing --no- form. This patch enables that (some options should not have --no- form, but that's easy not included here). The problem with completing --no- form is that the number of completable options now usually doubles, taking precious screen space and also making it hard to find the option you want. So the other half of this patch, the part in git-completion.bash, is to uncomplete --no- options. When you do "git checkout --<tab>", instead of displaying all --no- options, this patch simply displays one item: the --no- prefix. If you do "git checkout --no-<tab>" then all negative options are displayed. This helps reduce completable options quite efficiently. Of course life is not that simple, we do have --no- options by default sometimes (taking priority over the positive form), e.g. "git clone --no-checkout". Collapsing all --no-options into --no- would be a regression. To avoid it, the order of options --git-completion-helper returns does matter. The first 4 negative options are not collapsed. Only options after the 4th are. Extra --no- options are always printed at the end, after all the --no- defined in struct option, this kinda works. Not pretty but works. After all this "git checkout --<tab>" now looks like this > ~/w/git $ git co -- --conflict= --orphan= --detach --ours --ignore-other-worktrees --patch --ignore-skip-worktree-bits --progress --merge --quiet --no- --recurse-submodules --no-detach --theirs --no-quiet --track --no-track and all the no options > ~/w/git $ git co --no- --no-conflict --no-patch --no-detach --no-progress --no-ignore-other-worktrees --no-quiet --no-ignore-skip-worktree-bits --no-recurse-submodules --no-merge --no-track --no-orphan Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx> --- contrib/completion/git-completion.bash | 25 +++++++++++++++- parse-options.c | 40 +++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a757073945..85b9f24465 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -266,7 +266,7 @@ __gitcomp () case "$cur_" in --*=) ;; - *) + --no-*) local c i=0 IFS=$' \t\n' for c in $1; do c="$c${4-}" @@ -279,6 +279,29 @@ __gitcomp () fi done ;; + *) + local c i=0 IFS=$' \t\n' n=0 + for c in $1; do + c="$c${4-}" + if [[ $c == "$cur_"* ]]; then + case $c in + --*=*|*.) ;; + --no-*) + n=$(($n+1)) + if [ "$n" -eq 4 ]; then + c="--no-${4-} " + elif [ "$n" -gt 4 ]; then + continue + else + c="$c " + fi + ;; + *) c="$c " ;; + esac + COMPREPLY[i++]="${2-}$c" + fi + done + ;; esac } diff --git a/parse-options.c b/parse-options.c index 0f7059a8ab..f6cd7ca8d2 100644 --- a/parse-options.c +++ b/parse-options.c @@ -427,13 +427,11 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, parse_options_check(options); } -/* - * TODO: we are not completing the --no-XXX form yet because there are - * many options that do not suppress it properly. - */ static int show_gitcomp(struct parse_opt_ctx_t *ctx, const struct option *opts) { + const struct option *original_opts = opts; + for (; opts->type != OPTION_END; opts++) { const char *suffix = ""; @@ -465,6 +463,40 @@ static int show_gitcomp(struct parse_opt_ctx_t *ctx, suffix = "="; printf(" --%s%s", opts->long_name, suffix); } + for (opts = original_opts; opts->type != OPTION_END; opts++) { + int has_unset_form = 0; + + if (!opts->long_name) + continue; + if (opts->flags & (PARSE_OPT_HIDDEN | PARSE_OPT_NOCOMPLETE)) + continue; + if (opts->flags & PARSE_OPT_NONEG) + continue; + + switch (opts->type) { + case OPTION_STRING: + case OPTION_FILENAME: + case OPTION_INTEGER: + case OPTION_MAGNITUDE: + case OPTION_CALLBACK: + case OPTION_BIT: + case OPTION_NEGBIT: + case OPTION_COUNTUP: + case OPTION_SET_INT: + has_unset_form = 1; + break; + default: + break; + } + if (has_unset_form) { + const char *name; + + if (skip_prefix(opts->long_name, "no-", &name)) + printf(" --%s", name); + else + printf(" --no-%s", opts->long_name); + } + } fputc('\n', stdout); exit(0); } -- 2.17.0.367.g5dd2e386c3