mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 04:43:10 +00:00
allow complete -d ''
There isn't a good reason to disallow an explicitly empty completion description. Since I'm touching the code also modify the argument parsing the match the style of most of the builtins. Fixes #3557.
This commit is contained in:
parent
e8a31a13a1
commit
acd8363c38
4 changed files with 70 additions and 97 deletions
|
@ -2905,7 +2905,7 @@ static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **arg
|
||||||
int argc = builtin_count_args(argv);
|
int argc = builtin_count_args(argv);
|
||||||
|
|
||||||
if (argc > 2) {
|
if (argc > 2) {
|
||||||
streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]);
|
streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]);
|
||||||
builtin_print_help(parser, streams, argv[0], streams.err);
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
||||||
return STATUS_BUILTIN_ERROR;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,64 +122,46 @@ static void builtin_complete_remove(const wcstring_list_t &cmd, const wcstring_l
|
||||||
// complete.cpp for any heavy lifting.
|
// complete.cpp for any heavy lifting.
|
||||||
int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
ASSERT_IS_MAIN_THREAD();
|
ASSERT_IS_MAIN_THREAD();
|
||||||
wgetopter_t w;
|
static int recursion_level = 0;
|
||||||
bool res = false;
|
|
||||||
int argc = 0;
|
wchar_t *cmd = argv[0];
|
||||||
|
int argc = builtin_count_args(argv);
|
||||||
int result_mode = SHARED;
|
int result_mode = SHARED;
|
||||||
int remove = 0;
|
int remove = 0;
|
||||||
int authoritative = -1;
|
int authoritative = -1;
|
||||||
|
|
||||||
wcstring short_opt;
|
wcstring short_opt;
|
||||||
wcstring_list_t gnu_opt, old_opt;
|
wcstring_list_t gnu_opt, old_opt;
|
||||||
const wchar_t *comp = L"", *desc = L"", *condition = L"";
|
const wchar_t *comp = L"", *desc = L"", *condition = L"";
|
||||||
|
|
||||||
bool do_complete = false;
|
bool do_complete = false;
|
||||||
wcstring do_complete_param;
|
wcstring do_complete_param;
|
||||||
|
wcstring_list_t cmd_to_complete;
|
||||||
wcstring_list_t cmd;
|
|
||||||
wcstring_list_t path;
|
wcstring_list_t path;
|
||||||
wcstring_list_t wrap_targets;
|
wcstring_list_t wrap_targets;
|
||||||
|
|
||||||
static int recursion_level = 0;
|
const wchar_t *short_options = L":a:c:p:s:l:o:d:frxeuAn:C::w:h";
|
||||||
|
const struct woption long_options[] = {{L"exclusive", no_argument, NULL, 'x'},
|
||||||
argc = builtin_count_args(argv);
|
{L"no-files", no_argument, NULL, 'f'},
|
||||||
|
{L"require-parameter", no_argument, NULL, 'r'},
|
||||||
w.woptind = 0;
|
{L"path", required_argument, NULL, 'p'},
|
||||||
|
{L"command", required_argument, NULL, 'c'},
|
||||||
while (!res) {
|
{L"short-option", required_argument, NULL, 's'},
|
||||||
static const struct woption long_options[] = {{L"exclusive", no_argument, 0, 'x'},
|
{L"long-option", required_argument, NULL, 'l'},
|
||||||
{L"no-files", no_argument, 0, 'f'},
|
{L"old-option", required_argument, NULL, 'o'},
|
||||||
{L"require-parameter", no_argument, 0, 'r'},
|
{L"description", required_argument, NULL, 'd'},
|
||||||
{L"path", required_argument, 0, 'p'},
|
{L"arguments", required_argument, NULL, 'a'},
|
||||||
{L"command", required_argument, 0, 'c'},
|
{L"erase", no_argument, NULL, 'e'},
|
||||||
{L"short-option", required_argument, 0, 's'},
|
{L"unauthoritative", no_argument, NULL, 'u'},
|
||||||
{L"long-option", required_argument, 0, 'l'},
|
{L"authoritative", no_argument, NULL, 'A'},
|
||||||
{L"old-option", required_argument, 0, 'o'},
|
{L"condition", required_argument, NULL, 'n'},
|
||||||
{L"description", required_argument, 0, 'd'},
|
{L"wraps", required_argument, NULL, 'w'},
|
||||||
{L"arguments", required_argument, 0, 'a'},
|
{L"do-complete", optional_argument, NULL, 'C'},
|
||||||
{L"erase", no_argument, 0, 'e'},
|
{L"help", no_argument, NULL, 'h'},
|
||||||
{L"unauthoritative", no_argument, 0, 'u'},
|
{NULL, 0, NULL, 0}};
|
||||||
{L"authoritative", no_argument, 0, 'A'},
|
|
||||||
{L"condition", required_argument, 0, 'n'},
|
|
||||||
{L"wraps", required_argument, 0, 'w'},
|
|
||||||
{L"do-complete", optional_argument, 0, 'C'},
|
|
||||||
{L"help", no_argument, 0, 'h'},
|
|
||||||
{0, 0, 0, 0}};
|
|
||||||
|
|
||||||
int opt_index = 0;
|
|
||||||
int opt =
|
|
||||||
w.wgetopt_long(argc, argv, L"a:c:p:s:l:o:d:frxeuAn:C::w:h", long_options, &opt_index);
|
|
||||||
if (opt == -1) break;
|
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
wgetopter_t w;
|
||||||
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 0: {
|
|
||||||
if (long_options[opt_index].flag != 0) break;
|
|
||||||
streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0],
|
|
||||||
long_options[opt_index].name);
|
|
||||||
builtin_print_help(parser, streams, argv[0], streams.err);
|
|
||||||
res = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'x': {
|
case 'x': {
|
||||||
result_mode |= EXCLUSIVE;
|
result_mode |= EXCLUSIVE;
|
||||||
break;
|
break;
|
||||||
|
@ -199,19 +181,15 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
if (opt == 'p')
|
if (opt == 'p')
|
||||||
path.push_back(tmp);
|
path.push_back(tmp);
|
||||||
else
|
else
|
||||||
cmd.push_back(tmp);
|
cmd_to_complete.push_back(tmp);
|
||||||
} else {
|
} else {
|
||||||
streams.err.append_format(L"%ls: Invalid token '%ls'\n", argv[0], w.woptarg);
|
streams.err.append_format(L"%ls: Invalid token '%ls'\n", cmd, w.woptarg);
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'd': {
|
case 'd': {
|
||||||
desc = w.woptarg;
|
desc = w.woptarg;
|
||||||
if (w.woptarg[0] == '\0') {
|
|
||||||
streams.err.append_format(L"%ls: -d requires a non-empty string\n", argv[0]);
|
|
||||||
res = true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'u': {
|
case 'u': {
|
||||||
|
@ -225,24 +203,24 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
case 's': {
|
case 's': {
|
||||||
short_opt.append(w.woptarg);
|
short_opt.append(w.woptarg);
|
||||||
if (w.woptarg[0] == '\0') {
|
if (w.woptarg[0] == '\0') {
|
||||||
streams.err.append_format(L"%ls: -s requires a non-empty string\n", argv[0]);
|
streams.err.append_format(L"%ls: -s requires a non-empty string\n", cmd);
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'l': {
|
case 'l': {
|
||||||
gnu_opt.push_back(w.woptarg);
|
gnu_opt.push_back(w.woptarg);
|
||||||
if (w.woptarg[0] == '\0') {
|
if (w.woptarg[0] == '\0') {
|
||||||
streams.err.append_format(L"%ls: -l requires a non-empty string\n", argv[0]);
|
streams.err.append_format(L"%ls: -l requires a non-empty string\n", cmd);
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'o': {
|
case 'o': {
|
||||||
old_opt.push_back(w.woptarg);
|
old_opt.push_back(w.woptarg);
|
||||||
if (w.woptarg[0] == '\0') {
|
if (w.woptarg[0] == '\0') {
|
||||||
streams.err.append_format(L"%ls: -o requires a non-empty string\n", argv[0]);
|
streams.err.append_format(L"%ls: -o requires a non-empty string\n", cmd);
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -268,64 +246,67 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
if (arg == NULL) {
|
if (arg == NULL) {
|
||||||
// This corresponds to using 'complete -C' in non-interactive mode.
|
// This corresponds to using 'complete -C' in non-interactive mode.
|
||||||
// See #2361.
|
// See #2361.
|
||||||
builtin_missing_argument(parser, streams, argv[0], argv[w.woptind - 1]);
|
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
||||||
return STATUS_BUILTIN_ERROR;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
do_complete_param = arg;
|
do_complete_param = arg;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'h': {
|
case 'h': {
|
||||||
builtin_print_help(parser, streams, argv[0], streams.out);
|
builtin_print_help(parser, streams, cmd, streams.out);
|
||||||
return 0;
|
return STATUS_BUILTIN_OK;
|
||||||
|
}
|
||||||
|
case ':': {
|
||||||
|
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
||||||
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
case '?': {
|
case '?': {
|
||||||
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
DIE("unexpected opt");
|
DIE("unexpected retval from wgetopt_long");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res && condition && wcslen(condition)) {
|
if (w.woptind != argc) {
|
||||||
|
streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd);
|
||||||
|
builtin_print_help(parser, streams, cmd, streams.err);
|
||||||
|
return STATUS_BUILTIN_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition && wcslen(condition)) {
|
||||||
const wcstring condition_string = condition;
|
const wcstring condition_string = condition;
|
||||||
parse_error_list_t errors;
|
parse_error_list_t errors;
|
||||||
if (parse_util_detect_errors(condition_string, &errors,
|
if (parse_util_detect_errors(condition_string, &errors,
|
||||||
false /* do not accept incomplete */)) {
|
false /* do not accept incomplete */)) {
|
||||||
streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", argv[0],
|
streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd,
|
||||||
condition);
|
condition);
|
||||||
for (size_t i = 0; i < errors.size(); i++) {
|
for (size_t i = 0; i < errors.size(); i++) {
|
||||||
streams.err.append_format(L"\n%s: ", argv[0]);
|
streams.err.append_format(L"\n%s: ", cmd);
|
||||||
streams.err.append(errors.at(i).describe(condition_string));
|
streams.err.append(errors.at(i).describe(condition_string));
|
||||||
}
|
}
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res && comp && wcslen(comp)) {
|
if (comp && wcslen(comp)) {
|
||||||
wcstring prefix;
|
wcstring prefix;
|
||||||
if (argv[0]) {
|
prefix.append(cmd);
|
||||||
prefix.append(argv[0]);
|
|
||||||
prefix.append(L": ");
|
prefix.append(L": ");
|
||||||
}
|
|
||||||
|
|
||||||
wcstring err_text;
|
wcstring err_text;
|
||||||
if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) {
|
if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) {
|
||||||
streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", argv[0],
|
streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", cmd,
|
||||||
comp);
|
comp);
|
||||||
streams.err.append(err_text);
|
streams.err.append(err_text);
|
||||||
streams.err.push_back(L'\n');
|
streams.err.push_back(L'\n');
|
||||||
res = true;
|
return STATUS_BUILTIN_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (do_complete) {
|
if (do_complete) {
|
||||||
const wchar_t *token;
|
const wchar_t *token;
|
||||||
|
|
||||||
|
@ -378,12 +359,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
|
|
||||||
recursion_level--;
|
recursion_level--;
|
||||||
}
|
}
|
||||||
} else if (w.woptind != argc) {
|
} else if (cmd_to_complete.empty() && path.empty()) {
|
||||||
streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]);
|
|
||||||
builtin_print_help(parser, streams, argv[0], streams.err);
|
|
||||||
|
|
||||||
res = true;
|
|
||||||
} else if (cmd.empty() && path.empty()) {
|
|
||||||
// No arguments specified, meaning we print the definitions of all specified completions
|
// No arguments specified, meaning we print the definitions of all specified completions
|
||||||
// to stdout.
|
// to stdout.
|
||||||
streams.out.append(complete_print());
|
streams.out.append(complete_print());
|
||||||
|
@ -391,22 +367,21 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||||
int flags = COMPLETE_AUTO_SPACE;
|
int flags = COMPLETE_AUTO_SPACE;
|
||||||
|
|
||||||
if (remove) {
|
if (remove) {
|
||||||
builtin_complete_remove(cmd, path, short_opt.c_str(), gnu_opt, old_opt);
|
builtin_complete_remove(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
builtin_complete_add(cmd, path, short_opt.c_str(), gnu_opt, old_opt, result_mode,
|
builtin_complete_add(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt,
|
||||||
authoritative, condition, comp, desc, flags);
|
result_mode, authoritative, condition, comp, desc, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle wrap targets (probably empty). We only wrap commands, not paths.
|
// Handle wrap targets (probably empty). We only wrap commands, not paths.
|
||||||
for (size_t w = 0; w < wrap_targets.size(); w++) {
|
for (size_t w = 0; w < wrap_targets.size(); w++) {
|
||||||
const wcstring &wrap_target = wrap_targets.at(w);
|
const wcstring &wrap_target = wrap_targets.at(w);
|
||||||
for (size_t i = 0; i < cmd.size(); i++) {
|
for (size_t i = 0; i < cmd_to_complete.size(); i++) {
|
||||||
(remove ? complete_remove_wrapper : complete_add_wrapper)(cmd.at(i),
|
(remove ? complete_remove_wrapper : complete_add_wrapper)(cmd_to_complete.at(i),
|
||||||
wrap_target);
|
wrap_target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res ? 1 : 0;
|
return STATUS_BUILTIN_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
complete: -o requires a non-empty string
|
complete: -o requires a non-empty string
|
||||||
complete: -d requires a non-empty string
|
|
||||||
complete: -l requires a non-empty string
|
complete: -l requires a non-empty string
|
||||||
complete: -s requires a non-empty string
|
complete: -s requires a non-empty string
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# Regression test for issue #3129. In previous versions these statements would
|
# Regression test for issue #3129. In previous versions these statements would
|
||||||
# cause an `assert()` to fire thus killing the shell.
|
# cause an `assert()` to fire thus killing the shell.
|
||||||
complete -c pkill -o ''
|
complete -c pkill -o ''
|
||||||
complete -c pkill -d ''
|
|
||||||
complete -c pkill -l ''
|
complete -c pkill -l ''
|
||||||
complete -c pkill -s ''
|
complete -c pkill -s ''
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue