From 421fbdd52a16afae82765c0029c6b63102025e46 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 22:29:52 -0700 Subject: [PATCH] Instantize env_get_pwd_slash This requires threading environment_t through many places, such as completions and history. We introduce null_environment_t for when the environment isn't important. --- src/builtin_cd.cpp | 2 +- src/complete.cpp | 13 +++++++------ src/env.cpp | 11 ++++++++--- src/env.h | 19 +++++++++++++------ src/expand.cpp | 42 ++++++++++++++++++++++++----------------- src/expand.h | 12 ++++++++---- src/fish_tests.cpp | 24 +++++++++++++++++++---- src/highlight.cpp | 27 +++++++++++++------------- src/history.cpp | 6 +++--- src/history.h | 2 +- src/parse_execution.cpp | 20 ++++++++++++-------- src/parse_util.cpp | 5 +++-- src/parser.cpp | 4 +++- src/parser.h | 2 +- src/reader.cpp | 14 ++++++++------ 15 files changed, 127 insertions(+), 76 deletions(-) diff --git a/src/builtin_cd.cpp b/src/builtin_cd.cpp index 1ab7a2601..5a0432e63 100644 --- a/src/builtin_cd.cpp +++ b/src/builtin_cd.cpp @@ -47,7 +47,7 @@ int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { dir_in = maybe_dir_in->as_string(); } - if (!path_get_cdpath(dir_in, &dir, env_get_pwd_slash())) { + if (!path_get_cdpath(dir_in, &dir, parser.vars().get_pwd_slash())) { if (errno == ENOTDIR) { streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str()); } else if (errno == ENOENT) { diff --git a/src/complete.cpp b/src/complete.cpp index 36afd5441..5e1ee9f9c 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -535,7 +535,7 @@ 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(), NULL)) + if (!expand_one(tmp, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_WILDCARDS | this->expand_flags(), vars)) return; const wcstring wc = parse_util_unescape_wildcards(tmp); @@ -656,7 +656,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool expand_error_t result = expand_string(str_cmd, &this->completions, EXPAND_SPECIAL_FOR_COMMAND | EXPAND_FOR_COMPLETIONS | EXECUTABLES_ONLY | this->expand_flags(), - NULL); + vars, NULL); if (result != EXPAND_ERROR && this->wants_descriptions()) { this->complete_cmd_desc(str_cmd); } @@ -668,7 +668,8 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool expand_error_t ignore = // Append all matching directories expand_string(str_cmd, &this->completions, - EXPAND_FOR_COMPLETIONS | DIRECTORIES_ONLY | this->expand_flags(), NULL); + EXPAND_FOR_COMPLETIONS | DIRECTORIES_ONLY | this->expand_flags(), vars, + NULL); UNUSED(ignore); } @@ -740,7 +741,7 @@ void completer_t::complete_from_args(const wcstring &str, const wcstring &args, } std::vector possible_comp; - parser_t::expand_argument_list(args, eflags, &possible_comp); + parser_t::expand_argument_list(args, eflags, vars, &possible_comp); if (!is_autosuggest) { proc_pop_interactive(); @@ -1079,7 +1080,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file, // See #4954. const wcstring sep_string = wcstring(str, sep_index + 1); std::vector local_completions; - if (expand_string(sep_string, &local_completions, flags, NULL) == EXPAND_ERROR) { + if (expand_string(sep_string, &local_completions, flags, vars, NULL) == EXPAND_ERROR) { debug(3, L"Error while expanding string '%ls'", sep_string.c_str()); } @@ -1098,7 +1099,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file, // consider relaxing this if there was a preceding double-dash argument. if (string_prefixes_string(L"-", str)) flags &= ~EXPAND_FUZZY_MATCH; - if (expand_string(str, &this->completions, flags, NULL) == EXPAND_ERROR) { + if (expand_string(str, &this->completions, flags, vars, NULL) == EXPAND_ERROR) { debug(3, L"Error while expanding string '%ls'", str.c_str()); } } diff --git a/src/env.cpp b/src/env.cpp index 4d7305a19..82ca99df5 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -677,7 +677,7 @@ void env_stack_t::set_read_limit() { void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported(); } -wcstring env_stack_t::get_pwd_slash() { +wcstring environment_t::get_pwd_slash() const { // Return "/" if PWD is missing. // See https://github.com/fish-shell/fish-shell/issues/5080 auto pwd_var = env_get(L"PWD"); @@ -1433,8 +1433,6 @@ int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } -wcstring env_get_pwd_slash() { return env_stack_t::principal().get_pwd_slash(); } - void env_set_read_limit() { return env_stack_t::principal().set_read_limit(); } /// Returns true if the specified scope or any non-shadowed non-global subscopes contain an exported @@ -1606,6 +1604,13 @@ void env_stack_t::set_argv(const wchar_t *const *argv) { environment_t::~environment_t() = default; env_stack_t::~env_stack_t() = default; +null_environment_t::null_environment_t() = default; +null_environment_t::~null_environment_t() = default; +maybe_t null_environment_t::get(const wcstring &key, env_mode_flags_t mode) const { + return none(); +} +wcstring_list_t null_environment_t::get_names(int flags) const { return {}; } + env_stack_t &env_stack_t::principal() { static env_stack_t s_principal; return s_principal; diff --git a/src/env.h b/src/env.h index 918df75f1..e7e6855e8 100644 --- a/src/env.h +++ b/src/env.h @@ -141,6 +141,19 @@ class environment_t { env_mode_flags_t mode = ENV_DEFAULT) const = 0; virtual wcstring_list_t get_names(int flags) const = 0; virtual ~environment_t(); + + /// Returns the PWD with a terminating slash. + wcstring get_pwd_slash() const; +}; + +/// The null environment contains nothing. +class null_environment_t : public environment_t { + public: + null_environment_t(); + ~null_environment_t() override; + + maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; + wcstring_list_t get_names(int flags) const override; }; /// Gets the variable with the specified name, or none() if it does not exist. @@ -155,9 +168,6 @@ int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val); /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); -/// Returns the PWD with a terminating slash. -wcstring env_get_pwd_slash(); - /// Update the read_byte_limit variable. void env_set_read_limit(); @@ -229,9 +239,6 @@ class env_stack_t : public environment_t { /// Sets up argv as the given null terminated array of strings. void set_argv(const wchar_t *const *argv); - /// Returns the PWD with a terminating slash. - wcstring get_pwd_slash(); - /// Update the read_byte_limit variable. void set_read_limit(); diff --git a/src/expand.cpp b/src/expand.cpp index 2f4dc27a9..4b546839e 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -883,15 +883,17 @@ static void remove_internal_separator(wcstring *str, bool conv) { } /// A stage in string expansion is represented as a function that takes an input and returns a list -/// of output (by reference). We get flags and errors. It may return an error; if so expansion +/// of output (by reference). We get flags, vars and errors. It may return an error; if so expansion /// halts. typedef expand_error_t (*expand_stage_t)(wcstring input, //!OCLINT(unused param) std::vector *out, //!OCLINT(unused param) expand_flags_t flags, //!OCLINT(unused param) + const environment_t &vars, //!OCLINT(unused param) parse_error_list_t *errors); //!OCLINT(unused param) static expand_error_t expand_stage_cmdsubst(wcstring input, std::vector *out, - expand_flags_t flags, parse_error_list_t *errors) { + expand_flags_t flags, const environment_t &vars, + parse_error_list_t *errors) { if (EXPAND_SKIP_CMDSUBST & flags) { wchar_t *begin, *end; if (parse_util_locate_cmdsubst(input.c_str(), &begin, &end, true) == 0) { @@ -910,7 +912,8 @@ static expand_error_t expand_stage_cmdsubst(wcstring input, std::vector *out, - expand_flags_t flags, parse_error_list_t *errors) { + expand_flags_t flags, const environment_t &vars, + parse_error_list_t *errors) { // We accept incomplete strings here, since complete uses expand_string to expand incomplete // strings from the commandline. wcstring next; @@ -933,12 +936,14 @@ static expand_error_t expand_stage_variables(wcstring input, std::vector *out, - expand_flags_t flags, parse_error_list_t *errors) { + expand_flags_t flags, const environment_t &vars, + parse_error_list_t *errors) { return expand_braces(input, flags, out, errors); } static expand_error_t expand_stage_home_and_self(wcstring input, std::vector *out, - expand_flags_t flags, parse_error_list_t *errors) { + expand_flags_t flags, const environment_t &vars, + parse_error_list_t *errors) { (void)errors; if (!(EXPAND_SKIP_HOME_DIRECTORIES & flags)) { expand_home_directory(input); @@ -948,8 +953,8 @@ static expand_error_t expand_stage_home_and_self(wcstring input, std::vector *out, expand_flags_t flags, +static expand_error_t expand_stage_wildcards(wcstring path_to_expand, std::vector *out, + expand_flags_t flags, const environment_t &vars, parse_error_list_t *errors) { UNUSED(errors); expand_error_t result = EXPAND_OK; @@ -968,7 +973,7 @@ static expand_error_t expand_stage_wildcards(wcstring path_to_expand, // // So we're going to treat this input as a file path. Compute the "working directories", // which may be CDPATH if the special flag is set. - const wcstring working_dir = env_get_pwd_slash(); + 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); @@ -1042,7 +1047,8 @@ static expand_error_t expand_stage_wildcards(wcstring path_to_expand, } expand_error_t expand_string(wcstring input, std::vector *out_completions, - expand_flags_t flags, parse_error_list_t *errors) { + 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)) { append_completion(out_completions, std::move(input)); @@ -1064,7 +1070,7 @@ expand_error_t expand_string(wcstring input, std::vector *out_comp for (size_t i = 0; total_result != EXPAND_ERROR && i < completions.size(); i++) { wcstring &next = completions.at(i).completion; expand_error_t this_result = - stages[stage_idx](std::move(next), &output_storage, flags, errors); + stages[stage_idx](std::move(next), &output_storage, flags, vars, errors); // If this_result was no match, but total_result is that we have a match, then don't // change it. if (!(this_result == EXPAND_WILDCARD_NO_MATCH && @@ -1090,14 +1096,15 @@ expand_error_t expand_string(wcstring input, std::vector *out_comp return total_result; } -bool expand_one(wcstring &string, expand_flags_t flags, parse_error_list_t *errors) { +bool expand_one(wcstring &string, expand_flags_t flags, const environment_t &vars, + parse_error_list_t *errors) { std::vector completions; if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(string)) { return true; } - if (expand_string(string, &completions, flags | EXPAND_NO_DESCRIPTIONS, errors) && + if (expand_string(string, &completions, flags | EXPAND_NO_DESCRIPTIONS, vars, errors) && completions.size() == 1) { string = std::move(completions.at(0).completion); return true; @@ -1105,8 +1112,9 @@ bool expand_one(wcstring &string, expand_flags_t flags, parse_error_list_t *erro return false; } -expand_error_t expand_to_command_and_args(const wcstring &instr, wcstring *out_cmd, - wcstring_list_t *out_args, parse_error_list_t *errors) { +expand_error_t expand_to_command_and_args(const wcstring &instr, const environment_t &vars, + wcstring *out_cmd, wcstring_list_t *out_args, + parse_error_list_t *errors) { // Fast path. if (expand_is_clean(instr)) { *out_cmd = instr; @@ -1114,9 +1122,9 @@ expand_error_t expand_to_command_and_args(const wcstring &instr, wcstring *out_c } std::vector completions; - expand_error_t expand_err = - expand_string(instr, &completions, - EXPAND_SKIP_CMDSUBST | EXPAND_NO_DESCRIPTIONS | EXPAND_SKIP_JOBS, errors); + expand_error_t expand_err = expand_string( + instr, &completions, EXPAND_SKIP_CMDSUBST | EXPAND_NO_DESCRIPTIONS | EXPAND_SKIP_JOBS, vars, + errors); if (expand_err == EXPAND_OK || expand_err == EXPAND_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 f7336d405..95b3c151b 100644 --- a/src/expand.h +++ b/src/expand.h @@ -17,6 +17,7 @@ #include "parse_constants.h" class env_var_t; +class environment_t; enum { /// Flag specifying that cmdsubst expansion should be skipped. @@ -111,13 +112,15 @@ enum expand_error_t { /// \param output The list to which the result will be appended. /// \param flags Specifies if any expansion pass should be skipped. Legal values are any combination /// of EXPAND_SKIP_CMDSUBST EXPAND_SKIP_VARIABLES and EXPAND_SKIP_WILDCARDS +/// \param vars variables used during expansion. /// \param errors Resulting errors, or NULL to ignore /// /// \return One of EXPAND_OK, EXPAND_ERROR, EXPAND_WILDCARD_MATCH and EXPAND_WILDCARD_NO_MATCH. /// EXPAND_WILDCARD_NO_MATCH and EXPAND_WILDCARD_MATCH are normal exit conditions used only on /// strings containing wildcards to tell if the wildcard produced any matches. __warn_unused expand_error_t expand_string(wcstring input, std::vector *output, - expand_flags_t flags, parse_error_list_t *errors); + expand_flags_t flags, const environment_t &vars, + parse_error_list_t *errors); /// expand_one is identical to expand_string, except it will fail if in expands to more than one /// string. This is used for expanding command names. @@ -128,7 +131,8 @@ __warn_unused expand_error_t expand_string(wcstring input, std::vector get(const wcstring &key, + env_mode_flags_t mode = ENV_DEFAULT) const override { + if (key == L"PWD") { + return env_var_t{wgetcwd(), 0}; + } + return {}; + } + + wcstring_list_t get_names(int flags) const override { return {L"PWD"}; } +}; + /// Perform parameter expansion and test if the output equals the zero-terminated parameter list /// supplied. /// @@ -1515,7 +1528,7 @@ static bool expand_test(const wchar_t *in, expand_flags_t flags, ...) { wchar_t *arg; parse_error_list_t errors; - if (expand_string(in, &output, flags, &errors) == EXPAND_ERROR) { + if (expand_string(in, &output, flags, pwd_environment_t{}, &errors) == EXPAND_ERROR) { if (errors.empty()) { err(L"Bug: Parse error reported but no error text found."); } else { @@ -2173,7 +2186,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, &comps); + parser_t::expand_argument_list(str, 0, null_environment_t{}, &comps); for (completion_list_t::const_iterator it = comps.begin(), end = comps.end(); it != end; ++it) { argv.push_back(it->completion); } @@ -2341,6 +2354,9 @@ static void test_complete() { maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override { + if (key == L"PWD") { + return env_var_t{wgetcwd(), 0}; + } return {}; } }; @@ -2604,7 +2620,7 @@ static void test_completion_insertions() { static void perform_one_autosuggestion_cd_test(const wcstring &command, const wcstring &expected, long line) { std::vector comps; - complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, env_vars_snapshot_t{}); + complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, pwd_environment_t{}); bool expects_error = (expected == L""); diff --git a/src/highlight.cpp b/src/highlight.cpp index 211d7c0d7..7b8e4ddff 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -243,11 +243,11 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d // appropriately for commands. If we succeed, return true. static bool plain_statement_get_expanded_command(const wcstring &src, tnode_t stmt, - wcstring *out_cmd) { + const environment_t &vars, wcstring *out_cmd) { // Get the command. Try expanding it. If we cannot, it's an error. maybe_t cmd = command_for_plain_statement(stmt, src); if (!cmd) return false; - expand_error_t err = expand_to_command_and_args(*cmd, out_cmd, nullptr); + expand_error_t err = expand_to_command_and_args(*cmd, vars, out_cmd, nullptr); return err == EXPAND_OK || err == EXPAND_WILDCARD_MATCH; } @@ -310,8 +310,8 @@ static bool has_expand_reserved(const wcstring &str) { // Parse a command line. Return by reference the last command, and the last argument to that command // (as a string), if any. This is used by autosuggestions. -static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expanded_command, - wcstring *out_last_arg) { +static bool autosuggest_parse_command(const wcstring &buff, const environment_t &vars, + wcstring *out_expanded_command, wcstring *out_last_arg) { // Parse the buffer. parse_node_tree_t parse_tree; parse_tree_from_string(buff, @@ -321,7 +321,7 @@ static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expand // Find the last statement. auto last_statement = parse_tree.find_last_node(); if (last_statement && - plain_statement_get_expanded_command(buff, last_statement, out_expanded_command)) { + plain_statement_get_expanded_command(buff, last_statement, vars, out_expanded_command)) { // Find the last argument. If we don't get one, return an invalid node. if (auto last_arg = parse_tree.find_last_node(last_statement)) { *out_last_arg = last_arg.get_source(buff); @@ -341,11 +341,11 @@ bool autosuggest_validate_from_history(const history_item_t &item, // Parse the string. wcstring parsed_command; wcstring cd_dir; - if (!autosuggest_parse_command(item.str(), &parsed_command, &cd_dir)) return false; + if (!autosuggest_parse_command(item.str(), vars, &parsed_command, &cd_dir)) return false; if (parsed_command == L"cd" && !cd_dir.empty()) { // We can possibly handle this specially. - if (expand_one(cd_dir, EXPAND_SKIP_CMDSUBST)) { + if (expand_one(cd_dir, EXPAND_SKIP_CMDSUBST, vars)) { handled = true; bool is_help = string_prefixes_string(cd_dir, L"--help") || string_prefixes_string(cd_dir, L"-h"); @@ -823,7 +823,7 @@ bool highlighter_t::is_cd(tnode_t stmt) const { bool cmd_is_cd = false; if (this->io_ok && stmt.has_source()) { wcstring cmd_str; - if (plain_statement_get_expanded_command(this->buff, stmt, &cmd_str)) { + if (plain_statement_get_expanded_command(this->buff, stmt, vars, &cmd_str)) { cmd_is_cd = (cmd_str == L"cd"); } } @@ -840,7 +840,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)) { + if (expand_one(param, 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 && @@ -883,7 +883,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)) { + } else if (!expand_one(target, EXPAND_SKIP_CMDSUBST, vars)) { // Could not be expanded. target_is_valid = false; } else { @@ -1117,7 +1117,8 @@ const highlighter_t::color_array_t &highlighter_t::highlight() { wcstring expanded_cmd; // Check to see if the command is valid. // Try expanding it. If we cannot, it's an error. - bool expanded = plain_statement_get_expanded_command(buff, stmt, &expanded_cmd); + bool expanded = + plain_statement_get_expanded_command(buff, stmt, vars, &expanded_cmd); if (expanded && !has_expand_reserved(expanded_cmd)) { is_valid_cmd = command_is_valid(expanded_cmd, decoration, working_directory, vars); @@ -1200,7 +1201,7 @@ void highlight_shell(const wcstring &buff, std::vector &color, UNUSED(error); // Do something sucky and get the current working directory on this background thread. This // should really be passed in. - const wcstring working_directory = env_get_pwd_slash(); + const wcstring working_directory = vars.get_pwd_slash(); // Highlight it! highlighter_t highlighter(buff, pos, vars, working_directory, true /* can do IO */); @@ -1212,7 +1213,7 @@ void highlight_shell_no_io(const wcstring &buff, std::vector & UNUSED(error); // Do something sucky and get the current working directory on this background thread. This // should really be passed in. - const wcstring working_directory = env_get_pwd_slash(); + const wcstring working_directory = vars.get_pwd_slash(); // Highlight it! highlighter_t highlighter(buff, pos, vars, working_directory, false /* no IO allowed */); diff --git a/src/history.cpp b/src/history.cpp index 8ee7974e8..f978914ed 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -1918,7 +1918,8 @@ static bool string_could_be_path(const wcstring &potential_path) { return true; } -void history_t::add_pending_with_file_detection(const wcstring &str) { +void history_t::add_pending_with_file_detection(const wcstring &str, + const wcstring &working_dir_slash) { ASSERT_IS_MAIN_THREAD(); // Find all arguments that look like they could be file paths. @@ -1967,8 +1968,7 @@ void history_t::add_pending_with_file_detection(const wcstring &str) { // Check for which paths are valid on a background thread, // then on the main thread update our history item - const wcstring wd = env_get_pwd_slash(); - iothread_perform([=]() { return valid_paths(potential_paths, wd); }, + iothread_perform([=]() { return valid_paths(potential_paths, working_dir_slash); }, [=](path_list_t validated_paths) { this->set_valid_file_paths(validated_paths, identifier); this->enable_automatic_saving(); diff --git a/src/history.h b/src/history.h index 6419cdcc9..4b50554b6 100644 --- a/src/history.h +++ b/src/history.h @@ -229,7 +229,7 @@ class history_t { // Add a new pending history item to the end, and then begin file detection on the items to // determine which arguments are paths - void add_pending_with_file_detection(const wcstring &str); + void add_pending_with_file_detection(const wcstring &str, const wcstring &working_dir_slash); // Resolves any pending history items, so that they may be returned in history searches. void resolve_pending(); diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index a56fe4815..c0a98e5cb 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -121,13 +121,14 @@ tnode_t parse_execution_context_t::infinite_recursive_statem // are not infinite recursion. In particular that is what enables 'wrapper functions'. tnode_t statement = first_job.child<0>(); tnode_t continuation = first_job.child<1>(); + const null_environment_t nullenv{}; while (statement) { tnode_t plain_statement = statement.try_get_child() .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, NULL) && + if (cmd && expand_one(*cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES, nullenv) && cmd == forbidden_function_name) { // This is it. infinite_recursive_statement = plain_statement; @@ -371,7 +372,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, NULL)) { + if (!expand_one(for_var_name, 0, parser->vars())) { report_error(var_name_node, FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str()); return parse_execution_errored; } @@ -438,8 +439,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; - int expand_ret = - expand_string(switch_value, &switch_values_expanded, EXPAND_NO_DESCRIPTIONS, &errors); + int expand_ret = expand_string(switch_value, &switch_values_expanded, EXPAND_NO_DESCRIPTIONS, + parser->vars(), &errors); parse_error_offset_source_start(&errors, switch_value_n.source_range()->start); switch (expand_ret) { @@ -722,7 +723,8 @@ parse_execution_result_t parse_execution_context_t::expand_command( wcstring_list_t args; // Expand the string to produce completions, and report errors. - expand_error_t expand_err = expand_to_command_and_args(unexp_cmd, out_cmd, out_args, &errors); + expand_error_t expand_err = + expand_to_command_and_args(unexp_cmd, parser->vars(), out_cmd, out_args, &errors); if (expand_err == EXPAND_ERROR) { proc_set_last_status(STATUS_ILLEGAL_CMD); return report_errors(errors); @@ -813,7 +815,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( // Ok, no arguments or redirections; check to see if the command is a directory. wcstring implicit_cd_path; use_implicit_cd = - path_can_be_implicit_cd(cmd, env_get_pwd_slash(), &implicit_cd_path); + path_can_be_implicit_cd(cmd, parser->vars().get_pwd_slash(), &implicit_cd_path); } } @@ -883,7 +885,8 @@ parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes( // Expand this string. parse_error_list_t errors; arg_expanded.clear(); - int expand_ret = expand_string(arg_str, &arg_expanded, EXPAND_NO_DESCRIPTIONS, &errors); + int expand_ret = + expand_string(arg_str, &arg_expanded, EXPAND_NO_DESCRIPTIONS, parser->vars(), &errors); parse_error_offset_source_start(&errors, arg_node.source_range()->start); switch (expand_ret) { case EXPAND_ERROR: { @@ -931,7 +934,8 @@ bool parse_execution_context_t::determine_io_chain(tnode_tsrc, &source_fd, &target); // PCA: I can't justify this EXPAND_SKIP_VARIABLES flag. It was like this when I got here. - bool target_expanded = expand_one(target, no_exec ? EXPAND_SKIP_VARIABLES : 0, NULL); + bool target_expanded = + expand_one(target, no_exec ? EXPAND_SKIP_VARIABLES : 0, 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 83d4fa109..6d4d3f5a7 100644 --- a/src/parse_util.cpp +++ b/src/parse_util.cpp @@ -19,6 +19,7 @@ #include "future_feature_flags.h" #include "parse_constants.h" #include "parse_util.h" +#include "parser.h" #include "tnode.h" #include "tokenizer.h" #include "util.h" @@ -1131,8 +1132,8 @@ static bool detect_errors_in_plain_statement(const wcstring &buff_src, if (maybe_t unexp_command = command_for_plain_statement(pst, buff_src)) { wcstring command; // Check that we can expand the command. - if (expand_to_command_and_args(*unexp_command, &command, nullptr, parse_errors) == - EXPAND_ERROR) { + if (expand_to_command_and_args(*unexp_command, null_environment_t{}, &command, nullptr, + parse_errors) == EXPAND_ERROR) { errored = true; } diff --git a/src/parser.cpp b/src/parser.cpp index 65fcffcd4..d70ad32e3 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -314,6 +314,7 @@ void parser_t::emit_profiling(const char *path) const { } void parser_t::expand_argument_list(const wcstring &arg_list_src, expand_flags_t eflags, + const environment_t &vars, std::vector *output_arg_list) { assert(output_arg_list != NULL); @@ -330,7 +331,8 @@ void parser_t::expand_argument_list(const wcstring &arg_list_src, expand_flags_t tnode_t arg_list(&tree, &tree.at(0)); while (auto arg = arg_list.next_in_list()) { const wcstring arg_src = arg.get_source(arg_list_src); - if (expand_string(arg_src, output_arg_list, eflags, NULL) == EXPAND_ERROR) { + if (expand_string(arg_src, output_arg_list, eflags, vars, NULL /* errors */) == + EXPAND_ERROR) { break; // failed to expand a string } } diff --git a/src/parser.h b/src/parser.h index deef053ac..33f28f8f0 100644 --- a/src/parser.h +++ b/src/parser.h @@ -247,7 +247,7 @@ class parser_t { /// \param flags Some expand flags to use /// \param output List to insert output into static void expand_argument_list(const wcstring &arg_src, expand_flags_t flags, - std::vector *output); + const environment_t &vars, std::vector *output); /// Returns a string describing the current parser position in the format 'FILENAME (line /// LINE_NUMBER): LINE'. Example: diff --git a/src/reader.cpp b/src/reader.cpp index 394f5d406..a994d9dfe 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1277,10 +1277,10 @@ struct autosuggestion_result_t { // on a background thread) to determine the autosuggestion static std::function get_autosuggestion_performer( const wcstring &search_string, size_t cursor_pos, history_t *history) { + const auto &parser_vars = parser_t::principal_parser().vars(); const unsigned int generation_count = read_generation_count(); - const wcstring working_directory(env_get_pwd_slash()); - env_vars_snapshot_t vars(parser_t::principal_parser().vars(), - env_vars_snapshot_t::highlighting_keys); + const wcstring working_directory = parser_vars.get_pwd_slash(); + env_vars_snapshot_t vars(vars, env_vars_snapshot_t::highlighting_keys); // TODO: suspicious use of 'history' here // This is safe because histories are immortal, but perhaps // this should use shared_ptr @@ -2473,6 +2473,8 @@ const wchar_t *reader_readline(int nchars) { s_reset(&data->screen, screen_reset_abandon_line); reader_repaint(); + const auto &vars = parser_t::principal_parser().vars(); + // Get the current terminal modes. These will be restored when the function returns. if (tcgetattr(STDIN_FILENO, &old_modes) == -1 && errno == EIO) redirect_tty_output(); // Set the new modes. @@ -2685,8 +2687,7 @@ const wchar_t *reader_readline(int nchars) { complete_flags_t complete_flags = COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_DESCRIPTIONS | COMPLETION_REQUEST_FUZZY_MATCH; - data->complete_func(buffcpy, &comp, complete_flags, - parser_t::principal_parser().vars()); + data->complete_func(buffcpy, &comp, complete_flags, vars); // Munge our completions. completions_sort_and_prioritize(&comp); @@ -2893,7 +2894,8 @@ const wchar_t *reader_readline(int nchars) { // space. const editable_line_t *el = &data->command_line; if (data->history != NULL && !el->empty() && el->text.at(0) != L' ') { - data->history->add_pending_with_file_detection(el->text); + data->history->add_pending_with_file_detection(el->text, + vars.get_pwd_slash()); } finished = 1; update_buff_pos(&data->command_line, data->command_line.size());