completion: collapse extra --no-.. options

The commands that make use of --git-completion-helper feature could
now produce a lot of --no-xxx options that a command can take. This in
many case could nearly double the amount of completable options, using
more screen estate and also harder to search for the wanted option.

This patch attempts to mitigate that by collapsing extra --no-
options, the ones that are added by --git-completion-helper and not in
original struct option arrays. The "--no-..." option will be displayed
in this case to hint about more options, e.g.

    > ~/w/git $ git clone --
    --bare                 --origin=
    --branch=              --progress
    --checkout             --quiet
    --config=              --recurse-submodules
    --depth=               --reference=
    --dissociate           --reference-if-able=
    --filter=              --separate-git-dir=
    --hardlinks            --shallow-exclude=
    --ipv4                 --shallow-since=
    --ipv6                 --shallow-submodules
    --jobs=                --shared
    --local                --single-branch
    --mirror               --tags
    --no-...               --template=
    --no-checkout          --upload-pack=
    --no-hardlinks         --verbose
    --no-tags

and when you complete it with --no-<tab>, all negative options will be
presented:

    > ~/w/git $ git clone --no-
    --no-bare                 --no-quiet
    --no-branch               --no-recurse-submodules
    --no-checkout             --no-reference
    --no-config               --no-reference-if-able
    --no-depth                --no-separate-git-dir
    --no-dissociate           --no-shallow-exclude
    --no-filter               --no-shallow-since
    --no-hardlinks            --no-shallow-submodules
    --no-ipv4                 --no-shared
    --no-ipv6                 --no-single-branch
    --no-jobs                 --no-tags
    --no-local                --no-template
    --no-mirror               --no-upload-pack
    --no-origin               --no-verbose
    --no-progress

Corner case: to make sure that people will never accidentally complete
the fake option "--no-..." there must be one real --no- in the first
complete listing even if it's not from the original struct option.

PS. This could could be made simpler with ";&" to fall through from
"--no-*" block and share the code but ";&" is not available on bash-3
(i.e. Mac)

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Nguyễn Thái Ngọc Duy 2018-06-06 11:41:39 +02:00 committed by Junio C Hamano
parent 3fe735e723
commit b221b5ab9b
3 changed files with 115 additions and 29 deletions

View File

@ -266,9 +266,32 @@ __gitcomp ()
case "$cur_" in case "$cur_" in
--*=) --*=)
;; ;;
--no-*)
local c i=0 IFS=$' \t\n'
for c in $1; do
if [[ $c == "--" ]]; then
continue
fi
c="$c${4-}"
if [[ $c == "$cur_"* ]]; then
case $c in
--*=*|*.) ;;
*) c="$c " ;;
esac
COMPREPLY[i++]="${2-}$c"
fi
done
;;
*) *)
local c i=0 IFS=$' \t\n' local c i=0 IFS=$' \t\n'
for c in $1; do for c in $1; do
if [[ $c == "--" ]]; then
c="--no-...${4-}"
if [[ $c == "$cur_"* ]]; then
COMPREPLY[i++]="${2-}$c "
fi
break
fi
c="$c${4-}" c="$c${4-}"
if [[ $c == "$cur_"* ]]; then if [[ $c == "$cur_"* ]]; then
case $c in case $c in

View File

@ -427,12 +427,61 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
parse_options_check(options); parse_options_check(options);
} }
static void show_negated_gitcomp(const struct option *opts, int nr_noopts)
{
int printed_dashdash = 0;
for (; opts->type != OPTION_END; opts++) {
int has_unset_form = 0;
const char *name;
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)
continue;
if (skip_prefix(opts->long_name, "no-", &name)) {
if (nr_noopts < 0)
printf(" --%s", name);
} else if (nr_noopts >= 0) {
if (nr_noopts && !printed_dashdash) {
printf(" --");
printed_dashdash = 1;
}
printf(" --no-%s", opts->long_name);
nr_noopts++;
}
}
}
static int show_gitcomp(struct parse_opt_ctx_t *ctx, static int show_gitcomp(struct parse_opt_ctx_t *ctx,
const struct option *opts) const struct option *opts)
{ {
const struct option *original_opts = opts;
int nr_noopts = 0;
for (; opts->type != OPTION_END; opts++) { for (; opts->type != OPTION_END; opts++) {
const char *suffix = ""; const char *suffix = "";
int has_unset_form = 0;
if (!opts->long_name) if (!opts->long_name)
continue; continue;
@ -447,8 +496,6 @@ static int show_gitcomp(struct parse_opt_ctx_t *ctx,
case OPTION_INTEGER: case OPTION_INTEGER:
case OPTION_MAGNITUDE: case OPTION_MAGNITUDE:
case OPTION_CALLBACK: case OPTION_CALLBACK:
has_unset_form = 1;
if (opts->flags & PARSE_OPT_NOARG) if (opts->flags & PARSE_OPT_NOARG)
break; break;
if (opts->flags & PARSE_OPT_OPTARG) if (opts->flags & PARSE_OPT_OPTARG)
@ -457,28 +504,17 @@ static int show_gitcomp(struct parse_opt_ctx_t *ctx,
break; break;
suffix = "="; suffix = "=";
break; break;
case OPTION_BIT:
case OPTION_NEGBIT:
case OPTION_COUNTUP:
case OPTION_SET_INT:
has_unset_form = 1;
break;
default: default:
break; break;
} }
if (opts->flags & PARSE_OPT_COMP_ARG) if (opts->flags & PARSE_OPT_COMP_ARG)
suffix = "="; suffix = "=";
if (starts_with(opts->long_name, "no-"))
nr_noopts++;
printf(" --%s%s", opts->long_name, suffix); printf(" --%s%s", opts->long_name, suffix);
if (has_unset_form && !(opts->flags & PARSE_OPT_NONEG)) {
const char *name;
if (skip_prefix(opts->long_name, "no-", &name))
printf(" --%s", name);
else
printf(" --no-%s", opts->long_name);
}
} }
show_negated_gitcomp(original_opts, -1);
show_negated_gitcomp(original_opts, nr_noopts);
fputc('\n', stdout); fputc('\n', stdout);
exit(0); exit(0);
} }

View File

@ -459,6 +459,42 @@ test_expect_success '__gitcomp - suffix' '
EOF EOF
' '
test_expect_success '__gitcomp - ignore optional negative options' '
test_gitcomp "--" "--abc --def --no-one -- --no-two" <<-\EOF
--abc Z
--def Z
--no-one Z
--no-... Z
EOF
'
test_expect_success '__gitcomp - ignore/narrow optional negative options' '
test_gitcomp "--a" "--abc --abcdef --no-one -- --no-two" <<-\EOF
--abc Z
--abcdef Z
EOF
'
test_expect_success '__gitcomp - ignore/narrow optional negative options' '
test_gitcomp "--n" "--abc --def --no-one -- --no-two" <<-\EOF
--no-one Z
--no-... Z
EOF
'
test_expect_success '__gitcomp - expand all negative options' '
test_gitcomp "--no-" "--abc --def --no-one -- --no-two" <<-\EOF
--no-one Z
--no-two Z
EOF
'
test_expect_success '__gitcomp - expand/narrow all negative options' '
test_gitcomp "--no-o" "--abc --def --no-one -- --no-two" <<-\EOF
--no-one Z
EOF
'
test_expect_success '__gitcomp - doesnt fail because of invalid variable name' ' test_expect_success '__gitcomp - doesnt fail because of invalid variable name' '
__gitcomp "$invalid_variable_name" __gitcomp "$invalid_variable_name"
' '
@ -1237,29 +1273,20 @@ test_expect_success 'double dash "git" itself' '
test_expect_success 'double dash "git checkout"' ' test_expect_success 'double dash "git checkout"' '
test_completion "git checkout --" <<-\EOF test_completion "git checkout --" <<-\EOF
--quiet Z --quiet Z
--no-quiet Z
--detach Z --detach Z
--no-detach Z
--track Z --track Z
--no-track Z
--orphan=Z --orphan=Z
--no-orphan Z
--ours Z --ours Z
--theirs Z --theirs Z
--merge Z --merge Z
--no-merge Z
--conflict=Z --conflict=Z
--no-conflict Z
--patch Z --patch Z
--no-patch Z
--ignore-skip-worktree-bits Z --ignore-skip-worktree-bits Z
--no-ignore-skip-worktree-bits Z
--ignore-other-worktrees Z --ignore-other-worktrees Z
--no-ignore-other-worktrees Z
--recurse-submodules Z --recurse-submodules Z
--no-recurse-submodules Z
--progress Z --progress Z
--no-progress Z --no-quiet Z
--no-... Z
EOF EOF
' '