From 701d1411092706a8b636ca9d5c28cbf7e3ab097d Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 16 Jan 2016 15:46:43 -0800 Subject: Clean up complete_entry_opt_t Rather than storing short and long options separately, using a complicated set of invariants, store them in a single string and use an explicit type complete_option_type_t to track how they are interpreted. --- src/builtin_complete.cpp | 20 ++--- src/complete.cpp | 209 +++++++++++++++++++++++++++++------------------ src/complete.h | 12 ++- src/fish_tests.cpp | 2 +- 4 files changed, 146 insertions(+), 97 deletions(-) (limited to 'src') diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index e8dd2d9d..6fd4349f 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -54,9 +54,8 @@ static void builtin_complete_add2(const wchar_t *cmd, { complete_add(cmd, cmd_type, - *s, - 0, - 0, + wcstring(1, *s), + option_type_short, result_mode, condition, comp, @@ -68,9 +67,8 @@ static void builtin_complete_add2(const wchar_t *cmd, { complete_add(cmd, cmd_type, - 0, - gnu_opt.at(i).c_str(), - 0, + gnu_opt.at(i), + option_type_double_long, result_mode, condition, comp, @@ -82,9 +80,8 @@ static void builtin_complete_add2(const wchar_t *cmd, { complete_add(cmd, cmd_type, - 0, - old_opt.at(i).c_str(), - 1, + old_opt.at(i), + option_type_single_long, result_mode, condition, comp, @@ -96,9 +93,8 @@ static void builtin_complete_add2(const wchar_t *cmd, { complete_add(cmd, cmd_type, - 0, - 0, - 0, + wcstring(), + option_type_args_only, result_mode, condition, comp, diff --git a/src/complete.cpp b/src/complete.cpp index 2857364e..b8a05e49 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -100,20 +100,24 @@ static inline wcstring_list_t complete_get_variable_names(void) /** Struct describing a completion option entry. - If short_opt and long_opt are both zero, the comp field must not be + If option is empty, the comp field must not be empty and contains a list of arguments to the command. + + The type field determines how the option is to be interpreted: + either empty (args_only) or short, single-long ("old") or double-long ("GNU"). + An invariant is that the option is empty if and only if the type is args_only. - If either short_opt or long_opt are non-zero, they specify a switch + If option is non-empty, it specifies a switch for the command. If \c comp is also not empty, it contains a list of non-switch arguments that may only follow directly after the specified switch. */ typedef struct complete_entry_opt { - /** Short style option */ - wchar_t short_opt; - /** Long style option */ - wcstring long_opt; + /* Text of the option (like 'foo') */ + wcstring option; + /* Type of the option: args-oly, short, single_long, or double_long */ + complete_option_type_t type; /** Arguments to the option */ wcstring comp; /** Description of the completion */ @@ -124,8 +128,6 @@ typedef struct complete_entry_opt EXCLUSIVE, and determines how completions should be performed on the argument after the switch. */ int result_mode; - /** True if old style long options are used */ - int old_mode; /** Completion flags */ complete_flags_t flags; @@ -133,6 +135,21 @@ typedef struct complete_entry_opt { return C_(desc); } + + size_t expected_dash_count() const + { + switch (this->type) + { + case option_type_args_only: + return 0; + case option_type_short: + case option_type_single_long: + return 1; + case option_type_double_long: + return 2; + } + } + } complete_entry_opt_t; /* Last value used in the order field of completion_entry_t */ @@ -511,9 +528,8 @@ void complete_set_authoritative(const wchar_t *cmd, bool cmd_is_path, bool autho void complete_add(const wchar_t *cmd, bool cmd_is_path, - wchar_t short_opt, - const wchar_t *long_opt, - int old_mode, + const wcstring &option, + complete_option_type_t option_type, int result_mode, const wchar_t *condition, const wchar_t *comp, @@ -521,6 +537,8 @@ void complete_add(const wchar_t *cmd, complete_flags_t flags) { CHECK(cmd,); + // option should be empty iff the option type is arguments only + assert(option.empty() == (option_type == option_type_args_only)); /* Lock the lock that allows us to edit the completion entry list */ scoped_lock lock(completion_lock); @@ -533,13 +551,12 @@ void complete_add(const wchar_t *cmd, /* Create our new option */ complete_entry_opt_t opt; - opt.short_opt = short_opt; + opt.option = option; + opt.type = option_type; opt.result_mode = result_mode; - opt.old_mode=old_mode; if (comp) opt.comp = comp; if (condition) opt.condition = condition; - if (long_opt) opt.long_opt = long_opt; if (desc) opt.desc = desc; opt.flags = flags; @@ -564,8 +581,23 @@ bool completion_entry_t::remove_option(wchar_t short_opt, const wchar_t *long_op for (option_list_t::iterator iter = this->options.begin(); iter != this->options.end();) { complete_entry_opt_t &o = *iter; - if ((short_opt && short_opt == o.short_opt) || - (long_opt && long_opt == o.long_opt && old_mode == o.old_mode)) + if (o.option.empty()) + { + // Just arguments, no match possible + continue; + } + bool matches = false; + bool mode_is_single = (o.type == option_type_single_long); + if (o.type == option_type_short) + { + matches = (short_opt && o.option.at(0) == short_opt); + } + else + { + matches = (mode_is_single == old_mode && long_opt && o.option == long_opt); + } + + if (matches) { /* fwprintf( stderr, L"remove option -%lc --%ls\n", @@ -953,55 +985,66 @@ void completer_t::complete_from_args(const wcstring &str, this->complete_strings(escape_string(str, ESCAPE_ALL), desc.c_str(), 0, possible_comp, flags); } -/** - Match against an old style long option -*/ -static int param_match_old(const complete_entry_opt_t *e, - const wchar_t *optstr) + +static size_t leading_dash_count(const wchar_t *str) { - return (optstr[0] == L'-') && (e->long_opt == &optstr[1]); + size_t cursor = 0; + while (str[cursor] == L'-') + { + cursor++; + } + return cursor; } /** Match a parameter */ -static int param_match(const complete_entry_opt_t *e, - const wchar_t *optstr) +static bool param_match(const complete_entry_opt_t *e, const wchar_t *optstr) { - if (e->short_opt != L'\0' && - e->short_opt == optstr[1]) - return 1; - - if (!e->old_mode && (wcsncmp(L"--", optstr, 2) == 0)) + bool result = false; + if (e->type != option_type_args_only) { - if (e->long_opt == &optstr[2]) - { - return 1; - } + size_t dashes = leading_dash_count(optstr); + result = (dashes == e->expected_dash_count() && e->option == &optstr[dashes]); } - - return 0; + return result; } /** Test if a string is an option with an argument, like --color=auto or -I/usr/include */ -static wchar_t *param_match2(const complete_entry_opt_t *e, - const wchar_t *optstr) +static const wchar_t *param_match2(const complete_entry_opt_t *e, const wchar_t *optstr) { - if (e->short_opt != L'\0' && e->short_opt == optstr[1]) - return (wchar_t *)&optstr[2]; - if (!e->old_mode && (wcsncmp(L"--", optstr, 2) == 0)) + // We may get a complete_entry_opt_t with no options if it's just arguments + if (e->option.empty()) { - size_t len = e->long_opt.size(); + return NULL; + } - if (wcsncmp(e->long_opt.c_str(), &optstr[2],len) == 0) + /* Verify leading dashes */ + size_t cursor = leading_dash_count(optstr); + if (cursor != e->expected_dash_count()) + { + return NULL; + } + + /* Verify options match */ + if (! string_prefixes_string(e->option, &optstr[cursor])) + { + return NULL; + } + cursor += e->option.length(); + + /* 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'=') { - if (optstr[len+2] == L'=') - return (wchar_t *)&optstr[len+3]; + return NULL; } + cursor += 1; } - return 0; + return &optstr[cursor]; } /** @@ -1009,8 +1052,15 @@ static wchar_t *param_match2(const complete_entry_opt_t *e, 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, wchar_t nextopt, const option_list_t &options) +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; + } + const wchar_t nextopt = entry->option.at(0); + /* Empty strings are always 'OK' */ if (arg.empty()) { @@ -1018,7 +1068,7 @@ static bool short_ok(const wcstring &arg, wchar_t nextopt, const option_list_t & } /* The argument must start with exactly one dash */ - if (! string_prefixes_string(L"-", arg) || string_prefixes_string(L"--", arg)) + if (leading_dash_count(arg.c_str()) != 1) { return false; } @@ -1037,7 +1087,7 @@ static bool short_ok(const wcstring &arg, wchar_t nextopt, const option_list_t & const complete_entry_opt_t *match = NULL; for (option_list_t::const_iterator iter = options.begin(); iter != options.end(); ++iter) { - if (iter->short_opt == arg_char) + if (iter->type == option_type_short && iter->option.at(0) == arg_char) { match = &*iter; break; @@ -1140,8 +1190,8 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); ++oiter) { const complete_entry_opt_t *o = &*oiter; - wchar_t *arg; - if ((arg=param_match2(o, str))!=0 && this->condition_test(o->condition)) + const wchar_t *arg = param_match2(o, str); + if (arg != NULL && this->condition_test(o->condition)) { if (o->result_mode & NO_COMMON) use_common = false; if (o->result_mode & NO_FILES) use_files = false; @@ -1153,7 +1203,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop else if (popt[0] == L'-') { /* Set to true if we found a matching old-style switch */ - int old_style_match = 0; + bool old_style_match = false; /* If we are using old style long options, check for them @@ -1162,11 +1212,11 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); ++oiter) { const complete_entry_opt_t *o = &*oiter; - if (o->old_mode) + if (o->type == option_type_single_long) { - if (param_match_old(o, popt) && this->condition_test(o->condition)) + if (param_match(o, popt) && this->condition_test(o->condition)) { - old_style_match = 1; + old_style_match = true; if (o->result_mode & NO_COMMON) use_common = false; if (o->result_mode & NO_FILES) use_files = false; complete_from_args(str, o->comp, o->localized_desc(), o->flags); @@ -1189,7 +1239,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop be specified as a single token, so that it can be differed from a regular argument. */ - if (!o->old_mode && ! o->long_opt.empty() && !(o->result_mode & NO_COMMON)) + if (o->type == option_type_double_long && !(o->result_mode & NO_COMMON)) continue; if (param_match(o, popt) && this->condition_test(o->condition)) @@ -1217,9 +1267,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop if (!this->condition_test(o->condition)) continue; - - - if ((o->short_opt == L'\0') && (o->long_opt[0]==L'\0')) + if (o->option.empty()) { use_files = use_files && ((o->result_mode & NO_FILES)==0); complete_from_args(str, o->comp, o->localized_desc(), o->flags); @@ -1230,26 +1278,23 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop /* Check if the short style option matches */ - if (o->short_opt != L'\0' && short_ok(str, o->short_opt, options)) + if (short_ok(str, o, options)) { // It's a match const wcstring desc = o->localized_desc(); - // Make a "null terminated string" - wchar_t completion[2] = {o->short_opt, L'\0'}; - append_completion(&this->completions, completion, desc, 0); + append_completion(&this->completions, o->option, desc, 0); } /* Check if the long style option matches */ - if (o->long_opt[0] != L'\0') + if (o->type == option_type_single_long || o->type == option_type_double_long) { int match=0, match_no_case=0; - wcstring whole_opt; - whole_opt.append(o->old_mode?L"-":L"--"); - whole_opt.append(o->long_opt); + wcstring whole_opt(o->expected_dash_count(), L'-'); + whole_opt.append(o->option); match = string_prefixes_string(str, whole_opt); @@ -1278,7 +1323,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop has_arg = ! o->comp.empty(); req_arg = (o->result_mode & NO_COMMON); - if (!o->old_mode && (has_arg && !req_arg)) + if (o->type == option_type_double_long && (has_arg && !req_arg)) { /* @@ -1606,9 +1651,6 @@ void complete(const wcstring &cmd_with_subcmds, std::vector &comps if (!done) { - //const size_t prev_token_len = (prev_begin ? prev_end - prev_begin : 0); - //const wcstring prev_token(prev_begin, prev_token_len); - parse_node_tree_t tree; parse_tree_from_string(cmd, parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens | parse_flag_include_comments, &tree, NULL); @@ -1864,20 +1906,25 @@ wcstring complete_print() append_switch(out, e->cmd_is_path ? L"path" : L"command", escape_string(e->cmd, ESCAPE_ALL)); - - - if (o->short_opt != 0) + + switch (o->type) { - append_format(out, - L" --short-option '%lc'", - o->short_opt); + case option_type_args_only: + break; + + case option_type_short: + assert(! o->option.empty()); + append_format(out, L" --short-option '%lc'", o->option.at(0)); + break; + + case option_type_single_long: + case option_type_double_long: + append_switch(out, + o->type == option_type_single_long ? L"old-option" : L"long-option", + o->option); + break; } - - append_switch(out, - o->old_mode?L"old-option":L"long-option", - o->long_opt); - append_switch(out, L"description", C_(o->desc)); diff --git a/src/complete.h b/src/complete.h index e69e89cf..38b56bc0 100644 --- a/src/complete.h +++ b/src/complete.h @@ -179,11 +179,17 @@ typedef uint32_t completion_request_flags_t; If \c condition is empty, the completion is always used. \param flags A set of completion flags */ +enum complete_option_type_t +{ + option_type_args_only, // no option + option_type_short, // -x + option_type_single_long, // -foo + option_type_double_long // --foo +}; void complete_add(const wchar_t *cmd, bool cmd_is_path, - wchar_t short_opt, - const wchar_t *long_opt, - int long_mode, + const wcstring &option, + complete_option_type_t option_type, int result_mode, const wchar_t *condition, const wchar_t *comp, diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 6acefbaa..dc999a36 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2157,7 +2157,7 @@ static void test_complete(void) do_test(completions.size() == 0); /* Trailing spaces (#1261) */ - complete_add(L"foobarbaz", false, 0, NULL, 0, NO_FILES, NULL, L"qux", NULL, COMPLETE_AUTO_SPACE); + complete_add(L"foobarbaz", false, wcstring(), option_type_args_only, NO_FILES, NULL, L"qux", NULL, COMPLETE_AUTO_SPACE); completions.clear(); complete(L"foobarbaz ", completions, COMPLETION_REQUEST_DEFAULT); do_test(completions.size() == 1); -- cgit v1.2.3