Clean up complete.cpp code

Embrace C++11 ranged for loops.
This commit is contained in:
Mahmoud Al-Qudsi 2018-05-12 00:11:40 -05:00
parent feb6e9c90b
commit 3cda5ac59b
3 changed files with 100 additions and 116 deletions

View file

@ -22,6 +22,8 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <unordered_map>
#include <tuple>
#include <vector> #include <vector>
#include "fallback.h" // IWYU pragma: keep #include "fallback.h" // IWYU pragma: keep
@ -861,6 +863,22 @@ static const wchar_t *enum_to_str(T enum_val, const enum_map<T> map[]) {
return NULL; return NULL;
}; };
template<typename... Args>
using tuple_list = std::vector<std::tuple<Args...>>;
//Given a container mapping one X to many Y, return a list of {X,Y}
template<typename X, typename Y>
inline tuple_list<X, Y> flatten(const std::unordered_map<X, std::vector<Y>> &list) {
tuple_list<X, Y> results(list.size() * 1.5); //just a guess as to the initial size
for (auto &kv : list) {
for (auto &v : kv.second) {
results.emplace_back(std::make_tuple(kv.first, v));
}
}
return results;
}
void redirect_tty_output(); void redirect_tty_output();
// Minimum allowed terminal size and default size if the detected size is not reasonable. // Minimum allowed terminal size and default size if the detected size is not reasonable.

View file

@ -17,6 +17,7 @@
#include <iterator> #include <iterator>
#include <list> #include <list>
#include <memory> #include <memory>
#include <numeric>
#include <set> #include <set>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -178,9 +179,9 @@ typedef std::unordered_set<completion_entry_t> completion_entry_set_t;
static completion_entry_set_t completion_set; static completion_entry_set_t completion_set;
/// Comparison function to sort completions by their order field. /// Comparison function to sort completions by their order field.
static bool compare_completions_by_order(const completion_entry_t *p1, static bool compare_completions_by_order(const completion_entry_t &p1,
const completion_entry_t *p2) { const completion_entry_t &p2) {
return p1->order < p2->order; return p1.order < p2.order;
} }
/// The lock that guards the list of completion entries. /// The lock that guards the list of completion entries.
@ -372,12 +373,12 @@ void append_completion(std::vector<completion_t> *completions, wcstring comp, wc
bool completer_t::condition_test(const wcstring &condition) { bool completer_t::condition_test(const wcstring &condition) {
if (condition.empty()) { if (condition.empty()) {
// fwprintf( stderr, L"No condition specified\n" ); // fwprintf( stderr, L"No condition specified\n" );
return 1; return true;
} }
if (this->type() == COMPLETE_AUTOSUGGEST) { if (this->type() == COMPLETE_AUTOSUGGEST) {
// Autosuggestion can't support conditions. // Autosuggestion can't support conditions.
return 0; return false;
} }
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
@ -411,7 +412,7 @@ void complete_add(const wchar_t *cmd, bool cmd_is_path, const wcstring &option,
complete_option_type_t option_type, int result_mode, const wchar_t *condition, complete_option_type_t option_type, int result_mode, const wchar_t *condition,
const wchar_t *comp, const wchar_t *desc, complete_flags_t flags) { const wchar_t *comp, const wchar_t *desc, complete_flags_t flags) {
CHECK(cmd, ); CHECK(cmd, );
// option should be empty iff the option type is arguments only. // option should be empty iff the option type is arguments only.
assert(option.empty() == (option_type == option_type_args_only)); assert(option.empty() == (option_type == option_type_args_only));
// Lock the lock that allows us to edit the completion entry list. // Lock the lock that allows us to edit the completion entry list.
@ -542,7 +543,6 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
const wchar_t *cmd_start; const wchar_t *cmd_start;
int skip;
const wchar_t *const cmd = str.c_str(); const wchar_t *const cmd = str.c_str();
cmd_start = wcsrchr(cmd, L'/'); cmd_start = wcsrchr(cmd, L'/');
@ -559,13 +559,10 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
return; return;
} }
skip = 1; bool skip = true;
for (const auto &c : completions) {
for (size_t i = 0; i < this->completions.size(); i++) {
const completion_t &c = this->completions.at(i);
if (c.completion.empty() || (c.completion[c.completion.size() - 1] != L'/')) { if (c.completion.empty() || (c.completion[c.completion.size() - 1] != L'/')) {
skip = 0; skip = false;
break; break;
} }
} }
@ -577,20 +574,21 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
wcstring lookup_cmd(L"__fish_describe_command "); wcstring lookup_cmd(L"__fish_describe_command ");
lookup_cmd.append(escape_string(cmd_start, 1)); lookup_cmd.append(escape_string(cmd_start, 1));
std::unordered_map<wcstring, wcstring> lookup;
// First locate a list of possible descriptions using a single call to apropos or a direct // First locate a list of possible descriptions using a single call to apropos or a direct
// search if we know the location of the whatis database. This can take some time on slower // search if we know the location of the whatis database. This can take some time on slower
// systems with a large set of manuals, but it should be ok since apropos is only called once. // systems with a large set of manuals, but it should be ok since apropos is only called once.
wcstring_list_t list; wcstring_list_t list;
if (exec_subshell(lookup_cmd, list, false /* don't apply exit status */) != -1) { if (exec_subshell(lookup_cmd, list, false /* don't apply exit status */) != -1) {
std::unordered_map<wcstring, wcstring> lookup;
lookup.reserve(list.size());
// Then discard anything that is not a possible completion and put the result into a // Then discard anything that is not a possible completion and put the result into a
// hashtable with the completion as key and the description as value. // hashtable with the completion as key and the description as value.
// //
// Should be reasonably fast, since no memory allocations are needed. // Should be reasonably fast, since no memory allocations are needed.
for (size_t i = 0; i < list.size(); i++) { // mqudsi: I don't know if the above were ever true, but it's certainly not any more.
const wcstring &elstr = list.at(i); // Plenty of allocations below.
for (const wcstring &elstr : list) {
const wcstring fullkey(elstr, wcslen(cmd_start)); const wcstring fullkey(elstr, wcslen(cmd_start));
size_t tab_idx = fullkey.find(L'\t'); size_t tab_idx = fullkey.find(L'\t');
@ -610,8 +608,7 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
// //
// This needs to do a reallocation for every description added, but there shouldn't be that // This needs to do a reallocation for every description added, but there shouldn't be that
// many completions, so it should be ok. // many completions, so it should be ok.
for (size_t i = 0; i < this->completions.size(); i++) { for (auto &completion : completions) {
completion_t &completion = this->completions.at(i);
const wcstring &el = completion.completion; const wcstring &el = completion.completion;
if (el.empty()) continue; if (el.empty()) continue;
@ -899,9 +896,7 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
std::vector<option_list_t> all_options; std::vector<option_list_t> all_options;
{ {
scoped_lock lock(completion_lock); scoped_lock lock(completion_lock);
for (completion_entry_set_t::const_iterator iter = completion_set.begin(); for (const completion_entry_t &i : completion_set) {
iter != completion_set.end(); ++iter) {
const completion_entry_t &i = *iter;
const wcstring &match = i.cmd_is_path ? path : cmd; const wcstring &match = i.cmd_is_path ? path : cmd;
if (wildcard_match(match, i.cmd)) { if (wildcard_match(match, i.cmd)) {
// Copy all of their options into our list. // Copy all of their options into our list.
@ -912,22 +907,18 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
// Now release the lock and test each option that we captured above. We have to do this outside // 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. // the lock because callouts (like the condition) may add or remove completions. See issue 2.
for (std::vector<option_list_t>::const_iterator iter = all_options.begin(); for (const option_list_t &options : all_options) {
iter != all_options.end(); ++iter) {
const option_list_t &options = *iter;
use_common = 1; use_common = 1;
if (use_switches) { if (use_switches) {
if (str[0] == L'-') { if (str[0] == L'-') {
// Check if we are entering a combined option and argument (like --color=auto or // Check if we are entering a combined option and argument (like --color=auto or
// -I/usr/include). // -I/usr/include).
for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); for (const complete_entry_opt_t &o : options) {
++oiter) { const wchar_t *arg = param_match2(&o, str);
const complete_entry_opt_t *o = &*oiter; if (arg != NULL && this->condition_test(o.condition)) {
const wchar_t *arg = param_match2(o, str); if (o.result_mode & NO_COMMON) use_common = false;
if (arg != NULL && this->condition_test(o->condition)) { if (o.result_mode & NO_FILES) use_files = false;
if (o->result_mode & NO_COMMON) use_common = false; complete_from_args(arg, o.comp, o.localized_desc(), o.flags);
if (o->result_mode & NO_FILES) use_files = false;
complete_from_args(arg, o->comp, o->localized_desc(), o->flags);
} }
} }
} else if (popt[0] == L'-') { } else if (popt[0] == L'-') {
@ -937,35 +928,31 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
bool old_style_match = false; bool old_style_match = false;
// If we are using old style long options, check for them first. // If we are using old style long options, check for them first.
for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); for (const complete_entry_opt_t &o : options) {
++oiter) { if (o.type == option_type_single_long && param_match(&o, popt) &&
const complete_entry_opt_t *o = &*oiter; this->condition_test(o.condition)) {
if (o->type == option_type_single_long && param_match(o, popt) &&
this->condition_test(o->condition)) {
old_style_match = true; old_style_match = true;
if (o->result_mode & NO_COMMON) use_common = false; if (o.result_mode & NO_COMMON) use_common = false;
if (o->result_mode & NO_FILES) use_files = false; if (o.result_mode & NO_FILES) use_files = false;
complete_from_args(str, o->comp, o->localized_desc(), o->flags); complete_from_args(str, o.comp, o.localized_desc(), o.flags);
} }
} }
// No old style option matched, or we are not using old style options. We check if // 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) { if (!old_style_match) {
for (option_list_t::const_iterator oiter = options.begin(); for (const complete_entry_opt_t &o : options) {
oiter != options.end(); ++oiter) {
const complete_entry_opt_t *o = &*oiter;
// Gnu-style options with _optional_ arguments must be specified as a single // Gnu-style options with _optional_ arguments must be specified as a single
// token, so that it can be differed from a regular argument. // token, so that it can be differed from a regular argument.
// Here we are testing the previous argument for a GNU-style match, // Here we are testing the previous argument for a GNU-style match,
// to see how we should complete the current argument // to see how we should complete the current argument
if (o->type == option_type_double_long && !(o->result_mode & NO_COMMON)) if (o.type == option_type_double_long && !(o.result_mode & NO_COMMON))
continue; continue;
if (param_match(o, popt) && this->condition_test(o->condition)) { if (param_match(&o, popt) && this->condition_test(o.condition)) {
if (o->result_mode & NO_COMMON) use_common = false; if (o.result_mode & NO_COMMON) use_common = false;
if (o->result_mode & NO_FILES) use_files = false; if (o.result_mode & NO_FILES) use_files = false;
complete_from_args(str, o->comp, o->localized_desc(), o->flags); complete_from_args(str, o.comp, o.localized_desc(), o.flags);
} }
} }
} }
@ -977,44 +964,41 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
} }
// Now we try to complete an option itself // Now we try to complete an option itself
for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end(); for (const complete_entry_opt_t &o : options) {
++oiter) {
const complete_entry_opt_t *o = &*oiter;
// If this entry is for the base command, check if any of the arguments match. // If this entry is for the base command, check if any of the arguments match.
if (!this->condition_test(o->condition)) continue; if (!this->condition_test(o.condition)) continue;
if (o->option.empty()) { if (o.option.empty()) {
use_files = use_files && ((o->result_mode & NO_FILES) == 0); use_files = use_files && ((o.result_mode & NO_FILES) == 0);
complete_from_args(str, o->comp, o->localized_desc(), o->flags); complete_from_args(str, o.comp, o.localized_desc(), o.flags);
} }
if (wcslen(str) == 0 || !use_switches) { if (!use_switches || wcslen(str) == 0) {
continue; continue;
} }
// Check if the short style option matches. // Check if the short style option matches.
if (short_ok(str, o, options)) { if (short_ok(str, &o, options)) {
// It's a match. // It's a match.
const wcstring desc = o->localized_desc(); const wcstring desc = o.localized_desc();
// Append a short-style option // Append a short-style option
append_completion(&this->completions, o->option, desc, 0); append_completion(&this->completions, o.option, desc, 0);
} }
// Check if the long style option matches. // Check if the long style option matches.
if (o->type != option_type_single_long && o->type != option_type_double_long) { if (o.type != option_type_single_long && o.type != option_type_double_long) {
continue; continue;
} }
int match = 0, match_no_case = 0;
wcstring whole_opt(o->expected_dash_count(), L'-'); wcstring whole_opt(o.expected_dash_count(), L'-');
whole_opt.append(o->option); whole_opt.append(o.option);
match = string_prefixes_string(str, whole_opt); int match = string_prefixes_string(str, whole_opt);
if (!match) { if (!match) {
match_no_case = wcsncasecmp(str, whole_opt.c_str(), wcslen(str)) == 0; bool match_no_case = wcsncasecmp(str, whole_opt.c_str(), wcslen(str)) == 0;
}
if (!match && !match_no_case) { if (!match_no_case) {
continue; continue;
}
} }
int has_arg = 0; // does this switch have any known arguments int has_arg = 0; // does this switch have any known arguments
@ -1028,21 +1012,21 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
flags = COMPLETE_REPLACES_TOKEN; flags = COMPLETE_REPLACES_TOKEN;
} }
has_arg = !o->comp.empty(); has_arg = !o.comp.empty();
req_arg = (o->result_mode & NO_COMMON); req_arg = (o.result_mode & NO_COMMON);
if (o->type == option_type_double_long && (has_arg && !req_arg)) { if (o.type == option_type_double_long && (has_arg && !req_arg)) {
// Optional arguments to a switch can only be handled using the '=', so we add it as // Optional arguments to a switch can only be handled using the '=', so we add it as
// a completion. By default we avoid using '=' and instead rely on '--switch // a completion. By default we avoid using '=' and instead rely on '--switch
// switch-arg', since it is more commonly supported by homebrew getopt-like // switch-arg', since it is more commonly supported by homebrew getopt-like
// functions. // functions.
wcstring completion = format_string(L"%ls=", whole_opt.c_str() + offset); wcstring completion = format_string(L"%ls=", whole_opt.c_str() + offset);
// Append a long-style option with a mandatory trailing equal sign // Append a long-style option with a mandatory trailing equal sign
append_completion(&this->completions, completion, C_(o->desc), flags); append_completion(&this->completions, completion, C_(o.desc), flags);
} }
// Append a long-style option // Append a long-style option
append_completion(&this->completions, whole_opt.c_str() + offset, C_(o->desc), flags); append_completion(&this->completions, whole_opt.c_str() + offset, C_(o.desc), flags);
} }
} }
@ -1561,61 +1545,53 @@ wcstring complete_print() {
scoped_lock locker(completion_lock); scoped_lock locker(completion_lock);
// Get a list of all completions in a vector, then sort it by order. // Get a list of all completions in a vector, then sort it by order.
std::vector<const completion_entry_t *> all_completions; std::vector<std::reference_wrapper<const completion_entry_t>> all_completions;
for (completion_entry_set_t::const_iterator i = completion_set.begin(); for (const completion_entry_t &i : completion_set) {
i != completion_set.end(); ++i) { all_completions.emplace_back(i);
all_completions.push_back(&*i);
} }
sort(all_completions.begin(), all_completions.end(), compare_completions_by_order); sort(all_completions.begin(), all_completions.end(), compare_completions_by_order);
for (std::vector<const completion_entry_t *>::const_iterator iter = all_completions.begin(); for (const completion_entry_t &e : all_completions) {
iter != all_completions.end(); ++iter) { const option_list_t &options = e.get_options();
const completion_entry_t *e = *iter; for (const complete_entry_opt_t &o : options) {
const option_list_t &options = e->get_options();
for (option_list_t::const_iterator oiter = options.begin(); oiter != options.end();
++oiter) {
const complete_entry_opt_t *o = &*oiter;
const wchar_t *modestr[] = {L"", L" --no-files", L" --require-parameter", const wchar_t *modestr[] = {L"", L" --no-files", L" --require-parameter",
L" --exclusive"}; L" --exclusive"};
append_format(out, L"complete%ls", modestr[o->result_mode]); append_format(out, L"complete%ls", modestr[o.result_mode]);
append_switch(out, e->cmd_is_path ? L"path" : L"command", append_switch(out, e.cmd_is_path ? L"path" : L"command",
escape_string(e->cmd, ESCAPE_ALL)); escape_string(e.cmd, ESCAPE_ALL));
switch (o->type) { switch (o.type) {
case option_type_args_only: { case option_type_args_only: {
break; break;
} }
case option_type_short: { case option_type_short: {
assert(!o->option.empty()); //!OCLINT(multiple unary operator) assert(!o.option.empty()); //!OCLINT(multiple unary operator)
append_format(out, L" --short-option '%lc'", o->option.at(0)); append_format(out, L" --short-option '%lc'", o.option.at(0));
break; break;
} }
case option_type_single_long: case option_type_single_long:
case option_type_double_long: { case option_type_double_long: {
append_switch( append_switch(
out, o->type == option_type_single_long ? L"old-option" : L"long-option", out, o.type == option_type_single_long ? L"old-option" : L"long-option",
o->option); o.option);
break; break;
} }
} }
append_switch(out, L"description", C_(o->desc)); append_switch(out, L"description", C_(o.desc));
append_switch(out, L"arguments", o->comp); append_switch(out, L"arguments", o.comp);
append_switch(out, L"condition", o->condition); append_switch(out, L"condition", o.condition);
out.append(L"\n"); out.append(L"\n");
} }
} }
// Append wraps. This is a wonky interface where even values are the commands, and odd values // Append wraps. This is a wonky interface where even values are the commands, and odd values
// are the targets that they wrap. // are the targets that they wrap.
const wcstring_list_t wrap_pairs = complete_get_wrap_pairs(); auto wrap_pairs = complete_get_wrap_pairs();
assert(wrap_pairs.size() % 2 == 0); for (const auto &entry : wrap_pairs) {
for (size_t i = 0; i < wrap_pairs.size();) { append_format(out, L"complete --command %ls --wraps %ls\n", std::get<0>(entry).c_str(), std::get<1>(entry).c_str());
const wcstring &cmd = wrap_pairs.at(i++);
const wcstring &target = wrap_pairs.at(i++);
append_format(out, L"complete --command %ls --wraps %ls\n", cmd.c_str(), target.c_str());
} }
return out; return out;
} }
@ -1684,17 +1660,8 @@ wcstring_list_t complete_get_wrap_targets(const wcstring &command) {
return iter->second; return iter->second;
} }
wcstring_list_t complete_get_wrap_pairs() { tuple_list<wcstring, wcstring> complete_get_wrap_pairs() {
wcstring_list_t result;
scoped_lock locker(wrapper_lock); scoped_lock locker(wrapper_lock);
const wrapper_map_t &wraps = wrap_map(); const wrapper_map_t &wraps = wrap_map();
for (wrapper_map_t::const_iterator outer = wraps.begin(); outer != wraps.end(); ++outer) { return flatten(wrap_map());
const wcstring &cmd = outer->first;
const wcstring_list_t &targets = outer->second;
for (size_t i = 0; i < targets.size(); i++) {
result.push_back(cmd);
result.push_back(targets.at(i));
}
}
return result;
} }

View file

@ -193,8 +193,7 @@ bool complete_remove_wrapper(const wcstring &command, const wcstring &wrap_targe
/// Returns a list of wrap targets for a given command. /// Returns a list of wrap targets for a given command.
wcstring_list_t complete_get_wrap_targets(const wcstring &command); wcstring_list_t complete_get_wrap_targets(const wcstring &command);
// Wonky interface: returns all wraps. Even-values are the commands, odd values are the targets. tuple_list<wcstring, wcstring> complete_get_wrap_pairs();
wcstring_list_t complete_get_wrap_pairs();
// Observes that fish_complete_path has changed. // Observes that fish_complete_path has changed.
void complete_invalidate_path(); void complete_invalidate_path();