diff --git a/src/complete.cpp b/src/complete.cpp index cd2652cc6..40cbafdb1 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -359,11 +359,11 @@ class completer_t { expand_flags_t expand_flags() const { // Never do command substitution in autosuggestions. Sadly, we also can't yet do job // expansion because it's not thread safe. - expand_flags_t result = 0; - if (this->type() == COMPLETE_AUTOSUGGEST) result |= EXPAND_SKIP_CMDSUBST; + expand_flags_t result{}; + if (this->type() == COMPLETE_AUTOSUGGEST) result |= expand_flag::EXPAND_SKIP_CMDSUBST; // Allow fuzzy matching. - if (this->fuzzy()) result |= EXPAND_FUZZY_MATCH; + if (this->fuzzy()) result |= expand_flag::EXPAND_FUZZY_MATCH; return result; } @@ -545,7 +545,10 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description const std::vector &possible_comp, complete_flags_t flags) { wcstring tmp = wc_escaped; - if (!expand_one(tmp, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_WILDCARDS | this->expand_flags(), vars)) + if (!expand_one(tmp, + this->expand_flags() | expand_flag::EXPAND_SKIP_CMDSUBST | + expand_flag::EXPAND_SKIP_WILDCARDS, + vars)) return; const wcstring wc = parse_util_unescape_wildcards(tmp); @@ -665,10 +668,11 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool if (use_command) { // Append all possible executables - expand_result_t result = expand_string(str_cmd, &this->completions, - EXPAND_SPECIAL_FOR_COMMAND | EXPAND_FOR_COMPLETIONS | - EXECUTABLES_ONLY | this->expand_flags(), - vars, NULL); + expand_result_t result = + expand_string(str_cmd, &this->completions, + this->expand_flags() | expand_flag::EXPAND_SPECIAL_FOR_COMMAND | + expand_flag::EXPAND_FOR_COMPLETIONS | expand_flag::EXECUTABLES_ONLY, + vars, NULL); if (result != expand_result_t::error && this->wants_descriptions()) { this->complete_cmd_desc(str_cmd); } @@ -680,8 +684,9 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool expand_result_t ignore = // Append all matching directories expand_string(str_cmd, &this->completions, - EXPAND_FOR_COMPLETIONS | DIRECTORIES_ONLY | this->expand_flags(), vars, - NULL); + this->expand_flags() | expand_flag::EXPAND_FOR_COMPLETIONS | + expand_flag::DIRECTORIES_ONLY, + vars, NULL); UNUSED(ignore); } @@ -747,9 +752,10 @@ void completer_t::complete_from_args(const wcstring &str, const wcstring &args, proc_push_interactive(0); } - expand_flags_t eflags = 0; + expand_flags_t eflags{}; if (is_autosuggest) { - eflags |= EXPAND_NO_DESCRIPTIONS | EXPAND_SKIP_CMDSUBST; + eflags |= expand_flag::EXPAND_NO_DESCRIPTIONS; + eflags |= expand_flag::EXPAND_SKIP_CMDSUBST; } std::vector possible_comp; @@ -1061,19 +1067,22 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt, /// Perform generic (not command-specific) expansions on the specified string. void completer_t::complete_param_expand(const wcstring &str, bool do_file, bool handle_as_special_cd) { - expand_flags_t flags = EXPAND_SKIP_CMDSUBST | EXPAND_FOR_COMPLETIONS | this->expand_flags(); + expand_flags_t flags = this->expand_flags() | expand_flag::EXPAND_SKIP_CMDSUBST | + expand_flag::EXPAND_FOR_COMPLETIONS; - if (!do_file) flags |= EXPAND_SKIP_WILDCARDS; + if (!do_file) flags |= expand_flag::EXPAND_SKIP_WILDCARDS; if (handle_as_special_cd && do_file) { if (this->type() == COMPLETE_AUTOSUGGEST) { - flags |= EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST; + flags |= expand_flag::EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST; } - flags |= DIRECTORIES_ONLY | EXPAND_SPECIAL_FOR_CD | EXPAND_NO_DESCRIPTIONS; + flags |= expand_flags_t{expand_flag::DIRECTORIES_ONLY, expand_flag::EXPAND_SPECIAL_FOR_CD, + expand_flag::EXPAND_NO_DESCRIPTIONS}; } // Squelch file descriptions per issue #254. - if (this->type() == COMPLETE_AUTOSUGGEST || do_file) flags |= EXPAND_NO_DESCRIPTIONS; + if (this->type() == COMPLETE_AUTOSUGGEST || do_file) + flags |= expand_flag::EXPAND_NO_DESCRIPTIONS; // We have the following cases: // @@ -1110,7 +1119,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file, if (complete_from_start) { // Don't do fuzzy matching for files if the string begins with a dash (issue #568). We could // consider relaxing this if there was a preceding double-dash argument. - if (string_prefixes_string(L"-", str)) flags &= ~EXPAND_FUZZY_MATCH; + if (string_prefixes_string(L"-", str)) flags.clear(expand_flag::EXPAND_FUZZY_MATCH); if (expand_string(str, &this->completions, flags, vars, NULL) == expand_result_t::error) { debug(3, L"Error while expanding string '%ls'", str.c_str()); diff --git a/src/enum_set.h b/src/enum_set.h index cbec399d0..a8b9b229b 100644 --- a/src/enum_set.h +++ b/src/enum_set.h @@ -25,6 +25,7 @@ class enum_set_t : private std::bitset()> { static size_t index_of(T t) { return static_cast(t); } explicit enum_set_t(unsigned long raw) : super(raw) {} + explicit enum_set_t(super sup) : super(sup) {} public: enum_set_t() = default; @@ -52,6 +53,32 @@ class enum_set_t : private std::bitset()> { bool operator==(const enum_set_t &rhs) const { return super::operator==(rhs); } bool operator!=(const enum_set_t &rhs) const { return super::operator!=(rhs); } + + /// OR in a single flag, returning a new set. + enum_set_t operator|(T rhs) const { + enum_set_t result = *this; + result.set(rhs); + return result; + } + + /// Compute the union of two sets. + enum_set_t operator|(enum_set_t rhs) const { return from_raw(to_raw() | rhs.to_raw()); } + + /// OR in a single flag, modifying the set in place. + enum_set_t operator|=(T rhs) { + *this = *this | rhs; + return *this; + } + + /// Set this to the union of two sets. + enum_set_t operator|=(enum_set_t rhs) { + *this = *this | rhs; + return *this; + } + + /// Test a value of a single flag. Note this does not return an enum_set_t; there is no such + /// boolean conversion. This simply makes flags work more naturally as bit masks. + bool operator&(T rhs) const { return get(rhs); } }; /// An array of Elem indexed by an enum class. diff --git a/src/expand.cpp b/src/expand.cpp index 9e9f5c8cd..c932465ce 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -511,7 +511,7 @@ static expand_result_t expand_braces(const wcstring &instr, expand_flags_t flags } if (brace_count > 0) { - if (!(flags & EXPAND_FOR_COMPLETIONS)) { + if (!(flags & expand_flag::EXPAND_FOR_COMPLETIONS)) { syntax_error = true; } else { // The user hasn't typed an end brace yet; make one up and append it, then expand @@ -527,7 +527,7 @@ static expand_result_t expand_braces(const wcstring &instr, expand_flags_t flags } // Note: this code looks very fishy, apparently it has never worked. - return expand_braces(mod, 1, out, errors); + return expand_braces(mod, expand_flag::EXPAND_SKIP_CMDSUBST, out, errors); } } @@ -898,7 +898,7 @@ class expander_t { }; expand_result_t expander_t::stage_cmdsubst(wcstring input, std::vector *out) { - if (EXPAND_SKIP_CMDSUBST & flags) { + if (flags & expand_flag::EXPAND_SKIP_CMDSUBST) { size_t cur = 0, start = 0, end; switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) { case 0: @@ -924,7 +924,7 @@ expand_result_t expander_t::stage_variables(wcstring input, std::vector *out) { - if (!(EXPAND_SKIP_HOME_DIRECTORIES & flags)) { + if (!(flags & expand_flag::EXPAND_SKIP_HOME_DIRECTORIES)) { expand_home_directory(input, vars); } expand_percent_self(input); @@ -958,13 +958,14 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, std::vector *out) { expand_result_t result = expand_result_t::ok; - remove_internal_separator(&path_to_expand, flags & EXPAND_SKIP_WILDCARDS); + remove_internal_separator(&path_to_expand, flags & expand_flag::EXPAND_SKIP_WILDCARDS); const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_STRING */); + const bool for_completions = flags & expand_flag::EXPAND_FOR_COMPLETIONS; + const bool skip_wildcards = flags & expand_flag::EXPAND_SKIP_WILDCARDS; - if (has_wildcard && (flags & EXECUTABLES_ONLY)) { + if (has_wildcard && (flags & expand_flag::EXECUTABLES_ONLY)) { ; // don't do wildcard expansion for executables, see issue #785 - } else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) || - has_wildcard) { + } else if ((for_completions && !skip_wildcards) || has_wildcard) { // We either have a wildcard, or we don't have a wildcard but we're doing completion // expansion (so we want to get the completion of a file path). Note that if // EXPAND_SKIP_WILDCARDS is set, we stomped wildcards in remove_internal_separator above, so @@ -974,8 +975,8 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, // which may be CDPATH if the special flag is set. const wcstring working_dir = vars.get_pwd_slash(); wcstring_list_t effective_working_dirs; - bool for_cd = static_cast(flags & EXPAND_SPECIAL_FOR_CD); - bool for_command = static_cast(flags & EXPAND_SPECIAL_FOR_COMMAND); + bool for_cd = flags & expand_flag::EXPAND_SPECIAL_FOR_CD; + bool for_command = flags & expand_flag::EXPAND_SPECIAL_FOR_COMMAND; if (!for_cd && !for_command) { // Common case. effective_working_dirs.push_back(working_dir); @@ -1038,7 +1039,7 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, // Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing // to mean don't do file expansions, so if we're not doing file expansions, just drop this // completion on the floor. - if (!(flags & EXPAND_FOR_COMPLETIONS)) { + if (!(flags & expand_flag::EXPAND_FOR_COMPLETIONS)) { append_completion(out, std::move(path_to_expand)); } } @@ -1050,7 +1051,7 @@ expand_result_t expander_t::expand_string(wcstring input, expand_flags_t flags, const environment_t &vars, parse_error_list_t *errors) { // Early out. If we're not completing, and there's no magic in the input, we're done. - if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(input)) { + if (!(flags & expand_flag::EXPAND_FOR_COMPLETIONS) && expand_is_clean(input)) { append_completion(out_completions, std::move(input)); return expand_result_t::ok; } @@ -1092,7 +1093,7 @@ expand_result_t expander_t::expand_string(wcstring input, if (total_result != expand_result_t::error) { // Hack to un-expand tildes (see #647). - if (!(flags & EXPAND_SKIP_HOME_DIRECTORIES)) { + if (!(flags & expand_flag::EXPAND_SKIP_HOME_DIRECTORIES)) { unexpand_tildes(input, vars, &completions); } out_completions->insert(out_completions->end(), @@ -1113,12 +1114,12 @@ bool expand_one(wcstring &string, expand_flags_t flags, const environment_t &var parse_error_list_t *errors) { std::vector completions; - if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(string)) { + if (!flags.get(expand_flag::EXPAND_FOR_COMPLETIONS) && expand_is_clean(string)) { return true; } - if (expand_string(string, &completions, flags | EXPAND_NO_DESCRIPTIONS, vars, errors) != - expand_result_t::error && + if (expand_string(string, &completions, flags | expand_flag::EXPAND_NO_DESCRIPTIONS, vars, + errors) != expand_result_t::error && completions.size() == 1) { string = std::move(completions.at(0).completion); return true; @@ -1137,9 +1138,11 @@ expand_result_t expand_to_command_and_args(const wcstring &instr, const environm } std::vector completions; - expand_result_t expand_err = expand_string( - instr, &completions, EXPAND_SKIP_CMDSUBST | EXPAND_NO_DESCRIPTIONS | EXPAND_SKIP_JOBS, vars, - errors); + expand_result_t expand_err = + expand_string(instr, &completions, + {expand_flag::EXPAND_SKIP_CMDSUBST, expand_flag::EXPAND_NO_DESCRIPTIONS, + expand_flag::EXPAND_SKIP_JOBS}, + vars, errors); if (expand_err == expand_result_t::ok || expand_err == expand_result_t::wildcard_match) { // The first completion is the command, any remaning are arguments. bool first = true; diff --git a/src/expand.h b/src/expand.h index 46ac206fe..e733851e9 100644 --- a/src/expand.h +++ b/src/expand.h @@ -14,6 +14,7 @@ #include #include "common.h" +#include "enum_set.h" #include "maybe.h" #include "parse_constants.h" @@ -21,43 +22,51 @@ class environment_t; class env_var_t; class environment_t; -enum { +enum class expand_flag { /// Flag specifying that cmdsubst expansion should be skipped. - EXPAND_SKIP_CMDSUBST = 1 << 0, + EXPAND_SKIP_CMDSUBST, /// Flag specifying that variable expansion should be skipped. - EXPAND_SKIP_VARIABLES = 1 << 1, + EXPAND_SKIP_VARIABLES, /// Flag specifying that wildcard expansion should be skipped. - EXPAND_SKIP_WILDCARDS = 1 << 2, + EXPAND_SKIP_WILDCARDS, /// The expansion is being done for tab or auto completions. Returned completions may have the /// wildcard as a prefix instead of a match. - EXPAND_FOR_COMPLETIONS = 1 << 3, + EXPAND_FOR_COMPLETIONS, /// Only match files that are executable by the current user. - EXECUTABLES_ONLY = 1 << 4, + EXECUTABLES_ONLY, /// Only match directories. - DIRECTORIES_ONLY = 1 << 5, + DIRECTORIES_ONLY, /// Don't generate descriptions. - EXPAND_NO_DESCRIPTIONS = 1 << 6, + EXPAND_NO_DESCRIPTIONS, /// Don't expand jobs (but you can still expand processes). This is because /// job expansion is not thread safe. - EXPAND_SKIP_JOBS = 1 << 7, + EXPAND_SKIP_JOBS, /// Don't expand home directories. - EXPAND_SKIP_HOME_DIRECTORIES = 1 << 8, + EXPAND_SKIP_HOME_DIRECTORIES, /// Allow fuzzy matching. - EXPAND_FUZZY_MATCH = 1 << 9, + EXPAND_FUZZY_MATCH, /// Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if /// EXPAND_FUZZY_MATCH is set. - EXPAND_NO_FUZZY_DIRECTORIES = 1 << 10, + EXPAND_NO_FUZZY_DIRECTORIES, /// Do expansions specifically to support cd. This means using CDPATH as a list of potential /// working directories, and to use logical instead of physical paths. - EXPAND_SPECIAL_FOR_CD = 1 << 11, + EXPAND_SPECIAL_FOR_CD, /// Do expansions specifically for cd autosuggestion. This is to differentiate between cd /// completions and cd autosuggestions. - EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST = 1 << 12, + EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST, /// Do expansions specifically to support external command completions. This means using PATH as /// a list of potential working directories. - EXPAND_SPECIAL_FOR_COMMAND = 1 << 13 + EXPAND_SPECIAL_FOR_COMMAND, + + COUNT, }; -typedef int expand_flags_t; + +template <> +struct enum_info_t { + static constexpr auto count = expand_flag::COUNT; +}; + +using expand_flags_t = enum_set_t; class completion_t; diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index f0f69647c..37369b19f 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -305,6 +305,10 @@ static void test_enum_set() { do_test(es != enum_set_t::from_raw(1)); es.set(test_enum::beta); + do_test(es.get(test_enum::beta)); + do_test(!es.get(test_enum::alpha)); + do_test(es & test_enum::beta); + do_test(!(es & test_enum::alpha)); do_test(es.to_raw() == 2); do_test(es == enum_set_t::from_raw(2)); do_test(es == enum_set_t{test_enum::beta}); @@ -312,6 +316,10 @@ static void test_enum_set() { do_test(es.any()); do_test(!es.none()); + do_test((enum_set_t{test_enum::beta} | test_enum::alpha).to_raw() == 3); + do_test((enum_set_t{test_enum::beta} | enum_set_t{test_enum::alpha}) + .to_raw() == 3); + unsigned idx = 0; for (auto v : enum_iter_t{}) { do_test(static_cast(v) == idx); @@ -970,7 +978,8 @@ static void test_parser() { say(L"Testing eval_args"); completion_list_t comps; - parser_t::expand_argument_list(L"alpha 'beta gamma' delta", 0, null_environment_t{}, &comps); + parser_t::expand_argument_list(L"alpha 'beta gamma' delta", expand_flags_t{}, + null_environment_t{}, &comps); do_test(comps.size() == 3); do_test(comps.at(0).completion == L"alpha"); do_test(comps.at(1).completion == L"beta gamma"); @@ -1656,13 +1665,15 @@ static bool expand_test(const wchar_t *in, expand_flags_t flags, ...) { /// Test globbing and other parameter expansion. static void test_expand() { say(L"Testing parameter expansion"); + const expand_flags_t noflags{}; - expand_test(L"foo", 0, L"foo", 0, L"Strings do not expand to themselves"); - expand_test(L"a{b,c,d}e", 0, L"abe", L"ace", L"ade", 0, L"Bracket expansion is broken"); - expand_test(L"a*", EXPAND_SKIP_WILDCARDS, L"a*", 0, L"Cannot skip wildcard expansion"); - expand_test(L"/bin/l\\0", EXPAND_FOR_COMPLETIONS, 0, + expand_test(L"foo", noflags, L"foo", 0, L"Strings do not expand to themselves"); + expand_test(L"a{b,c,d}e", noflags, L"abe", L"ace", L"ade", 0, L"Bracket expansion is broken"); + expand_test(L"a*", expand_flag::EXPAND_SKIP_WILDCARDS, L"a*", 0, + L"Cannot skip wildcard expansion"); + expand_test(L"/bin/l\\0", expand_flag::EXPAND_FOR_COMPLETIONS, 0, L"Failed to handle null escape in expansion"); - expand_test(L"foo\\$bar", EXPAND_SKIP_VARIABLES, L"foo$bar", 0, + expand_test(L"foo\\$bar", expand_flag::EXPAND_SKIP_VARIABLES, L"foo$bar", 0, L"Failed to handle dollar sign in variable-skipping expansion"); // bb @@ -1700,89 +1711,91 @@ static void test_expand() { // (https://github.com/fish-shell/fish-shell/issues/270). But it does have to match literal // components (e.g. "./*" has to match the same as "*". const wchar_t *const wnull = NULL; - expand_test(L"test/fish_expand_test/.*", 0, L"test/fish_expand_test/.foo", wnull, + expand_test(L"test/fish_expand_test/.*", noflags, L"test/fish_expand_test/.foo", wnull, L"Expansion not correctly handling dotfiles"); - expand_test(L"test/fish_expand_test/./.*", 0, L"test/fish_expand_test/./.foo", wnull, + expand_test(L"test/fish_expand_test/./.*", noflags, L"test/fish_expand_test/./.foo", wnull, L"Expansion not correctly handling literal path components in dotfiles"); - expand_test(L"test/fish_expand_test/*/xxx", 0, L"test/fish_expand_test/bax/xxx", + expand_test(L"test/fish_expand_test/*/xxx", noflags, L"test/fish_expand_test/bax/xxx", L"test/fish_expand_test/baz/xxx", wnull, L"Glob did the wrong thing 1"); - expand_test(L"test/fish_expand_test/*z/xxx", 0, L"test/fish_expand_test/baz/xxx", wnull, + expand_test(L"test/fish_expand_test/*z/xxx", noflags, L"test/fish_expand_test/baz/xxx", wnull, L"Glob did the wrong thing 2"); - expand_test(L"test/fish_expand_test/**z/xxx", 0, L"test/fish_expand_test/baz/xxx", wnull, + expand_test(L"test/fish_expand_test/**z/xxx", noflags, L"test/fish_expand_test/baz/xxx", wnull, L"Glob did the wrong thing 3"); - expand_test(L"test/fish_expand_test////baz/xxx", 0, L"test/fish_expand_test////baz/xxx", wnull, - L"Glob did the wrong thing 3"); + expand_test(L"test/fish_expand_test////baz/xxx", noflags, L"test/fish_expand_test////baz/xxx", + wnull, L"Glob did the wrong thing 3"); - expand_test(L"test/fish_expand_test/b**", 0, L"test/fish_expand_test/bb", + expand_test(L"test/fish_expand_test/b**", noflags, L"test/fish_expand_test/bb", L"test/fish_expand_test/bb/x", L"test/fish_expand_test/bar", L"test/fish_expand_test/bax", L"test/fish_expand_test/bax/xxx", L"test/fish_expand_test/baz", L"test/fish_expand_test/baz/xxx", L"test/fish_expand_test/baz/yyy", wnull, L"Glob did the wrong thing 4"); // A trailing slash should only produce directories. - expand_test(L"test/fish_expand_test/b*/", 0, L"test/fish_expand_test/bb/", + expand_test(L"test/fish_expand_test/b*/", noflags, L"test/fish_expand_test/bb/", L"test/fish_expand_test/baz/", L"test/fish_expand_test/bax/", wnull, L"Glob did the wrong thing 5"); - expand_test(L"test/fish_expand_test/b**/", 0, L"test/fish_expand_test/bb/", + expand_test(L"test/fish_expand_test/b**/", noflags, L"test/fish_expand_test/bb/", L"test/fish_expand_test/baz/", L"test/fish_expand_test/bax/", wnull, L"Glob did the wrong thing 6"); - expand_test(L"test/fish_expand_test/**/q", 0, L"test/fish_expand_test/lol/nub/q", wnull, + expand_test(L"test/fish_expand_test/**/q", noflags, L"test/fish_expand_test/lol/nub/q", wnull, L"Glob did the wrong thing 7"); - expand_test(L"test/fish_expand_test/BA", EXPAND_FOR_COMPLETIONS, L"test/fish_expand_test/bar", - L"test/fish_expand_test/bax/", L"test/fish_expand_test/baz/", wnull, - L"Case insensitive test did the wrong thing"); + expand_test(L"test/fish_expand_test/BA", expand_flag::EXPAND_FOR_COMPLETIONS, + L"test/fish_expand_test/bar", L"test/fish_expand_test/bax/", + L"test/fish_expand_test/baz/", wnull, L"Case insensitive test did the wrong thing"); - expand_test(L"test/fish_expand_test/BA", EXPAND_FOR_COMPLETIONS, L"test/fish_expand_test/bar", - L"test/fish_expand_test/bax/", L"test/fish_expand_test/baz/", wnull, - L"Case insensitive test did the wrong thing"); + expand_test(L"test/fish_expand_test/BA", expand_flag::EXPAND_FOR_COMPLETIONS, + L"test/fish_expand_test/bar", L"test/fish_expand_test/bax/", + L"test/fish_expand_test/baz/", wnull, L"Case insensitive test did the wrong thing"); - expand_test(L"test/fish_expand_test/bb/yyy", EXPAND_FOR_COMPLETIONS, + expand_test(L"test/fish_expand_test/bb/yyy", expand_flag::EXPAND_FOR_COMPLETIONS, /* nothing! */ wnull, L"Wrong fuzzy matching 1"); - expand_test(L"test/fish_expand_test/bb/x", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, L"", - wnull, // we just expect the empty string since this is an exact match - L"Wrong fuzzy matching 2"); + expand_test( + L"test/fish_expand_test/bb/x", + expand_flags_t{expand_flag::EXPAND_FOR_COMPLETIONS, expand_flag::EXPAND_FUZZY_MATCH}, L"", + wnull, // we just expect the empty string since this is an exact match + L"Wrong fuzzy matching 2"); // Some vswprintfs refuse to append ANY_STRING in a format specifiers, so don't use // format_string here. + const expand_flags_t fuzzy_comp{expand_flag::EXPAND_FOR_COMPLETIONS, + expand_flag::EXPAND_FUZZY_MATCH}; const wcstring any_str_str(1, ANY_STRING); - expand_test(L"test/fish_expand_test/b/xx*", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, + expand_test(L"test/fish_expand_test/b/xx*", fuzzy_comp, (L"test/fish_expand_test/bax/xx" + any_str_str).c_str(), (L"test/fish_expand_test/baz/xx" + any_str_str).c_str(), wnull, L"Wrong fuzzy matching 3"); - expand_test(L"test/fish_expand_test/b/yyy", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, - L"test/fish_expand_test/baz/yyy", wnull, L"Wrong fuzzy matching 4"); + expand_test(L"test/fish_expand_test/b/yyy", fuzzy_comp, L"test/fish_expand_test/baz/yyy", wnull, + L"Wrong fuzzy matching 4"); - expand_test(L"test/fish_expand_test/aa/x", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, - L"test/fish_expand_test/aaa2/x", wnull, L"Wrong fuzzy matching 5"); + expand_test(L"test/fish_expand_test/aa/x", fuzzy_comp, L"test/fish_expand_test/aaa2/x", wnull, + L"Wrong fuzzy matching 5"); - expand_test(L"test/fish_expand_test/aaa/x", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, wnull, + expand_test(L"test/fish_expand_test/aaa/x", fuzzy_comp, wnull, L"Wrong fuzzy matching 6 - shouldn't remove valid directory names (#3211)"); - if (!expand_test(L"test/fish_expand_test/.*", 0, L"test/fish_expand_test/.foo", 0)) { + if (!expand_test(L"test/fish_expand_test/.*", noflags, L"test/fish_expand_test/.foo", 0)) { err(L"Expansion not correctly handling dotfiles"); } - if (!expand_test(L"test/fish_expand_test/./.*", 0, L"test/fish_expand_test/./.foo", 0)) { + if (!expand_test(L"test/fish_expand_test/./.*", noflags, L"test/fish_expand_test/./.foo", 0)) { err(L"Expansion not correctly handling literal path components in dotfiles"); } if (!pushd("test/fish_expand_test")) return; - expand_test(L"b/xx", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, L"bax/xxx", L"baz/xxx", wnull, - L"Wrong fuzzy matching 5"); + expand_test(L"b/xx", fuzzy_comp, L"bax/xxx", L"baz/xxx", wnull, L"Wrong fuzzy matching 5"); // multiple slashes with fuzzy matching - #3185 - expand_test(L"l///n", EXPAND_FOR_COMPLETIONS | EXPAND_FUZZY_MATCH, L"lol///nub/", wnull, - L"Wrong fuzzy matching 6"); + expand_test(L"l///n", fuzzy_comp, L"lol///nub/", wnull, L"Wrong fuzzy matching 6"); popd(); } @@ -2255,7 +2268,7 @@ static bool run_test_test(int expected, const wcstring &str) { // We need to tokenize the string in the same manner a normal shell would do. This is because we // need to test things like quoted strings that have leading and trailing whitespace. - parser_t::expand_argument_list(str, 0, null_environment_t{}, &comps); + parser_t::expand_argument_list(str, expand_flags_t{}, null_environment_t{}, &comps); for (completion_list_t::const_iterator it = comps.begin(), end = comps.end(); it != end; ++it) { argv.push_back(it->completion); } diff --git a/src/highlight.cpp b/src/highlight.cpp index fe993b121..e05539e76 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -432,7 +432,7 @@ bool autosuggest_validate_from_history(const history_item_t &item, if (parsed_command == L"cd" && !cd_dir.empty()) { // We can possibly handle this specially. - if (expand_one(cd_dir, EXPAND_SKIP_CMDSUBST, vars)) { + if (expand_one(cd_dir, expand_flag::EXPAND_SKIP_CMDSUBST, vars)) { handled = true; bool is_help = string_prefixes_string(cd_dir, L"--help") || string_prefixes_string(cd_dir, L"-h"); @@ -926,7 +926,7 @@ void highlighter_t::color_arguments(const std::vector> &arg if (cmd_is_cd) { // Mark this as an error if it's not 'help' and not a valid cd path. wcstring param = arg.get_source(this->buff); - if (expand_one(param, EXPAND_SKIP_CMDSUBST, vars)) { + if (expand_one(param, expand_flag::EXPAND_SKIP_CMDSUBST, vars)) { bool is_help = string_prefixes_string(param, L"--help") || string_prefixes_string(param, L"-h"); if (!is_help && this->io_ok && @@ -969,7 +969,7 @@ void highlighter_t::color_redirection(tnode_t redirection_node) // I/O is disallowed, so we don't have much hope of catching anything but gross // errors. Assume it's valid. target_is_valid = true; - } else if (!expand_one(target, EXPAND_SKIP_CMDSUBST, vars)) { + } else if (!expand_one(target, expand_flag::EXPAND_SKIP_CMDSUBST, vars)) { // Could not be expanded. target_is_valid = false; } else { diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 018a35481..8146c20a7 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -127,7 +127,10 @@ tnode_t parse_execution_context_t::infinite_recursive_statem .try_get_child(); if (plain_statement) { maybe_t cmd = command_for_plain_statement(plain_statement, pstree->src); - if (cmd && expand_one(*cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES, nullenv) && + if (cmd && + expand_one(*cmd, + {expand_flag::EXPAND_SKIP_CMDSUBST, expand_flag::EXPAND_SKIP_VARIABLES}, + nullenv) && cmd == forbidden_function_name) { // This is it. infinite_recursive_statement = plain_statement; @@ -376,7 +379,7 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( // in just one. tnode_t var_name_node = header.child<1>(); wcstring for_var_name = get_source(var_name_node); - if (!expand_one(for_var_name, 0, parser->vars())) { + if (!expand_one(for_var_name, expand_flags_t{}, parser->vars())) { report_error(var_name_node, FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str()); return parse_execution_errored; } @@ -449,8 +452,8 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( // Expand it. We need to offset any errors by the position of the string. std::vector switch_values_expanded; parse_error_list_t errors; - auto expand_ret = expand_string(switch_value, &switch_values_expanded, EXPAND_NO_DESCRIPTIONS, - parser->vars(), &errors); + auto expand_ret = expand_string(switch_value, &switch_values_expanded, + expand_flag::EXPAND_NO_DESCRIPTIONS, parser->vars(), &errors); parse_error_offset_source_start(&errors, switch_value_n.source_range()->start); switch (expand_ret) { @@ -906,8 +909,8 @@ parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes( // Expand this string. parse_error_list_t errors; arg_expanded.clear(); - auto expand_ret = - expand_string(arg_str, &arg_expanded, EXPAND_NO_DESCRIPTIONS, parser->vars(), &errors); + auto expand_ret = expand_string(arg_str, &arg_expanded, expand_flag::EXPAND_NO_DESCRIPTIONS, + parser->vars(), &errors); parse_error_offset_source_start(&errors, arg_node.source_range()->start); switch (expand_ret) { case expand_result_t::error: { @@ -956,7 +959,8 @@ bool parse_execution_context_t::determine_io_chain(tnode_tvars()); + expand_one(target, no_exec ? expand_flag::EXPAND_SKIP_VARIABLES : expand_flags_t{}, + parser->vars()); if (!target_expanded || target.empty()) { // TODO: Improve this error message. errored = diff --git a/src/parse_util.cpp b/src/parse_util.cpp index a575cebd8..9a75507f8 100644 --- a/src/parse_util.cpp +++ b/src/parse_util.cpp @@ -1197,7 +1197,7 @@ static bool detect_errors_in_plain_statement(const wcstring &buff_src, // Check that we don't do an invalid builtin (issue #1252). if (!errored && decoration == parse_statement_decoration_builtin && - expand_one(*unexp_command, 0, null_environment_t{}, parse_errors) && + expand_one(*unexp_command, expand_flags_t{}, null_environment_t{}, parse_errors) && !builtin_exists(*unexp_command)) { errored = append_syntax_error(parse_errors, source_start, UNKNOWN_BUILTIN_ERR_MSG, unexp_command->c_str()); diff --git a/src/wildcard.cpp b/src/wildcard.cpp index 5e0b47b22..05a8e1204 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -167,7 +167,7 @@ static wcstring resolve_description(const wcstring &full_completion, wcstring *c completion->resize(complete_sep_loc); return description; } - if (expand_flags & EXPAND_NO_DESCRIPTIONS) return {}; + if (expand_flags & expand_flag::EXPAND_NO_DESCRIPTIONS) return {}; return desc_func ? desc_func(full_completion) : wcstring{}; } @@ -219,7 +219,7 @@ static bool wildcard_complete_internal(const wchar_t *str, const wchar_t *wc, // If we're allowing fuzzy match, any match is OK. Otherwise we require a prefix match. bool match_acceptable; - if (params.expand_flags & EXPAND_FUZZY_MATCH) { + if (params.expand_flags & expand_flag::EXPAND_FUZZY_MATCH) { match_acceptable = match.type != fuzzy_match_none; } else { match_acceptable = match_type_shares_prefix(match.type); @@ -415,12 +415,12 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc const bool is_directory = stat_res == 0 && S_ISDIR(stat_buf.st_mode); const bool is_executable = stat_res == 0 && S_ISREG(stat_buf.st_mode); - const bool need_directory = expand_flags & DIRECTORIES_ONLY; + const bool need_directory = expand_flags & expand_flag::DIRECTORIES_ONLY; if (need_directory && !is_directory) { return false; } - const bool executables_only = expand_flags & EXECUTABLES_ONLY; + const bool executables_only = expand_flags & expand_flag::EXECUTABLES_ONLY; if (executables_only && (!is_executable || waccess(filepath, X_OK) != 0)) { return false; } @@ -432,7 +432,7 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc // Compute the description. wcstring desc; - if (!(expand_flags & EXPAND_NO_DESCRIPTIONS)) { + if (!(expand_flags & expand_flag::EXPAND_NO_DESCRIPTIONS)) { desc = file_get_desc(filepath, lstat_res, lstat_buf, stat_res, stat_buf, stat_errno); if (file_size >= 0) { @@ -507,8 +507,7 @@ class wildcard_expander_t { void add_expansion_result(const wcstring &result) { // This function is only for the non-completions case. - assert(!static_cast(this->flags & - EXPAND_FOR_COMPLETIONS)); //!OCLINT(multiple unary operator) + assert(!(this->flags & expand_flag::EXPAND_FOR_COMPLETIONS)); if (this->completion_set.insert(result).second) { append_completion(this->resolved_completions, result); this->did_add = true; @@ -569,13 +568,13 @@ class wildcard_expander_t { void try_add_completion_result(const wcstring &filepath, const wcstring &filename, const wcstring &wildcard, const wcstring &prefix) { // This function is only for the completions case. - assert(this->flags & EXPAND_FOR_COMPLETIONS); + assert(this->flags & expand_flag::EXPAND_FOR_COMPLETIONS); wcstring abs_path = this->working_directory; append_path_component(abs_path, filepath); // We must normalize the path to allow 'cd ..' to operate on logical paths. - if (flags & EXPAND_SPECIAL_FOR_CD) abs_path = normalize_path(abs_path); + if (flags & expand_flag::EXPAND_SPECIAL_FOR_CD) abs_path = normalize_path(abs_path); size_t before = this->resolved_completions->size(); if (wildcard_test_flags_then_complete(abs_path, filename, wildcard.c_str(), this->flags, @@ -597,7 +596,7 @@ class wildcard_expander_t { // hierarchy we can, and then appending any components to each new result. // Only descend deepest unique for cd autosuggest and not for cd tab completion // (issue #4402). - if (flags & EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST) { + if (flags & expand_flag::EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST) { wcstring unique_hierarchy = this->descend_unique_hierarchy(abs_path); if (!unique_hierarchy.empty()) { for (size_t i = before; i < after; i++) { @@ -615,7 +614,7 @@ class wildcard_expander_t { DIR *open_dir(const wcstring &base_dir) const { wcstring path = this->working_directory; append_path_component(path, base_dir); - if (flags & EXPAND_SPECIAL_FOR_CD) { + if (flags & expand_flag::EXPAND_SPECIAL_FOR_CD) { // cd operates on logical paths. // for example, cd ../ should complete "without resolving symlinks". path = normalize_path(path); @@ -661,7 +660,7 @@ void wildcard_expander_t::expand_trailing_slash(const wcstring &base_dir, const return; } - if (!(flags & EXPAND_FOR_COMPLETIONS)) { + if (!(flags & expand_flag::EXPAND_FOR_COMPLETIONS)) { // Trailing slash and not accepting incomplete, e.g. `echo /xyz/`. Insert this file if it // exists. if (waccess(base_dir, F_OK) == 0) { @@ -784,7 +783,7 @@ void wildcard_expander_t::expand_last_segment(const wcstring &base_dir, DIR *bas const wcstring &wc, const wcstring &prefix) { wcstring name_str; while (wreaddir(base_dir_fp, name_str)) { - if (flags & EXPAND_FOR_COMPLETIONS) { + if (flags & expand_flag::EXPAND_FOR_COMPLETIONS) { this->try_add_completion_result(base_dir + name_str, name_str, wc, prefix); } else { // Normal wildcard expansion, not for completions. @@ -855,11 +854,11 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc, // Maybe try a fuzzy match (#94) if nothing was found with the literal match. Respect // EXPAND_NO_DIRECTORY_ABBREVIATIONS (issue #2413). // Don't do fuzzy matches if the literal segment was valid (#3211) - bool allow_fuzzy = (this->flags & (EXPAND_FUZZY_MATCH | EXPAND_NO_FUZZY_DIRECTORIES)) == - EXPAND_FUZZY_MATCH; + bool allow_fuzzy = this->flags.get(expand_flag::EXPAND_FUZZY_MATCH) && + !this->flags.get(expand_flag::EXPAND_NO_FUZZY_DIRECTORIES); if (allow_fuzzy && this->resolved_completions->size() == before && waccess(intermediate_dirpath, F_OK) != 0) { - assert(this->flags & EXPAND_FOR_COMPLETIONS); + assert(this->flags & expand_flag::EXPAND_FOR_COMPLETIONS); DIR *base_dir_fd = open_dir(base_dir); if (base_dir_fd != NULL) { this->expand_literal_intermediate_segment_with_fuzz( @@ -905,13 +904,15 @@ int wildcard_expand_string(const wcstring &wc, const wcstring &working_directory expand_flags_t flags, std::vector *output) { assert(output != NULL); // Fuzzy matching only if we're doing completions. - assert((flags & (EXPAND_FUZZY_MATCH | EXPAND_FOR_COMPLETIONS)) != EXPAND_FUZZY_MATCH); + assert(flags.get(expand_flag::EXPAND_FOR_COMPLETIONS) || + !flags.get(expand_flag::EXPAND_FUZZY_MATCH)); - // EXPAND_SPECIAL_FOR_CD requires DIRECTORIES_ONLY and EXPAND_FOR_COMPLETIONS and - // EXPAND_NO_DESCRIPTIONS. - assert(!(flags & EXPAND_SPECIAL_FOR_CD) || - ((flags & DIRECTORIES_ONLY) && (flags & EXPAND_FOR_COMPLETIONS) && - (flags & EXPAND_NO_DESCRIPTIONS))); + // expand_flag::EXPAND_SPECIAL_FOR_CD requires expand_flag::DIRECTORIES_ONLY and + // expand_flag::EXPAND_FOR_COMPLETIONS and expand_flag::EXPAND_NO_DESCRIPTIONS. + assert(!(flags.get(expand_flag::EXPAND_SPECIAL_FOR_CD)) || + ((flags.get(expand_flag::DIRECTORIES_ONLY)) && + (flags.get(expand_flag::EXPAND_FOR_COMPLETIONS)) && + (flags.get(expand_flag::EXPAND_NO_DESCRIPTIONS)))); // Hackish fix for issue #1631. We are about to call c_str(), which will produce a string // truncated at any embedded nulls. We could fix this by passing around the size, etc. However