mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-28 13:53:10 +00:00
Merge pull request #6103 from krobelus/expand-arg-to-short-option
Completion: complete argument to last of a group of short options
This commit is contained in:
commit
a6f5d9c0eb
2 changed files with 104 additions and 49 deletions
117
src/complete.cpp
117
src/complete.cpp
|
@ -745,7 +745,7 @@ void completer_t::complete_abbr(const wcstring &cmd) {
|
|||
/// @param desc
|
||||
/// Description of the completion
|
||||
/// @param flags
|
||||
/// The list into which the results will be inserted
|
||||
/// The flags
|
||||
///
|
||||
void completer_t::complete_from_args(const wcstring &str, const wcstring &args,
|
||||
const wcstring &desc, complete_flags_t flags) {
|
||||
|
@ -812,58 +812,43 @@ static const wchar_t *param_match2(const complete_entry_opt_t *e, const wchar_t
|
|||
|
||||
// Short options are like -DNDEBUG. Long options are like --color=auto. So check for an equal
|
||||
// sign for long options.
|
||||
if (e->type != option_type_short) {
|
||||
if (optstr[cursor] != L'=') {
|
||||
return NULL;
|
||||
}
|
||||
cursor += 1;
|
||||
assert(e->type != option_type_short);
|
||||
if (optstr[cursor] != L'=') {
|
||||
return NULL;
|
||||
}
|
||||
cursor += 1;
|
||||
return &optstr[cursor];
|
||||
}
|
||||
|
||||
/// Tests whether a short option is a viable completion. arg_str will be like '-xzv', nextopt will
|
||||
/// be a character like 'f' options will be the list of all options, used to validate the argument.
|
||||
static bool short_ok(const wcstring &arg, const complete_entry_opt_t *entry,
|
||||
const option_list_t &options) {
|
||||
// Ensure it's a short option.
|
||||
if (entry->type != option_type_short || entry->option.empty()) {
|
||||
return false;
|
||||
/// Parses a token of short options plus one optional parameter like
|
||||
/// '-xzPARAM', where x and z are short options.
|
||||
///
|
||||
/// Returns the position of the last option character (e.g. the position of z which is 2).
|
||||
/// Everything after that is assumed to be part of the parameter.
|
||||
/// Returns wcstring::npos if there is no valid short option.
|
||||
size_t short_option_pos(const wcstring &arg, const option_list_t &options) {
|
||||
if (arg.size() <= 1 || leading_dash_count(arg.c_str()) != 1) {
|
||||
return wcstring::npos;
|
||||
}
|
||||
const wchar_t nextopt = entry->option.at(0);
|
||||
|
||||
// Empty strings are always 'OK'.
|
||||
if (arg.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The argument must start with exactly one dash.
|
||||
if (leading_dash_count(arg.c_str()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Short option must not be already present.
|
||||
if (arg.find(nextopt) != wcstring::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that all characters in our combined short option list are present as short options in
|
||||
// the options list. If we get a short option that can't be combined (NO_COMMON), then we stop.
|
||||
bool result = true;
|
||||
for (size_t i = 1; i < arg.size(); i++) {
|
||||
wchar_t arg_char = arg.at(i);
|
||||
const complete_entry_opt_t *match = NULL;
|
||||
for (option_list_t::const_iterator iter = options.begin(); iter != options.end(); ++iter) {
|
||||
if (iter->type == option_type_short && iter->option.at(0) == arg_char) {
|
||||
match = &*iter;
|
||||
for (size_t pos = 1; pos < arg.size(); pos++) {
|
||||
wchar_t arg_char = arg.at(pos);
|
||||
const complete_entry_opt_t *match = nullptr;
|
||||
for (const complete_entry_opt_t &o : options) {
|
||||
if (o.type == option_type_short && o.option.at(0) == arg_char) {
|
||||
match = &o;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match == NULL || (match->result_mode.requires_param)) {
|
||||
result = false;
|
||||
break;
|
||||
if (match == nullptr) {
|
||||
// The first character after the dash is not a valid option.
|
||||
if (pos == 1) return wcstring::npos;
|
||||
return pos - 1;
|
||||
}
|
||||
if (match->result_mode.requires_param) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return arg.size() - 1;
|
||||
}
|
||||
|
||||
/// Load command-specific completions for the specified command.
|
||||
|
@ -954,13 +939,23 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
|
|||
// Now release the lock and test each option that we captured above. We have to do this outside
|
||||
// the lock because callouts (like the condition) may add or remove completions. See issue 2.
|
||||
for (const option_list_t &options : all_options) {
|
||||
size_t short_opt_pos = short_option_pos(str, options);
|
||||
bool last_option_requires_param = false;
|
||||
use_common = 1;
|
||||
if (use_switches) {
|
||||
if (str[0] == L'-') {
|
||||
// Check if we are entering a combined option and argument (like --color=auto or
|
||||
// -I/usr/include).
|
||||
for (const complete_entry_opt_t &o : options) {
|
||||
const wchar_t *arg = param_match2(&o, str.c_str());
|
||||
const wchar_t *arg;
|
||||
if (o.type == option_type_short) {
|
||||
if (short_opt_pos == wcstring::npos) continue;
|
||||
if (o.option.at(0) != str.at(short_opt_pos)) continue;
|
||||
last_option_requires_param = o.result_mode.requires_param;
|
||||
arg = str.c_str() + short_opt_pos + 1;
|
||||
} else {
|
||||
arg = param_match2(&o, str.c_str());
|
||||
}
|
||||
if (arg != NULL && this->condition_test(o.condition)) {
|
||||
if (o.result_mode.requires_param) use_common = false;
|
||||
if (o.result_mode.no_files) use_files = false;
|
||||
|
@ -987,17 +982,27 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
|
|||
}
|
||||
|
||||
// No old style option matched, or we are not using old style options. We check if
|
||||
// any short (or gnu style options do.
|
||||
// any short (or gnu style) options do.
|
||||
if (!old_style_match) {
|
||||
size_t prev_short_opt_pos = short_option_pos(popt, options);
|
||||
for (const complete_entry_opt_t &o : options) {
|
||||
// Gnu-style options with _optional_ arguments must be specified as a single
|
||||
// token, so that it can be differed from a regular argument.
|
||||
// Here we are testing the previous argument for a GNU-style match,
|
||||
// to see how we should complete the current argument
|
||||
if (o.type == option_type_double_long && !o.result_mode.requires_param)
|
||||
continue;
|
||||
if (!o.result_mode.requires_param) continue;
|
||||
|
||||
if (param_match(&o, popt.c_str()) && this->condition_test(o.condition)) {
|
||||
bool match = false;
|
||||
if (o.type == option_type_short) {
|
||||
match = prev_short_opt_pos != wcstring::npos &&
|
||||
// Only if the option was the last char in the token,
|
||||
// i.e. there is no parameter yet.
|
||||
prev_short_opt_pos + 1 == popt.size() &&
|
||||
o.option.at(0) == popt.at(prev_short_opt_pos);
|
||||
} else if (o.type == option_type_double_long) {
|
||||
match = param_match(&o, popt.c_str());
|
||||
}
|
||||
if (match && this->condition_test(o.condition)) {
|
||||
if (o.result_mode.requires_param) use_common = false;
|
||||
if (o.result_mode.no_files) use_files = false;
|
||||
if (o.result_mode.force_files) has_force = true;
|
||||
|
@ -1026,7 +1031,21 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
|
|||
}
|
||||
|
||||
// Check if the short style option matches.
|
||||
if (short_ok(str, &o, options)) {
|
||||
if (o.type == option_type_short) {
|
||||
wchar_t optchar = o.option.at(0);
|
||||
if (short_opt_pos == wcstring::npos) {
|
||||
// str has no short option at all (but perhaps it is the
|
||||
// prefix of a single long option).
|
||||
// Only complete short options if there is no character after the dash.
|
||||
if (str != L"-") continue;
|
||||
} else {
|
||||
// Only complete when the last short option has no parameter yet..
|
||||
if (short_opt_pos + 1 != str.size()) continue;
|
||||
// .. and it does not require one ..
|
||||
if (last_option_requires_param) continue;
|
||||
// .. and the option is not already there.
|
||||
if (str.find(optchar) != wcstring::npos) continue;
|
||||
}
|
||||
// It's a match.
|
||||
wcstring desc = o.localized_desc();
|
||||
// Append a short-style option
|
||||
|
|
|
@ -88,3 +88,39 @@ complete -C'complete_test_recurse1 '
|
|||
# CHECKERR: recursing
|
||||
# CHECKERR: recursing
|
||||
# CHECKERR: complete: maximum recursion depth reached
|
||||
|
||||
# short options
|
||||
complete -c foo -f -a non-option-argument
|
||||
complete -c foo -f --short-option x
|
||||
complete -c foo -f --short-option y -a 'ARGY'
|
||||
complete -c foo -f --short-option z -a 'ARGZ' -r
|
||||
complete -c foo -f --old-option single-long-ending-in-z
|
||||
complete -c foo -f --old-option x-single-long
|
||||
complete -c foo -f --old-option y-single-long
|
||||
complete -c foo -f --old-option z-single-long
|
||||
complete -c foo -f --long-option x-long -a 'ARGLONG'
|
||||
# Make sure that arguments of concatenated short options are expanded (#332)
|
||||
complete -C'foo -xy'
|
||||
# CHECK: -xyARGY
|
||||
# CHECK: -xyz
|
||||
# A required parameter means we don't want more short options.
|
||||
complete -C'foo -yz'
|
||||
# CHECK: -yzARGZ
|
||||
# Required parameter with space: complete only the parameter (no non-option arguments).
|
||||
complete -C'foo -xz '
|
||||
# CHECK: ARGZ
|
||||
# Optional parameter with space: complete only non-option arguments.
|
||||
complete -C'foo -xy '
|
||||
# CHECK: non-option-argument
|
||||
complete -C'foo -single-long-ending-in-z'
|
||||
# CHECK: -single-long-ending-in-z
|
||||
complete -C'foo -single-long-ending-in-z '
|
||||
# CHECK: non-option-argument
|
||||
# CHECK: -x-single-long
|
||||
complete -C'foo -x' | string match -- -x-single-long
|
||||
# CHECK: -y-single-long
|
||||
complete -C'foo -y' | string match -- -y-single-long
|
||||
# This does NOT suggest -z-single-long, but will probably not occur in practise.
|
||||
# CHECK: -zARGZ
|
||||
complete -C'foo -z'
|
||||
|
||||
|
|
Loading…
Reference in a new issue