From 895c2c4af077ac3ead37bdecc644f194dcb9be1d Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 9 Sep 2018 01:36:21 -0700 Subject: [PATCH 01/24] Minor cleanup of parser interface --- src/fish.cpp | 4 ++-- src/fish_tests.cpp | 6 +++--- src/parser.cpp | 16 ++++++---------- src/parser.h | 17 +++++++---------- src/proc.cpp | 7 +++++++ src/proc.h | 6 ++++++ 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/fish.cpp b/src/fish.cpp index c213321d9..b9857e8af 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -184,9 +184,9 @@ static void source_config_in_directory(const wcstring &dir) { const wcstring cmd = L"builtin source " + escaped_pathname; parser_t &parser = parser_t::principal_parser(); - parser.set_is_within_fish_initialization(true); + set_is_within_fish_initialization(true); parser.eval(cmd, io_chain_t(), TOP); - parser.set_is_within_fish_initialization(false); + set_is_within_fish_initialization(false); } /// Parse init files. exec_path is the path of fish executable as determined by argv[0]. diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 4270fd699..c35e3c551 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2142,7 +2142,7 @@ static void test_is_potential_path() { /// Test the 'test' builtin. int builtin_test(parser_t &parser, io_streams_t &streams, wchar_t **argv); static bool run_one_test_test(int expected, wcstring_list_t &lst, bool bracket) { - parser_t parser; + parser_t &parser = parser_t::principal_parser(); size_t i, count = lst.size(); wchar_t **argv = new wchar_t *[count + 3]; argv[0] = (wchar_t *)(bracket ? L"[" : L"test"); @@ -2185,7 +2185,7 @@ static bool run_test_test(int expected, const wcstring &str) { static void test_test_brackets() { // Ensure [ knows it needs a ]. - parser_t parser; + parser_t &parser = parser_t::principal_parser(); io_streams_t streams(0); null_terminated_array_t args; @@ -4389,7 +4389,7 @@ static void test_pcre2_escape() { int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv); static void run_one_string_test(const wchar_t *const *argv, int expected_rc, const wchar_t *expected_out) { - parser_t parser; + parser_t &parser = parser_t::principal_parser(); io_streams_t streams(0); streams.stdin_is_directly_redirected = false; // read from argv instead of stdin int rc = builtin_string(parser, streams, const_cast(argv)); diff --git a/src/parser.cpp b/src/parser.cpp index 4196c3dd8..fd33f58fd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -100,26 +100,22 @@ static wcstring user_presentable_path(const wcstring &path) { return replace_home_directory_with_tilde(path); } -parser_t::parser_t() : cancellation_requested(false), is_within_fish_initialization(false) {} +parser_t::parser_t() = default; // Out of line destructor to enable forward declaration of parse_execution_context_t parser_t::~parser_t() = default; -static parser_t s_principal_parser; +parser_t parser_t::principal; parser_t &parser_t::principal_parser() { ASSERT_IS_MAIN_THREAD(); - return s_principal_parser; -} - -void parser_t::set_is_within_fish_initialization(bool flag) { - is_within_fish_initialization = flag; + return principal; } void parser_t::skip_all_blocks() { // Tell all blocks to skip. // This may be called from a signal handler! - s_principal_parser.cancellation_requested = true; + principal.cancellation_requested = true; } // Given a new-allocated block, push it onto our block stack, acquiring ownership @@ -399,7 +395,7 @@ void parser_t::stack_trace_internal(size_t block_idx, wcstring *buff) const { if (file) { append_format(*buff, _(L"\tcalled on line %d of file %ls\n"), b->src_lineno, user_presentable_path(file).c_str()); - } else if (is_within_fish_initialization) { + } else if (is_within_fish_initialization()) { append_format(*buff, _(L"\tcalled during startup\n")); } else { append_format(*buff, _(L"\tcalled on standard input\n")); @@ -536,7 +532,7 @@ wcstring parser_t::current_line() { if (file) { append_format(prefix, _(L"%ls (line %d): "), user_presentable_path(file).c_str(), lineno); - } else if (is_within_fish_initialization) { + } else if (is_within_fish_initialization()) { append_format(prefix, L"%ls (line %d): ", _(L"Startup"), lineno); } else { append_format(prefix, L"%ls (line %d): ", _(L"Standard input"), lineno); diff --git a/src/parser.h b/src/parser.h index 5449db970..bfeea4406 100644 --- a/src/parser.h +++ b/src/parser.h @@ -162,9 +162,7 @@ class parser_t { private: /// Indication that we should skip all blocks. - volatile sig_atomic_t cancellation_requested; - /// Indicates that we are within the process of initializing fish. - bool is_within_fish_initialization; + volatile sig_atomic_t cancellation_requested = false; /// The current execution context. std::unique_ptr execution_context; /// List of called functions, used to help prevent infinite recursion. @@ -206,6 +204,12 @@ class parser_t { /// Helper for push_block() void push_block_int(block_t *b); + /// Create a parser. + parser_t(); + + /// The main parser. + static parser_t principal; + public: /// Get the "principal" parser, whatever that is. static parser_t &principal_parser(); @@ -214,9 +218,6 @@ class parser_t { /// from signal handlers! static void skip_all_blocks(); - /// Create a parser. - parser_t(); - /// Global event blocks. event_blockage_list_t global_event_blocks; @@ -270,10 +271,6 @@ class parser_t { /// Get the list of jobs. job_list_t &job_list() { return my_job_list; } - // Hackish. In order to correctly report the origin of code with no associated file, we need to - // know whether it's run during initialization or not. - void set_is_within_fish_initialization(bool flag); - /// Pushes a new block created with the given arguments /// Returns a pointer to the block. The pointer is valid /// until the call to pop_block() diff --git a/src/proc.cpp b/src/proc.cpp index 69494c8dc..6f692fb50 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #if HAVE_TERM_H #include @@ -1289,3 +1290,9 @@ void hup_background_jobs() { } } } + +static std::atomic s_is_within_fish_initialization{false}; + +void set_is_within_fish_initialization(bool flag) { s_is_within_fish_initialization.store(flag); } + +bool is_within_fish_initialization() { return s_is_within_fish_initialization.load(); } diff --git a/src/proc.h b/src/proc.h index 2f14c89da..b48e5d215 100644 --- a/src/proc.h +++ b/src/proc.h @@ -399,6 +399,12 @@ int proc_format_status(int status); /// Wait for any process finishing. pid_t proc_wait_any(); +/// Set and get whether we are in initialization. +// Hackish. In order to correctly report the origin of code with no associated file, we need to +// know whether it's run during initialization or not. +void set_is_within_fish_initialization(bool flag); +bool is_within_fish_initialization(); + /// Terminate all background jobs void hup_background_jobs(); From 391af6af0ccd229d3a050ccdf13ba4260dc71990 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 9 Sep 2018 02:25:51 -0700 Subject: [PATCH 02/24] Introduce class environment_t This will be used as a base class for variable snapshots and variable stacks. --- src/autoload.cpp | 2 +- src/autoload.h | 4 ++-- src/env.cpp | 11 +++++++---- src/env.h | 17 +++++++++++++++-- src/function.cpp | 2 +- src/function.h | 2 +- src/highlight.cpp | 14 +++++++------- src/highlight.h | 8 ++++---- src/path.cpp | 6 +++--- src/path.h | 6 +++--- src/proc.cpp | 2 +- src/reader.h | 4 ++-- 12 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/autoload.cpp b/src/autoload.cpp index 6d577fc46..8614731e3 100644 --- a/src/autoload.cpp +++ b/src/autoload.cpp @@ -96,7 +96,7 @@ int autoload_t::load(const wcstring &cmd, bool reload) { return res; } -bool autoload_t::can_load(const wcstring &cmd, const env_vars_snapshot_t &vars) { +bool autoload_t::can_load(const wcstring &cmd, const environment_t &vars) { auto path_var = vars.get(env_var_name); if (path_var.missing_or_empty()) return false; diff --git a/src/autoload.h b/src/autoload.h index ceb703c53..249b49c34 100644 --- a/src/autoload.h +++ b/src/autoload.h @@ -40,7 +40,7 @@ struct autoload_function_t { bool is_placeholder; }; -class env_vars_snapshot_t; +class environment_t; /// Class representing a path from which we can autoload and the autoloaded contents. class autoload_t : public lru_cache_t { @@ -88,7 +88,7 @@ class autoload_t : public lru_cache_t { int unload(const wcstring &cmd); /// Check whether the given command could be loaded, but do not load it. - bool can_load(const wcstring &cmd, const env_vars_snapshot_t &vars); + bool can_load(const wcstring &cmd, const environment_t &vars); /// Invalidates all entries. Uesd when the underlying path variable changes. void invalidate(); diff --git a/src/env.cpp b/src/env.cpp index 148e0eb71..7a9b18e80 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1589,6 +1589,8 @@ void env_set_argv(const wchar_t *const *argv) { } } +environment_t::~environment_t() = default; + env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; @@ -1596,12 +1598,13 @@ env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) { key.assign(keys[i]); const auto var = env_get(key); if (var) { - vars[key] = *var; + vars[key] = std::move(*var); } } } -env_vars_snapshot_t::env_vars_snapshot_t() {} +env_vars_snapshot_t::env_vars_snapshot_t() = default; +env_vars_snapshot_t::~env_vars_snapshot_t() = default; // The "current" variables are not a snapshot at all, but instead trampoline to env_get, etc. // We identify the current snapshot based on pointer values. @@ -1610,10 +1613,10 @@ const env_vars_snapshot_t &env_vars_snapshot_t::current() { return sCurrentSnaps bool env_vars_snapshot_t::is_current() const { return this == &sCurrentSnapshot; } -maybe_t env_vars_snapshot_t::get(const wcstring &key) const { +maybe_t env_vars_snapshot_t::get(const wcstring &key, env_mode_flags_t mode) const { // If we represent the current state, bounce to env_get. if (this->is_current()) { - return env_get(key); + return env_get(key, mode); } auto iter = vars.find(key); if (iter == vars.end()) return none(); diff --git a/src/env.h b/src/env.h index 30fb4ab0d..26b186c1f 100644 --- a/src/env.h +++ b/src/env.h @@ -131,6 +131,17 @@ class env_var_t { bool operator!=(const env_var_t &rhs) const { return ! (*this == rhs); } }; +/// An environment is read-only access to variable values. +class environment_t { + protected: + environment_t() = default; + + public: + virtual maybe_t get(const wcstring &key, + env_mode_flags_t mode = ENV_DEFAULT) const = 0; + virtual ~environment_t(); +}; + /// Gets the variable with the specified name, or none() if it does not exist. maybe_t env_get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT); @@ -180,7 +191,7 @@ wcstring env_get_pwd_slash(); /// Update the read_byte_limit variable. void env_set_read_limit(); -class env_vars_snapshot_t { +class env_vars_snapshot_t : public environment_t { std::map vars; bool is_current() const; @@ -191,7 +202,9 @@ class env_vars_snapshot_t { env_vars_snapshot_t(const wchar_t *const *keys); env_vars_snapshot_t(); - maybe_t get(const wcstring &key) const; + ~env_vars_snapshot_t(); + + maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; // Returns the fake snapshot representing the live variables array. static const env_vars_snapshot_t ¤t(); diff --git a/src/function.cpp b/src/function.cpp index 30593812a..f709b08f0 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -198,7 +198,7 @@ void function_load(const wcstring &cmd) { } } -int function_exists_no_autoload(const wcstring &cmd, const env_vars_snapshot_t &vars) { +int function_exists_no_autoload(const wcstring &cmd, const environment_t &vars) { if (parser_keywords_is_reserved(cmd)) return 0; scoped_rlock locker(functions_lock); return loaded_functions.find(cmd) != loaded_functions.end() || diff --git a/src/function.h b/src/function.h index 7520e19c7..d823bdeeb 100644 --- a/src/function.h +++ b/src/function.h @@ -77,7 +77,7 @@ int function_exists(const wcstring &name); void function_load(const wcstring &name); /// Returns true if the function with the name name exists, without triggering autoload. -int function_exists_no_autoload(const wcstring &name, const env_vars_snapshot_t &vars); +int function_exists_no_autoload(const wcstring &name, const environment_t &vars); /// Returns all function names. /// diff --git a/src/highlight.cpp b/src/highlight.cpp index a6612b68f..211d7c0d7 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -333,7 +333,7 @@ static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expand bool autosuggest_validate_from_history(const history_item_t &item, const wcstring &working_directory, - const env_vars_snapshot_t &vars) { + const environment_t &vars) { ASSERT_IS_BACKGROUND_THREAD(); bool handled = false, suggestionOK = false; @@ -667,7 +667,7 @@ class highlighter_t { // Cursor position. const size_t cursor_pos; // Environment variables. Again, a reference member variable! - const env_vars_snapshot_t &vars; + const environment_t &vars; // Whether it's OK to do I/O. const bool io_ok; // Working directory. @@ -698,7 +698,7 @@ class highlighter_t { public: // Constructor - highlighter_t(const wcstring &str, size_t pos, const env_vars_snapshot_t &ev, wcstring wd, + highlighter_t(const wcstring &str, size_t pos, const environment_t &ev, wcstring wd, bool can_do_io) : buff(str), cursor_pos(pos), @@ -985,7 +985,7 @@ void highlighter_t::color_children(const parse_node_t &parent, parse_token_type_ /// Determine if a command is valid. static bool command_is_valid(const wcstring &cmd, enum parse_statement_decoration_t decoration, - const wcstring &working_directory, const env_vars_snapshot_t &vars) { + const wcstring &working_directory, const environment_t &vars) { // Determine which types we check, based on the decoration. bool builtin_ok = true, function_ok = true, abbreviation_ok = true, command_ok = true, implicit_cd_ok = true; @@ -1196,7 +1196,7 @@ const highlighter_t::color_array_t &highlighter_t::highlight() { } void highlight_shell(const wcstring &buff, std::vector &color, size_t pos, - wcstring_list_t *error, const env_vars_snapshot_t &vars) { + wcstring_list_t *error, const environment_t &vars) { UNUSED(error); // Do something sucky and get the current working directory on this background thread. This // should really be passed in. @@ -1208,7 +1208,7 @@ void highlight_shell(const wcstring &buff, std::vector &color, } void highlight_shell_no_io(const wcstring &buff, std::vector &color, size_t pos, - wcstring_list_t *error, const env_vars_snapshot_t &vars) { + wcstring_list_t *error, const environment_t &vars) { UNUSED(error); // Do something sucky and get the current working directory on this background thread. This // should really be passed in. @@ -1306,7 +1306,7 @@ static void highlight_universal_internal(const wcstring &buffstr, } void highlight_universal(const wcstring &buff, std::vector &color, size_t pos, - wcstring_list_t *error, const env_vars_snapshot_t &vars) { + wcstring_list_t *error, const environment_t &vars) { UNUSED(error); UNUSED(vars); assert(buff.size() == color.size()); diff --git a/src/highlight.h b/src/highlight.h index e4e255f27..bfac1d3f2 100644 --- a/src/highlight.h +++ b/src/highlight.h @@ -75,12 +75,12 @@ class history_item_t; /// \param error a list in which a description of each error will be inserted. May be 0, in whcich /// case no error descriptions will be generated. void highlight_shell(const wcstring &buffstr, std::vector &color, size_t pos, - wcstring_list_t *error, const env_vars_snapshot_t &vars); + wcstring_list_t *error, const environment_t &vars); /// Perform a non-blocking shell highlighting. The function will not do any I/O that may block. As a /// result, invalid commands may not be detected, etc. void highlight_shell_no_io(const wcstring &buffstr, std::vector &color, - size_t pos, wcstring_list_t *error, const env_vars_snapshot_t &vars); + size_t pos, wcstring_list_t *error, const environment_t &vars); /// Perform syntax highlighting for the text in buff. Matching quotes and paranthesis are /// highlighted. The result is stored in the color array as a color_code from the HIGHLIGHT_ enum @@ -93,7 +93,7 @@ void highlight_shell_no_io(const wcstring &buffstr, std::vector &color, size_t pos, - wcstring_list_t *error, const env_vars_snapshot_t &vars); + wcstring_list_t *error, const environment_t &vars); /// Translate from HIGHLIGHT_* to FISH_COLOR_* according to environment variables. Defaults to /// FISH_COLOR_NORMAL. @@ -109,7 +109,7 @@ rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background); /// reference whether the suggestion is valid or not. bool autosuggest_validate_from_history(const history_item_t &item, const wcstring &working_directory, - const env_vars_snapshot_t &vars); + const environment_t &vars); // Tests whether the specified string cpath is the prefix of anything we could cd to. directories is // a list of possible parent directories (typically either the working directory, or the cdpath). diff --git a/src/path.cpp b/src/path.cpp index 2b1b027dc..a4a8e1c3f 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -115,7 +115,7 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, return false; } -bool path_get_path(const wcstring &cmd, wcstring *out_path, const env_vars_snapshot_t &vars) { +bool path_get_path(const wcstring &cmd, wcstring *out_path, const environment_t &vars) { return path_get_path_core(cmd, out_path, vars.get(L"PATH")); } @@ -158,7 +158,7 @@ wcstring_list_t path_get_paths(const wcstring &cmd) { } bool path_get_cdpath(const wcstring &dir, wcstring *out, const wcstring &wd, - const env_vars_snapshot_t &env_vars) { + const environment_t &env_vars) { int err = ENOENT; if (dir.empty()) return false; @@ -216,7 +216,7 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wcstring &wd, } bool path_can_be_implicit_cd(const wcstring &path, const wcstring &wd, wcstring *out_path, - const env_vars_snapshot_t &vars) { + const environment_t &vars) { wcstring exp_path = path; expand_tilde(exp_path); diff --git a/src/path.h b/src/path.h index bb166c812..e6c2174aa 100644 --- a/src/path.h +++ b/src/path.h @@ -40,7 +40,7 @@ bool path_get_data(wcstring &path); /// false if the command can not be found else true. The result /// should be freed with free(). bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL, - const env_vars_snapshot_t &vars = env_vars_snapshot_t::current()); + const environment_t &vars = env_vars_snapshot_t::current()); /// Return all the paths that match the given command. wcstring_list_t path_get_paths(const wcstring &cmd); @@ -61,13 +61,13 @@ wcstring_list_t path_get_paths(const wcstring &cmd); /// \return 0 if the command can not be found, the path of the command otherwise. The path should be /// free'd with free(). bool path_get_cdpath(const wcstring &dir, wcstring *out_or_NULL, const wcstring &wd, - const env_vars_snapshot_t &vars = env_vars_snapshot_t::current()); + const environment_t &vars = env_vars_snapshot_t::current()); /// Returns whether the path can be used for an implicit cd command; if so, also returns the path by /// reference (if desired). This requires it to start with one of the allowed prefixes (., .., ~) /// and resolve to a directory. bool path_can_be_implicit_cd(const wcstring &path, const wcstring &wd, wcstring *out_path = NULL, - const env_vars_snapshot_t &vars = env_vars_snapshot_t::current()); + const environment_t &vars = env_vars_snapshot_t::current()); /// Remove double slashes and trailing slashes from a path, e.g. transform foo//bar/ into foo/bar. /// The string is modified in-place. diff --git a/src/proc.cpp b/src/proc.cpp index 6f692fb50..f8794b82e 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -6,6 +6,7 @@ // IWYU pragma: no_include <__bit_reference> #include "config.h" +#include #include #include #include @@ -13,7 +14,6 @@ #include #include #include -#include #if HAVE_TERM_H #include diff --git a/src/reader.h b/src/reader.h index 2d4338b76..1ecca836c 100644 --- a/src/reader.h +++ b/src/reader.h @@ -14,8 +14,8 @@ #include "highlight.h" #include "parse_constants.h" +class environment_t; class history_t; -class env_vars_snapshot_t; class io_chain_t; /// Helper class for storing a command line. @@ -160,7 +160,7 @@ void reader_set_complete_function(complete_function_t); /// The type of a highlight function. typedef void (*highlight_function_t)(const wcstring &, std::vector &, size_t, - wcstring_list_t *, const env_vars_snapshot_t &vars); + wcstring_list_t *, const environment_t &vars); /// Function type for testing if a string is valid for the reader to return. using test_function_t = parser_test_error_bits_t (*)(const wcstring &); From 8d7cae63ff1b0d76949c9fbcf6bba0a371e2eb64 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 9 Sep 2018 12:17:31 -0700 Subject: [PATCH 03/24] Introduce env_stack_t This will instance environment variable stacks. --- src/env.cpp | 142 ++++++++++++++++++++++++++++++--------------- src/env.h | 77 +++++++++++++++++++++++- src/fish_tests.cpp | 4 +- 3 files changed, 170 insertions(+), 53 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 7a9b18e80..854bd6765 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -141,8 +141,6 @@ class env_node_t { static std::mutex env_lock; -static bool local_scope_exports(const env_node_t *n); - // A class wrapping up a variable stack // Currently there is only one variable stack in fish, // but we can imagine having separate (linked) stacks @@ -163,7 +161,7 @@ struct var_stack_t { void mark_changed_exported() { has_changed_exported = true; } void update_export_array_if_necessary(); - var_stack_t() : top(new env_node_t(false)) { this->global_env = this->top.get(); } + var_stack_t() : top(new env_node_t(false)), global_env(top.get()) {} // Pushes a new node onto our stack // Optionally creates a new scope for the node @@ -180,6 +178,10 @@ struct var_stack_t { // Returns the scope used for unspecified scopes. An unspecified scope is either the topmost // shadowing scope, or the global scope if none. This implements the default behavior of `set`. env_node_t *resolve_unspecified_scope(); + + private: + bool local_scope_exports(const env_node_t *n) const; + void get_exported(const env_node_t *n, var_table_t &h) const; }; void var_stack_t::push(bool new_scope) { @@ -272,11 +274,12 @@ env_node_t *var_stack_t::resolve_unspecified_scope() { return node ? node : this->global_env; } -// Get the global variable stack -static var_stack_t &vars_stack() { - static var_stack_t global_stack; - return global_stack; -} +env_stack_t::env_stack_t() : vars_(make_unique()) {} + +// Get the variable stack +var_stack_t &env_stack_t::vars_stack() { return *vars_; } + +const var_stack_t &env_stack_t::vars_stack() const { return *vars_; } /// Universal variables global instance. Initialized in env_init. static env_universal_t *s_universal_variables = NULL; @@ -616,11 +619,11 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key) { /// Universal variable callback function. This function makes sure the proper events are triggered /// when an event occurs. -static void universal_callback(const callback_data_t &cb) { +static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; react_to_variable_change(op, cb.key); - vars_stack().mark_changed_exported(); + stack->mark_changed_exported(); event_t ev = event_t::variable_event(cb.key); ev.arguments.push_back(L"VARIABLE"); @@ -650,7 +653,7 @@ static void env_set_termsize() { } /// Update the PWD variable directory from the result of getcwd(). -void env_set_pwd_from_getcwd() { +void env_stack_t::set_pwd_from_getcwd() { wcstring cwd = wgetcwd(); if (cwd.empty()) { debug(0, @@ -662,7 +665,7 @@ void env_set_pwd_from_getcwd() { /// Allow the user to override the limit on how much data the `read` command will process. /// This is primarily for testing but could be used by users in special situations. -void env_set_read_limit() { +void env_stack_t::set_read_limit() { auto read_byte_limit_var = env_get(L"fish_read_limit"); if (!read_byte_limit_var.missing_or_empty()) { size_t limit = fish_wcstoull(read_byte_limit_var->as_string().c_str()); @@ -674,7 +677,9 @@ void env_set_read_limit() { } } -wcstring env_get_pwd_slash() { +void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported(); } + +wcstring env_stack_t::get_pwd_slash() { // Return "/" if PWD is missing. // See https://github.com/fish-shell/fish-shell/issues/5080 auto pwd_var = env_get(L"PWD"); @@ -716,13 +721,13 @@ void misc_init() { } } -static void env_universal_callbacks(const callback_data_list_t &callbacks) { +static void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks) { for (const callback_data_t &cb : callbacks) { - universal_callback(cb); + universal_callback(stack, cb); } } -void env_universal_barrier() { +void env_stack_t::universal_barrier() { ASSERT_IS_MAIN_THREAD(); if (!uvars()) return; @@ -732,7 +737,7 @@ void env_universal_barrier() { universal_notifier_t::default_notifier().post_notification(); } - env_universal_callbacks(callbacks); + env_universal_callbacks(this, callbacks); } static void handle_fish_term_change(const wcstring &op, const wcstring &var_name) { @@ -976,7 +981,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // initialize the PWD variable if necessary // Note we may inherit a virtual PWD that doesn't match what getcwd would return; respect that. if (env_get(L"PWD").missing_or_empty()) { - env_set_pwd_from_getcwd(); + env_stack_t::principal().set_pwd_from_getcwd(); } env_set_termsize(); // initialize the terminal size variables env_set_read_limit(); // initialize the read_byte_limit @@ -1000,7 +1005,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { s_universal_variables = new env_universal_t(L""); callback_data_list_t callbacks; s_universal_variables->initialize(callbacks); - env_universal_callbacks(callbacks); + env_universal_callbacks(&env_stack_t::principal(), callbacks); // Now that the global scope is fully initialized, add a toplevel local scope. This same local // scope will persist throughout the lifetime of the fish process, and it will ensure that `set @@ -1010,7 +1015,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { /// Search all visible scopes in order for the specified key. Return the first scope in which it was /// found. -static env_node_t *env_get_node(const wcstring &key) { +env_node_t *env_stack_t::get_node(const wcstring &key) { env_node_t *env = vars_stack().top.get(); while (env != NULL) { if (env->find_entry(key)) break; @@ -1033,7 +1038,7 @@ static int set_umask(const wcstring_list_t &list_val) { /// Set a universal variable, inheriting as applicable from the given old variable. static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, - env_mode_flags_t input_var_mode) { + env_mode_flags_t input_var_mode, env_stack_t *stack) { ASSERT_IS_MAIN_THREAD(); if (!uvars()) return; env_mode_flags_t var_mode = input_var_mode; @@ -1068,7 +1073,7 @@ static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, uvars()->set(key, new_var); env_universal_barrier(); if (new_var.exports() || (oldvar && oldvar->exports())) { - vars_stack().mark_changed_exported(); + stack->mark_changed_exported(); } } @@ -1088,8 +1093,7 @@ static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, /// * ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric /// variables set from the local or universal scopes, or set as exported. /// * ENV_INVALID, the variable value was invalid. This applies only to special variables. -static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode, - wcstring_list_t val) { +int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mode, wcstring_list_t val) { ASSERT_IS_MAIN_THREAD(); env_mode_flags_t var_mode = input_var_mode; bool has_changed_old = vars_stack().has_changed_exported; @@ -1100,7 +1104,7 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode wcstring val_canonical = val.front(); path_make_canonical(val_canonical); if (val.front() != val_canonical) { - return env_set_internal(key, var_mode, {val_canonical}); + return set_internal(key, var_mode, {val_canonical}); } } @@ -1121,12 +1125,12 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode if (var_mode & ENV_UNIVERSAL) { if (uvars()) { - env_set_internal_universal(key, std::move(val), var_mode); + env_set_internal_universal(key, std::move(val), var_mode, this); } } else { // Determine the node. bool has_changed_new = false; - env_node_t *preexisting_node = env_get_node(key); + env_node_t *preexisting_node = get_node(key); maybe_t preexisting_flags{}; if (preexisting_node != NULL) { var_table_t::const_iterator result = preexisting_node->env.find(key); @@ -1157,7 +1161,7 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode } if (uvars() && uvars()->get(key)) { // Modifying an existing universal variable. - env_set_internal_universal(key, std::move(val), var_mode); + env_set_internal_universal(key, std::move(val), var_mode, this); done = true; } else { // New variable with unspecified scope @@ -1229,26 +1233,26 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode } /// Sets the variable with the specified name to the given values. -int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { - return env_set_internal(key, mode, std::move(vals)); +int env_stack_t::set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { + return set_internal(key, mode, std::move(vals)); } /// Sets the variable with the specified name to a single value. -int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { +int env_stack_t::set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { wcstring_list_t vals; vals.push_back(std::move(val)); - return env_set_internal(key, mode, std::move(vals)); + return set_internal(key, mode, std::move(vals)); } /// Sets the variable with the specified name without any (i.e., zero) values. -int env_set_empty(const wcstring &key, env_mode_flags_t mode) { - return env_set_internal(key, mode, {}); +int env_stack_t::set_empty(const wcstring &key, env_mode_flags_t mode) { + return set_internal(key, mode, {}); } /// Attempt to remove/free the specified key/value pair from the specified map. /// /// \return zero if the variable was not found, non-zero otherwise -static bool try_remove(env_node_t *n, const wchar_t *key, int var_mode) { +bool env_stack_t::try_remove(env_node_t *n, const wchar_t *key, int var_mode) { if (n == NULL) { return false; } @@ -1256,7 +1260,7 @@ static bool try_remove(env_node_t *n, const wchar_t *key, int var_mode) { var_table_t::iterator result = n->env.find(key); if (result != n->env.end()) { if (result->second.exports()) { - vars_stack().mark_changed_exported(); + mark_changed_exported(); } n->env.erase(result); return true; @@ -1272,7 +1276,7 @@ static bool try_remove(env_node_t *n, const wchar_t *key, int var_mode) { return try_remove(n->next.get(), key, var_mode); } -int env_remove(const wcstring &key, int var_mode) { +int env_stack_t::remove(const wcstring &key, int var_mode) { ASSERT_IS_MAIN_THREAD(); env_node_t *first_node; int erased = 0; @@ -1345,7 +1349,7 @@ env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { return result; } -maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { +maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const { const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL); const bool search_local = !has_scope || (mode & ENV_LOCAL); const bool search_global = !has_scope || (mode & ENV_GLOBAL); @@ -1383,7 +1387,7 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { if (search_local || search_global) { scoped_lock locker(env_lock); // lock around a local region - env_node_t *env = search_local ? vars_stack().top.get() : vars_stack().global_env; + const env_node_t *env = search_local ? vars_stack().top.get() : vars_stack().global_env; while (env != NULL) { if (env == vars_stack().global_env && !search_global) { @@ -1422,11 +1426,46 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { return none(); } +/// Legacy versions. +maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { + return env_stack_t::principal().get(key, mode); +} + +int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { + return env_stack_t::principal().set(key, mode, std::move(vals)); +} + +int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { + return env_stack_t::principal().set_one(key, mode, std::move(val)); +} + +int env_set_empty(const wcstring &key, env_mode_flags_t mode) { + return env_stack_t::principal().set_empty(key, mode); +} + +int env_remove(const wcstring &key, int mode) { return env_stack_t::principal().remove(key, mode); } + +void env_push(bool new_scope) { env_stack_t::principal().push(new_scope); } + +void env_pop() { env_stack_t::principal().pop(); } + +void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } + +const char *const *env_export_arr() { return env_stack_t::principal().export_arr(); } + +void env_set_argv(const wchar_t *const *argv) { return env_stack_t::principal().set_argv(argv); } + +wcstring_list_t env_get_names(int flags) { return env_stack_t::principal().get_names(flags); } + +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 /// variable. -static bool local_scope_exports(const env_node_t *n) { +bool var_stack_t::local_scope_exports(const env_node_t *n) const { assert(n != NULL); - if (n == vars_stack().global_env) return false; + if (n == global_env) return false; if (n->exportv) return true; @@ -1435,9 +1474,9 @@ static bool local_scope_exports(const env_node_t *n) { return local_scope_exports(n->next.get()); } -void env_push(bool new_scope) { vars_stack().push(new_scope); } +void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); } -void env_pop() { vars_stack().pop(); } +void env_stack_t::pop() { vars_stack().pop(); } /// Function used with to insert keys of one table into a set::set. static void add_key_to_string_set(const var_table_t &envs, std::set *str_set, @@ -1453,7 +1492,7 @@ static void add_key_to_string_set(const var_table_t &envs, std::set *s } } -wcstring_list_t env_get_names(int flags) { +wcstring_list_t env_stack_t::get_names(int flags) { scoped_lock locker(env_lock); wcstring_list_t result; @@ -1499,11 +1538,11 @@ wcstring_list_t env_get_names(int flags) { } /// Get list of all exported variables. -static void get_exported(const env_node_t *n, var_table_t &h) { +void var_stack_t::get_exported(const env_node_t *n, var_table_t &h) const { if (!n) return; if (n->new_scope) { - get_exported(vars_stack().global_env, h); + get_exported(global_env, h); } else { get_exported(n->next.get(), h); } @@ -1569,14 +1608,14 @@ void var_stack_t::update_export_array_if_necessary() { has_changed_exported = false; } -const char *const *env_export_arr() { +const char *const *env_stack_t::export_arr() { ASSERT_IS_MAIN_THREAD(); ASSERT_IS_NOT_FORKED_CHILD(); vars_stack().update_export_array_if_necessary(); return vars_stack().export_array.get(); } -void env_set_argv(const wchar_t *const *argv) { +void env_stack_t::set_argv(const wchar_t *const *argv) { if (argv && *argv) { wcstring_list_t list; for (auto arg = argv; *arg; arg++) { @@ -1591,6 +1630,13 @@ void env_set_argv(const wchar_t *const *argv) { environment_t::~environment_t() = default; +env_stack_t::~env_stack_t() = default; + +env_stack_t &env_stack_t::principal() { + static env_stack_t s_principal; + return s_principal; +} + env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; diff --git a/src/env.h b/src/env.h index 26b186c1f..73b21a9a8 100644 --- a/src/env.h +++ b/src/env.h @@ -182,15 +182,86 @@ void env_set_argv(const wchar_t *const *argv); /// Returns all variable names. wcstring_list_t env_get_names(int flags); -/// Update the PWD variable based on the result of getcwd. -void env_set_pwd_from_getcwd(); - /// Returns the PWD with a terminating slash. wcstring env_get_pwd_slash(); /// Update the read_byte_limit variable. void env_set_read_limit(); +/// A environment stack of scopes. This is the main class that tracks fish variables. +struct var_stack_t; +class env_node_t; +class env_stack_t : public environment_t { + std::unique_ptr vars_; + + int set_internal(const wcstring &key, env_mode_flags_t var_mode, wcstring_list_t val); + + bool try_remove(env_node_t *n, const wchar_t *key, int var_mode); + env_node_t *get_node(const wcstring &key); + + var_stack_t &vars_stack(); + const var_stack_t &vars_stack() const; + + env_stack_t(); + ~env_stack_t() override; + + public: + /// Gets the variable with the specified name, or none() if it does not exist. + maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; + + /// Sets the variable with the specified name to the given values. + int set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals); + + /// Sets the variable with the specified name to a single value. + int set_one(const wcstring &key, env_mode_flags_t mode, wcstring val); + + /// Sets the variable with the specified name to no values. + int set_empty(const wcstring &key, env_mode_flags_t mode); + + /// Update the PWD variable based on the result of getcwd. + void set_pwd_from_getcwd(); + + /// Remove environment variable. + /// + /// \param key The name of the variable to remove + /// \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If + /// this is a user request, read-only variables can not be removed. The mode may also specify + /// the scope of the variable that should be erased. + /// + /// \return zero if the variable existed, and non-zero if the variable did not exist + int remove(const wcstring &key, int mode); + + /// Push the variable stack. Used for implementing local variables for functions and for-loops. + void push(bool new_scope); + + /// Pop the variable stack. Used for implementing local variables for functions and for-loops. + void pop(); + + /// Synchronizes all universal variable changes: writes everything out, reads stuff in. + void universal_barrier(); + + /// Returns an array containing all exported variables in a format suitable for execv + const char *const *export_arr(); + + /// Returns all variable names. + wcstring_list_t get_names(int flags); + + /// 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(); + + /// Mark that exported variables have changed. + void mark_changed_exported(); + + // Compatibility hack; access the "environment stack" from back when there was just one. + static env_stack_t &principal(); +}; + class env_vars_snapshot_t : public environment_t { std::map vars; bool is_current() const; diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index c35e3c551..0f1a430c0 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -174,7 +174,7 @@ static bool pushd(const char *path) { return false; } - env_set_pwd_from_getcwd(); + env_stack_t::principal().set_pwd_from_getcwd(); return true; } @@ -184,7 +184,7 @@ static void popd() { err(L"chdir(\"%s\") from popd() failed: errno = %d", old_cwd.c_str(), errno); } pushed_dirs.pop_back(); - env_set_pwd_from_getcwd(); + env_stack_t::principal().set_pwd_from_getcwd(); } // The odd formulation of these macros is to avoid "multiple unary operator" warnings from oclint From bba66a3ecccdb43fe3dfa94a187011574fb70efe Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 01:17:37 -0700 Subject: [PATCH 04/24] Use shared_ptr instead of unique_ptr in environments This prepares for multiple environment stacks sharing the same parent. --- src/env.cpp | 119 +++++++++++++++++++++++++--------------------------- src/env.h | 5 ++- 2 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 854bd6765..b984be483 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -118,9 +118,6 @@ static void init_curses(); // Struct representing one level in the function variable stack. // Only our variable stack should create and destroy these class env_node_t { - friend struct var_stack_t; - env_node_t(bool is_new_scope) : new_scope(is_new_scope) {} - public: /// Variable table. var_table_t env; @@ -132,13 +129,17 @@ class env_node_t { /// or does it redefine any variables to not be exported? bool exportv = false; /// Pointer to next level. - std::unique_ptr next; + std::shared_ptr next; + + env_node_t(bool is_new_scope) : new_scope(is_new_scope) {} maybe_t find_entry(const wcstring &key); bool contains_any_of(const wcstring_list_t &vars) const; }; +using env_node_ref_t = std::shared_ptr; + static std::mutex env_lock; // A class wrapping up a variable stack @@ -147,11 +148,10 @@ static std::mutex env_lock; // if we introduce multiple threads of execution struct var_stack_t { // Top node on the function stack. - std::unique_ptr top = NULL; + env_node_ref_t top; - // Bottom node on the function stack - // This is an observer pointer - env_node_t *global_env = NULL; + // Bottom node on the function stack. + env_node_ref_t global_env; // Exported variable array used by execv. null_terminated_array_t export_array; @@ -161,7 +161,7 @@ struct var_stack_t { void mark_changed_exported() { has_changed_exported = true; } void update_export_array_if_necessary(); - var_stack_t() : top(new env_node_t(false)), global_env(top.get()) {} + var_stack_t() : top(globals()), global_env(globals()) {} // Pushes a new node onto our stack // Optionally creates a new scope for the node @@ -170,25 +170,32 @@ struct var_stack_t { // Pops the top node if it's not global void pop(); - // Returns the next scope to search for a given node, respecting the new_scope lag - // Returns NULL if we're done - env_node_t *next_scope_to_search(env_node_t *node); - const env_node_t *next_scope_to_search(const env_node_t *node) const; + // Returns the next scope to search for a given node, respecting the new_scope flag. + // Returns an empty pointer if we're done. + env_node_ref_t next_scope_to_search(const env_node_ref_t &node) const; // Returns the scope used for unspecified scopes. An unspecified scope is either the topmost // shadowing scope, or the global scope if none. This implements the default behavior of `set`. - env_node_t *resolve_unspecified_scope(); + env_node_ref_t resolve_unspecified_scope(); private: - bool local_scope_exports(const env_node_t *n) const; + bool local_scope_exports(const env_node_ref_t &n) const; void get_exported(const env_node_t *n, var_table_t &h) const; + + /// Returns the global variable set. + static env_node_ref_t globals(); }; +env_node_ref_t var_stack_t::globals() { + static env_node_ref_t s_globals{std::make_shared(false)}; + return s_globals; +} + void var_stack_t::push(bool new_scope) { - std::unique_ptr node(new env_node_t(new_scope)); + auto node = std::make_shared(new_scope); // Copy local-exported variables. - auto top_node = top.get(); + auto top_node = top; // Only if we introduce a new shadowing scope; i.e. not if it's just `begin; end` or // "--no-scope-shadowing". if (new_scope && top_node != this->global_env) { @@ -197,9 +204,9 @@ void var_stack_t::push(bool new_scope) { } } - node->next = std::move(this->top); - this->top = std::move(node); - if (new_scope && local_scope_exports(this->top.get())) { + node->next = this->top; + this->top = node; + if (new_scope && local_scope_exports(this->top)) { this->mark_changed_exported(); } } @@ -214,7 +221,7 @@ bool env_node_t::contains_any_of(const wcstring_list_t &vars) const { void var_stack_t::pop() { // Don't pop the top-most, global, level. - if (top.get() == this->global_env) { + if (top == this->global_env) { debug(0, _(L"Tried to pop empty environment stack.")); sanity_lose(); return; @@ -224,20 +231,14 @@ void var_stack_t::pop() { bool curses_changed = top->contains_any_of(curses_variables); if (top->new_scope) { //!OCLINT(collapsible if statements) - if (top->exportv || local_scope_exports(top->next.get())) { + if (top->exportv || local_scope_exports(top->next)) { this->mark_changed_exported(); } } - // Actually do the pop! Move the top pointer into a local variable, then replace the top pointer - // with the next pointer afterwards we should have a node with no next pointer, and our top - // should be non-null. - std::unique_ptr old_top = std::move(this->top); - this->top = std::move(old_top->next); - old_top->next.reset(); - assert(this->top && old_top && !old_top->next); - assert(this->top != NULL); - + // Actually do the pop! + env_node_ref_t old_top = this->top; + this->top = old_top->next; for (const auto &entry_pair : old_top->env) { const env_var_t &var = entry_pair.second; if (var.exports()) { @@ -250,26 +251,18 @@ void var_stack_t::pop() { if (curses_changed) init_curses(); } -const env_node_t *var_stack_t::next_scope_to_search(const env_node_t *node) const { +env_node_ref_t var_stack_t::next_scope_to_search(const env_node_ref_t &node) const { assert(node != NULL); if (node == this->global_env) { - return NULL; + return nullptr; } - return node->new_scope ? this->global_env : node->next.get(); + return node->new_scope ? this->global_env : node->next; } -env_node_t *var_stack_t::next_scope_to_search(env_node_t *node) { - assert(node != NULL); - if (node == this->global_env) { - return NULL; - } - return node->new_scope ? this->global_env : node->next.get(); -} - -env_node_t *var_stack_t::resolve_unspecified_scope() { - env_node_t *node = this->top.get(); +env_node_ref_t var_stack_t::resolve_unspecified_scope() { + env_node_ref_t node = this->top; while (node && !node->new_scope) { - node = node->next.get(); + node = node->next; } return node ? node : this->global_env; } @@ -1015,9 +1008,9 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { /// Search all visible scopes in order for the specified key. Return the first scope in which it was /// found. -env_node_t *env_stack_t::get_node(const wcstring &key) { - env_node_t *env = vars_stack().top.get(); - while (env != NULL) { +env_node_ref_t env_stack_t::get_node(const wcstring &key) { + env_node_ref_t env = vars_stack().top; + while (env) { if (env->find_entry(key)) break; env = vars_stack().next_scope_to_search(env); } @@ -1130,7 +1123,7 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo } else { // Determine the node. bool has_changed_new = false; - env_node_t *preexisting_node = get_node(key); + env_node_ref_t preexisting_node = get_node(key); maybe_t preexisting_flags{}; if (preexisting_node != NULL) { var_table_t::const_iterator result = preexisting_node->env.find(key); @@ -1141,11 +1134,11 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo } } - env_node_t *node = NULL; + env_node_ref_t node = nullptr; if (var_mode & ENV_GLOBAL) { node = vars_stack().global_env; } else if (var_mode & ENV_LOCAL) { - node = vars_stack().top.get(); + node = vars_stack().top; } else if (preexisting_node != NULL) { node = preexisting_node; if ((var_mode & (ENV_EXPORT | ENV_UNEXPORT)) == 0) { @@ -1252,8 +1245,8 @@ int env_stack_t::set_empty(const wcstring &key, env_mode_flags_t mode) { /// Attempt to remove/free the specified key/value pair from the specified map. /// /// \return zero if the variable was not found, non-zero otherwise -bool env_stack_t::try_remove(env_node_t *n, const wchar_t *key, int var_mode) { - if (n == NULL) { +bool env_stack_t::try_remove(env_node_ref_t n, const wchar_t *key, int var_mode) { + if (n == nullptr) { return false; } @@ -1273,19 +1266,19 @@ bool env_stack_t::try_remove(env_node_t *n, const wchar_t *key, int var_mode) { if (n->new_scope) { return try_remove(vars_stack().global_env, key, var_mode); } - return try_remove(n->next.get(), key, var_mode); + return try_remove(n->next, key, var_mode); } int env_stack_t::remove(const wcstring &key, int var_mode) { ASSERT_IS_MAIN_THREAD(); - env_node_t *first_node; + env_node_ref_t first_node{}; int erased = 0; if ((var_mode & ENV_USER) && is_read_only(key)) { return ENV_SCOPE; } - first_node = vars_stack().top.get(); + first_node = vars_stack().top; if (!(var_mode & ENV_UNIVERSAL)) { if (var_mode & ENV_GLOBAL) { @@ -1387,7 +1380,7 @@ maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) if (search_local || search_global) { scoped_lock locker(env_lock); // lock around a local region - const env_node_t *env = search_local ? vars_stack().top.get() : vars_stack().global_env; + env_node_ref_t env = search_local ? vars_stack().top : vars_stack().global_env; while (env != NULL) { if (env == vars_stack().global_env && !search_global) { @@ -1463,15 +1456,15 @@ 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 /// variable. -bool var_stack_t::local_scope_exports(const env_node_t *n) const { - assert(n != NULL); +bool var_stack_t::local_scope_exports(const env_node_ref_t &n) const { + assert(n != nullptr); if (n == global_env) return false; if (n->exportv) return true; if (n->new_scope) return false; - return local_scope_exports(n->next.get()); + return local_scope_exports(n->next); } void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); } @@ -1501,7 +1494,7 @@ wcstring_list_t env_stack_t::get_names(int flags) { int show_global = flags & ENV_GLOBAL; int show_universal = flags & ENV_UNIVERSAL; - const env_node_t *n = vars_stack().top.get(); + env_node_ref_t n = vars_stack().top; const bool show_exported = (flags & ENV_EXPORT) || !(flags & ENV_UNEXPORT); const bool show_unexported = (flags & ENV_UNEXPORT) || !(flags & ENV_EXPORT); @@ -1517,7 +1510,7 @@ wcstring_list_t env_stack_t::get_names(int flags) { if (n->new_scope) break; else - n = n->next.get(); + n = n->next; } } @@ -1542,7 +1535,7 @@ void var_stack_t::get_exported(const env_node_t *n, var_table_t &h) const { if (!n) return; if (n->new_scope) { - get_exported(global_env, h); + get_exported(global_env.get(), h); } else { get_exported(n->next.get(), h); } diff --git a/src/env.h b/src/env.h index 73b21a9a8..7317b3e77 100644 --- a/src/env.h +++ b/src/env.h @@ -192,12 +192,13 @@ void env_set_read_limit(); struct var_stack_t; class env_node_t; class env_stack_t : public environment_t { + friend class parser_t; std::unique_ptr vars_; int set_internal(const wcstring &key, env_mode_flags_t var_mode, wcstring_list_t val); - bool try_remove(env_node_t *n, const wchar_t *key, int var_mode); - env_node_t *get_node(const wcstring &key); + bool try_remove(std::shared_ptr n, const wchar_t *key, int var_mode); + std::shared_ptr get_node(const wcstring &key); var_stack_t &vars_stack(); const var_stack_t &vars_stack() const; From a47f6859bdcf3b344c465940ae5c8fd628a02450 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 01:17:57 -0700 Subject: [PATCH 05/24] Equip parser_t with a variable stack Prepares to eliminate env_get and env_set by accessing variables through a parser. --- src/parser.cpp | 2 +- src/parser.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index fd33f58fd..ce79a0c04 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -100,7 +100,7 @@ static wcstring user_presentable_path(const wcstring &path) { return replace_home_directory_with_tilde(path); } -parser_t::parser_t() = default; +parser_t::parser_t() : variables(env_stack_t::principal()) {} // Out of line destructor to enable forward declaration of parse_execution_context_t parser_t::~parser_t() = default; diff --git a/src/parser.h b/src/parser.h index bfeea4406..deef053ac 100644 --- a/src/parser.h +++ b/src/parser.h @@ -173,7 +173,8 @@ class parser_t { std::vector> block_stack; /// The 'depth' of the fish call stack. int eval_level = -1; - + /// Set of variables for the parser. + env_stack_t &variables; #if 0 // TODO: Lint says this isn't used (which is true). Should this be removed? /// Gets a description of the block stack, for debugging. @@ -271,6 +272,10 @@ class parser_t { /// Get the list of jobs. job_list_t &job_list() { return my_job_list; } + /// Get the variables. + env_stack_t &vars() { return variables; } + const env_stack_t &vars() const { return variables; } + /// Pushes a new block created with the given arguments /// Returns a pointer to the block. The pointer is valid /// until the call to pop_block() From e6872b83b0344d066259525f4c384c6c0b852074 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 01:26:30 -0700 Subject: [PATCH 06/24] Eliminate global env_export_arr() This assumes the set of exported variables is a global property; but we want it to be a local property. --- src/env.cpp | 4 +--- src/env.h | 3 --- src/exec.cpp | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index b984be483..4e82ac56c 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1444,8 +1444,6 @@ void env_pop() { env_stack_t::principal().pop(); } void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } -const char *const *env_export_arr() { return env_stack_t::principal().export_arr(); } - void env_set_argv(const wchar_t *const *argv) { return env_stack_t::principal().set_argv(argv); } wcstring_list_t env_get_names(int flags) { return env_stack_t::principal().get_names(flags); } @@ -1580,7 +1578,7 @@ void var_stack_t::update_export_array_if_necessary() { return; } - debug(4, L"env_export_arr() recalc"); + debug(4, L"export_arr() recalc"); var_table_t vals; get_exported(this->top.get(), vals); diff --git a/src/env.h b/src/env.h index 7317b3e77..148144f45 100644 --- a/src/env.h +++ b/src/env.h @@ -173,9 +173,6 @@ void env_pop(); /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); -/// Returns an array containing all exported variables in a format suitable for execv -const char *const *env_export_arr(); - /// Sets up argv as the given null terminated array of strings. void env_set_argv(const wchar_t *const *argv); diff --git a/src/exec.cpp b/src/exec.cpp index 0bbd8721b..5b13d7b23 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -202,14 +202,14 @@ static void safe_launch_process(process_t *p, const char *actual_cmd, const char /// This function is similar to launch_process, except it is not called after a fork (i.e. it only /// calls exec) and therefore it can allocate memory. -static void launch_process_nofork(process_t *p) { +static void launch_process_nofork(env_stack_t &vars, process_t *p) { ASSERT_IS_MAIN_THREAD(); ASSERT_IS_NOT_FORKED_CHILD(); null_terminated_array_t argv_array; convert_wide_array_to_narrow(p->get_argv_array(), &argv_array); - const char *const *envv = env_export_arr(); + const char *const *envv = vars.export_arr(); char *actual_cmd = wcs2str(p->actual_cmd); // Ensure the terminal modes are what they were before we changed them. @@ -358,7 +358,7 @@ static bool can_use_posix_spawn_for_job(const std::shared_ptr &job, return result; } -void internal_exec(job_t *j, const io_chain_t &&all_ios) { +void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &all_ios) { // Do a regular launch - but without forking first... // setup_child_process makes sure signals are properly set up. @@ -380,7 +380,7 @@ void internal_exec(job_t *j, const io_chain_t &&all_ios) { env_set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, shlvl_str); // launch_process _never_ returns. - launch_process_nofork(j->processes.front().get()); + launch_process_nofork(vars, j->processes.front().get()); } else { j->set_flag(job_flag_t::CONSTRUCTED, true); j->processes.front()->completed = 1; @@ -648,8 +648,8 @@ static bool handle_builtin_output(const std::shared_ptr &j, process_t *p, /// Executes an external command. /// \return true on success, false if there is an exec error. -static bool exec_external_command(const std::shared_ptr &j, process_t *p, - const io_chain_t &proc_io_chain) { +static bool exec_external_command(env_stack_t &vars, const std::shared_ptr &j, + process_t *p, const io_chain_t &proc_io_chain) { assert(p->type == EXTERNAL && "Process is not external"); // Get argv and envv before we fork. null_terminated_array_t argv_array; @@ -663,7 +663,7 @@ static bool exec_external_command(const std::shared_ptr &j, process_t *p, make_fd_blocking(STDIN_FILENO); const char *const *argv = argv_array.get(); - const char *const *envv = env_export_arr(); + const char *const *envv = vars.export_arr(); std::string actual_cmd_str = wcs2string(p->actual_cmd); const char *actual_cmd = actual_cmd_str.c_str(); @@ -919,7 +919,7 @@ static bool exec_process_in_job(parser_t &parser, process_t *p, std::shared_ptr< set_proc_had_barrier(true); env_universal_barrier(); } - env_export_arr(); + parser.vars().export_arr(); } // Set up fds that will be used in the pipe. @@ -975,7 +975,7 @@ static bool exec_process_in_job(parser_t &parser, process_t *p, std::shared_ptr< } case EXTERNAL: { - if (!exec_external_command(j, p, process_net_io_chain)) { + if (!exec_external_command(parser.vars(), j, p, process_net_io_chain)) { return false; } break; @@ -1028,7 +1028,7 @@ bool exec_job(parser_t &parser, shared_ptr j) { } if (j->processes.front()->type == INTERNAL_EXEC) { - internal_exec(j.get(), std::move(all_ios)); + internal_exec(parser.vars(), j.get(), all_ios); DIE("this should be unreachable"); } From e6b13c6bac331cfab6df1e46638f1512b123c4cf Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 09:58:02 -0700 Subject: [PATCH 07/24] Begin to thread environments explicitly through completions --- src/builtin_complete.cpp | 2 +- src/builtin_set.cpp | 7 ++-- src/complete.cpp | 34 +++++++++---------- src/complete.h | 4 ++- src/env.cpp | 31 +++++++++-------- src/env.h | 19 ++++++----- src/fish_tests.cpp | 72 +++++++++++++++++++++------------------- src/reader.cpp | 11 +++--- src/reader.h | 8 ++--- 9 files changed, 95 insertions(+), 93 deletions(-) diff --git a/src/builtin_complete.cpp b/src/builtin_complete.cpp index d40a4e303..2a01fcd26 100644 --- a/src/builtin_complete.cpp +++ b/src/builtin_complete.cpp @@ -318,7 +318,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { recursion_level++; std::vector comp; - complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT); + complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT, parser.vars()); for (size_t i = 0; i < comp.size(); i++) { const completion_t &next = comp.at(i); diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index e78fc4ee7..6ab298a0b 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -22,12 +22,11 @@ #include "expand.h" #include "fallback.h" // IWYU pragma: keep #include "io.h" +#include "parser.h" #include "proc.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep -class parser_t; - struct set_cmd_opts_t { bool print_help = false; bool show = false; @@ -475,7 +474,7 @@ static int builtin_set_list(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, UNUSED(parser); bool names_only = opts.list; - wcstring_list_t names = env_get_names(compute_scope(opts)); + wcstring_list_t names = parser.vars().get_names(compute_scope(opts)); sort(names.begin(), names.end()); for (size_t i = 0; i < names.size(); i++) { @@ -592,7 +591,7 @@ static int builtin_set_show(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, UNUSED(opts); if (argc == 0) { // show all vars - wcstring_list_t names = env_get_names(ENV_USER); + wcstring_list_t names = parser.vars().get_names(ENV_USER); sort(names.begin(), names.end()); for (auto it : names) { show_scope(it.c_str(), ENV_LOCAL, streams); diff --git a/src/complete.cpp b/src/complete.cpp index 440d5f3c9..fbe453264 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -81,11 +81,11 @@ void complete_set_variable_names(const wcstring_list_t *names) { s_override_variable_names = names; } -static inline wcstring_list_t complete_get_variable_names() { +static inline wcstring_list_t complete_get_variable_names(const environment_t &vars) { if (s_override_variable_names != NULL) { return *s_override_variable_names; } - return env_get_names(0); + return vars.get_names(0); } /// Struct describing a completion option entry. @@ -301,8 +301,16 @@ void completions_sort_and_prioritize(std::vector *comps, /// Class representing an attempt to compute completions. class completer_t { + /// Environment inside which we are completing. + const environment_t &vars; + + /// The command to complete. const wcstring cmd; + + /// Flags associated with the completion request. const completion_request_flags_t flags; + + /// The output cmopletions. std::vector completions; /// Table of completions conditions that have already been tested and the corresponding test @@ -372,7 +380,8 @@ class completer_t { void mark_completions_duplicating_arguments(const wcstring &prefix, const arg_list_t &args); public: - completer_t(wcstring c, completion_request_flags_t f) : cmd(std::move(c)), flags(f) {} + completer_t(const environment_t &vars, wcstring c, completion_request_flags_t f) + : vars(vars), cmd(std::move(c)), flags(f) {} void perform(); @@ -886,22 +895,11 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt, } }; - // This was originally written as a static variable protected by a mutex that is updated only if - // `scmd.size() == 1` to prevent too many lookups, but it turns out that this is mainly only - // called when the user explicitly presses after a command, so the overhead of the - // additional env lookup should be negligible. - env_vars_snapshot_t completion_snapshot; - // debug(0, L"\nThinking about looking up completions for %ls\n", cmd.c_str()); bool head_exists = builtin_exists(cmd); // Only reload environment variables if builtin_exists returned false, as an optimization if (head_exists == false) { - run_on_main_thread([&completion_snapshot]() { - completion_snapshot = std::move( - env_vars_snapshot_t((wchar_t const *const[]){L"fish_function_path", nullptr})); - }); - - head_exists = function_exists_no_autoload(cmd, completion_snapshot); + head_exists = function_exists_no_autoload(cmd.c_str(), vars); // While it may seem like first testing `path_get_path` before resorting to an env lookup // may be faster, path_get_path can potentially do a lot of FS/IO access, so env.get() + // function_exists() should still be faster. @@ -1127,7 +1125,7 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) { size_t varlen = str.length() - start_offset; bool res = false; - const wcstring_list_t names = complete_get_variable_names(); + const wcstring_list_t names = complete_get_variable_names(vars); for (size_t i = 0; i < names.size(); i++) { const wcstring &env_name = names.at(i); @@ -1578,14 +1576,14 @@ void completer_t::perform() { } void complete(const wcstring &cmd_with_subcmds, std::vector *out_comps, - completion_request_flags_t flags) { + completion_request_flags_t flags, const environment_t &vars) { // Determine the innermost subcommand. const wchar_t *cmdsubst_begin, *cmdsubst_end; parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin, &cmdsubst_end); assert(cmdsubst_begin != NULL && cmdsubst_end != NULL && cmdsubst_end >= cmdsubst_begin); wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin); - completer_t completer(std::move(cmd), flags); + completer_t completer(vars, std::move(cmd), flags); completer.perform(); *out_comps = completer.acquire_completions(); } diff --git a/src/complete.h b/src/complete.h index 2349d1ff1..0abd68208 100644 --- a/src/complete.h +++ b/src/complete.h @@ -30,6 +30,8 @@ /// Character that separates the completion and description on programmable completions. #define PROG_COMPLETE_SEP L'\t' +class environment_t; + enum { /// Do not insert space afterwards if this is the only completion. (The default is to try insert /// a space). @@ -172,7 +174,7 @@ void complete_remove_all(const wcstring &cmd, bool cmd_is_path); /// Find all completions of the command cmd, insert them into out. void complete(const wcstring &cmd, std::vector *out_comps, - completion_request_flags_t flags); + completion_request_flags_t flags, const environment_t &vars); /// Return a list of all current completions. wcstring complete_print(); diff --git a/src/env.cpp b/src/env.cpp index 4e82ac56c..50341b890 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1446,8 +1446,6 @@ void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } void env_set_argv(const wchar_t *const *argv) { return env_stack_t::principal().set_argv(argv); } -wcstring_list_t env_get_names(int flags) { return env_stack_t::principal().get_names(flags); } - 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(); } @@ -1483,7 +1481,7 @@ static void add_key_to_string_set(const var_table_t &envs, std::set *s } } -wcstring_list_t env_stack_t::get_names(int flags) { +wcstring_list_t env_stack_t::get_names(int flags) const { scoped_lock locker(env_lock); wcstring_list_t result; @@ -1620,7 +1618,6 @@ void env_stack_t::set_argv(const wchar_t *const *argv) { } environment_t::~environment_t() = default; - env_stack_t::~env_stack_t() = default; env_stack_t &env_stack_t::principal() { @@ -1628,27 +1625,28 @@ env_stack_t &env_stack_t::principal() { return s_principal; } -env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) { +env_vars_snapshot_t::env_vars_snapshot_t(const environment_t &source, const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; for (size_t i = 0; keys[i]; i++) { key.assign(keys[i]); - const auto var = env_get(key); + const auto var = source.get(key); if (var) { vars[key] = std::move(*var); } } + names = source.get_names(0); } -env_vars_snapshot_t::env_vars_snapshot_t() = default; env_vars_snapshot_t::~env_vars_snapshot_t() = default; // The "current" variables are not a snapshot at all, but instead trampoline to env_get, etc. // We identify the current snapshot based on pointer values. -static const env_vars_snapshot_t sCurrentSnapshot; -const env_vars_snapshot_t &env_vars_snapshot_t::current() { return sCurrentSnapshot; } +// This is an ugly thing that has to go away. +const env_vars_snapshot_t env_vars_snapshot_t::s_current; +const env_vars_snapshot_t &env_vars_snapshot_t::current() { return s_current; } -bool env_vars_snapshot_t::is_current() const { return this == &sCurrentSnapshot; } +bool env_vars_snapshot_t::is_current() const { return this == &s_current; } maybe_t env_vars_snapshot_t::get(const wcstring &key, env_mode_flags_t mode) const { // If we represent the current state, bounce to env_get. @@ -1660,6 +1658,13 @@ maybe_t env_vars_snapshot_t::get(const wcstring &key, env_mode_flags_ return iter->second; } +wcstring_list_t env_vars_snapshot_t::get_names(int flags) const { return names; } + +const wchar_t *const env_vars_snapshot_t::highlighting_keys[] = {L"PATH", L"CDPATH", + L"fish_function_path", NULL}; + +const wchar_t *const env_vars_snapshot_t::completing_keys[] = {L"PATH", L"CDPATH", + L"fish_function_path", NULL}; #if defined(__APPLE__) || defined(__CYGWIN__) static int check_runtime_path(const char *path) { @@ -1722,9 +1727,3 @@ wcstring env_get_runtime_path() { } return result; } - - -const wchar_t *const env_vars_snapshot_t::highlighting_keys[] = {L"PATH", L"CDPATH", - L"fish_function_path", NULL}; - -const wchar_t *const env_vars_snapshot_t::completing_keys[] = {L"PATH", L"CDPATH", NULL}; diff --git a/src/env.h b/src/env.h index 148144f45..379175880 100644 --- a/src/env.h +++ b/src/env.h @@ -139,6 +139,7 @@ class environment_t { public: virtual maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const = 0; + virtual wcstring_list_t get_names(int flags) const = 0; virtual ~environment_t(); }; @@ -176,9 +177,6 @@ void env_universal_barrier(); /// Sets up argv as the given null terminated array of strings. void env_set_argv(const wchar_t *const *argv); -/// Returns all variable names. -wcstring_list_t env_get_names(int flags); - /// Returns the PWD with a terminating slash. wcstring env_get_pwd_slash(); @@ -242,7 +240,7 @@ class env_stack_t : public environment_t { const char *const *export_arr(); /// Returns all variable names. - wcstring_list_t get_names(int flags); + wcstring_list_t get_names(int flags) const override; /// Sets up argv as the given null terminated array of strings. void set_argv(const wchar_t *const *argv); @@ -262,19 +260,22 @@ class env_stack_t : public environment_t { class env_vars_snapshot_t : public environment_t { std::map vars; + wcstring_list_t names; bool is_current() const; + static const env_vars_snapshot_t s_current; + public: + env_vars_snapshot_t() = default; env_vars_snapshot_t(const env_vars_snapshot_t &) = default; env_vars_snapshot_t &operator=(const env_vars_snapshot_t &) = default; - - env_vars_snapshot_t(const wchar_t *const *keys); - env_vars_snapshot_t(); - - ~env_vars_snapshot_t(); + env_vars_snapshot_t(const environment_t &source, const wchar_t *const *keys); + ~env_vars_snapshot_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; + // Returns the fake snapshot representing the live variables array. static const env_vars_snapshot_t ¤t(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 0f1a430c0..82497060b 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2338,8 +2338,9 @@ static void test_complete() { const wcstring_list_t names(name_strs, name_strs + count); std::vector completions; complete_set_variable_names(&names); + env_vars_snapshot_t vars; - complete(L"$", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"$", &completions, COMPLETION_REQUEST_DEFAULT, vars); completions_sort_and_prioritize(&completions); do_test(completions.size() == 6); do_test(completions.at(0).completion == L"Bar1"); @@ -2350,7 +2351,7 @@ static void test_complete() { do_test(completions.at(5).completion == L"Foo3"); completions.clear(); - complete(L"$F", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"$F", &completions, COMPLETION_REQUEST_DEFAULT, vars); completions_sort_and_prioritize(&completions); do_test(completions.size() == 3); do_test(completions.at(0).completion == L"oo1"); @@ -2358,12 +2359,13 @@ static void test_complete() { do_test(completions.at(2).completion == L"oo3"); completions.clear(); - complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT, vars); completions_sort_and_prioritize(&completions); do_test(completions.empty()); completions.clear(); - complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH); + complete(L"$1", &completions, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH, + vars); completions_sort_and_prioritize(&completions); do_test(completions.size() == 2); do_test(completions.at(0).completion == L"$Bar1"); @@ -2375,24 +2377,25 @@ static void test_complete() { if (system("chmod 700 'test/complete_test/testfile'")) err(L"chmod failed"); completions.clear(); - complete(L"echo (test/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (test/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); completions.clear(); - complete(L"echo (ls test/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (ls test/complete_test/testfil", &completions, COMPLETION_REQUEST_DEFAULT, + vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); completions.clear(); complete(L"echo (command ls test/complete_test/testfil", &completions, - COMPLETION_REQUEST_DEFAULT); + COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"e"); // Completing after spaces - see #2447 completions.clear(); - complete(L"echo (ls test/complete_test/has\\ ", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (ls test/complete_test/has\\ ", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"space"); @@ -2405,104 +2408,104 @@ static void test_complete() { // Complete a function name. completions.clear(); - complete(L"echo (scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"t"); // But not with the command prefix. completions.clear(); - complete(L"echo (command scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (command scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); // Not with the builtin prefix. completions.clear(); - complete(L"echo (builtin scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo (builtin scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); // Not after a redirection. completions.clear(); - complete(L"echo hi > scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo hi > scuttlebut", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); // Trailing spaces (#1261). 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); + complete(L"foobarbaz ", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"qux"); // Don't complete variable names in single quotes (#1023). completions.clear(); - complete(L"echo '$Foo", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo '$Foo", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"echo \\$Foo", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo \\$Foo", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); // File completions. completions.clear(); - complete(L"cat test/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat test/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"echo sup > test/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo sup > test/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"echo sup > test/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"echo sup > test/complete_test/te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); if (!pushd("test/complete_test")) return; - complete(L"cat te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); do_test(!(completions.at(0).flags & COMPLETE_REPLACES_TOKEN)); do_test(!(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT)); completions.clear(); - complete(L"cat testfile te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat testfile te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT); completions.clear(); - complete(L"cat testfile TE", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat testfile TE", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"testfile"); do_test(completions.at(0).flags & COMPLETE_REPLACES_TOKEN); do_test(completions.at(0).flags & COMPLETE_DUPLICATES_ARGUMENT); completions.clear(); - complete(L"something --abc=te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something --abc=te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something -abc=te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something -abc=te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something abc=te", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something abc=te", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"stfile"); completions.clear(); - complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.size() == 0); completions.clear(); - complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_FUZZY_MATCH); + complete(L"something abc=stfile", &completions, COMPLETION_REQUEST_FUZZY_MATCH, vars); do_test(completions.size() == 1); do_test(completions.at(0).completion == L"abc=testfile"); // Zero escapes can cause problems. See issue #1631. completions.clear(); - complete(L"cat foo\\0", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat foo\\0", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"cat foo\\0bar", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat foo\\0bar", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"cat \\0", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat \\0", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); completions.clear(); - complete(L"cat te\\0", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"cat te\\0", &completions, COMPLETION_REQUEST_DEFAULT, vars); do_test(completions.empty()); popd(); @@ -2510,11 +2513,12 @@ static void test_complete() { complete_set_variable_names(NULL); // Test abbreviations. + auto &pvars = parser_t::principal_parser().vars(); function_data_t fd; fd.name = L"testabbrsonetwothreefour"; function_add(fd, parser_t::principal_parser()); int ret = env_set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); - complete(L"testabbrsonetwothree", &completions, COMPLETION_REQUEST_DEFAULT); + complete(L"testabbrsonetwothree", &completions, COMPLETION_REQUEST_DEFAULT, pvars); do_test(ret == 0); do_test(completions.size() == 2); do_test(completions.at(0).completion == L"four"); @@ -2594,7 +2598,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); + complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, env_vars_snapshot_t{}); bool expects_error = (expected == L""); @@ -2630,7 +2634,7 @@ static void perform_one_autosuggestion_cd_test(const wcstring &command, const wc static void perform_one_completion_cd_test(const wcstring &command, const wcstring &expected, long line) { std::vector comps; - complete(command, &comps, COMPLETION_REQUEST_DEFAULT); + complete(command, &comps, COMPLETION_REQUEST_DEFAULT, env_vars_snapshot_t{}); bool expects_error = (expected == L""); @@ -2758,7 +2762,7 @@ static void test_autosuggest_suggest_special() { static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, long line) { completion_list_t comps; - complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION); + complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, env_vars_snapshot_t{}); do_test(comps.empty()); if (!comps.empty()) { const wcstring &suggestion = comps.front().completion; diff --git a/src/reader.cpp b/src/reader.cpp index 70b6c4a3d..394f5d406 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1279,7 +1279,8 @@ static std::function get_autosuggestion_performer const wcstring &search_string, size_t cursor_pos, history_t *history) { const unsigned int generation_count = read_generation_count(); const wcstring working_directory(env_get_pwd_slash()); - env_vars_snapshot_t vars(env_vars_snapshot_t::highlighting_keys); + env_vars_snapshot_t vars(parser_t::principal_parser().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 @@ -1329,7 +1330,7 @@ static std::function get_autosuggestion_performer // Try normal completions. completion_request_flags_t complete_flags = COMPLETION_REQUEST_AUTOSUGGESTION; std::vector completions; - complete(search_string, &completions, complete_flags); + complete(search_string, &completions, complete_flags, vars); completions_sort_and_prioritize(&completions, complete_flags); if (!completions.empty()) { const completion_t &comp = completions.at(0); @@ -2201,7 +2202,8 @@ static void highlight_complete(highlight_result_t result) { static std::function get_highlight_performer(const wcstring &text, long match_highlight_pos, bool no_io) { - env_vars_snapshot_t vars(env_vars_snapshot_t::highlighting_keys); + env_vars_snapshot_t vars(parser_t::principal_parser().vars(), + env_vars_snapshot_t::highlighting_keys); unsigned int generation_count = read_generation_count(); highlight_function_t highlight_func = no_io ? highlight_shell_no_io : current_data()->highlight_func; @@ -2683,7 +2685,8 @@ 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); + data->complete_func(buffcpy, &comp, complete_flags, + parser_t::principal_parser().vars()); // Munge our completions. completions_sort_and_prioritize(&comp); diff --git a/src/reader.h b/src/reader.h index 1ecca836c..3854ae4f7 100644 --- a/src/reader.h +++ b/src/reader.h @@ -149,13 +149,9 @@ void reader_push(const wcstring &name); /// Return to previous reader environment. void reader_pop(); -/// Specify function to use for finding possible tab completions. The function must take these -/// arguments: -/// -/// - The command to be completed as a null terminated array of wchar_t -/// - An array_list_t in which completions will be inserted. +/// Specify function to use for finding possible tab completions. typedef void (*complete_function_t)(const wcstring &, std::vector *, - completion_request_flags_t); + completion_request_flags_t, const environment_t &); void reader_set_complete_function(complete_function_t); /// The type of a highlight function. From 94adb53b1f9154369e85fc783e15daad866301d5 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 18:36:04 -0700 Subject: [PATCH 08/24] Eliminate complete_set_variable_names --- src/complete.cpp | 20 ++------------------ src/complete.h | 2 -- src/fish_tests.cpp | 19 ++++++++++++------- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/complete.cpp b/src/complete.cpp index fbe453264..36afd5441 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -74,20 +74,6 @@ static const wcstring &C_(const wcstring &s) { return s; } static void complete_load(const wcstring &name, bool reload); -/// Testing apparatus. -const wcstring_list_t *s_override_variable_names = NULL; - -void complete_set_variable_names(const wcstring_list_t *names) { - s_override_variable_names = names; -} - -static inline wcstring_list_t complete_get_variable_names(const environment_t &vars) { - if (s_override_variable_names != NULL) { - return *s_override_variable_names; - } - return vars.get_names(0); -} - /// Struct describing a completion option entry. /// /// If option is empty, the comp field must not be empty and contains a list of arguments to the @@ -1125,10 +1111,8 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) { size_t varlen = str.length() - start_offset; bool res = false; - const wcstring_list_t names = complete_get_variable_names(vars); - for (size_t i = 0; i < names.size(); i++) { - const wcstring &env_name = names.at(i); - + const wcstring_list_t names = vars.get_names(0); + for (const wcstring &env_name : vars.get_names(0)) { string_fuzzy_match_t match = string_fuzzy_match_string(var, env_name, this->max_fuzzy_match_type()); if (match.type == fuzzy_match_none) { diff --git a/src/complete.h b/src/complete.h index 0abd68208..d73ae1d8c 100644 --- a/src/complete.h +++ b/src/complete.h @@ -196,8 +196,6 @@ void append_completion(std::vector *completions, wcstring comp, wcstring desc = wcstring(), int flags = 0, string_fuzzy_match_t match = string_fuzzy_match_t(fuzzy_match_exact)); -/// Function used for testing. -void complete_set_variable_names(const wcstring_list_t *names); /// Support for "wrap targets." A wrap target is a command that completes like another command. bool complete_add_wrapper(const wcstring &command, const wcstring &wrap_target); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 82497060b..24decdb44 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2333,13 +2333,19 @@ static void test_colors() { static void test_complete() { say(L"Testing complete"); - const wchar_t *name_strs[] = {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"}; - size_t count = sizeof name_strs / sizeof *name_strs; - const wcstring_list_t names(name_strs, name_strs + count); - std::vector completions; - complete_set_variable_names(&names); - env_vars_snapshot_t vars; + struct test_complete_vars_t : environment_t { + wcstring_list_t get_names(int flags) const override { + return {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"}; + } + maybe_t get(const wcstring &key, + env_mode_flags_t mode = ENV_DEFAULT) const override { + return {}; + } + }; + test_complete_vars_t vars; + + completion_list_t completions; complete(L"$", &completions, COMPLETION_REQUEST_DEFAULT, vars); completions_sort_and_prioritize(&completions); do_test(completions.size() == 6); @@ -2510,7 +2516,6 @@ static void test_complete() { popd(); completions.clear(); - complete_set_variable_names(NULL); // Test abbreviations. auto &pvars = parser_t::principal_parser().vars(); From 5055621e02d17296d05495ebb3e74520a53b24cb Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 18:59:57 -0700 Subject: [PATCH 09/24] Eliminate env_push and env_pop --- src/builtin_argparse.cpp | 37 ++++++++++++++++++++----------------- src/env.cpp | 16 ++++++---------- src/env.h | 6 ------ src/fish_tests.cpp | 5 +++-- src/parser.cpp | 4 ++-- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/builtin_argparse.cpp b/src/builtin_argparse.cpp index ae15cfc6e..4fe148032 100644 --- a/src/builtin_argparse.cpp +++ b/src/builtin_argparse.cpp @@ -22,6 +22,7 @@ #include "exec.h" #include "fallback.h" // IWYU pragma: keep #include "io.h" +#include "parser.h" #include "wgetopt.h" // IWYU pragma: keep #include "wutil.h" // IWYU pragma: keep @@ -433,14 +434,14 @@ static void populate_option_strings(const argparse_cmd_opts_t &opts, wcstring *s long_options->push_back({NULL, 0, NULL, 0}); } -static int validate_arg(const argparse_cmd_opts_t &opts, option_spec_t *opt_spec, bool is_long_flag, - const wchar_t *woptarg, io_streams_t &streams) { +static int validate_arg(parser_t &parser, const argparse_cmd_opts_t &opts, option_spec_t *opt_spec, + bool is_long_flag, const wchar_t *woptarg, io_streams_t &streams) { // Obviously if there is no arg validation command we assume the arg is okay. if (opt_spec->validation_command.empty()) return STATUS_CMD_OK; wcstring_list_t cmd_output; - env_push(true); + parser.vars().push(true); env_set_one(L"_argparse_cmd", ENV_LOCAL, opts.name); if (is_long_flag) { env_set_one(var_name_prefix + L"name", ENV_LOCAL, opt_spec->long_flag); @@ -455,7 +456,7 @@ static int validate_arg(const argparse_cmd_opts_t &opts, option_spec_t *opt_spec streams.err.append(output); streams.err.push_back(L'\n'); } - env_pop(); + parser.vars().pop(); return retval; } @@ -473,13 +474,14 @@ static bool is_implicit_int(const argparse_cmd_opts_t &opts, const wchar_t *val) } // Store this value under the implicit int option. -static int validate_and_store_implicit_int(const argparse_cmd_opts_t &opts, const wchar_t *val, - wgetopter_t &w, int long_idx, io_streams_t &streams) { +static int validate_and_store_implicit_int(parser_t &parser, const argparse_cmd_opts_t &opts, + const wchar_t *val, wgetopter_t &w, int long_idx, + io_streams_t &streams) { // See if this option passes the validation checks. auto found = opts.options.find(opts.implicit_int_flag); assert(found != opts.options.end()); const auto &opt_spec = found->second; - int retval = validate_arg(opts, opt_spec.get(), long_idx != -1, val, streams); + int retval = validate_arg(parser, opts, opt_spec.get(), long_idx != -1, val, streams); if (retval != STATUS_CMD_OK) return retval; // It's a valid integer so store it and return success. @@ -490,8 +492,8 @@ static int validate_and_store_implicit_int(const argparse_cmd_opts_t &opts, cons return STATUS_CMD_OK; } -static int handle_flag(const argparse_cmd_opts_t &opts, option_spec_t *opt_spec, int long_idx, - const wchar_t *woptarg, io_streams_t &streams) { +static int handle_flag(parser_t &parser, const argparse_cmd_opts_t &opts, option_spec_t *opt_spec, + int long_idx, const wchar_t *woptarg, io_streams_t &streams) { opt_spec->num_seen++; if (opt_spec->num_allowed == 0) { // It's a boolean flag. Save the flag we saw since it might be useful to know if the @@ -506,7 +508,7 @@ static int handle_flag(const argparse_cmd_opts_t &opts, option_spec_t *opt_spec, } if (woptarg) { - int retval = validate_arg(opts, opt_spec, long_idx != -1, woptarg, streams); + int retval = validate_arg(parser, opts, opt_spec, long_idx != -1, woptarg, streams); if (retval != STATUS_CMD_OK) return retval; } @@ -526,9 +528,9 @@ static int handle_flag(const argparse_cmd_opts_t &opts, option_spec_t *opt_spec, return STATUS_CMD_OK; } -static int argparse_parse_flags(const argparse_cmd_opts_t &opts, const wchar_t *short_options, - const woption *long_options, const wchar_t *cmd, int argc, - wchar_t **argv, int *optind, parser_t &parser, +static int argparse_parse_flags(parser_t &parser, const argparse_cmd_opts_t &opts, + const wchar_t *short_options, const woption *long_options, + const wchar_t *cmd, int argc, wchar_t **argv, int *optind, io_streams_t &streams) { int opt; int long_idx = -1; @@ -544,7 +546,8 @@ static int argparse_parse_flags(const argparse_cmd_opts_t &opts, const wchar_t * const wchar_t *arg_contents = argv[w.woptind - 1] + 1; int retval = STATUS_CMD_OK; if (is_implicit_int(opts, arg_contents)) { - retval = validate_and_store_implicit_int(opts, arg_contents, w, long_idx, streams); + retval = validate_and_store_implicit_int(parser, opts, arg_contents, w, long_idx, + streams); } else { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); retval = STATUS_INVALID_ARGS; @@ -558,7 +561,7 @@ static int argparse_parse_flags(const argparse_cmd_opts_t &opts, const wchar_t * auto found = opts.options.find(opt); assert(found != opts.options.end()); - int retval = handle_flag(opts, found->second.get(), long_idx, w.woptarg, streams); + int retval = handle_flag(parser, opts, found->second.get(), long_idx, w.woptarg, streams); if (retval != STATUS_CMD_OK) return retval; long_idx = -1; } @@ -590,8 +593,8 @@ static int argparse_parse_args(argparse_cmd_opts_t &opts, const wcstring_list_t auto argv = argv_container.get(); int optind; - int retval = argparse_parse_flags(opts, short_options.c_str(), long_options.data(), cmd, argc, - argv, &optind, parser, streams); + int retval = argparse_parse_flags(parser, opts, short_options.c_str(), long_options.data(), cmd, + argc, argv, &optind, streams); if (retval != STATUS_CMD_OK) return retval; retval = check_for_mutually_exclusive_flags(opts, streams); diff --git a/src/env.cpp b/src/env.cpp index 50341b890..df7282a72 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -161,7 +161,12 @@ struct var_stack_t { void mark_changed_exported() { has_changed_exported = true; } void update_export_array_if_necessary(); - var_stack_t() : top(globals()), global_env(globals()) {} + var_stack_t() : top(globals()), global_env(globals()) { + // Add a toplevel local scope on top of the global scope. This local scope will persist + // throughout the lifetime of the fish process, and it will ensure that `set -l` commands + // run at the command-line don't affect the global scope. + push(false); + } // Pushes a new node onto our stack // Optionally creates a new scope for the node @@ -999,11 +1004,6 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { callback_data_list_t callbacks; s_universal_variables->initialize(callbacks); env_universal_callbacks(&env_stack_t::principal(), callbacks); - - // Now that the global scope is fully initialized, add a toplevel local scope. This same local - // scope will persist throughout the lifetime of the fish process, and it will ensure that `set - // -l` commands run at the command-line don't affect the global scope. - env_push(false); } /// Search all visible scopes in order for the specified key. Return the first scope in which it was @@ -1438,10 +1438,6 @@ int env_set_empty(const wcstring &key, env_mode_flags_t mode) { int env_remove(const wcstring &key, int mode) { return env_stack_t::principal().remove(key, mode); } -void env_push(bool new_scope) { env_stack_t::principal().push(new_scope); } - -void env_pop() { env_stack_t::principal().pop(); } - void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } void env_set_argv(const wchar_t *const *argv) { return env_stack_t::principal().set_argv(argv); } diff --git a/src/env.h b/src/env.h index 379175880..22606c45c 100644 --- a/src/env.h +++ b/src/env.h @@ -165,12 +165,6 @@ int env_set_empty(const wcstring &key, env_mode_flags_t mode); /// \return zero if the variable existed, and non-zero if the variable did not exist int env_remove(const wcstring &key, int mode); -/// Push the variable stack. Used for implementing local variables for functions and for-loops. -void env_push(bool new_scope); - -/// Pop the variable stack. Used for implementing local variables for functions and for-loops. -void env_pop(); - /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 24decdb44..6b6c8b6eb 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1755,7 +1755,8 @@ static void test_ifind_fuzzy() { static void test_abbreviations() { say(L"Testing abbreviations"); - env_push(true); + auto &vars = parser_t::principal_parser().vars(); + vars.push(true); const std::vector> abbreviations = { {L"gc", L"git checkout"}, @@ -1822,7 +1823,7 @@ static void test_abbreviations() { expanded = reader_expand_abbreviation_in_command(L"command gc", wcslen(L"command gc"), &result); if (expanded) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); - env_pop(); + vars.pop(); } /// Test path functions. diff --git a/src/parser.cpp b/src/parser.cpp index ce79a0c04..65fcffcd4 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -154,7 +154,7 @@ void parser_t::push_block_int(block_t *new_current) { } if (new_current->type() != TOP) { - env_push(type == FUNCTION_CALL); + vars().push(type == FUNCTION_CALL); new_current->wants_pop_env = true; } } @@ -172,7 +172,7 @@ void parser_t::pop_block(const block_t *expected) { std::unique_ptr old = std::move(block_stack.back()); block_stack.pop_back(); - if (old->wants_pop_env) env_pop(); + if (old->wants_pop_env) vars().pop(); // Figure out if `status is-block` should consider us to be in a block now. bool new_is_block = false; From ede66ccaacad3c9161693c36c04d437f357c7e4d Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 19:17:44 -0700 Subject: [PATCH 10/24] Instance env_set_argv and env_set_pwd --- src/builtin_cd.cpp | 2 +- src/builtin_source.cpp | 2 +- src/env.cpp | 23 ++++++++++------------- src/env.h | 9 ++++++--- src/exec.cpp | 2 +- src/function.cpp | 5 +++-- src/function.h | 3 ++- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/builtin_cd.cpp b/src/builtin_cd.cpp index 10362f444..1ab7a2601 100644 --- a/src/builtin_cd.cpp +++ b/src/builtin_cd.cpp @@ -86,6 +86,6 @@ int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { return STATUS_CMD_ERROR; } - env_set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(norm_dir)); + parser.vars().set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(norm_dir)); return STATUS_CMD_OK; } diff --git a/src/builtin_source.cpp b/src/builtin_source.cpp index 1e09b702e..8784b0603 100644 --- a/src/builtin_source.cpp +++ b/src/builtin_source.cpp @@ -78,7 +78,7 @@ int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // This is slightly subtle. If this is a bare `source` with no args then `argv + optind` already // points to the end of argv. Otherwise we want to skip the file name to get to the args if any. - env_set_argv(argv + optind + (argc == optind ? 0 : 1)); + parser.vars().set_argv(argv + optind + (argc == optind ? 0 : 1)); retval = reader_read(fd, streams.io_chain ? *streams.io_chain : io_chain_t()); diff --git a/src/env.cpp b/src/env.cpp index df7282a72..c147e22d7 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -642,11 +642,11 @@ static void setup_path() { /// If they don't already exist initialize the `COLUMNS` and `LINES` env vars to reasonable /// defaults. They will be updated later by the `get_current_winsize()` function if they need to be /// adjusted. -static void env_set_termsize() { - auto cols = env_get(L"COLUMNS"); +void env_stack_t::set_termsize() { + auto cols = get(L"COLUMNS"); if (cols.missing_or_empty()) env_set_one(L"COLUMNS", ENV_GLOBAL, DFLT_TERM_COL_STR); - auto rows = env_get(L"LINES"); + auto rows = get(L"LINES"); if (rows.missing_or_empty()) env_set_one(L"LINES", ENV_GLOBAL, DFLT_TERM_ROW_STR); } @@ -708,8 +708,6 @@ static void setup_user(bool force) { /// Various things we need to initialize at run-time that don't really fit any of the other init /// routines. void misc_init() { - env_set_read_limit(); - // If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might // be intermixed with `write()` calls and we need to ensure the writes are not reordered. See // issue #3748. @@ -976,21 +974,22 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { } } + env_stack_t &vars = env_stack_t::principal(); // initialize the PWD variable if necessary // Note we may inherit a virtual PWD that doesn't match what getcwd would return; respect that. - if (env_get(L"PWD").missing_or_empty()) { - env_stack_t::principal().set_pwd_from_getcwd(); + if (vars.get(L"PWD").missing_or_empty()) { + vars.set_pwd_from_getcwd(); } - env_set_termsize(); // initialize the terminal size variables - env_set_read_limit(); // initialize the read_byte_limit + vars.set_termsize(); // initialize the terminal size variables + vars.set_read_limit(); // initialize the read_byte_limit // Set g_use_posix_spawn. Default to true. - auto use_posix_spawn = env_get(L"fish_use_posix_spawn"); + auto use_posix_spawn = vars.get(L"fish_use_posix_spawn"); g_use_posix_spawn = use_posix_spawn.missing_or_empty() ? true : from_string(use_posix_spawn->as_string()); // Set fish_bind_mode to "default". - env_set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); + vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE); // This is somewhat subtle. At this point we consider our environment to be sufficiently // initialized that we can react to changes to variables. Prior to doing this we expect that the @@ -1440,8 +1439,6 @@ int env_remove(const wcstring &key, int mode) { return env_stack_t::principal(). void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } -void env_set_argv(const wchar_t *const *argv) { return env_stack_t::principal().set_argv(argv); } - 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(); } diff --git a/src/env.h b/src/env.h index 22606c45c..83e6434e4 100644 --- a/src/env.h +++ b/src/env.h @@ -168,9 +168,6 @@ int env_remove(const wcstring &key, int mode); /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); -/// Sets up argv as the given null terminated array of strings. -void env_set_argv(const wchar_t *const *argv); - /// Returns the PWD with a terminating slash. wcstring env_get_pwd_slash(); @@ -236,6 +233,12 @@ class env_stack_t : public environment_t { /// Returns all variable names. wcstring_list_t get_names(int flags) const override; + /// Update the termsize variable. + void set_termsize(); + + /// Update the PWD variable directory. + bool set_pwd(); + /// Sets up argv as the given null terminated array of strings. void set_argv(const wchar_t *const *argv); diff --git a/src/exec.cpp b/src/exec.cpp index 5b13d7b23..3e7af698a 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -787,7 +787,7 @@ static bool exec_block_or_func_process(parser_t &parser, std::shared_ptr function_block_t *fb = parser.push_block(p, func_name, props->shadow_scope); - function_prepare_environment(func_name, p->get_argv() + 1, inherit_vars); + function_prepare_environment(parser.vars(), func_name, p->get_argv() + 1, inherit_vars); parser.forbid_function(func_name); internal_exec_helper(parser, props->parsed_source, props->body_node, io_chain, j); diff --git a/src/function.cpp b/src/function.cpp index f709b08f0..c5438bf36 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -344,9 +344,10 @@ void function_invalidate_path() { function_autoloader.invalidate(); } // 1. argv // 2. named arguments // 3. inherited variables -void function_prepare_environment(const wcstring &name, const wchar_t *const *argv, +void function_prepare_environment(env_stack_t &vars, const wcstring &name, + const wchar_t *const *argv, const std::map &inherited_vars) { - env_set_argv(argv); + vars.set_argv(argv); auto props = function_get_properties(name); if (props && !props->named_arguments.empty()) { const wchar_t *const *arg = argv; diff --git a/src/function.h b/src/function.h index d823bdeeb..9ee7df6eb 100644 --- a/src/function.h +++ b/src/function.h @@ -109,7 +109,8 @@ std::map function_get_inherit_vars(const wcstring &name); bool function_copy(const wcstring &name, const wcstring &new_name); /// Prepares the environment for executing a function. -void function_prepare_environment(const wcstring &name, const wchar_t *const *argv, +void function_prepare_environment(env_stack_t &vars, const wcstring &name, + const wchar_t *const *argv, const std::map &inherited_vars); /// Observes that fish_function_path has changed. From a00de96a57886f85aa6f48aa83a454f6bac9de87 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 20:57:10 -0700 Subject: [PATCH 11/24] Instance env_remove --- src/builtin_set.cpp | 2 +- src/env.cpp | 2 -- src/env.h | 10 ---------- src/fish_tests.cpp | 13 +++++++------ 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index 6ab298a0b..4e5f253e8 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -652,7 +652,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, } if (idx_count == 0) { // unset the var - retval = env_remove(dest, scope); + retval = parser.vars().remove(dest, scope); // Temporarily swallowing ENV_NOT_FOUND errors to prevent // breaking all tests that unset variables that aren't set. if (retval != ENV_NOT_FOUND) { diff --git a/src/env.cpp b/src/env.cpp index c147e22d7..6258c845a 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1435,8 +1435,6 @@ int env_set_empty(const wcstring &key, env_mode_flags_t mode) { return env_stack_t::principal().set_empty(key, mode); } -int env_remove(const wcstring &key, int mode) { return env_stack_t::principal().remove(key, mode); } - void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } wcstring env_get_pwd_slash() { return env_stack_t::principal().get_pwd_slash(); } diff --git a/src/env.h b/src/env.h index 83e6434e4..a10880d3b 100644 --- a/src/env.h +++ b/src/env.h @@ -155,16 +155,6 @@ int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val); /// Sets the variable with the specified name to no values. int env_set_empty(const wcstring &key, env_mode_flags_t mode); -/// Remove environment variable. -/// -/// \param key The name of the variable to remove -/// \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If this -/// is a user request, read-only variables can not be removed. The mode may also specify the scope -/// of the variable that should be erased. -/// -/// \return zero if the variable existed, and non-zero if the variable did not exist -int env_remove(const wcstring &key, int mode); - /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 6b6c8b6eb..6d241e22c 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2762,7 +2762,7 @@ static void test_autosuggest_suggest_special() { perform_one_completion_cd_test(L"cd ~haha", L"ha/", __LINE__); perform_one_completion_cd_test(L"cd ~hahaha/", L"path1/", __LINE__); - env_remove(L"HOME", ENV_LOCAL | ENV_EXPORT); + parser_t::principal_parser().vars().remove(L"HOME", ENV_LOCAL | ENV_EXPORT); popd(); } @@ -4284,9 +4284,10 @@ static void test_highlighting() { {L"self%not", highlight_spec_param}, }); + auto &vars = parser_t::principal_parser().vars(); // Verify variables and wildcards in commands using /bin/cat. - env_set(L"VARIABLE_IN_COMMAND", ENV_LOCAL, {L"a"}); - env_set(L"VARIABLE_IN_COMMAND2", ENV_LOCAL, {L"at"}); + vars.set(L"VARIABLE_IN_COMMAND", ENV_LOCAL, {L"a"}); + vars.set(L"VARIABLE_IN_COMMAND2", ENV_LOCAL, {L"at"}); highlight_tests.push_back( {{L"/bin/ca", highlight_spec_command, ns}, {L"*", highlight_spec_operator, ns}}); @@ -4319,7 +4320,7 @@ static void test_highlighting() { do_test(expected_colors.size() == text.size()); std::vector colors(text.size()); - highlight_shell(text, colors, 20, NULL, env_vars_snapshot_t::current()); + highlight_shell(text, colors, 20, NULL, vars); if (expected_colors.size() != colors.size()) { err(L"Color vector has wrong size! Expected %lu, actual %lu", expected_colors.size(), @@ -4338,8 +4339,8 @@ static void test_highlighting() { } } } - env_remove(L"VARIABLE_IN_COMMAND", ENV_DEFAULT); - env_remove(L"VARIABLE_IN_COMMAND2", ENV_DEFAULT); + vars.remove(L"VARIABLE_IN_COMMAND", ENV_DEFAULT); + vars.remove(L"VARIABLE_IN_COMMAND2", ENV_DEFAULT); } static void test_wcstring_tok() { From 26fc705c07c4530d38bed3180c1c8883e7d5e0c7 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 21:27:25 -0700 Subject: [PATCH 12/24] Instance env_set_empty --- src/builtin_read.cpp | 3 ++- src/env.cpp | 19 +++++++------------ src/env.h | 3 --- src/function.cpp | 2 +- src/parse_execution.cpp | 2 +- 5 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index 0ce1e3be0..4494ae886 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -26,6 +26,7 @@ #include "highlight.h" #include "history.h" #include "io.h" +#include "parser.h" #include "proc.h" #include "reader.h" #include "wcstringutil.h" @@ -452,7 +453,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { auto vars_left = [&] () { return argv + argc - var_ptr; }; auto clear_remaining_vars = [&] () { while (vars_left()) { - env_set_empty(*var_ptr, opts.place); + parser.vars().set_empty(*var_ptr, opts.place); // env_set_one(*var_ptr, opts.place, L""); ++var_ptr; } diff --git a/src/env.cpp b/src/env.cpp index 6258c845a..4d7305a19 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -852,6 +852,7 @@ static void setup_var_dispatch_table() { void env_init(const struct config_paths_t *paths /* or NULL */) { setup_var_dispatch_table(); + env_stack_t &vars = env_stack_t::principal(); // Now the environment variable handling is set up, the next step is to insert valid data. // Import environment variables. Walk backwards so that the first one out of any duplicates wins @@ -866,7 +867,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { if (eql == wcstring::npos) { // No equal-sign found so treat it as a defined var that has no value(s). if (is_read_only(key_and_val) || is_electric(key_and_val)) continue; - env_set_empty(key_and_val, ENV_EXPORT | ENV_GLOBAL); + vars.set_empty(key_and_val, ENV_EXPORT | ENV_GLOBAL); } else { key.assign(key_and_val, 0, eql); val.assign(key_and_val, eql+1, wcstring::npos); @@ -965,16 +966,15 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { } else { // We cannot get $HOME. This triggers warnings for history and config.fish already, // so it isn't necessary to warn here as well. - env_set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT); + vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT); } free(unam_narrow); } else { // If $USER is empty as well (which we tried to set above), we can't get $HOME. - env_set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT); + vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT); } } - env_stack_t &vars = env_stack_t::principal(); // initialize the PWD variable if necessary // Note we may inherit a virtual PWD that doesn't match what getcwd would return; respect that. if (vars.get(L"PWD").missing_or_empty()) { @@ -1431,10 +1431,6 @@ int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { return env_stack_t::principal().set_one(key, mode, std::move(val)); } -int env_set_empty(const wcstring &key, env_mode_flags_t mode) { - return env_stack_t::principal().set_empty(key, mode); -} - void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } wcstring env_get_pwd_slash() { return env_stack_t::principal().get_pwd_slash(); } @@ -1599,12 +1595,11 @@ void env_stack_t::set_argv(const wchar_t *const *argv) { if (argv && *argv) { wcstring_list_t list; for (auto arg = argv; *arg; arg++) { - list.push_back(*arg); + list.emplace_back(*arg); } - - env_set(L"argv", ENV_LOCAL, list); + set(L"argv", ENV_LOCAL, std::move(list)); } else { - env_set_empty(L"argv", ENV_LOCAL); + set_empty(L"argv", ENV_LOCAL); } } diff --git a/src/env.h b/src/env.h index a10880d3b..918df75f1 100644 --- a/src/env.h +++ b/src/env.h @@ -152,9 +152,6 @@ int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals); /// Sets the variable with the specified name to a single value. int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val); -/// Sets the variable with the specified name to no values. -int env_set_empty(const wcstring &key, env_mode_flags_t mode); - /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); diff --git a/src/function.cpp b/src/function.cpp index c5438bf36..e8a424414 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -356,7 +356,7 @@ void function_prepare_environment(env_stack_t &vars, const wcstring &name, env_set_one(named_arg, ENV_LOCAL | ENV_USER, *arg); arg++; } else { - env_set_empty(named_arg, ENV_LOCAL | ENV_USER); + vars.set_empty(named_arg, ENV_LOCAL | ENV_USER); } } } diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 09b7f7d9d..a56fe4815 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -387,7 +387,7 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( auto var = env_get(for_var_name, ENV_LOCAL); if (!var && !is_function_context()) var = env_get(for_var_name, ENV_DEFAULT); if (!var || var->read_only()) { - int retval = env_set_empty(for_var_name, ENV_LOCAL | ENV_USER); + int retval = parser->vars().set_empty(for_var_name, ENV_LOCAL | ENV_USER); if (retval != ENV_OK) { report_error(var_name_node, L"You cannot use read-only variable '%ls' in a for loop", for_var_name.c_str()); From 421fbdd52a16afae82765c0029c6b63102025e46 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 10 Sep 2018 22:29:52 -0700 Subject: [PATCH 13/24] 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()); From c1dd284b3e79f5ed35ec2606b0c4c833bb7c76b6 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 14 Sep 2018 00:36:26 -0700 Subject: [PATCH 14/24] Instantize env_set Switch env_set to an instance method on environmnet_t. --- src/builtin_argparse.cpp | 26 ++++--- src/builtin_fg.cpp | 3 +- src/builtin_read.cpp | 13 ++-- src/builtin_set.cpp | 9 ++- src/common.cpp | 6 +- src/common.h | 4 +- src/env.cpp | 157 +++++++++++++++++++++++---------------- src/env.h | 15 ++-- src/exec.cpp | 4 +- src/fish.cpp | 2 +- src/fish_tests.cpp | 14 ++-- src/function.cpp | 4 +- src/history.cpp | 13 ++-- src/input.cpp | 13 ++-- src/input.h | 4 +- src/maybe.h | 8 ++ src/parse_execution.cpp | 2 +- src/path.cpp | 5 +- src/reader.cpp | 21 ++++-- 19 files changed, 193 insertions(+), 130 deletions(-) diff --git a/src/builtin_argparse.cpp b/src/builtin_argparse.cpp index 4fe148032..df85cdd32 100644 --- a/src/builtin_argparse.cpp +++ b/src/builtin_argparse.cpp @@ -441,22 +441,24 @@ static int validate_arg(parser_t &parser, const argparse_cmd_opts_t &opts, optio wcstring_list_t cmd_output; - parser.vars().push(true); - env_set_one(L"_argparse_cmd", ENV_LOCAL, opts.name); + auto &vars = parser.vars(); + + vars.push(true); + vars.set_one(L"_argparse_cmd", ENV_LOCAL, opts.name); if (is_long_flag) { - env_set_one(var_name_prefix + L"name", ENV_LOCAL, opt_spec->long_flag); + vars.set_one(var_name_prefix + L"name", ENV_LOCAL, opt_spec->long_flag); } else { - env_set_one(var_name_prefix + L"name", ENV_LOCAL, - wcstring(1, opt_spec->short_flag).c_str()); + vars.set_one(var_name_prefix + L"name", ENV_LOCAL, + wcstring(1, opt_spec->short_flag).c_str()); } - env_set_one(var_name_prefix + L"value", ENV_LOCAL, woptarg); + vars.set_one(var_name_prefix + L"value", ENV_LOCAL, woptarg); int retval = exec_subshell(opt_spec->validation_command, cmd_output, false); for (const auto &output : cmd_output) { streams.err.append(output); streams.err.push_back(L'\n'); } - parser.vars().pop(); + vars.pop(); return retval; } @@ -623,13 +625,13 @@ static int check_min_max_args_constraints(const argparse_cmd_opts_t &opts, parse } /// Put the result of parsing the supplied args into the caller environment as local vars. -static void set_argparse_result_vars(const argparse_cmd_opts_t &opts) { +static void set_argparse_result_vars(env_stack_t &vars, const argparse_cmd_opts_t &opts) { for (const auto &kv : opts.options) { const auto &opt_spec = kv.second; if (!opt_spec->num_seen) continue; if (opt_spec->short_flag_valid) { - env_set(var_name_prefix + opt_spec->short_flag, ENV_LOCAL, opt_spec->vals); + vars.set(var_name_prefix + opt_spec->short_flag, ENV_LOCAL, opt_spec->vals); } if (!opt_spec->long_flag.empty()) { // We do a simple replacement of all non alphanum chars rather than calling @@ -638,11 +640,11 @@ static void set_argparse_result_vars(const argparse_cmd_opts_t &opts) { for (size_t pos = 0; pos < long_flag.size(); pos++) { if (!iswalnum(long_flag[pos])) long_flag[pos] = L'_'; } - env_set(var_name_prefix + long_flag, ENV_LOCAL, opt_spec->vals); + vars.set(var_name_prefix + long_flag, ENV_LOCAL, opt_spec->vals); } } - env_set(L"argv", ENV_LOCAL, opts.argv); + vars.set(L"argv", ENV_LOCAL, opts.argv); } /// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this @@ -679,6 +681,6 @@ int builtin_argparse(parser_t &parser, io_streams_t &streams, wchar_t **argv) { retval = check_min_max_args_constraints(opts, parser, streams); if (retval != STATUS_CMD_OK) return retval; - set_argparse_result_vars(opts); + set_argparse_result_vars(parser.vars(), opts); return retval; } diff --git a/src/builtin_fg.cpp b/src/builtin_fg.cpp index ac4af798a..605a1301a 100644 --- a/src/builtin_fg.cpp +++ b/src/builtin_fg.cpp @@ -12,6 +12,7 @@ #include "env.h" #include "fallback.h" // IWYU pragma: keep #include "io.h" +#include "parser.h" #include "proc.h" #include "reader.h" #include "tokenizer.h" @@ -103,7 +104,7 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const wcstring ft = tok_first(j->command()); //For compatibility with fish 2.0's $_, now replaced with `status current-command` - if (!ft.empty()) env_set_one(L"_", ENV_EXPORT, ft); + if (!ft.empty()) parser.vars().set_one(L"_", ENV_EXPORT, ft); reader_write_title(j->command()); j->promote(); diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index 4494ae886..bfe2e0089 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -415,6 +415,7 @@ static int validate_read_args(const wchar_t *cmd, read_cmd_opts_t &opts, int arg /// The read builtin. Reads from stdin and stores the values in environment variables. int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { + auto &vars = parser.vars(); wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); wcstring buff; @@ -513,13 +514,13 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (opts.array) { // Array mode: assign each char as a separate element of the sole var. - env_set(*var_ptr++, opts.place, chars); + vars.set(*var_ptr++, opts.place, chars); } else { // Not array mode: assign each char to a separate var with the remainder being assigned // to the last var. auto c = chars.begin(); for (; c != chars.end() && vars_left(); ++c) { - env_set_one(*var_ptr++, opts.place, *c); + vars.set_one(*var_ptr++, opts.place, *c); } } } else if (opts.array) { @@ -535,14 +536,14 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { loc.first != wcstring::npos; loc = wcstring_tok(buff, opts.delimiter, loc)) { tokens.emplace_back(wcstring(buff, loc.first, loc.second)); } - env_set(*var_ptr++, opts.place, tokens); + vars.set(*var_ptr++, opts.place, tokens); } else { // We're using a delimiter provided by the user so use the `string split` behavior. wcstring_list_t splits; split_about(buff.begin(), buff.end(), opts.delimiter.begin(), opts.delimiter.end(), &splits); - env_set(*var_ptr++, opts.place, splits); + vars.set(*var_ptr++, opts.place, splits); } } else { // Not array mode. Split the input into tokens and assign each to the vars in sequence. @@ -556,7 +557,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (loc.first != wcstring::npos) { substr = wcstring(buff, loc.first, loc.second); } - env_set_one(*var_ptr++, opts.place, substr); + vars.set_one(*var_ptr++, opts.place, substr); } } else { // We're using a delimiter provided by the user so use the `string split` behavior. @@ -567,7 +568,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { &splits, argc - 1); assert(splits.size() <= (size_t) vars_left()); for (const auto &split : splits) { - env_set_one(*var_ptr++, opts.place, split); + vars.set_one(*var_ptr++, opts.place, split); } } } diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index 4e5f253e8..fdd38b33b 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -344,12 +344,13 @@ static void handle_env_return(int retval, const wchar_t *cmd, const wchar_t *key /// Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a /// description of the problem to stderr. static int env_set_reporting_errors(const wchar_t *cmd, const wchar_t *key, int scope, - wcstring_list_t &list, io_streams_t &streams) { + const wcstring_list_t &list, io_streams_t &streams, + env_stack_t &vars) { if (is_path_variable(key) && !validate_path_warning_on_colons(cmd, key, list, streams)) { return STATUS_CMD_ERROR; } - int retval = env_set(key, scope | ENV_USER, list); + int retval = vars.set(key, scope | ENV_USER, list); handle_env_return(retval, cmd, key, streams); return retval; @@ -664,7 +665,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wcstring_list_t result; dest_var->to_list(result); erase_values(result, indexes); - retval = env_set_reporting_errors(cmd, dest, scope, result, streams); + retval = env_set_reporting_errors(cmd, dest, scope, result, streams, parser.vars()); } if (retval != STATUS_CMD_OK) return retval; @@ -773,7 +774,7 @@ static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, w } if (retval != STATUS_CMD_OK) return retval; - retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams); + retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams, parser.vars()); if (retval != STATUS_CMD_OK) return retval; return check_global_scope_exists(cmd, opts, varname, streams); } diff --git a/src/common.cpp b/src/common.cpp index e13f68738..193a6320d 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1758,15 +1758,17 @@ static void validate_new_termsize(struct winsize *new_termsize) { /// Export the new terminal size as env vars and to the kernel if possible. static void export_new_termsize(struct winsize *new_termsize) { + auto &vars = env_stack_t::globals(); wchar_t buf[64]; auto cols = env_get(L"COLUMNS", ENV_EXPORT); swprintf(buf, 64, L"%d", (int)new_termsize->ws_col); - env_set_one(L"COLUMNS", ENV_GLOBAL | (cols.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); + vars.set_one(L"COLUMNS", ENV_GLOBAL | (cols.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), + buf); auto lines = env_get(L"LINES", ENV_EXPORT); swprintf(buf, 64, L"%d", (int)new_termsize->ws_row); - env_set_one(L"LINES", ENV_GLOBAL | (lines.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); + vars.set_one(L"LINES", ENV_GLOBAL | (lines.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); #ifdef HAVE_WINSIZE // Only write the new terminal size if we are in the foreground (#4477) diff --git a/src/common.h b/src/common.h index dac5d9a6d..d52204929 100644 --- a/src/common.h +++ b/src/common.h @@ -624,8 +624,8 @@ class null_terminated_array_t { CharType_t **array{NULL}; // No assignment or copying. - void operator=(null_terminated_array_t rhs); - null_terminated_array_t(const null_terminated_array_t &); + void operator=(null_terminated_array_t rhs) = delete; + null_terminated_array_t(const null_terminated_array_t &) = delete; typedef std::vector> string_list_t; diff --git a/src/env.cpp b/src/env.cpp index 82ca99df5..f3fa69416 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -95,7 +95,7 @@ bool term_has_xn = false; /// found in `TERMINFO_DIRS` we don't to call `handle_curses()` before we've imported the latter. static bool env_initialized = false; -typedef std::unordered_map +typedef std::unordered_map var_dispatch_table_t; static var_dispatch_table_t var_dispatch_table; @@ -147,6 +147,8 @@ static std::mutex env_lock; // but we can imagine having separate (linked) stacks // if we introduce multiple threads of execution struct var_stack_t { + var_stack_t(var_stack_t &&) = default; + // Top node on the function stack. env_node_ref_t top; @@ -154,11 +156,13 @@ struct var_stack_t { env_node_ref_t global_env; // Exported variable array used by execv. - null_terminated_array_t export_array; + maybe_t> export_array; /// Flag for checking if we need to regenerate the exported variable array. - bool has_changed_exported = true; - void mark_changed_exported() { has_changed_exported = true; } + void mark_changed_exported() { export_array.reset(); } + + bool has_changed_exported() const { return !export_array; } + void update_export_array_if_necessary(); var_stack_t() : top(globals()), global_env(globals()) { @@ -183,7 +187,15 @@ struct var_stack_t { // shadowing scope, or the global scope if none. This implements the default behavior of `set`. env_node_ref_t resolve_unspecified_scope(); + /// Copy this vars_stack. + var_stack_t clone() const { + return var_stack_t(*this); + } + private: + /// Copy constructor. This does not copy the export array; it just allows it to be regenerated. + var_stack_t(const var_stack_t &rhs) : top(rhs.top), global_env(rhs.global_env) {} + bool local_scope_exports(const env_node_ref_t &n) const; void get_exported(const env_node_t *n, var_table_t &h) const; @@ -273,6 +285,7 @@ env_node_ref_t var_stack_t::resolve_unspecified_scope() { } env_stack_t::env_stack_t() : vars_(make_unique()) {} +env_stack_t::env_stack_t(std::unique_ptr vars) : vars_(std::move(vars)) {} // Get the variable stack var_stack_t &env_stack_t::vars_stack() { return *vars_; } @@ -351,8 +364,8 @@ static void handle_timezone(const wchar_t *env_var_name) { /// Some env vars contain a list of paths where an empty path element is equivalent to ".". /// Unfortunately that convention causes problems for fish scripts. So this function replaces the /// empty path element with an explicit ".". See issue #3914. -static void fix_colon_delimited_var(const wcstring &var_name) { - const auto paths = env_get(var_name); +static void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) { + const auto paths = vars.get(var_name); if (paths.missing_or_empty()) return; // See if there's any empties. @@ -362,7 +375,7 @@ static void fix_colon_delimited_var(const wcstring &var_name) { // Copy the list and replace empties with L"." wcstring_list_t newstrs = strs; std::replace(newstrs.begin(), newstrs.end(), empty, wcstring(L".")); - int retval = env_set(var_name, ENV_DEFAULT | ENV_USER, std::move(newstrs)); + int retval = vars.set(var_name, ENV_DEFAULT | ENV_USER, std::move(newstrs)); if (retval != ENV_OK) { debug(0, L"fix_colon_delimited_var failed unexpectedly with retval %d", retval); } @@ -527,8 +540,8 @@ static bool initialize_curses_using_fallback(const char *term) { /// elements are converted to explicit "." to make the vars easier to use in fish scripts. static void init_path_vars() { // Do not replace empties in MATHPATH - see #4158. - fix_colon_delimited_var(L"PATH"); - fix_colon_delimited_var(L"CDPATH"); + fix_colon_delimited_var(L"PATH", env_stack_t::globals()); + fix_colon_delimited_var(L"CDPATH", env_stack_t::globals()); } /// Update the value of g_guessed_fish_emoji_width @@ -597,7 +610,7 @@ static void init_curses() { } /// React to modifying the given variable. -static void react_to_variable_change(const wchar_t *op, const wcstring &key) { +static void react_to_variable_change(const wchar_t *op, const wcstring &key, env_stack_t &vars) { // Don't do any of this until `env_init()` has run. We only want to do this in response to // variables set by the user; e.g., in a script like *config.fish* or interactively or as part // of loading the universal variables for the first time. Variables we import from the @@ -607,7 +620,7 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key) { auto dispatch = var_dispatch_table.find(key); if (dispatch != var_dispatch_table.end()) { - (*dispatch->second)(op, key); + (*dispatch->second)(op, key, vars); } else if (string_prefixes_string(L"_fish_abbr_", key)) { update_abbr_cache(op, key); } else if (string_prefixes_string(L"fish_color_", key)) { @@ -620,7 +633,7 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key) { static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; - react_to_variable_change(op, cb.key); + react_to_variable_change(op, cb.key, *stack); stack->mark_changed_exported(); event_t ev = event_t::variable_event(cb.key); @@ -632,10 +645,11 @@ static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { /// Make sure the PATH variable contains something. static void setup_path() { - const auto path = env_get(L"PATH"); + auto &vars = env_stack_t::globals(); + const auto path = vars.get(L"PATH"); if (path.missing_or_empty()) { wcstring_list_t value({L"/usr/bin", L"/bin"}); - env_set(L"PATH", ENV_GLOBAL | ENV_EXPORT, value); + vars.set(L"PATH", ENV_GLOBAL | ENV_EXPORT, value); } } @@ -643,11 +657,12 @@ static void setup_path() { /// defaults. They will be updated later by the `get_current_winsize()` function if they need to be /// adjusted. void env_stack_t::set_termsize() { + auto &vars = env_stack_t::globals(); auto cols = get(L"COLUMNS"); - if (cols.missing_or_empty()) env_set_one(L"COLUMNS", ENV_GLOBAL, DFLT_TERM_COL_STR); + if (cols.missing_or_empty()) vars.set_one(L"COLUMNS", ENV_GLOBAL, DFLT_TERM_COL_STR); auto rows = get(L"LINES"); - if (rows.missing_or_empty()) env_set_one(L"LINES", ENV_GLOBAL, DFLT_TERM_ROW_STR); + if (rows.missing_or_empty()) vars.set_one(L"LINES", ENV_GLOBAL, DFLT_TERM_ROW_STR); } /// Update the PWD variable directory from the result of getcwd(). @@ -658,7 +673,7 @@ void env_stack_t::set_pwd_from_getcwd() { _(L"Could not determine current working directory. Is your locale set correctly?")); return; } - env_set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(cwd)); + set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, cwd); } /// Allow the user to override the limit on how much data the `read` command will process. @@ -700,7 +715,7 @@ static void setup_user(bool force) { int retval = getpwuid_r(getuid(), &userinfo, buf, sizeof(buf), &result); if (!retval && result) { const wcstring uname = str2wcstring(userinfo.pw_name); - env_set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname); + env_stack_t::globals().set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname); } } } @@ -736,20 +751,23 @@ void env_stack_t::universal_barrier() { env_universal_callbacks(this, callbacks); } -static void handle_fish_term_change(const wcstring &op, const wcstring &var_name) { +static void handle_fish_term_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); update_fish_color_support(); reader_react_to_color_change(); } -static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name) { +static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); update_wait_on_escape_ms(); } -static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name) { +static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { (void)op; (void)var_name; int new_width = 0; @@ -759,7 +777,8 @@ static void handle_change_emoji_width(const wcstring &op, const wcstring &var_na g_fish_emoji_width = std::max(0, new_width); } -static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name) { +static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { (void)op; (void)var_name; int new_width = 1; @@ -769,53 +788,59 @@ static void handle_change_ambiguous_width(const wcstring &op, const wcstring &va g_fish_ambiguous_width = std::max(0, new_width); } -static void handle_term_size_change(const wcstring &op, const wcstring &var_name) { +static void handle_term_size_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars } -static void handle_read_limit_change(const wcstring &op, const wcstring &var_name) { +static void handle_read_limit_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); env_set_read_limit(); } -static void handle_fish_history_change(const wcstring &op, const wcstring &var_name) { +static void handle_fish_history_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); reader_change_history(history_session_id().c_str()); } -static void handle_function_path_change(const wcstring &op, const wcstring &var_name) { +static void handle_function_path_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); function_invalidate_path(); } -static void handle_complete_path_change(const wcstring &op, const wcstring &var_name) { +static void handle_complete_path_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); complete_invalidate_path(); } -static void handle_tz_change(const wcstring &op, const wcstring &var_name) { +static void handle_tz_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); handle_timezone(var_name.c_str()); } -static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name) { +static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); - fix_colon_delimited_var(var_name); + fix_colon_delimited_var(var_name, vars); } -static void handle_locale_change(const wcstring &op, const wcstring &var_name) { +static void handle_locale_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); UNUSED(var_name); init_locale(); } -static void handle_curses_change(const wcstring &op, const wcstring &var_name) { +static void handle_curses_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); UNUSED(var_name); guess_emoji_width(); @@ -852,9 +877,7 @@ static void setup_var_dispatch_table() { void env_init(const struct config_paths_t *paths /* or NULL */) { setup_var_dispatch_table(); - env_stack_t &vars = env_stack_t::principal(); - // Now the environment variable handling is set up, the next step is to insert valid data. - + env_stack_t &vars = env_stack_t::globals(); // Import environment variables. Walk backwards so that the first one out of any duplicates wins // (See issue #2784). wcstring key, val; @@ -872,25 +895,25 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { key.assign(key_and_val, 0, eql); val.assign(key_and_val, eql+1, wcstring::npos); if (is_read_only(key) || is_electric(key)) continue; - env_set(key, ENV_EXPORT | ENV_GLOBAL, {val}); + vars.set(key, ENV_EXPORT | ENV_GLOBAL, {val}); } } // Set the given paths in the environment, if we have any. if (paths != NULL) { - env_set_one(FISH_DATADIR_VAR, ENV_GLOBAL, paths->data); - env_set_one(FISH_SYSCONFDIR_VAR, ENV_GLOBAL, paths->sysconf); - env_set_one(FISH_HELPDIR_VAR, ENV_GLOBAL, paths->doc); - env_set_one(FISH_BIN_DIR, ENV_GLOBAL, paths->bin); + vars.set_one(FISH_DATADIR_VAR, ENV_GLOBAL, paths->data); + vars.set_one(FISH_SYSCONFDIR_VAR, ENV_GLOBAL, paths->sysconf); + vars.set_one(FISH_HELPDIR_VAR, ENV_GLOBAL, paths->doc); + vars.set_one(FISH_BIN_DIR, ENV_GLOBAL, paths->bin); } wcstring user_config_dir; path_get_config(user_config_dir); - env_set_one(FISH_CONFIG_DIR, ENV_GLOBAL, user_config_dir); + vars.set_one(FISH_CONFIG_DIR, ENV_GLOBAL, user_config_dir); wcstring user_data_dir; path_get_data(user_data_dir); - env_set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir); + vars.set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir); init_locale(); init_curses(); @@ -910,20 +933,20 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { setup_user(uid == 0); // Set up $IFS - this used to be in share/config.fish, but really breaks if it isn't done. - env_set_one(L"IFS", ENV_GLOBAL, L"\n \t"); + vars.set_one(L"IFS", ENV_GLOBAL, L"\n \t"); // Set up the version variable. wcstring version = str2wcstring(get_fish_version()); - env_set_one(L"version", ENV_GLOBAL, version); - env_set_one(L"FISH_VERSION", ENV_GLOBAL, version); + vars.set_one(L"version", ENV_GLOBAL, version); + vars.set_one(L"FISH_VERSION", ENV_GLOBAL, version); // Set the $fish_pid variable. - env_set_one(L"fish_pid", ENV_GLOBAL, to_string(getpid())); + vars.set_one(L"fish_pid", ENV_GLOBAL, to_string(getpid())); // Set the $hostname variable wcstring hostname = L"fish"; get_hostname_identifier(hostname); - env_set_one(L"hostname", ENV_GLOBAL, hostname); + vars.set_one(L"hostname", ENV_GLOBAL, hostname); // Set up SHLVL variable. Not we can't use env_get because SHLVL is read-only, and therefore was // not inherited from the environment. @@ -936,7 +959,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { nshlvl_str = to_string(shlvl_i + 1); } } - env_set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, nshlvl_str); + vars.set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, nshlvl_str); // Set up the HOME variable. // Unlike $USER, it doesn't seem that `su`s pass this along @@ -962,7 +985,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { } if (!retval && result && userinfo.pw_dir) { const wcstring dir = str2wcstring(userinfo.pw_dir); - env_set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT, dir); + vars.set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT, dir); } else { // We cannot get $HOME. This triggers warnings for history and config.fish already, // so it isn't necessary to warn here as well. @@ -1088,7 +1111,7 @@ static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mode, wcstring_list_t val) { ASSERT_IS_MAIN_THREAD(); env_mode_flags_t var_mode = input_var_mode; - bool has_changed_old = vars_stack().has_changed_exported; + bool has_changed_old = vars_stack().has_changed_exported(); int done = 0; if (val.size() == 1 && (key == L"PWD" || key == L"HOME")) { @@ -1220,7 +1243,7 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo event_fire(&ev); // debug(1, L"env_set: return from event firing"); - react_to_variable_change(L"SET", key); + react_to_variable_change(L"SET", key, *this); return ENV_OK; } @@ -1315,7 +1338,7 @@ int env_stack_t::remove(const wcstring &key, int var_mode) { if (is_exported) vars_stack().mark_changed_exported(); } - react_to_variable_change(L"ERASE", key); + react_to_variable_change(L"ERASE", key, *this); return erased ? ENV_OK : ENV_NOT_FOUND; } @@ -1423,14 +1446,6 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { return env_stack_t::principal().get(key, mode); } -int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { - return env_stack_t::principal().set(key, mode, std::move(vals)); -} - -int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { - return env_stack_t::principal().set_one(key, mode, std::move(val)); -} - void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } void env_set_read_limit() { return env_stack_t::principal().set_read_limit(); } @@ -1557,7 +1572,7 @@ static std::vector get_export_list(const var_table_t &envs) { } void var_stack_t::update_export_array_if_necessary() { - if (!this->has_changed_exported) { + if (!this->has_changed_exported()) { return; } @@ -1578,15 +1593,15 @@ void var_stack_t::update_export_array_if_necessary() { } } - export_array.set(get_export_list(vals)); - has_changed_exported = false; + export_array.emplace(get_export_list(vals)); } const char *const *env_stack_t::export_arr() { ASSERT_IS_MAIN_THREAD(); ASSERT_IS_NOT_FORKED_CHILD(); vars_stack().update_export_array_if_necessary(); - return vars_stack().export_array.get(); + assert(vars_stack().export_array && "Should have export array"); + return vars_stack().export_array->get(); } void env_stack_t::set_argv(const wchar_t *const *argv) { @@ -1603,6 +1618,7 @@ void env_stack_t::set_argv(const wchar_t *const *argv) { environment_t::~environment_t() = default; env_stack_t::~env_stack_t() = default; +env_stack_t::env_stack_t(env_stack_t &&) = default; null_environment_t::null_environment_t() = default; null_environment_t::~null_environment_t() = default; @@ -1611,11 +1627,22 @@ maybe_t null_environment_t::get(const wcstring &key, env_mode_flags_t } wcstring_list_t null_environment_t::get_names(int flags) const { return {}; } +env_stack_t env_stack_t::make_principal() { + const env_stack_t &gl = env_stack_t::globals(); + std::unique_ptr dup_stack = make_unique(gl.vars_stack().clone()); + return env_stack_t{std::move(dup_stack)}; +} + env_stack_t &env_stack_t::principal() { - static env_stack_t s_principal; + static env_stack_t s_principal = make_principal(); return s_principal; } +env_stack_t &env_stack_t::globals() { + static env_stack_t s_global; + return s_global; +} + env_vars_snapshot_t::env_vars_snapshot_t(const environment_t &source, const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; diff --git a/src/env.h b/src/env.h index e7e6855e8..32a425f5e 100644 --- a/src/env.h +++ b/src/env.h @@ -159,12 +159,6 @@ class null_environment_t : public environment_t { /// Gets the variable with the specified name, or none() if it does not exist. maybe_t env_get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT); -/// Sets the variable with the specified name to the given values. -int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals); - -/// Sets the variable with the specified name to a single value. -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(); @@ -183,12 +177,17 @@ class env_stack_t : public environment_t { bool try_remove(std::shared_ptr n, const wchar_t *key, int var_mode); std::shared_ptr get_node(const wcstring &key); + static env_stack_t make_principal(); + var_stack_t &vars_stack(); const var_stack_t &vars_stack() const; + explicit env_stack_t(std::unique_ptr vars_); env_stack_t(); ~env_stack_t() override; + env_stack_t(env_stack_t &&); + public: /// Gets the variable with the specified name, or none() if it does not exist. maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; @@ -247,6 +246,10 @@ class env_stack_t : public environment_t { // Compatibility hack; access the "environment stack" from back when there was just one. static env_stack_t &principal(); + + // Access a variable stack that only represents globals. + // Do not push or pop from this. + static env_stack_t &globals(); }; class env_vars_snapshot_t : public environment_t { diff --git a/src/exec.cpp b/src/exec.cpp index 3e7af698a..25143e863 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -377,7 +377,7 @@ void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &all_ios) { shlvl_str = to_string(shlvl - 1); } } - env_set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, shlvl_str); + vars.set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, std::move(shlvl_str)); // launch_process _never_ returns. launch_process_nofork(vars, j->processes.front().get()); @@ -1074,7 +1074,7 @@ bool exec_job(parser_t &parser, shared_ptr j) { j->set_flag(job_flag_t::CONSTRUCTED, true); if (!j->is_foreground()) { - env_set_one(L"last_pid", ENV_GLOBAL, to_string(j->pgid)); + parser.vars().set_one(L"last_pid", ENV_GLOBAL, to_string(j->pgid)); } if (exec_error) { diff --git a/src/fish.cpp b/src/fish.cpp index b9857e8af..4cf5e268c 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -417,7 +417,7 @@ int main(int argc, char **argv) { for (char **ptr = argv + my_optind; *ptr; ptr++) { list.push_back(str2wcstring(*ptr)); } - env_set(L"argv", ENV_DEFAULT, list); + parser.vars().set(L"argv", ENV_DEFAULT, list); const wcstring rel_filename = str2wcstring(file); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 9ec724d91..ba6bd38a6 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1777,7 +1777,7 @@ static void test_abbreviations() { {L"gx", L"git checkout"}, }; for (const auto &kv : abbreviations) { - int ret = env_set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second); + int ret = vars.set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second); if (ret != 0) err(L"Unable to set abbreviation variable"); } @@ -2539,7 +2539,7 @@ static void test_complete() { function_data_t fd; fd.name = L"testabbrsonetwothreefour"; function_add(fd, parser_t::principal_parser()); - int ret = env_set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); + int ret = pvars.set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); complete(L"testabbrsonetwothree", &completions, COMPLETION_REQUEST_DEFAULT, pvars); do_test(ret == 0); do_test(completions.size() == 2); @@ -2692,6 +2692,7 @@ static void perform_one_completion_cd_test(const wcstring &command, const wcstri // Testing test_autosuggest_suggest_special, in particular for properly handling quotes and // backslashes. static void test_autosuggest_suggest_special() { + auto &vars = parser_t::principal_parser().vars(); if (system("mkdir -p 'test/autosuggest_test/0foobar'")) err(L"mkdir failed"); if (system("mkdir -p 'test/autosuggest_test/1foo bar'")) err(L"mkdir failed"); if (system("mkdir -p 'test/autosuggest_test/2foo bar'")) err(L"mkdir failed"); @@ -2705,7 +2706,7 @@ static void test_autosuggest_suggest_special() { // This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia` // test below. // Fake out the home directory - env_set_one(L"HOME", ENV_LOCAL | ENV_EXPORT, L"test/test-home"); + parser_t::principal_parser().vars().set_one(L"HOME", ENV_LOCAL | ENV_EXPORT, L"test/test-home"); if (system("mkdir -p test/test-home/test_autosuggest_suggest_special/")) { err(L"mkdir failed"); } @@ -2740,7 +2741,7 @@ static void test_autosuggest_suggest_special() { perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/5", L"foo\"bar/", __LINE__); perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/5", L"foo\"bar/", __LINE__); - env_set_one(L"AUTOSUGGEST_TEST_LOC", ENV_LOCAL, wd); + vars.set_one(L"AUTOSUGGEST_TEST_LOC", ENV_LOCAL, wd); perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", L"foobar/", __LINE__); perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", L"l/", __LINE__); @@ -4755,14 +4756,15 @@ static void test_string() { /// Helper for test_timezone_env_vars(). long return_timezone_hour(time_t tstamp, const wchar_t *timezone) { + auto &vars = parser_t::principal_parser().vars(); struct tm ltime; char ltime_str[3]; char *str_ptr; size_t n; - env_set_one(L"TZ", ENV_EXPORT, timezone); + vars.set_one(L"TZ", ENV_EXPORT, timezone); - const auto var = env_get(L"TZ", ENV_DEFAULT); + const auto var = vars.get(L"TZ", ENV_DEFAULT); (void)var; localtime_r(&tstamp, <ime); diff --git a/src/function.cpp b/src/function.cpp index e8a424414..2a8e21710 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -353,7 +353,7 @@ void function_prepare_environment(env_stack_t &vars, const wcstring &name, const wchar_t *const *arg = argv; for (const wcstring &named_arg : props->named_arguments) { if (*arg) { - env_set_one(named_arg, ENV_LOCAL | ENV_USER, *arg); + vars.set_one(named_arg, ENV_LOCAL | ENV_USER, *arg); arg++; } else { vars.set_empty(named_arg, ENV_LOCAL | ENV_USER); @@ -362,6 +362,6 @@ void function_prepare_environment(env_stack_t &vars, const wcstring &name, } for (const auto &kv : inherited_vars) { - env_set(kv.first, ENV_LOCAL | ENV_USER, kv.second.as_list()); + vars.set(kv.first, ENV_LOCAL | ENV_USER, kv.second.as_list()); } } diff --git a/src/history.cpp b/src/history.cpp index f978914ed..685e61916 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -36,6 +36,7 @@ #include "io.h" #include "iothread.h" #include "lru.h" +#include "parser.h" #include "parse_constants.h" #include "parse_util.h" #include "path.h" @@ -1992,13 +1993,15 @@ void history_t::resolve_pending() { } -static bool private_mode = false; +static std::atomic private_mode{false}; + void start_private_mode() { - private_mode = true; - env_set_one(L"fish_history", ENV_GLOBAL, L""); - env_set_one(L"fish_private_mode", ENV_GLOBAL, L"1"); + private_mode.store(true); + auto &vars = parser_t::principal_parser().vars(); + vars.set_one(L"fish_history", ENV_GLOBAL, L""); + vars.set_one(L"fish_private_mode", ENV_GLOBAL, L"1"); } bool in_private_mode() { - return private_mode; + return private_mode.load(); } diff --git a/src/input.cpp b/src/input.cpp index 9374fb253..2fe26049d 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -164,8 +164,8 @@ static bool input_function_status; static int input_function_args_index = 0; /// Return the current bind mode. -wcstring input_get_bind_mode() { - auto mode = env_get(FISH_BIND_MODE_VAR); +wcstring input_get_bind_mode(const environment_t &vars) { + auto mode = vars.get(FISH_BIND_MODE_VAR); return mode ? mode->as_string() : DEFAULT_BIND_MODE; } @@ -173,9 +173,11 @@ wcstring input_get_bind_mode() { void input_set_bind_mode(const wcstring &bm) { // Only set this if it differs to not execute variable handlers all the time. // modes may not be empty - empty is a sentinel value meaning to not change the mode + ASSERT_IS_MAIN_THREAD(); + auto &vars = parser_t::principal_parser().vars(); assert(!bm.empty()); - if (input_get_bind_mode() != bm.c_str()) { - env_set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, bm); + if (input_get_bind_mode(vars) != bm.c_str()) { + vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, bm); } } @@ -417,7 +419,8 @@ void input_queue_ch(wint_t ch) { input_common_queue_ch(ch); } static void input_mapping_execute_matching_or_generic(bool allow_commands) { const input_mapping_t *generic = NULL; - const wcstring bind_mode = input_get_bind_mode(); + const auto &vars = parser_t::principal_parser().vars(); + const wcstring bind_mode = input_get_bind_mode(vars); for (auto& m : mapping_list) { if (m.mode != bind_mode) { diff --git a/src/input.h b/src/input.h index 553a68b19..757a243ac 100644 --- a/src/input.h +++ b/src/input.h @@ -12,6 +12,8 @@ #define FISH_BIND_MODE_VAR L"fish_bind_mode" +class environment_t; + wcstring describe_char(wint_t c); /// Set to true when the input subsytem has been initialized. @@ -74,7 +76,7 @@ bool input_mapping_get(const wcstring &sequence, const wcstring &mode, wcstring_ wcstring *out_new_mode); /// Return the current bind mode. -wcstring input_get_bind_mode(); +wcstring input_get_bind_mode(const environment_t &vars); /// Set the current bind mode. void input_set_bind_mode(const wcstring &bind_mode); diff --git a/src/maybe.h b/src/maybe.h index 9e8301ed5..3281dca02 100644 --- a/src/maybe.h +++ b/src/maybe.h @@ -53,6 +53,14 @@ class maybe_t { } } + // Construct a value in-place. + template + void emplace(Args &&... args) { + reset(); + filled = true; + new (storage) T(std::forward(args)...); + } + // Access the value. T &value() { assert(filled && "maybe_t does not have a value"); diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index c0a98e5cb..a960b8fa3 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -405,7 +405,7 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( break; } - int retval = env_set_one(for_var_name, ENV_DEFAULT | ENV_USER, val); + int retval = parser->vars().set_one(for_var_name, ENV_DEFAULT | ENV_USER, val); assert(retval == ENV_OK && "for loop variable should have been successfully set"); (void)retval; diff --git a/src/path.cpp b/src/path.cpp index a4a8e1c3f..55b32dee6 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -266,10 +266,11 @@ wcstring path_apply_working_directory(const wcstring &path, const wcstring &work static void maybe_issue_path_warning(const wcstring &which_dir, const wcstring &custom_error_msg, bool using_xdg, const wcstring &xdg_var, const wcstring &path, int saved_errno) { + auto &vars = env_stack_t::globals(); wcstring warning_var_name = L"_FISH_WARNED_" + which_dir; - auto var = env_get(warning_var_name, ENV_GLOBAL | ENV_EXPORT); + auto var = vars.get(warning_var_name, ENV_GLOBAL | ENV_EXPORT); if (!var) return; - env_set_one(warning_var_name, ENV_GLOBAL | ENV_EXPORT, L"1"); + vars.set_one(warning_var_name, ENV_GLOBAL | ENV_EXPORT, L"1"); debug(0, custom_error_msg.c_str()); if (path.empty()) { diff --git a/src/reader.cpp b/src/reader.cpp index a994d9dfe..6784c4138 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -397,6 +397,11 @@ class reader_data_t { /// Expand abbreviations at the current cursor position, minus backtrack_amt. bool expand_abbreviation_as_necessary(size_t cursor_backtrack) const; + /// Return the variable set used for e.g. command duration. + env_stack_t &vars() { return parser_t::principal_parser().vars(); } + + const env_stack_t &vars() const { return parser_t::principal_parser().vars(); } + /// Constructor reader_data_t(history_t *hist) : history(hist) {} }; @@ -903,10 +908,12 @@ static void exec_prompt() { void reader_init() { DIE_ON_FAILURE(pthread_key_create(&generation_count_key, NULL)); + auto &vars = parser_t::principal_parser().vars(); + // Ensure this var is present even before an interactive command is run so that if it is used // in a function like `fish_prompt` or `fish_right_prompt` it is defined at the time the first // prompt is written. - env_set_one(ENV_CMD_DURATION, ENV_UNEXPORT, L"0"); + vars.set_one(ENV_CMD_DURATION, ENV_UNEXPORT, L"0"); // Save the initial terminal mode. tcgetattr(STDIN_FILENO, &terminal_mode_on_startup); @@ -1825,7 +1832,7 @@ static void reader_interactive_init() { invalidate_termsize(); // For compatibility with fish 2.0's $_, now replaced with `status current-command` - env_set_one(L"_", ENV_GLOBAL, L"fish"); + parser_t::principal_parser().vars().set_one(L"_", ENV_GLOBAL, L"fish"); } /// Destroy data for interactive use. @@ -1998,7 +2005,7 @@ bool reader_get_selection(size_t *start, size_t *len) { return result; } -void set_env_cmd_duration(struct timeval *after, struct timeval *before) { +void set_env_cmd_duration(struct timeval *after, struct timeval *before, env_stack_t &vars) { time_t secs = after->tv_sec - before->tv_sec; suseconds_t usecs = after->tv_usec - before->tv_usec; wchar_t buf[16]; @@ -2009,7 +2016,7 @@ void set_env_cmd_duration(struct timeval *after, struct timeval *before) { } swprintf(buf, 16, L"%d", (secs * 1000) + (usecs / 1000)); - env_set_one(ENV_CMD_DURATION, ENV_UNEXPORT, buf); + vars.set_one(ENV_CMD_DURATION, ENV_UNEXPORT, buf); } void reader_run_command(parser_t &parser, const wcstring &cmd) { @@ -2018,7 +2025,7 @@ void reader_run_command(parser_t &parser, const wcstring &cmd) { wcstring ft = tok_first(cmd); // For compatibility with fish 2.0's $_, now replaced with `status current-command` - if (!ft.empty()) env_set_one(L"_", ENV_GLOBAL, ft); + if (!ft.empty()) parser.vars().set_one(L"_", ENV_GLOBAL, ft); reader_write_title(cmd); @@ -2033,12 +2040,12 @@ void reader_run_command(parser_t &parser, const wcstring &cmd) { // update the execution duration iff a command is requested for execution // issue - #4926 - if (!ft.empty()) set_env_cmd_duration(&time_after, &time_before); + if (!ft.empty()) set_env_cmd_duration(&time_after, &time_before, parser.vars()); term_steal(); // For compatibility with fish 2.0's $_, now replaced with `status current-command` - env_set_one(L"_", ENV_GLOBAL, program_name); + parser.vars().set_one(L"_", ENV_GLOBAL, program_name); #ifdef HAVE__PROC_SELF_STAT proc_update_jiffies(); From 03b92ffe001bc8966022e2658de63e8c47270279 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 16 Sep 2018 04:05:17 -0700 Subject: [PATCH 15/24] Clean up path_get_cdpath and path_can_be_implicit_cd --- src/builtin_cd.cpp | 7 ++++--- src/highlight.cpp | 7 +++---- src/parse_execution.cpp | 4 ++-- src/path.cpp | 33 ++++++++++++++------------------- src/path.h | 25 +++++++++++-------------- 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/builtin_cd.cpp b/src/builtin_cd.cpp index 5a0432e63..260e31844 100644 --- a/src/builtin_cd.cpp +++ b/src/builtin_cd.cpp @@ -34,8 +34,6 @@ int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } wcstring dir_in; - wcstring dir; - if (argv[optind]) { dir_in = argv[optind]; } else { @@ -47,7 +45,9 @@ 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, parser.vars().get_pwd_slash())) { + wcstring pwd = parser.vars().get_pwd_slash(); + maybe_t mdir = path_get_cdpath(dir_in, pwd, parser.vars()); + if (!mdir) { if (errno == ENOTDIR) { streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str()); } else if (errno == ENOENT) { @@ -64,6 +64,7 @@ int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { return STATUS_CMD_ERROR; } + const wcstring &dir = *mdir; wcstring norm_dir = normalize_path(dir); diff --git a/src/highlight.cpp b/src/highlight.cpp index 7b8e4ddff..2b49d64da 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -350,9 +350,8 @@ bool autosuggest_validate_from_history(const history_item_t &item, bool is_help = string_prefixes_string(cd_dir, L"--help") || string_prefixes_string(cd_dir, L"-h"); if (!is_help) { - wcstring path; - bool can_cd = path_get_cdpath(cd_dir, &path, working_directory, vars); - if (can_cd && !paths_are_same_file(working_directory, path)) { + auto path = path_get_cdpath(cd_dir, working_directory, vars); + if (path && !paths_are_same_file(working_directory, *path)) { suggestionOK = true; } } @@ -1021,7 +1020,7 @@ static bool command_is_valid(const wcstring &cmd, enum parse_statement_decoratio // Implicit cd if (!is_valid && implicit_cd_ok) { - is_valid = path_can_be_implicit_cd(cmd, working_directory, NULL, vars); + is_valid = path_as_implicit_cd(cmd, working_directory, vars).has_value(); } // Return what we got. diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index a960b8fa3..f5e4d4aa2 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -813,9 +813,9 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( if (args_from_cmd_expansion.empty() && !args.try_get_child() && !args.try_get_child()) { // 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, parser->vars().get_pwd_slash(), &implicit_cd_path); + path_as_implicit_cd(cmd, parser->vars().get_pwd_slash(), parser->vars()) + .has_value(); } } diff --git a/src/path.cpp b/src/path.cpp index 55b32dee6..6a7b3673f 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -157,13 +157,12 @@ wcstring_list_t path_get_paths(const wcstring &cmd) { return paths; } -bool path_get_cdpath(const wcstring &dir, wcstring *out, const wcstring &wd, - const environment_t &env_vars) { +maybe_t path_get_cdpath(const wcstring &dir, const wcstring &wd, + const environment_t &env_vars) { int err = ENOENT; - if (dir.empty()) return false; - - assert(!wd.empty() && wd.back() == L'/'); + if (dir.empty()) return none(); + assert(wd.empty() || wd.back() == L'/'); wcstring_list_t paths; if (dir.at(0) == L'/') { // Absolute path. @@ -197,36 +196,32 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wcstring &wd, } } - bool success = false; for (const wcstring &dir : paths) { struct stat buf; if (wstat(dir, &buf) == 0) { if (S_ISDIR(buf.st_mode)) { - success = true; - if (out) out->assign(dir); - break; - } else { - err = ENOTDIR; + return dir; } + err = ENOTDIR; } } - if (!success) errno = err; - return success; + errno = err; + return none(); } -bool path_can_be_implicit_cd(const wcstring &path, const wcstring &wd, wcstring *out_path, - const environment_t &vars) { +maybe_t path_as_implicit_cd(const wcstring &path, const wcstring &wd, + const environment_t &vars) { wcstring exp_path = path; expand_tilde(exp_path); - - bool result = false; if (string_prefixes_string(L"/", exp_path) || string_prefixes_string(L"./", exp_path) || string_prefixes_string(L"../", exp_path) || string_suffixes_string(L"/", exp_path) || exp_path == L"..") { - result = path_get_cdpath(exp_path, out_path, wd, vars); + // These paths can be implicit cd, so see if you cd to the path. Note that a single period + // cannot (that's used for sourcing files anyways). + return path_get_cdpath(exp_path, wd, vars); } - return result; + return none(); } // If the given path looks like it's relative to the working directory, then prepend that working diff --git a/src/path.h b/src/path.h index e6c2174aa..3d9b596d0 100644 --- a/src/path.h +++ b/src/path.h @@ -46,28 +46,25 @@ bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL, wcstring_list_t path_get_paths(const wcstring &cmd); /// Returns the full path of the specified directory, using the CDPATH variable as a list of base -/// directories for relative paths. The returned string is allocated using halloc and the specified -/// context. +/// directories for relative paths. /// -/// If no valid path is found, null is returned and errno is set to ENOTDIR if at least one such +/// If no valid path is found, false is returned and errno is set to ENOTDIR if at least one such /// path was found, but it did not point to a directory, EROTTEN if a arotten symbolic link was /// found, or ENOENT if no file of the specified name was found. If both a rotten symlink and a file /// are found, it is undefined which error status will be returned. /// /// \param dir The name of the directory. /// \param out_or_NULL If non-NULL, return the path to the resolved directory -/// \param wd The working directory, which should have a slash appended at the end. -/// \param vars The environment variable snapshot to use (for the CDPATH variable) -/// \return 0 if the command can not be found, the path of the command otherwise. The path should be -/// free'd with free(). -bool path_get_cdpath(const wcstring &dir, wcstring *out_or_NULL, const wcstring &wd, - const environment_t &vars = env_vars_snapshot_t::current()); +/// \param wd The working directory. The working directory should have a slash appended at the end. +/// \param vars The environment variables to use (for the CDPATH variable) +/// \return the command, or none() if it could not be found. +maybe_t path_get_cdpath(const wcstring &dir, const wcstring &wd, + const environment_t &vars); -/// Returns whether the path can be used for an implicit cd command; if so, also returns the path by -/// reference (if desired). This requires it to start with one of the allowed prefixes (., .., ~) -/// and resolve to a directory. -bool path_can_be_implicit_cd(const wcstring &path, const wcstring &wd, wcstring *out_path = NULL, - const environment_t &vars = env_vars_snapshot_t::current()); +/// Returns the path resolved as an implicit cd command, or none() if none. This requires it to +/// start with one of the allowed prefixes (., .., ~) and resolve to a directory. +maybe_t path_as_implicit_cd(const wcstring &path, const wcstring &wd, + const environment_t &vars); /// Remove double slashes and trailing slashes from a path, e.g. transform foo//bar/ into foo/bar. /// The string is modified in-place. From abcd24f716f664d45e333e4776cec8e5dfd89997 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 16 Sep 2018 12:48:50 -0700 Subject: [PATCH 16/24] Eliminate env_snapshot_t::current() These uses are better served by passing in the real environment stack, now that we have environment_t as a shared base class. --- src/builtin_command.cpp | 3 ++- src/builtin_read.cpp | 1 - src/complete.cpp | 17 +++++++++-------- src/env.cpp | 12 ------------ src/env.h | 6 ------ src/fish_indent.cpp | 2 +- src/highlight.cpp | 2 +- src/parse_execution.cpp | 2 +- src/path.h | 5 ++--- 9 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/builtin_command.cpp b/src/builtin_command.cpp index 50b985691..6a04a1262 100644 --- a/src/builtin_command.cpp +++ b/src/builtin_command.cpp @@ -10,6 +10,7 @@ #include "common.h" #include "fallback.h" // IWYU pragma: keep #include "io.h" +#include "parser.h" #include "path.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep @@ -102,7 +103,7 @@ int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } } else { wcstring path; - if (path_get_path(command_name, &path)) { + if (path_get_path(command_name, &path, parser.vars())) { if (!opts.quiet) streams.out.append_format(L"%ls\n", path.c_str()); ++found; } diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index bfe2e0089..1bab82707 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -455,7 +455,6 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { auto clear_remaining_vars = [&] () { while (vars_left()) { parser.vars().set_empty(*var_ptr, opts.place); - // env_set_one(*var_ptr, opts.place, L""); ++var_ptr; } }; diff --git a/src/complete.cpp b/src/complete.cpp index 5e1ee9f9c..f9a633383 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -495,18 +495,19 @@ void complete_remove_all(const wcstring &cmd, bool cmd_is_path) { } /// Find the full path and commandname from a command string 'str'. -static void parse_cmd_string(const wcstring &str, wcstring &path, wcstring &cmd) { - if (!path_get_path(str, &path)) { +static void parse_cmd_string(const wcstring &str, wcstring *path, wcstring *cmd, + const environment_t &vars) { + if (!path_get_path(str, path, vars)) { /// Use the empty string as the 'path' for commands that can not be found. - path = L""; + *path = L""; } // Make sure the path is not included in the command. size_t last_slash = str.find_last_of(L'/'); if (last_slash != wcstring::npos) { - cmd = str.substr(last_slash + 1); + *cmd = str.substr(last_slash + 1); } else { - cmd = str; + *cmd = str; } } @@ -865,7 +866,7 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt, bool use_common = 1, use_files = 1; wcstring cmd, path; - parse_cmd_string(cmd_orig, path, cmd); + parse_cmd_string(cmd_orig, &path, &cmd, vars); // mqudsi: run_on_main_thread() already just runs `func` if we're on the main thread, // but it makes a kcall to get the current thread id to ascertain that. Perhaps even @@ -891,8 +892,8 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt, // may be faster, path_get_path can potentially do a lot of FS/IO access, so env.get() + // function_exists() should still be faster. head_exists = - head_exists || - path_get_path(cmd_orig, nullptr); // use cmd_orig here as it is potentially pathed + head_exists || path_get_path(cmd_orig, nullptr, + vars); // use cmd_orig here as it is potentially pathed } if (!head_exists) { diff --git a/src/env.cpp b/src/env.cpp index f3fa69416..4e7d9738c 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1658,19 +1658,7 @@ env_vars_snapshot_t::env_vars_snapshot_t(const environment_t &source, const wcha env_vars_snapshot_t::~env_vars_snapshot_t() = default; -// The "current" variables are not a snapshot at all, but instead trampoline to env_get, etc. -// We identify the current snapshot based on pointer values. -// This is an ugly thing that has to go away. -const env_vars_snapshot_t env_vars_snapshot_t::s_current; -const env_vars_snapshot_t &env_vars_snapshot_t::current() { return s_current; } - -bool env_vars_snapshot_t::is_current() const { return this == &s_current; } - maybe_t env_vars_snapshot_t::get(const wcstring &key, env_mode_flags_t mode) const { - // If we represent the current state, bounce to env_get. - if (this->is_current()) { - return env_get(key, mode); - } auto iter = vars.find(key); if (iter == vars.end()) return none(); return iter->second; diff --git a/src/env.h b/src/env.h index 32a425f5e..ef9a9b5ae 100644 --- a/src/env.h +++ b/src/env.h @@ -255,9 +255,6 @@ class env_stack_t : public environment_t { class env_vars_snapshot_t : public environment_t { std::map vars; wcstring_list_t names; - bool is_current() const; - - static const env_vars_snapshot_t s_current; public: env_vars_snapshot_t() = default; @@ -270,9 +267,6 @@ class env_vars_snapshot_t : public environment_t { wcstring_list_t get_names(int flags) const override; - // Returns the fake snapshot representing the live variables array. - static const env_vars_snapshot_t ¤t(); - // Vars necessary for highlighting. static const wchar_t *const highlighting_keys[]; diff --git a/src/fish_indent.cpp b/src/fish_indent.cpp index 428bd417f..f985c4b19 100644 --- a/src/fish_indent.cpp +++ b/src/fish_indent.cpp @@ -529,7 +529,7 @@ int main(int argc, char *argv[]) { std::vector colors; if (output_type != output_type_plain_text) { highlight_shell_no_io(output_wtext, colors, output_wtext.size(), NULL, - env_vars_snapshot_t::current()); + env_stack_t::globals()); } std::string colored_output; diff --git a/src/highlight.cpp b/src/highlight.cpp index 2b49d64da..2d1179140 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -364,7 +364,7 @@ bool autosuggest_validate_from_history(const history_item_t &item, // Not handled specially so handle it here. bool cmd_ok = false; - if (path_get_path(parsed_command, NULL)) { + if (path_get_path(parsed_command, NULL, vars)) { cmd_ok = true; } else if (builtin_exists(parsed_command) || function_exists_no_autoload(parsed_command, vars)) { diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index f5e4d4aa2..a2d739277 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -800,7 +800,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( wcstring path_to_external_command; if (process_type == EXTERNAL || process_type == INTERNAL_EXEC) { // Determine the actual command. This may be an implicit cd. - bool has_command = path_get_path(cmd, &path_to_external_command); + bool has_command = path_get_path(cmd, &path_to_external_command, parser->vars()); // If there was no command, then we care about the value of errno after checking for it, to // distinguish between e.g. no file vs permissions problem. diff --git a/src/path.h b/src/path.h index 3d9b596d0..48ac8d654 100644 --- a/src/path.h +++ b/src/path.h @@ -34,13 +34,12 @@ bool path_get_data(wcstring &path); /// Args: /// cmd - The name of the executable. /// output_or_NULL - If non-NULL, store the full path. -/// vars - The environment variables snapshot to use +/// vars - The environment variables to use /// /// Returns: /// false if the command can not be found else true. The result /// should be freed with free(). -bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL, - const environment_t &vars = env_vars_snapshot_t::current()); +bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL, const environment_t &vars); /// Return all the paths that match the given command. wcstring_list_t path_get_paths(const wcstring &cmd); From 3eb15109cfe24e78334d7c227732bc8963443fd6 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 17 Sep 2018 21:26:21 -0700 Subject: [PATCH 17/24] Instantize env_set in env.h and env.cpp --- src/env.cpp | 83 +++++++++++++++++++++++--------------------- src/env.h | 2 +- src/input_common.cpp | 4 +-- src/input_common.h | 2 +- 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 4e7d9738c..5e1d4b6e9 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -112,8 +112,8 @@ static const wcstring_list_t locale_variables({L"LANG", L"LANGUAGE", L"LC_ALL", static const wcstring_list_t curses_variables({L"TERM", L"TERMINFO", L"TERMINFO_DIRS"}); // Some forward declarations to make it easy to logically group the code. -static void init_locale(); -static void init_curses(); +static void init_locale(const environment_t &vars); +static void init_curses(const environment_t &vars); // Struct representing one level in the function variable stack. // Only our variable stack should create and destroy these @@ -264,8 +264,10 @@ void var_stack_t::pop() { } } - if (locale_changed) init_locale(); - if (curses_changed) init_curses(); + // TODO: instantize this locale and curses + const auto &vars = env_stack_t::principal(); + if (locale_changed) init_locale(vars); + if (curses_changed) init_curses(vars); } env_node_ref_t var_stack_t::next_scope_to_search(const env_node_ref_t &node) const { @@ -346,9 +348,9 @@ static mode_t get_umask() { } /// Properly sets all timezone information. -static void handle_timezone(const wchar_t *env_var_name) { +static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) { // const env_var_t var = env_get(env_var_name, ENV_EXPORT); - const auto var = env_get(env_var_name, ENV_DEFAULT); + const auto var = vars.get(env_var_name, ENV_DEFAULT); debug(2, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name, !var ? L"MISSING" : var->as_string().c_str()); const std::string &name = wcs2string(env_var_name); @@ -383,13 +385,13 @@ static void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) } /// Initialize the locale subsystem. -static void init_locale() { +static void init_locale(const environment_t &vars) { // We have to make a copy because the subsequent setlocale() call to change the locale will // invalidate the pointer from the this setlocale() call. char *old_msg_locale = strdup(setlocale(LC_MESSAGES, NULL)); for (const auto &var_name : locale_variables) { - const auto var = env_get(var_name, ENV_EXPORT); + const auto var = vars.get(var_name, ENV_EXPORT); const std::string &name = wcs2string(var_name); if (var.missing_or_empty()) { debug(5, L"locale var %s missing or empty", name.c_str()); @@ -434,8 +436,8 @@ bool term_supports_setting_title() { return can_set_term_title; } /// terminal title if the underlying terminal does so, but will print garbage on terminals that /// don't. Since we can't see the underlying terminal below screen there is no way to fix this. static const wchar_t *const title_terms[] = {L"xterm", L"screen", L"tmux", L"nxterm", L"rxvt"}; -static bool does_term_support_setting_title() { - const auto term_var = env_get(L"TERM"); +static bool does_term_support_setting_title(const environment_t &vars) { + const auto term_var = vars.get(L"TERM"); if (term_var.missing_or_empty()) return false; const wcstring term_str = term_var->as_string(); @@ -460,11 +462,11 @@ static bool does_term_support_setting_title() { } /// Updates our idea of whether we support term256 and term24bit (see issue #10222). -static void update_fish_color_support() { +static void update_fish_color_support(const environment_t &vars) { // Detect or infer term256 support. If fish_term256 is set, we respect it; // otherwise infer it from the TERM variable or use terminfo. - auto fish_term256 = env_get(L"fish_term256"); - auto term_var = env_get(L"TERM"); + auto fish_term256 = vars.get(L"fish_term256"); + auto term_var = vars.get(L"TERM"); wcstring term = term_var.missing_or_empty() ? L"" : term_var->as_string(); bool support_term256 = false; // default to no support if (!fish_term256.missing_or_empty()) { @@ -476,8 +478,8 @@ static void update_fish_color_support() { debug(2, L"256 color support enabled for '256color' in TERM"); } else if (term.find(L"xterm") != wcstring::npos) { // Assume that all xterms are 256, except for OS X SnowLeopard - const auto prog_var = env_get(L"TERM_PROGRAM"); - const auto progver_var = env_get(L"TERM_PROGRAM_VERSION"); + const auto prog_var = vars.get(L"TERM_PROGRAM"); + const auto progver_var = vars.get(L"TERM_PROGRAM_VERSION"); wcstring term_program = prog_var.missing_or_empty() ? L"" : prog_var->as_string(); if (term_program == L"Apple_Terminal" && !progver_var.missing_or_empty()) { // OS X Lion is version 300+, it has 256 color support @@ -497,7 +499,7 @@ static void update_fish_color_support() { debug(2, L"256 color support not enabled (yet)"); } - auto fish_term24bit = env_get(L"fish_term24bit"); + auto fish_term24bit = vars.get(L"fish_term24bit"); bool support_term24bit; if (!fish_term24bit.missing_or_empty()) { support_term24bit = from_string(fish_term24bit->as_string()); @@ -520,7 +522,8 @@ static void update_fish_color_support() { static bool initialize_curses_using_fallback(const char *term) { // If $TERM is already set to the fallback name we're about to use there isn't any point in // seeing if the fallback name can be used. - auto term_var = env_get(L"TERM"); + auto &vars = env_stack_t::globals(); + auto term_var = vars.get(L"TERM"); if (term_var.missing_or_empty()) return false; auto term_env = wcs2string(term_var->as_string()); @@ -547,12 +550,13 @@ static void init_path_vars() { /// Update the value of g_guessed_fish_emoji_width static void guess_emoji_width() { wcstring term; - if (auto term_var = env_get(L"TERM_PROGRAM")) { + auto &vars = env_stack_t::globals(); + if (auto term_var = vars.get(L"TERM_PROGRAM")) { term = term_var->as_string(); } double version = 0; - if (auto version_var = env_get(L"TERM_PROGRAM_VERSION")) { + if (auto version_var = vars.get(L"TERM_PROGRAM_VERSION")) { std::string narrow_version = wcs2string(version_var->as_string()); version = strtod(narrow_version.c_str(), NULL); } @@ -569,10 +573,10 @@ static void guess_emoji_width() { } /// Initialize the curses subsystem. -static void init_curses() { +static void init_curses(const environment_t &vars) { for (const auto &var_name : curses_variables) { std::string name = wcs2string(var_name); - const auto var = env_get(var_name, ENV_EXPORT); + const auto var = vars.get(var_name, ENV_EXPORT); if (var.missing_or_empty()) { debug(2, L"curses var %s missing or empty", name.c_str()); unsetenv(name.c_str()); @@ -585,7 +589,7 @@ static void init_curses() { int err_ret; if (setupterm(NULL, STDOUT_FILENO, &err_ret) == ERR) { - auto term = env_get(L"TERM"); + auto term = vars.get(L"TERM"); if (is_interactive_session) { debug(1, _(L"Could not set up terminal.")); if (term.missing_or_empty()) { @@ -601,9 +605,9 @@ static void init_curses() { } } - can_set_term_title = does_term_support_setting_title(); + can_set_term_title = does_term_support_setting_title(vars); term_has_xn = tigetflag((char *)"xenl") == 1; // does terminal have the eat_newline_glitch - update_fish_color_support(); + update_fish_color_support(vars); // Invalidate the cached escape sequences since they may no longer be valid. cached_layouts.clear(); curses_initialized = true; @@ -679,7 +683,7 @@ void env_stack_t::set_pwd_from_getcwd() { /// Allow the user to override the limit on how much data the `read` command will process. /// This is primarily for testing but could be used by users in special situations. void env_stack_t::set_read_limit() { - auto read_byte_limit_var = env_get(L"fish_read_limit"); + auto read_byte_limit_var = this->get(L"fish_read_limit"); if (!read_byte_limit_var.missing_or_empty()) { size_t limit = fish_wcstoull(read_byte_limit_var->as_string().c_str()); if (errno) { @@ -708,14 +712,15 @@ wcstring environment_t::get_pwd_slash() const { /// Set up the USER variable. static void setup_user(bool force) { - if (force || env_get(L"USER").missing_or_empty()) { + auto &vars = env_stack_t::globals(); + if (force || vars.get(L"USER").missing_or_empty()) { struct passwd userinfo; struct passwd *result; char buf[8192]; int retval = getpwuid_r(getuid(), &userinfo, buf, sizeof(buf), &result); if (!retval && result) { const wcstring uname = str2wcstring(userinfo.pw_name); - env_stack_t::globals().set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname); + vars.set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname); } } } @@ -755,7 +760,7 @@ static void handle_fish_term_change(const wcstring &op, const wcstring &var_name env_stack_t &vars) { UNUSED(op); UNUSED(var_name); - update_fish_color_support(); + update_fish_color_support(vars); reader_react_to_color_change(); } @@ -763,7 +768,7 @@ static void handle_escape_delay_change(const wcstring &op, const wcstring &var_n env_stack_t &vars) { UNUSED(op); UNUSED(var_name); - update_wait_on_escape_ms(); + update_wait_on_escape_ms(vars); } static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name, @@ -771,7 +776,7 @@ static void handle_change_emoji_width(const wcstring &op, const wcstring &var_na (void)op; (void)var_name; int new_width = 0; - if (auto width_str = env_get(L"fish_emoji_width")) { + if (auto width_str = vars.get(L"fish_emoji_width")) { new_width = fish_wcstol(width_str->as_string().c_str()); } g_fish_emoji_width = std::max(0, new_width); @@ -782,7 +787,7 @@ static void handle_change_ambiguous_width(const wcstring &op, const wcstring &va (void)op; (void)var_name; int new_width = 1; - if (auto width_str = env_get(L"fish_ambiguous_width")) { + if (auto width_str = vars.get(L"fish_ambiguous_width")) { new_width = fish_wcstol(width_str->as_string().c_str()); } g_fish_ambiguous_width = std::max(0, new_width); @@ -825,7 +830,7 @@ static void handle_complete_path_change(const wcstring &op, const wcstring &var_ static void handle_tz_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); - handle_timezone(var_name.c_str()); + handle_timezone(var_name.c_str(), vars); } static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name, @@ -837,14 +842,14 @@ static void handle_magic_colon_var_change(const wcstring &op, const wcstring &va static void handle_locale_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); UNUSED(var_name); - init_locale(); + init_locale(vars); } static void handle_curses_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); UNUSED(var_name); guess_emoji_width(); - init_curses(); + init_curses(vars); } /// Populate the dispatch table used by `react_to_variable_change()` to efficiently call the @@ -915,8 +920,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { path_get_data(user_data_dir); vars.set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir); - init_locale(); - init_curses(); + init_locale(vars); + init_curses(vars); init_input(); init_path_vars(); guess_emoji_width(); @@ -966,8 +971,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // if the target user is root, unless "--preserve-environment" is used. // Since that is an explicit choice, we should allow it to enable e.g. // env HOME=(mktemp -d) su --preserve-environment fish - if (env_get(L"HOME").missing_or_empty()) { - auto user_var = env_get(L"USER"); + if (vars.get(L"HOME").missing_or_empty()) { + auto user_var = vars.get(L"USER"); if (!user_var.missing_or_empty()) { char *unam_narrow = wcs2str(user_var->as_string()); struct passwd userinfo; @@ -977,7 +982,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { if (retval || !result) { // Maybe USER is set but it's bogus. Reset USER from the db and try again. setup_user(true); - user_var = env_get(L"USER"); + user_var = vars.get(L"USER"); if (!user_var.missing_or_empty()) { unam_narrow = wcs2str(user_var->as_string()); retval = getpwnam_r(unam_narrow, &userinfo, buf, sizeof(buf), &result); diff --git a/src/env.h b/src/env.h index ef9a9b5ae..f5bcacaa0 100644 --- a/src/env.h +++ b/src/env.h @@ -168,7 +168,7 @@ void env_set_read_limit(); /// A environment stack of scopes. This is the main class that tracks fish variables. struct var_stack_t; class env_node_t; -class env_stack_t : public environment_t { +class env_stack_t final : public environment_t { friend class parser_t; std::unique_ptr vars_; diff --git a/src/input_common.cpp b/src/input_common.cpp index 59e0e77d7..a9fc3bc11 100644 --- a/src/input_common.cpp +++ b/src/input_common.cpp @@ -158,8 +158,8 @@ static wint_t readb() { // Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being // set. -void update_wait_on_escape_ms() { - auto escape_time_ms = env_get(L"fish_escape_delay_ms"); +void update_wait_on_escape_ms(const environment_t &vars) { + auto escape_time_ms = vars.get(L"fish_escape_delay_ms"); if (escape_time_ms.missing_or_empty()) { wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT; return; diff --git a/src/input_common.h b/src/input_common.h index ef4550cba..c80bf6f97 100644 --- a/src/input_common.h +++ b/src/input_common.h @@ -89,7 +89,7 @@ void input_common_init(int (*ih)()); void input_common_destroy(); /// Adjust the escape timeout. -void update_wait_on_escape_ms(); +void update_wait_on_escape_ms(const environment_t &vars); /// Function used by input_readch to read bytes from stdin until enough bytes have been read to /// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously been From 50c83463f1f5334671495849cc814c685a9ac4da Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 18 Sep 2018 09:18:47 -0700 Subject: [PATCH 18/24] Switch some uses of env_get to instanced environment_t --- src/builtin_history.cpp | 3 ++- src/builtin_read.cpp | 3 ++- src/env.cpp | 4 ++-- src/expand.cpp | 2 +- src/history.cpp | 9 ++++----- src/history.h | 6 +++++- src/reader.cpp | 9 +++++---- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/builtin_history.cpp b/src/builtin_history.cpp index d86311531..bd0a49fa0 100644 --- a/src/builtin_history.cpp +++ b/src/builtin_history.cpp @@ -15,6 +15,7 @@ #include "fallback.h" // IWYU pragma: keep #include "history.h" #include "io.h" +#include "parser.h" #include "reader.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep @@ -218,7 +219,7 @@ int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // Use the default history if we have none (which happens if invoked non-interactively, e.g. // from webconfig.py. history_t *history = reader_get_history(); - if (!history) history = &history_t::history_with_name(history_session_id()); + if (!history) history = &history_t::history_with_name(history_session_id(parser.vars())); // If a history command hasn't already been specified via a flag check the first word. // Note that this can be simplified after we eliminate allowing subcommands as flags. diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index 1bab82707..0ea09bd94 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -204,7 +204,8 @@ static int read_interactive(wcstring &buff, int nchars, bool shell, bool silent, int exit_res = STATUS_CMD_OK; const wchar_t *line; - wcstring read_history_ID = history_session_id(); + auto &vars = env_stack_t::principal(); + wcstring read_history_ID = history_session_id(vars); if (!read_history_ID.empty()) read_history_ID += L"_read"; reader_push(read_history_ID); diff --git a/src/env.cpp b/src/env.cpp index 5e1d4b6e9..07c8e3a41 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -811,7 +811,7 @@ static void handle_fish_history_change(const wcstring &op, const wcstring &var_n env_stack_t &vars) { UNUSED(op); UNUSED(var_name); - reader_change_history(history_session_id().c_str()); + reader_change_history(history_session_id(vars).c_str()); } static void handle_function_path_change(const wcstring &op, const wcstring &var_name, @@ -1391,7 +1391,7 @@ maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) history_t *history = reader_get_history(); if (!history) { - history = &history_t::history_with_name(history_session_id()); + history = &history_t::history_with_name(history_session_id(*this)); } wcstring_list_t result; if (history) history->get_history(result); diff --git a/src/expand.cpp b/src/expand.cpp index 4b546839e..c9bba8fcd 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -353,7 +353,7 @@ static bool expand_variables(wcstring instr, std::vector *out, siz // Note reader_get_history may return null, if we are running non-interactively (e.g. from // web_config). if (is_main_thread()) { - history = &history_t::history_with_name(history_session_id()); + history = &history_t::history_with_name(history_session_id(env_stack_t::principal())); } } else if (var_name != wcstring{VARIABLE_EXPAND_EMPTY}) { var = env_get(var_name); diff --git a/src/history.cpp b/src/history.cpp index 685e61916..920b4c46d 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -1694,6 +1694,8 @@ void history_t::clear() { this->clear_file_state(); } +bool history_t::is_default() const { return name == DFLT_FISH_HISTORY_SESSION_ID; } + bool history_t::is_empty() { scoped_lock locker(lock); @@ -1804,9 +1806,6 @@ static bool should_import_bash_history_line(const std::string &line) { /// commands. We can't actually parse bash syntax and the bash history file does not unambiguously /// encode multiline commands. void history_t::populate_from_bash(FILE *stream) { - // We do not import bash history if an alternative fish history file is being used. - if (history_session_id() != DFLT_FISH_HISTORY_SESSION_ID) return; - // Process the entire history file until EOF is observed. bool eof = false; while (!eof) { @@ -1867,10 +1866,10 @@ void history_collection_t::save() { void history_save_all() { histories.save(); } /// Return the prefix for the files to be used for command and read history. -wcstring history_session_id() { +wcstring history_session_id(const environment_t &vars) { wcstring result = DFLT_FISH_HISTORY_SESSION_ID; - const auto var = env_get(L"fish_history"); + const auto var = vars.get(L"fish_history"); if (var) { wcstring session_id = var->as_string(); if (session_id.empty()) { diff --git a/src/history.h b/src/history.h index 4b50554b6..033766060 100644 --- a/src/history.h +++ b/src/history.h @@ -22,6 +22,7 @@ #include "wutil.h" // IWYU pragma: keep struct io_streams_t; +class environment_t; // Fish supports multiple shells writing to history at once. Here is its strategy: // @@ -214,6 +215,9 @@ class history_t { // Returns history with the given name, creating it if necessary. static history_t &history_with_name(const wcstring &name); + /// Returns whether this is using the default name. + bool is_default() const; + // Determines whether the history is empty. Unfortunately this cannot be const, since it may // require populating the history. bool is_empty(); @@ -357,7 +361,7 @@ class history_search_t { void history_save_all(); /// Return the prefix for the files to be used for command and read history. -wcstring history_session_id(); +wcstring history_session_id(const environment_t &vars); /// Given a list of paths and a working directory, return the paths that are valid /// This does disk I/O and may only be called in a background thread diff --git a/src/reader.cpp b/src/reader.cpp index 6784c4138..26c2cab80 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -2150,8 +2150,9 @@ void reader_import_history_if_necessary() { data->history->populate_from_config_path(); } - // Import history from bash, etc. if our current history is still empty. - if (data->history && data->history->is_empty()) { + // Import history from bash, etc. if our current history is still empty and is the default + // history. + if (data->history && data->history->is_empty() && data->history->is_default()) { // Try opening a bash file. We make an effort to respect $HISTFILE; this isn't very complete // (AFAIK it doesn't have to be exported), and to really get this right we ought to ask bash // itself. But this is better than nothing. @@ -2346,7 +2347,8 @@ uint32_t reader_run_count() { return run_count; } /// Read interactively. Read input from stdin while providing editing facilities. static int read_i() { - reader_push(history_session_id()); + parser_t &parser = parser_t::principal_parser(); + reader_push(history_session_id(parser.vars())); reader_set_complete_function(&complete); reader_set_highlight_function(&highlight_shell); reader_set_test_function(&reader_shell_test); @@ -2354,7 +2356,6 @@ static int read_i() { reader_set_expand_abbreviations(true); reader_import_history_if_necessary(); - parser_t &parser = parser_t::principal_parser(); reader_data_t *data = current_data(); data->prev_end_loop = 0; From 9f62a530777285146b69d12a8c41adf84877dd90 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 18 Sep 2018 21:03:01 -0700 Subject: [PATCH 19/24] Instantize env_get inside highlighting --- src/fish_tests.cpp | 30 ++++++++++++++++-------------- src/highlight.cpp | 24 +++++++++++++----------- src/highlight.h | 2 +- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index ba6bd38a6..23283eadd 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2133,24 +2133,26 @@ static void test_is_potential_path() { const wcstring wd = L"test/is_potential_path_test/"; const wcstring_list_t wds({L".", wd}); - do_test(is_potential_path(L"al", wds, PATH_REQUIRE_DIR)); - do_test(is_potential_path(L"alpha/", wds, PATH_REQUIRE_DIR)); - do_test(is_potential_path(L"aard", wds, 0)); + const auto &vars = env_stack_t::principal(); + do_test(is_potential_path(L"al", wds, vars, PATH_REQUIRE_DIR)); + do_test(is_potential_path(L"alpha/", wds, vars, PATH_REQUIRE_DIR)); + do_test(is_potential_path(L"aard", wds, vars, 0)); - do_test(!is_potential_path(L"balpha/", wds, PATH_REQUIRE_DIR)); - do_test(!is_potential_path(L"aard", wds, PATH_REQUIRE_DIR)); - do_test(!is_potential_path(L"aarde", wds, PATH_REQUIRE_DIR)); - do_test(!is_potential_path(L"aarde", wds, 0)); + do_test(!is_potential_path(L"balpha/", wds, vars, PATH_REQUIRE_DIR)); + do_test(!is_potential_path(L"aard", wds, vars, PATH_REQUIRE_DIR)); + do_test(!is_potential_path(L"aarde", wds, vars, PATH_REQUIRE_DIR)); + do_test(!is_potential_path(L"aarde", wds, vars, 0)); - do_test(is_potential_path(L"test/is_potential_path_test/aardvark", wds, 0)); - do_test(is_potential_path(L"test/is_potential_path_test/al", wds, PATH_REQUIRE_DIR)); - do_test(is_potential_path(L"test/is_potential_path_test/aardv", wds, 0)); + do_test(is_potential_path(L"test/is_potential_path_test/aardvark", wds, vars, 0)); + do_test(is_potential_path(L"test/is_potential_path_test/al", wds, vars, PATH_REQUIRE_DIR)); + do_test(is_potential_path(L"test/is_potential_path_test/aardv", wds, vars, 0)); - do_test(!is_potential_path(L"test/is_potential_path_test/aardvark", wds, PATH_REQUIRE_DIR)); - do_test(!is_potential_path(L"test/is_potential_path_test/al/", wds, 0)); - do_test(!is_potential_path(L"test/is_potential_path_test/ar", wds, 0)); + do_test( + !is_potential_path(L"test/is_potential_path_test/aardvark", wds, vars, PATH_REQUIRE_DIR)); + do_test(!is_potential_path(L"test/is_potential_path_test/al/", wds, vars, 0)); + do_test(!is_potential_path(L"test/is_potential_path_test/ar", wds, vars, 0)); - do_test(is_potential_path(L"/usr", wds, PATH_REQUIRE_DIR)); + do_test(is_potential_path(L"/usr", wds, vars, PATH_REQUIRE_DIR)); } /// Test the 'test' builtin. diff --git a/src/highlight.cpp b/src/highlight.cpp index 2d1179140..758660981 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -105,7 +105,7 @@ bool fs_is_case_insensitive(const wcstring &path, int fd, /// /// We expect the path to already be unescaped. bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_list_t &directories, - path_flags_t flags) { + const environment_t &vars, path_flags_t flags) { ASSERT_IS_BACKGROUND_THREAD(); const bool require_dir = static_cast(flags & PATH_REQUIRE_DIR); @@ -216,7 +216,7 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l // Given a string, return whether it prefixes a path that we could cd into. Return that path in // out_path. Expects path to be unescaped. static bool is_potential_cd_path(const wcstring &path, const wcstring &working_directory, - path_flags_t flags) { + const environment_t &vars, path_flags_t flags) { wcstring_list_t directories; if (string_prefixes_string(L"./", path)) { @@ -224,7 +224,7 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d directories.push_back(working_directory); } else { // Get the CDPATH. - auto cdpath = env_get(L"CDPATH"); + auto cdpath = vars.get(L"CDPATH"); std::vector pathsv = cdpath.missing_or_empty() ? wcstring_list_t{L"."} : cdpath->as_list(); @@ -236,7 +236,7 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d } // Call is_potential_path with all of these directories. - return is_potential_path(path, directories, flags | PATH_REQUIRE_DIR); + return is_potential_path(path, directories, vars, flags | PATH_REQUIRE_DIR); } // Given a plain statement node in a parse tree, get the command and return it, expanded @@ -252,6 +252,8 @@ static bool plain_statement_get_expanded_command(const wcstring &src, } rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background) { + // TODO: rationalize this principal_vars. + const auto &vars = env_stack_t::principal(); rgb_color_t result = rgb_color_t::normal(); // If sloppy_background is set, then we look at the foreground color even if is_background is @@ -264,17 +266,17 @@ rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background) return rgb_color_t::normal(); } - auto var = env_get(highlight_var[idx]); + auto var = vars.get(highlight_var[idx]); // debug( 1, L"%d -> %d -> %ls", highlight, idx, val ); - if (!var) var = env_get(highlight_var[0]); + if (!var) var = vars.get(highlight_var[0]); if (var) result = parse_color(*var, treat_as_background); // Handle modifiers. if (highlight & highlight_modifier_valid_path) { - auto var2 = env_get(L"fish_color_valid_path"); + auto var2 = vars.get(L"fish_color_valid_path"); if (var2) { rgb_color_t result2 = parse_color(*var2, is_background); if (result.is_normal()) @@ -800,7 +802,7 @@ void highlighter_t::color_argument(tnode_t node) { /// Indicates whether the source range of the given node forms a valid path in the given /// working_directory. static bool node_is_potential_path(const wcstring &src, const parse_node_t &node, - const wcstring &working_directory) { + const environment_t &vars, const wcstring &working_directory) { if (!node.has_source()) return false; // Get the node source, unescape it, and then pass it to is_potential_path along with the @@ -813,7 +815,7 @@ static bool node_is_potential_path(const wcstring &src, const parse_node_t &node if (!token.empty() && token.at(0) == HOME_DIRECTORY) token.at(0) = L'~'; const wcstring_list_t working_directory_list(1, working_directory); - result = is_potential_path(token, working_directory_list, PATH_EXPAND_TILDE); + result = is_potential_path(token, working_directory_list, vars, PATH_EXPAND_TILDE); } return result; } @@ -843,7 +845,7 @@ void highlighter_t::color_arguments(const std::vector> &arg bool is_help = string_prefixes_string(param, L"--help") || string_prefixes_string(param, L"-h"); if (!is_help && this->io_ok && - !is_potential_cd_path(param, working_directory, PATH_EXPAND_TILDE)) { + !is_potential_cd_path(param, working_directory, vars, PATH_EXPAND_TILDE)) { this->color_node(arg, highlight_spec_error); } } @@ -1180,7 +1182,7 @@ const highlighter_t::color_array_t &highlighter_t::highlight() { // (and the cursor is just beyond the last token), we may still underline it. if (this->cursor_pos >= node.source_start && this->cursor_pos - node.source_start <= node.source_length && - node_is_potential_path(buff, node, working_directory)) { + node_is_potential_path(buff, node, vars, working_directory)) { // It is, underline it. for (size_t i = node.source_start; i < node.source_start + node.source_length; i++) { // Don't color highlight_spec_error because it looks dorky. For example, diff --git a/src/highlight.h b/src/highlight.h index bfac1d3f2..a7aa66bcb 100644 --- a/src/highlight.h +++ b/src/highlight.h @@ -124,6 +124,6 @@ enum { }; typedef unsigned int path_flags_t; bool is_potential_path(const wcstring &const_path, const wcstring_list_t &directories, - path_flags_t flags); + const environment_t &vars, path_flags_t flags); #endif From 038f3cca6df08f96efde7713410c0c4d1597b56f Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 24 Sep 2018 01:47:38 -0400 Subject: [PATCH 20/24] Remove the abbreviation cache Read abbreviations directly from the environment. --- src/env.cpp | 2 -- src/expand.cpp | 51 +++++++++++++++++++++++----------------------- src/expand.h | 8 ++++---- src/fish_tests.cpp | 18 ++++++++-------- src/highlight.cpp | 2 +- src/reader.cpp | 5 ++--- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/env.cpp b/src/env.cpp index 07c8e3a41..52054e97a 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -625,8 +625,6 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key, env auto dispatch = var_dispatch_table.find(key); if (dispatch != var_dispatch_table.end()) { (*dispatch->second)(op, key, vars); - } else if (string_prefixes_string(L"_fish_abbr_", key)) { - update_abbr_cache(op, key); } else if (string_prefixes_string(L"fish_color_", key)) { reader_react_to_color_change(); } diff --git a/src/expand.cpp b/src/expand.cpp index c9bba8fcd..d885dada3 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -1188,34 +1188,33 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector *cmds, int argc return result; } -static owning_lock> s_abbreviations; -void update_abbr_cache(const wchar_t *op, const wcstring &varname) { - wcstring abbr; - if (!unescape_string(varname.substr(wcslen(L"_fish_abbr_")), &abbr, 0, STRING_STYLE_VAR)) { - debug(1, L"Abbreviation var '%ls' is not correctly encoded, ignoring it.", varname.c_str()); - return; - } - auto abbreviations = s_abbreviations.acquire(); - abbreviations->erase(abbr); - if (wcscmp(op, L"ERASE") != 0) { - const auto expansion = env_get(varname); - if (!expansion.missing_or_empty()) { - abbreviations->emplace(abbr, expansion->as_string()); - } - } -} +maybe_t expand_abbreviation(const wcstring &src) { + if (src.empty()) return none(); -bool expand_abbreviation(const wcstring &src, wcstring *output) { - if (src.empty()) return false; - - auto abbreviations = s_abbreviations.acquire(); - auto abbr = abbreviations->find(src); - if (abbr == abbreviations->end()) return false; - if (output != NULL) output->assign(abbr->second); - return true; + const auto &vars = env_stack_t::principal(); + wcstring unesc_src; + if (!unescape_string(src, &unesc_src, STRING_STYLE_VAR)) { + return none(); + } + wcstring var_name = L"_fish_abbr_" + unesc_src; + auto var_value = vars.get(var_name); + if (var_value) { + return var_value->as_string(); + } + return none(); } std::map get_abbreviations() { - auto abbreviations = s_abbreviations.acquire(); - return *abbreviations; + // TODO: try to make this cheaper + const auto &vars = env_stack_t::principal(); + const size_t fish_abbr_len = wcslen(L"_fish_abbr_"); + auto names = vars.get_names(0); + std::map result; + for (const wcstring &name : names) { + if (string_prefixes_string(L"_fish_abbr_", name)) { + result[name.substr(fish_abbr_len)] = vars.get(name)->as_string(); + } + } + return result; } + diff --git a/src/expand.h b/src/expand.h index 95b3c151b..d64f64eed 100644 --- a/src/expand.h +++ b/src/expand.h @@ -14,6 +14,7 @@ #include #include "common.h" +#include "maybe.h" #include "parse_constants.h" class env_var_t; @@ -156,10 +157,9 @@ void expand_tilde(wcstring &input); /// Perform the opposite of tilde expansion on the string, which is modified in place. wcstring replace_home_directory_with_tilde(const wcstring &str); -/// Abbreviation support. Expand src as an abbreviation, returning true if one was found, false if -/// not. If result is not-null, returns the abbreviation by reference. -void update_abbr_cache(const wchar_t *op, const wcstring &varname); -bool expand_abbreviation(const wcstring &src, wcstring *output); +/// Abbreviation support. Expand src as an abbreviation, returning the expanded form if found, +/// none() if not. +maybe_t expand_abbreviation(const wcstring &src); /// \return a snapshot of all abbreviations as a map abbreviation->expansion. std::map get_abbreviations(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 23283eadd..ce640296b 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1781,19 +1781,19 @@ static void test_abbreviations() { if (ret != 0) err(L"Unable to set abbreviation variable"); } - wcstring result; - if (expand_abbreviation(L"", &result)) err(L"Unexpected success with empty abbreviation"); - if (expand_abbreviation(L"nothing", &result)) - err(L"Unexpected success with missing abbreviation"); + if (expand_abbreviation(L"")) err(L"Unexpected success with empty abbreviation"); + if (expand_abbreviation(L"nothing")) err(L"Unexpected success with missing abbreviation"); - if (!expand_abbreviation(L"gc", &result)) err(L"Unexpected failure with gc abbreviation"); - if (result != L"git checkout") err(L"Wrong abbreviation result for gc"); - result.clear(); + auto mresult = expand_abbreviation(L"gc"); + if (!mresult) err(L"Unexpected failure with gc abbreviation"); + if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc"); - if (!expand_abbreviation(L"foo", &result)) err(L"Unexpected failure with foo abbreviation"); - if (result != L"bar") err(L"Wrong abbreviation result for foo"); + mresult = expand_abbreviation(L"foo"); + if (!mresult) err(L"Unexpected failure with foo abbreviation"); + if (*mresult != L"bar") err(L"Wrong abbreviation result for foo"); bool expanded; + wcstring result; expanded = reader_expand_abbreviation_in_command(L"just a command", 3, &result); if (expanded) err(L"Command wrongly expanded on line %ld", (long)__LINE__); expanded = reader_expand_abbreviation_in_command(L"gc somebranch", 0, &result); diff --git a/src/highlight.cpp b/src/highlight.cpp index 758660981..94a5ae52b 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -1015,7 +1015,7 @@ static bool command_is_valid(const wcstring &cmd, enum parse_statement_decoratio if (!is_valid && function_ok) is_valid = function_exists_no_autoload(cmd, vars); // Abbreviations - if (!is_valid && abbreviation_ok) is_valid = expand_abbreviation(cmd, NULL); + if (!is_valid && abbreviation_ok) is_valid = expand_abbreviation(cmd).has_value(); // Regular commands if (!is_valid && command_ok) is_valid = path_get_path(cmd, NULL, vars); diff --git a/src/reader.cpp b/src/reader.cpp index 26c2cab80..e7737696a 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -747,14 +747,13 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso bool result = false; if (matching_cmd_node) { const wcstring token = matching_cmd_node.get_source(subcmd); - wcstring abbreviation; - if (expand_abbreviation(token, &abbreviation)) { + if (auto abbreviation = expand_abbreviation(token)) { // There was an abbreviation! Replace the token in the full command. Maintain the // relative position of the cursor. if (output != NULL) { output->assign(cmdline); source_range_t r = *matching_cmd_node.source_range(); - output->replace(subcmd_offset + r.start, r.length, abbreviation); + output->replace(subcmd_offset + r.start, r.length, *abbreviation); } result = true; } From 6f52e6bb1c81b4ac9037cd4c603ef413c662d39c Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 21 Sep 2018 21:52:47 -0700 Subject: [PATCH 21/24] Instantize contents of exec.cpp and others --- src/autoload.cpp | 4 +- src/builtin.cpp | 2 +- src/builtin_argparse.cpp | 2 +- src/complete.cpp | 8 ++- src/exec.cpp | 18 +++---- src/exec.h | 5 +- src/expand.cpp | 39 ++++++++------ src/expand.h | 5 +- src/fish_tests.cpp | 111 ++++++++++++++++++++++----------------- src/highlight.cpp | 2 +- src/parser.cpp | 4 +- src/parser.h | 3 ++ src/path.cpp | 4 +- src/reader.cpp | 17 +++--- 14 files changed, 131 insertions(+), 93 deletions(-) diff --git a/src/autoload.cpp b/src/autoload.cpp index 8614731e3..18b75aef0 100644 --- a/src/autoload.cpp +++ b/src/autoload.cpp @@ -19,6 +19,7 @@ #include "common.h" #include "env.h" #include "exec.h" +#include "parser.h" #include "wutil.h" // IWYU pragma: keep /// The time before we'll recheck an autoloaded file. @@ -256,7 +257,8 @@ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_ // If we have a script, either built-in or a file source, then run it. if (really_load && !script_source.empty()) { // Do nothing on failure. - exec_subshell(script_source, false /* do not apply exit status */); + exec_subshell(script_source, parser_t::principal_parser(), + false /* do not apply exit status */); } if (really_load) { diff --git a/src/builtin.cpp b/src/builtin.cpp index 2e3306f94..8e3ddeca5 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -169,7 +169,7 @@ wcstring builtin_help_get(parser_t &parser, io_streams_t &streams, const wchar_t wcstring out; const wcstring name_esc = escape_string(name, 1); wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str()); - if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0) { + if (exec_subshell(cmd, parser, lst, false /* don't apply exit status */) >= 0) { for (size_t i = 0; i < lst.size(); i++) { out.append(lst.at(i)); out.push_back(L'\n'); diff --git a/src/builtin_argparse.cpp b/src/builtin_argparse.cpp index df85cdd32..c4b88736e 100644 --- a/src/builtin_argparse.cpp +++ b/src/builtin_argparse.cpp @@ -453,7 +453,7 @@ static int validate_arg(parser_t &parser, const argparse_cmd_opts_t &opts, optio } vars.set_one(var_name_prefix + L"value", ENV_LOCAL, woptarg); - int retval = exec_subshell(opt_spec->validation_command, cmd_output, false); + int retval = exec_subshell(opt_spec->validation_command, parser, cmd_output, false); for (const auto &output : cmd_output) { streams.err.append(output); streams.err.push_back(L'\n'); diff --git a/src/complete.cpp b/src/complete.cpp index f9a633383..88c3208c0 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -410,7 +410,9 @@ bool completer_t::condition_test(const wcstring &condition) { condition_cache_t::iterator cached_entry = condition_cache.find(condition); if (cached_entry == condition_cache.end()) { // Compute new value and reinsert it. - test_res = (0 == exec_subshell(condition, false /* don't apply exit status */)); + // TODO: rationalize this parser_t usage. + test_res = (0 == exec_subshell(condition, parser_t::principal_parser(), + false /* don't apply exit status */)); condition_cache[condition] = test_res; } else { // Use the old value. @@ -591,7 +593,9 @@ void completer_t::complete_cmd_desc(const wcstring &str) { // 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. wcstring_list_t list; - if (exec_subshell(lookup_cmd, list, false /* don't apply exit status */) != -1) { + // TODO: rationalize this use of principal_parser. + if (exec_subshell(lookup_cmd, parser_t::principal_parser(), list, + false /* don't apply exit status */) != -1) { std::unordered_map lookup; lookup.reserve(list.size()); diff --git a/src/exec.cpp b/src/exec.cpp index 25143e863..294f6eef7 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -369,7 +369,7 @@ void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &all_ios) { // really make sense, so I'm not trying to fix it here. if (!setup_child_process(0, all_ios)) { // Decrement SHLVL as we're removing ourselves from the shell "stack". - auto shlvl_var = env_get(L"SHLVL", ENV_GLOBAL | ENV_EXPORT); + auto shlvl_var = vars.get(L"SHLVL", ENV_GLOBAL | ENV_EXPORT); wcstring shlvl_str = L"0"; if (shlvl_var) { long shlvl = fish_wcstol(shlvl_var->as_string().c_str()); @@ -1085,14 +1085,14 @@ bool exec_job(parser_t &parser, shared_ptr j) { return true; } -static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, bool apply_exit_status, - bool is_subcmd) { +static int exec_subshell_internal(const wcstring &cmd, parser_t &parser, wcstring_list_t *lst, + bool apply_exit_status, bool is_subcmd) { ASSERT_IS_MAIN_THREAD(); bool prev_subshell = is_subshell; const int prev_status = proc_get_last_status(); bool split_output = false; - const auto ifs = env_get(L"IFS"); + const auto ifs = parser.vars().get(L"IFS"); if (!ifs.missing_or_empty()) { split_output = true; } @@ -1163,13 +1163,13 @@ static int exec_subshell_internal(const wcstring &cmd, wcstring_list_t *lst, boo return subcommand_status; } -int exec_subshell(const wcstring &cmd, std::vector &outputs, bool apply_exit_status, - bool is_subcmd) { +int exec_subshell(const wcstring &cmd, parser_t &parser, std::vector &outputs, + bool apply_exit_status, bool is_subcmd) { ASSERT_IS_MAIN_THREAD(); - return exec_subshell_internal(cmd, &outputs, apply_exit_status, is_subcmd); + return exec_subshell_internal(cmd, parser, &outputs, apply_exit_status, is_subcmd); } -int exec_subshell(const wcstring &cmd, bool apply_exit_status, bool is_subcmd) { +int exec_subshell(const wcstring &cmd, parser_t &parser, bool apply_exit_status, bool is_subcmd) { ASSERT_IS_MAIN_THREAD(); - return exec_subshell_internal(cmd, NULL, apply_exit_status, is_subcmd); + return exec_subshell_internal(cmd, parser, NULL, apply_exit_status, is_subcmd); } diff --git a/src/exec.h b/src/exec.h index 5e04fe3e5..010de4654 100644 --- a/src/exec.h +++ b/src/exec.h @@ -23,9 +23,10 @@ bool exec_job(parser_t &parser, std::shared_ptr j); /// \param outputs The list to insert output into. /// /// \return the status of the last job to exit, or -1 if en error was encountered. -int exec_subshell(const wcstring &cmd, std::vector &outputs, bool preserve_exit_status, +int exec_subshell(const wcstring &cmd, parser_t &parser, std::vector &outputs, + bool preserve_exit_status, bool is_subcmd = false); +int exec_subshell(const wcstring &cmd, parser_t &parser, bool preserve_exit_status, bool is_subcmd = false); -int exec_subshell(const wcstring &cmd, bool preserve_exit_status, bool is_subcmd = false); /// Loops over close until the syscall was run without being interrupted. void exec_close(int fd); diff --git a/src/expand.cpp b/src/expand.cpp index d885dada3..4beb86782 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -43,6 +43,7 @@ #include "iothread.h" #include "parse_constants.h" #include "parse_util.h" +#include "parser.h" #include "path.h" #include "proc.h" #include "reader.h" @@ -292,7 +293,7 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector *out, size_t last_idx, - parse_error_list_t *errors) { + const environment_t &vars, parse_error_list_t *errors) { const size_t insize = instr.size(); // last_idx may be 1 past the end of the string, but no further. @@ -356,7 +357,7 @@ static bool expand_variables(wcstring instr, std::vector *out, siz history = &history_t::history_with_name(history_session_id(env_stack_t::principal())); } } else if (var_name != wcstring{VARIABLE_EXPAND_EMPTY}) { - var = env_get(var_name); + var = vars.get(var_name); } // Parse out any following slice. @@ -406,7 +407,7 @@ static bool expand_variables(wcstring instr, std::vector *out, siz res.push_back(VARIABLE_EXPAND_EMPTY); } res.append(instr, var_name_and_slice_stop, wcstring::npos); - return expand_variables(std::move(res), out, varexp_char_idx, errors); + return expand_variables(std::move(res), out, varexp_char_idx, vars, errors); } } @@ -463,7 +464,7 @@ static bool expand_variables(wcstring instr, std::vector *out, siz // Append all entries in var_item_list, separated by the delimiter. res.append(join_strings(var_item_list, delimit)); res.append(instr, var_name_and_slice_stop, wcstring::npos); - return expand_variables(std::move(res), out, varexp_char_idx, errors); + return expand_variables(std::move(res), out, varexp_char_idx, vars, errors); } else { // Normal cartesian-product expansion. for (const wcstring &item : var_item_list) { @@ -480,7 +481,7 @@ static bool expand_variables(wcstring instr, std::vector *out, siz } new_in.append(item); new_in.append(instr, var_name_and_slice_stop, wcstring::npos); - if (!expand_variables(std::move(new_in), out, varexp_char_idx, errors)) { + if (!expand_variables(std::move(new_in), out, varexp_char_idx, vars, errors)) { return false; } } @@ -637,7 +638,10 @@ static bool expand_cmdsubst(const wcstring &input, std::vector *ou wcstring_list_t sub_res; const wcstring subcmd(paren_begin + 1, paren_end - paren_begin - 1); - if (exec_subshell(subcmd, sub_res, true /* apply_exit_status */, true /* is_subcmd */) == -1) { + // TODO: justify this parser_t::principal_parser + auto &parser = parser_t::principal_parser(); + if (exec_subshell(subcmd, parser, sub_res, true /* apply_exit_status */, + true /* is_subcmd */) == -1) { append_cmdsub_error(errors, SOURCE_LOCATION_UNKNOWN, L"Unknown error while evaulating command substitution"); return false; @@ -742,7 +746,7 @@ static wcstring get_home_directory_name(const wcstring &input, size_t *out_tail_ } /// Attempts tilde expansion of the string specified, modifying it in place. -static void expand_home_directory(wcstring &input) { +static void expand_home_directory(wcstring &input, const environment_t &vars) { if (!input.empty() && input.at(0) == HOME_DIRECTORY) { size_t tail_idx; wcstring username = get_home_directory_name(input, &tail_idx); @@ -750,7 +754,7 @@ static void expand_home_directory(wcstring &input) { maybe_t home; if (username.empty()) { // Current users home directory. - auto home_var = env_get(L"HOME"); + auto home_var = vars.get(L"HOME"); if (home_var.missing_or_empty()) { input.clear(); return; @@ -787,16 +791,17 @@ static void expand_percent_self(wcstring &input) { } } -void expand_tilde(wcstring &input) { +void expand_tilde(wcstring &input, const environment_t &vars) { // Avoid needless COW behavior by ensuring we use const at. const wcstring &tmp = input; if (!tmp.empty() && tmp.at(0) == L'~') { input.at(0) = HOME_DIRECTORY; - expand_home_directory(input); + expand_home_directory(input, vars); } } -static void unexpand_tildes(const wcstring &input, std::vector *completions) { +static void unexpand_tildes(const wcstring &input, const environment_t &vars, + std::vector *completions) { // If input begins with tilde, then try to replace the corresponding string in each completion // with the tilde. If it does not, there's nothing to do. if (input.empty() || input.at(0) != L'~') return; @@ -818,7 +823,7 @@ static void unexpand_tildes(const wcstring &input, std::vector *co // Expand username_with_tilde. wcstring home = username_with_tilde; - expand_tilde(home); + expand_tilde(home, vars); // Now for each completion that starts with home, replace it with the username_with_tilde. for (size_t i = 0; i < completions->size(); i++) { @@ -835,12 +840,12 @@ static void unexpand_tildes(const wcstring &input, std::vector *co // If the given path contains the user's home directory, replace that with a tilde. We don't try to // be smart about case insensitivity, etc. -wcstring replace_home_directory_with_tilde(const wcstring &str) { +wcstring replace_home_directory_with_tilde(const wcstring &str, const environment_t &vars) { // Only absolute paths get this treatment. wcstring result = str; if (string_prefixes_string(L"/", result)) { wcstring home_directory = L"~"; - expand_tilde(home_directory); + expand_tilde(home_directory, vars); if (!string_suffixes_string(L"/", home_directory)) { home_directory.push_back(L'/'); } @@ -928,7 +933,7 @@ static expand_error_t expand_stage_variables(wcstring input, std::vector *out_comp if (total_result != EXPAND_ERROR) { // Hack to un-expand tildes (see #647). if (!(flags & EXPAND_SKIP_HOME_DIRECTORIES)) { - unexpand_tildes(input, &completions); + unexpand_tildes(input, vars, &completions); } out_completions->insert(out_completions->end(), std::make_move_iterator(completions.begin()), diff --git a/src/expand.h b/src/expand.h index d64f64eed..4cf67e71c 100644 --- a/src/expand.h +++ b/src/expand.h @@ -17,6 +17,7 @@ #include "maybe.h" #include "parse_constants.h" +class environment_t; class env_var_t; class environment_t; @@ -152,10 +153,10 @@ wcstring expand_escape_variable(const env_var_t &var); /// Perform tilde expansion and nothing else on the specified string, which is modified in place. /// /// \param input the string to tilde expand -void expand_tilde(wcstring &input); +void expand_tilde(wcstring &input, const environment_t &vars); /// Perform the opposite of tilde expansion on the string, which is modified in place. -wcstring replace_home_directory_with_tilde(const wcstring &str); +wcstring replace_home_directory_with_tilde(const wcstring &str, const environment_t &vars); /// Abbreviation support. Expand src as an abbreviation, returning the expanded form if found, /// none() if not. diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index ce640296b..9efe2c2e7 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1502,11 +1502,17 @@ static void test_lru() { /// A crappy environment_t that only knows about PWD. struct pwd_environment_t : public environment_t { + std::map extras; + virtual 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}; } + auto extra = extras.find(key); + if (extra != extras.end()) { + return env_var_t(extra->second, ENV_DEFAULT); + } return {}; } @@ -2620,9 +2626,9 @@ static void test_completion_insertions() { } static void perform_one_autosuggestion_cd_test(const wcstring &command, const wcstring &expected, - long line) { + const environment_t &vars, long line) { std::vector comps; - complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, pwd_environment_t{}); + complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, vars); bool expects_error = (expected == L""); @@ -2694,7 +2700,6 @@ static void perform_one_completion_cd_test(const wcstring &command, const wcstri // Testing test_autosuggest_suggest_special, in particular for properly handling quotes and // backslashes. static void test_autosuggest_suggest_special() { - auto &vars = parser_t::principal_parser().vars(); if (system("mkdir -p 'test/autosuggest_test/0foobar'")) err(L"mkdir failed"); if (system("mkdir -p 'test/autosuggest_test/1foo bar'")) err(L"mkdir failed"); if (system("mkdir -p 'test/autosuggest_test/2foo bar'")) err(L"mkdir failed"); @@ -2724,60 +2729,72 @@ static void test_autosuggest_suggest_special() { const wcstring wd = L"test/autosuggest_test"; - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/1", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/1", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/1", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/2", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/2", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/2", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/3", L"foo\\bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/3", L"foo\\bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/3", L"foo\\bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/4", L"foo'bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/4", L"foo'bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/4", L"foo'bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/5", L"foo\"bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/5", L"foo\"bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/5", L"foo\"bar/", __LINE__); + pwd_environment_t vars{}; + vars.extras[L"HOME"] = parser_t::principal_parser().vars().get(L"HOME")->as_string(); - vars.set_one(L"AUTOSUGGEST_TEST_LOC", ENV_LOCAL, wd); - perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", L"l/", __LINE__); - - perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/start/", L"unique2/unique3/", + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/1", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/1", L"foo bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/1", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/2", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/2", L"foo bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/2", L"foo bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/3", L"foo\\bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/3", L"foo\\bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/3", L"foo\\bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/4", L"foo'bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/4", L"foo'bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/4", L"foo'bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/5", L"foo\"bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/5", L"foo\"bar/", vars, + __LINE__); + perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/5", L"foo\"bar/", vars, __LINE__); + vars.extras[L"AUTOSUGGEST_TEST_LOC"] = wd; + perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", L"l/", vars, + __LINE__); + + perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/start/", L"unique2/unique3/", + vars, __LINE__); + if (!pushd(wcs2string(wd).c_str())) return; - perform_one_autosuggestion_cd_test(L"cd 0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd '0", L"foobar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 1", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"1", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd '1", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 2", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"2", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd '2", L"foo bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 3", L"foo\\bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"3", L"foo\\bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd '3", L"foo\\bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 4", L"foo'bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"4", L"foo'bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd '4", L"foo'bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd 5", L"foo\"bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd \"5", L"foo\"bar/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd '5", L"foo\"bar/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd 0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd '0", L"foobar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd 1", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"1", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd '1", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd 2", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"2", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd '2", L"foo bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd 3", L"foo\\bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"3", L"foo\\bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd '3", L"foo\\bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd 4", L"foo'bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"4", L"foo'bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd '4", L"foo'bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd 5", L"foo\"bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd \"5", L"foo\"bar/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd '5", L"foo\"bar/", vars, __LINE__); // A single quote should defeat tilde expansion. - perform_one_autosuggestion_cd_test(L"cd '~/test_autosuggest_suggest_specia'", L"", + perform_one_autosuggestion_cd_test(L"cd '~/test_autosuggest_suggest_specia'", L"", vars, __LINE__); // Don't crash on ~ (issue #2696). Note this is cwd dependent. if (system("mkdir -p '~hahaha/path1/path2/'")) err(L"mkdir failed"); - perform_one_autosuggestion_cd_test(L"cd ~haha", L"ha/path1/path2/", __LINE__); - perform_one_autosuggestion_cd_test(L"cd ~hahaha/", L"path1/path2/", __LINE__); + perform_one_autosuggestion_cd_test(L"cd ~haha", L"ha/path1/path2/", vars, __LINE__); + perform_one_autosuggestion_cd_test(L"cd ~hahaha/", L"path1/path2/", vars, __LINE__); perform_one_completion_cd_test(L"cd ~haha", L"ha/", __LINE__); perform_one_completion_cd_test(L"cd ~hahaha/", L"path1/", __LINE__); diff --git a/src/highlight.cpp b/src/highlight.cpp index 94a5ae52b..9b844f89f 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -114,7 +114,7 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l bool result = false; wcstring path_with_magic(potential_path_fragment); - if (flags & PATH_EXPAND_TILDE) expand_tilde(path_with_magic); + if (flags & PATH_EXPAND_TILDE) expand_tilde(path_with_magic, vars); // debug( 1, L"%ls -> %ls ->%ls", path, tilde, unescaped ); diff --git a/src/parser.cpp b/src/parser.cpp index d70ad32e3..124fdbb6b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -96,8 +96,8 @@ static const struct block_lookup_entry block_lookup[] = { {(block_type_t)0, 0, 0}}; // Given a file path, return something nicer. Currently we just "unexpand" tildes. -static wcstring user_presentable_path(const wcstring &path) { - return replace_home_directory_with_tilde(path); +wcstring parser_t::user_presentable_path(const wcstring &path) const { + return replace_home_directory_with_tilde(path, vars()); } parser_t::parser_t() : variables(env_stack_t::principal()) {} diff --git a/src/parser.h b/src/parser.h index 33f28f8f0..6edea461e 100644 --- a/src/parser.h +++ b/src/parser.h @@ -199,6 +199,9 @@ class parser_t { /// every block if it is of type FUNCTION_CALL. const wchar_t *is_function(size_t idx = 0) const; + // Given a file path, return something nicer. Currently we just "unexpand" tildes. + wcstring user_presentable_path(const wcstring &path) const; + /// Helper for stack_trace(). void stack_trace_internal(size_t block_idx, wcstring *out) const; diff --git a/src/path.cpp b/src/path.cpp index 6a7b3673f..32c4932c9 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -187,7 +187,7 @@ maybe_t path_get_cdpath(const wcstring &dir, const wcstring &wd, // TODO: if next_path starts with ./ we need to replace the . with the wd. next_path = wd; } - expand_tilde(next_path); + expand_tilde(next_path, env_vars); if (next_path.empty()) continue; wcstring whole_path = next_path; @@ -213,7 +213,7 @@ maybe_t path_get_cdpath(const wcstring &dir, const wcstring &wd, maybe_t path_as_implicit_cd(const wcstring &path, const wcstring &wd, const environment_t &vars) { wcstring exp_path = path; - expand_tilde(exp_path); + expand_tilde(exp_path, vars); if (string_prefixes_string(L"/", exp_path) || string_prefixes_string(L"./", exp_path) || string_prefixes_string(L"../", exp_path) || string_suffixes_string(L"/", exp_path) || exp_path == L"..") { diff --git a/src/reader.cpp b/src/reader.cpp index e7737696a..339e9d360 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -400,6 +400,9 @@ class reader_data_t { /// Return the variable set used for e.g. command duration. env_stack_t &vars() { return parser_t::principal_parser().vars(); } + /// Hackish access to the parser. TODO: rationalize this. + parser_t &parser() { return parser_t::principal_parser(); } + const env_stack_t &vars() const { return parser_t::principal_parser().vars(); } /// Constructor @@ -829,7 +832,8 @@ void reader_write_title(const wcstring &cmd, bool reset_cursor_position) { wcstring_list_t lst; proc_push_interactive(0); - if (exec_subshell(fish_title_command, lst, false /* ignore exit status */) != -1 && + if (exec_subshell(fish_title_command, current_data()->parser(), lst, + false /* ignore exit status */) != -1 && !lst.empty()) { fputws(L"\x1B]0;", stdout); for (size_t i = 0; i < lst.size(); i++) { @@ -867,7 +871,8 @@ static void exec_prompt() { // Prepend any mode indicator to the left prompt (issue #1988). if (function_exists(MODE_PROMPT_FUNCTION_NAME)) { wcstring_list_t mode_indicator_list; - exec_subshell(MODE_PROMPT_FUNCTION_NAME, mode_indicator_list, apply_exit_status); + exec_subshell(MODE_PROMPT_FUNCTION_NAME, data->parser(), mode_indicator_list, + apply_exit_status); // We do not support multiple lines in the mode indicator, so just concatenate all of // them. for (size_t i = 0; i < mode_indicator_list.size(); i++) { @@ -878,7 +883,7 @@ static void exec_prompt() { if (!data->left_prompt.empty()) { wcstring_list_t prompt_list; // Ignore return status. - exec_subshell(data->left_prompt, prompt_list, apply_exit_status); + exec_subshell(data->left_prompt, data->parser(), prompt_list, apply_exit_status); for (size_t i = 0; i < prompt_list.size(); i++) { if (i > 0) data->left_prompt_buff += L'\n'; data->left_prompt_buff += prompt_list.at(i); @@ -888,7 +893,7 @@ static void exec_prompt() { if (!data->right_prompt.empty()) { wcstring_list_t prompt_list; // Status is ignored. - exec_subshell(data->right_prompt, prompt_list, apply_exit_status); + exec_subshell(data->right_prompt, data->parser(), prompt_list, apply_exit_status); for (size_t i = 0; i < prompt_list.size(); i++) { // Right prompt does not support multiple lines, so just concatenate all of them. data->right_prompt_buff += prompt_list.at(i); @@ -2155,9 +2160,9 @@ void reader_import_history_if_necessary() { // Try opening a bash file. We make an effort to respect $HISTFILE; this isn't very complete // (AFAIK it doesn't have to be exported), and to really get this right we ought to ask bash // itself. But this is better than nothing. - const auto var = env_get(L"HISTFILE"); + const auto var = data->vars().get(L"HISTFILE"); wcstring path = (var ? var->as_string() : L"~/.bash_history"); - expand_tilde(path); + expand_tilde(path, data->vars()); FILE *f = wfopen(path, "r"); if (f) { data->history->populate_from_bash(f); From 3b1709180f7fbb292229571f003fd9c38b5b1bc8 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 24 Sep 2018 22:26:46 -0400 Subject: [PATCH 22/24] Instantize env_get --- src/autoload.cpp | 7 ++++- src/builtin_cd.cpp | 2 +- src/builtin_command.cpp | 2 +- src/builtin_read.cpp | 5 ++-- src/builtin_set.cpp | 58 ++++++++++++++++++++------------------- src/builtin_set_color.cpp | 5 ++-- src/common.cpp | 19 ++++++------- src/complete.cpp | 6 ++-- src/fish.cpp | 3 +- src/function.cpp | 52 +++++++++++++++++++---------------- src/output.cpp | 2 +- src/parse_execution.cpp | 5 ++-- src/path.cpp | 15 ++++------ src/path.h | 2 +- 14 files changed, 98 insertions(+), 85 deletions(-) diff --git a/src/autoload.cpp b/src/autoload.cpp index 18b75aef0..bcd980796 100644 --- a/src/autoload.cpp +++ b/src/autoload.cpp @@ -66,8 +66,12 @@ int autoload_t::load(const wcstring &cmd, bool reload) { CHECK_BLOCK(0); ASSERT_IS_MAIN_THREAD(); + // TODO: Justify this principal_parser. + auto &parser = parser_t::principal_parser(); + auto &vars = parser.vars(); + if (!this->paths) { - auto path_var = env_get(env_var_name); + auto path_var = vars.get(env_var_name); if (path_var.missing_or_empty()) return 0; this->paths = path_var->as_list(); } @@ -257,6 +261,7 @@ bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_ // If we have a script, either built-in or a file source, then run it. if (really_load && !script_source.empty()) { // Do nothing on failure. + // TODO: rationalize this use of principal_parser, or inject the loading from outside. exec_subshell(script_source, parser_t::principal_parser(), false /* do not apply exit status */); } diff --git a/src/builtin_cd.cpp b/src/builtin_cd.cpp index 260e31844..92f7d9ce4 100644 --- a/src/builtin_cd.cpp +++ b/src/builtin_cd.cpp @@ -37,7 +37,7 @@ int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (argv[optind]) { dir_in = argv[optind]; } else { - auto maybe_dir_in = env_get(L"HOME"); + auto maybe_dir_in = parser.vars().get(L"HOME"); if (maybe_dir_in.missing_or_empty()) { streams.err.append_format(_(L"%ls: Could not find home directory\n"), cmd); return STATUS_CMD_ERROR; diff --git a/src/builtin_command.cpp b/src/builtin_command.cpp index 6a04a1262..1e038e68d 100644 --- a/src/builtin_command.cpp +++ b/src/builtin_command.cpp @@ -96,7 +96,7 @@ int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) { for (int idx = optind; argv[idx]; ++idx) { const wchar_t *command_name = argv[idx]; if (opts.all_paths) { - wcstring_list_t paths = path_get_paths(command_name); + wcstring_list_t paths = path_get_paths(command_name, parser.vars()); for (auto path : paths) { if (!opts.quiet) streams.out.append_format(L"%ls\n", path.c_str()); ++found; diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index 0ea09bd94..0c1ea1a00 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -204,7 +204,8 @@ static int read_interactive(wcstring &buff, int nchars, bool shell, bool silent, int exit_res = STATUS_CMD_OK; const wchar_t *line; - auto &vars = env_stack_t::principal(); + // TODO: rationalize this. + const auto &vars = env_stack_t::principal(); wcstring read_history_ID = history_session_id(vars); if (!read_history_ID.empty()) read_history_ID += L"_read"; reader_push(read_history_ID); @@ -490,7 +491,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } if (!opts.have_delimiter) { - auto ifs = env_get(L"IFS"); + auto ifs = parser.vars().get(L"IFS"); if (!ifs.missing_or_empty()) opts.delimiter = ifs->as_string(); } diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index fdd38b33b..0858e5413 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -234,9 +234,9 @@ static int validate_cmd_opts(const wchar_t *cmd, set_cmd_opts_t &opts, //!OCLIN // Check if we are setting a uvar and a global of the same name exists. See // https://github.com/fish-shell/fish-shell/issues/806 static int check_global_scope_exists(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t *dest, - io_streams_t &streams) { + io_streams_t &streams, const environment_t &vars) { if (opts.universal) { - auto global_dest = env_get(dest, ENV_GLOBAL); + auto global_dest = vars.get(dest, ENV_GLOBAL); if (global_dest && shell_is_interactive()) { streams.err.append_format(BUILTIN_SET_UVAR_ERR, cmd, dest); } @@ -249,7 +249,8 @@ static int check_global_scope_exists(const wchar_t *cmd, set_cmd_opts_t &opts, c // contain a colon, then complain. Return true if any path element was valid, false if not. static bool validate_path_warning_on_colons(const wchar_t *cmd, const wchar_t *key, //!OCLINT(npath complexity) - const wcstring_list_t &list, io_streams_t &streams) { + const wcstring_list_t &list, io_streams_t &streams, + const environment_t &vars) { // Always allow setting an empty value. if (list.empty()) return true; @@ -265,7 +266,7 @@ static bool validate_path_warning_on_colons(const wchar_t *cmd, // not the (missing) local value. Also don't bother to complain about relative paths, which // don't start with /. wcstring_list_t existing_values; - const auto existing_variable = env_get(key, ENV_DEFAULT); + const auto existing_variable = vars.get(key, ENV_DEFAULT); if (!existing_variable.missing_or_empty()) existing_variable->to_list(existing_values); for (const wcstring &dir : list) { @@ -346,7 +347,7 @@ static void handle_env_return(int retval, const wchar_t *cmd, const wchar_t *key static int env_set_reporting_errors(const wchar_t *cmd, const wchar_t *key, int scope, const wcstring_list_t &list, io_streams_t &streams, env_stack_t &vars) { - if (is_path_variable(key) && !validate_path_warning_on_colons(cmd, key, list, streams)) { + if (is_path_variable(key) && !validate_path_warning_on_colons(cmd, key, list, streams, vars)) { return STATUS_CMD_ERROR; } @@ -365,13 +366,14 @@ static int env_set_reporting_errors(const wchar_t *cmd, const wchar_t *key, int /// Returns: /// The total number of indexes parsed, or -1 on error. If any indexes were found the `src` string /// is modified to omit the index expression leaving just the var name. -static int parse_index(std::vector &indexes, wchar_t *src, int scope, io_streams_t &streams) { +static int parse_index(std::vector &indexes, wchar_t *src, int scope, io_streams_t &streams, + const environment_t &vars) { wchar_t *p = wcschr(src, L'['); if (!p) return 0; // no slices so nothing for us to do *p = L'\0'; // split the var name from the indexes/slices p++; - auto var_str = env_get(src, scope); + auto var_str = vars.get(src, scope); wcstring_list_t var; if (var_str) var_str->to_list(var); @@ -484,7 +486,7 @@ static int builtin_set_list(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, streams.out.append(e_key); if (!names_only) { - auto var = env_get(key, compute_scope(opts)); + auto var = parser.vars().get(key, compute_scope(opts)); if (!var.missing_or_empty()) { bool shorten = false; wcstring val = expand_escape_variable(*var); @@ -517,7 +519,7 @@ static int builtin_set_query(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, assert(dest); std::vector indexes; - int idx_count = parse_index(indexes, dest, scope, streams); + int idx_count = parse_index(indexes, dest, scope, streams, parser.vars()); if (idx_count == -1) { free(dest); builtin_print_help(parser, streams, cmd, streams.err); @@ -526,14 +528,14 @@ static int builtin_set_query(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, if (idx_count) { wcstring_list_t result; - auto dest_str = env_get(dest, scope); + auto dest_str = parser.vars().get(dest, scope); if (dest_str) dest_str->to_list(result); for (auto idx : indexes) { if (idx < 1 || (size_t)idx > result.size()) retval++; } } else { - if (! env_get(arg, scope)) retval++; + if (!parser.vars().get(arg, scope)) retval++; } free(dest); @@ -542,7 +544,8 @@ static int builtin_set_query(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, return retval; } -static void show_scope(const wchar_t *var_name, int scope, io_streams_t &streams) { +static void show_scope(const wchar_t *var_name, int scope, io_streams_t &streams, + const environment_t &vars) { const wchar_t *scope_name; switch (scope) { case ENV_LOCAL: { @@ -563,7 +566,7 @@ static void show_scope(const wchar_t *var_name, int scope, io_streams_t &streams } } - const auto var = env_get(var_name, scope); + const auto var = vars.get(var_name, scope); if (!var) { streams.out.append_format(_(L"$%ls: not set in %ls scope\n"), var_name, scope_name); return; @@ -590,14 +593,14 @@ static void show_scope(const wchar_t *var_name, int scope, io_streams_t &streams static int builtin_set_show(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(opts); - + auto &vars = parser.vars(); if (argc == 0) { // show all vars wcstring_list_t names = parser.vars().get_names(ENV_USER); sort(names.begin(), names.end()); for (auto it : names) { - show_scope(it.c_str(), ENV_LOCAL, streams); - show_scope(it.c_str(), ENV_GLOBAL, streams); - show_scope(it.c_str(), ENV_UNIVERSAL, streams); + show_scope(it.c_str(), ENV_LOCAL, streams, vars); + show_scope(it.c_str(), ENV_GLOBAL, streams, vars); + show_scope(it.c_str(), ENV_UNIVERSAL, streams, vars); streams.out.push_back(L'\n'); } } else { @@ -616,9 +619,9 @@ static int builtin_set_show(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, return STATUS_CMD_ERROR; } - show_scope(arg, ENV_LOCAL, streams); - show_scope(arg, ENV_GLOBAL, streams); - show_scope(arg, ENV_UNIVERSAL, streams); + show_scope(arg, ENV_LOCAL, streams, vars); + show_scope(arg, ENV_GLOBAL, streams, vars); + show_scope(arg, ENV_UNIVERSAL, streams, vars); streams.out.push_back(L'\n'); } } @@ -639,7 +642,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t *dest = argv[0]; std::vector indexes; - int idx_count = parse_index(indexes, dest, scope, streams); + int idx_count = parse_index(indexes, dest, scope, streams, parser.vars()); if (idx_count == -1) { builtin_print_help(parser, streams, cmd, streams.err); return STATUS_CMD_ERROR; @@ -660,7 +663,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, handle_env_return(retval, cmd, dest, streams); } } else { // remove just the specified indexes of the var - const auto dest_var = env_get(dest, scope); + const auto dest_var = parser.vars().get(dest, scope); if (!dest_var) return STATUS_CMD_ERROR; wcstring_list_t result; dest_var->to_list(result); @@ -669,7 +672,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, } if (retval != STATUS_CMD_OK) return retval; - return check_global_scope_exists(cmd, opts, dest, streams); + return check_global_scope_exists(cmd, opts, dest, streams, parser.vars()); } /// This handles the common case of setting the entire var to a set of values. @@ -684,8 +687,7 @@ static int set_var_array(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t if (opts.prepend) { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } - - auto var_str = env_get(varname, ENV_DEFAULT); + auto var_str = parser.vars().get(varname, ENV_DEFAULT); wcstring_list_t var_array; if (var_str) var_str->to_list(var_array); new_values.insert(new_values.end(), var_array.begin(), var_array.end()); @@ -719,7 +721,7 @@ static int set_var_slices(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_ } int scope = compute_scope(opts); // calculate the variable scope based on the provided options - const auto var_str = env_get(varname, scope); + const auto var_str = parser.vars().get(varname, scope); if (var_str) var_str->to_list(new_values); // Slice indexes have been calculated, do the actual work. @@ -750,7 +752,7 @@ static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, w argc--; std::vector indexes; - int idx_count = parse_index(indexes, varname, scope, streams); + int idx_count = parse_index(indexes, varname, scope, streams, parser.vars()); if (idx_count == -1) { builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; @@ -776,7 +778,7 @@ static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, w retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams, parser.vars()); if (retval != STATUS_CMD_OK) return retval; - return check_global_scope_exists(cmd, opts, varname, streams); + return check_global_scope_exists(cmd, opts, varname, streams, parser.vars()); } /// The set builtin creates, updates, and erases (removes, deletes) variables. diff --git a/src/builtin_set_color.cpp b/src/builtin_set_color.cpp index e0f98261f..f1b11efe4 100644 --- a/src/builtin_set_color.cpp +++ b/src/builtin_set_color.cpp @@ -27,6 +27,7 @@ #include "env.h" #include "io.h" #include "output.h" +#include "parser.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep @@ -75,10 +76,10 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // Hack in missing italics and dim capabilities omitted from MacOS xterm-256color terminfo // Helps Terminal.app/iTerm #if __APPLE__ - const auto term_prog = env_get(L"TERM_PROGRAM"); + const auto term_prog = parser.vars().get(L"TERM_PROGRAM"); if (!term_prog.missing_or_empty() && (term_prog->as_string() == L"Apple_Terminal" || term_prog->as_string() == L"iTerm.app")) { - const auto term = env_get(L"TERM"); + const auto term = parser.vars().get(L"TERM"); if (!term.missing_or_empty() && (term->as_string() == L"xterm-256color")) { enter_italics_mode = sitm_esc; exit_italics_mode = ritm_esc; diff --git a/src/common.cpp b/src/common.cpp index 193a6320d..718bffed0 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1721,7 +1721,7 @@ void common_handle_winch(int signal) { /// Validate the new terminal size. Fallback to the env vars if necessary. Ensure the values are /// sane and if not fallback to a default of 80x24. -static void validate_new_termsize(struct winsize *new_termsize) { +static void validate_new_termsize(struct winsize *new_termsize, const environment_t &vars) { if (new_termsize->ws_col == 0 || new_termsize->ws_row == 0) { #ifdef HAVE_WINSIZE if (shell_is_interactive()) { @@ -1731,8 +1731,8 @@ static void validate_new_termsize(struct winsize *new_termsize) { } #endif // Fallback to the environment vars. - maybe_t col_var = env_get(L"COLUMNS"); - maybe_t row_var = env_get(L"LINES"); + maybe_t col_var = vars.get(L"COLUMNS"); + maybe_t row_var = vars.get(L"LINES"); if (!col_var.missing_or_empty() && !row_var.missing_or_empty()) { // Both vars have to have valid values. int col = fish_wcstoi(col_var->as_string().c_str()); @@ -1757,16 +1757,15 @@ static void validate_new_termsize(struct winsize *new_termsize) { } /// Export the new terminal size as env vars and to the kernel if possible. -static void export_new_termsize(struct winsize *new_termsize) { - auto &vars = env_stack_t::globals(); +static void export_new_termsize(struct winsize *new_termsize, env_stack_t &vars) { wchar_t buf[64]; - auto cols = env_get(L"COLUMNS", ENV_EXPORT); + auto cols = vars.get(L"COLUMNS", ENV_EXPORT); swprintf(buf, 64, L"%d", (int)new_termsize->ws_col); vars.set_one(L"COLUMNS", ENV_GLOBAL | (cols.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); - auto lines = env_get(L"LINES", ENV_EXPORT); + auto lines = vars.get(L"LINES", ENV_EXPORT); swprintf(buf, 64, L"%d", (int)new_termsize->ws_row); vars.set_one(L"LINES", ENV_GLOBAL | (lines.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); @@ -1793,9 +1792,9 @@ struct winsize get_current_winsize() { return termsize; } #endif - - validate_new_termsize(&new_termsize); - export_new_termsize(&new_termsize); + auto &vars = env_stack_t::globals(); + validate_new_termsize(&new_termsize, vars); + export_new_termsize(&new_termsize, vars); termsize.ws_col = new_termsize.ws_col; termsize.ws_row = new_termsize.ws_row; termsize_valid = true; diff --git a/src/complete.cpp b/src/complete.cpp index 88c3208c0..effcb05e9 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -410,7 +410,7 @@ bool completer_t::condition_test(const wcstring &condition) { condition_cache_t::iterator cached_entry = condition_cache.find(condition); if (cached_entry == condition_cache.end()) { // Compute new value and reinsert it. - // TODO: rationalize this parser_t usage. + // TODO: rationalize this principal_parser. test_res = (0 == exec_subshell(condition, parser_t::principal_parser(), false /* don't apply exit status */)); condition_cache[condition] = test_res; @@ -593,7 +593,7 @@ void completer_t::complete_cmd_desc(const wcstring &str) { // 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. wcstring_list_t list; - // TODO: rationalize this use of principal_parser. + // TODO: justify this use of parser_t::principal_parser. if (exec_subshell(lookup_cmd, parser_t::principal_parser(), list, false /* don't apply exit status */) != -1) { std::unordered_map lookup; @@ -1140,7 +1140,7 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) { wcstring desc; if (this->wants_descriptions()) { // Can't use this->vars here, it could be any variable. - auto var = env_get(env_name); + auto var = vars.get(env_name); if (!var) continue; wcstring value = expand_escape_variable(*var); diff --git a/src/fish.cpp b/src/fish.cpp index 4cf5e268c..ceb49c053 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -364,12 +364,13 @@ int main(int argc, char **argv) { save_term_foreground_process_group(); } + auto &globals = env_stack_t::globals(); const struct config_paths_t paths = determine_config_directory_paths(argv[0]); env_init(&paths); // Set features early in case other initialization depends on them. // Start with the ones set in the environment, then those set on the command line (so the // command line takes precedence). - if (auto features_var = env_get(L"fish_features")) { + if (auto features_var = globals.get(L"fish_features")) { for (const wcstring &s : features_var->as_list()) { mutable_fish_features().set_from_string(s); } diff --git a/src/function.cpp b/src/function.cpp index 2a8e21710..d2ef6e9c7 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -25,29 +25,31 @@ #include "fallback.h" // IWYU pragma: keep #include "function.h" #include "intern.h" +#include "parser.h" #include "parser_keywords.h" #include "reader.h" #include "wutil.h" // IWYU pragma: keep class function_info_t { - public: - /// Immutable properties of the function. - std::shared_ptr props; - /// Function description. This may be changed after the function is created. - wcstring description; - /// File where this function was defined (intern'd string). - const wchar_t *const definition_file; - /// Mapping of all variables that were inherited from the function definition scope to their - /// values. - const std::map inherit_vars; - /// Flag for specifying that this function was automatically loaded. - const bool is_autoload; +public: + /// Immutable properties of the function. + std::shared_ptr props; + /// Function description. This may be changed after the function is created. + wcstring description; + /// File where this function was defined (intern'd string). + const wchar_t *const definition_file; + /// Mapping of all variables that were inherited from the function definition scope to their + /// values. + const std::map inherit_vars; + /// Flag for specifying that this function was automatically loaded. + const bool is_autoload; - /// Constructs relevant information from the function_data. - function_info_t(function_data_t data, const wchar_t *filename, bool autoload); + /// Constructs relevant information from the function_data. + function_info_t(function_data_t data, const environment_t &vars, const wchar_t *filename, + bool autoload); - /// Used by function_copy. - function_info_t(const function_info_t &data, const wchar_t *filename, bool autoload); + /// Used by function_copy. + function_info_t(const function_info_t &data, const wchar_t *filename, bool autoload); }; /// Table containing all functions. @@ -101,7 +103,9 @@ static int load(const wcstring &name) { static void autoload_names(std::unordered_set &names, int get_hidden) { size_t i; - const auto path_var = env_get(L"fish_function_path"); + // TODO: justfy this. + auto &vars = env_stack_t::principal(); + const auto path_var = vars.get(L"fish_function_path"); if (path_var.missing_or_empty()) return; wcstring_list_t path_list; @@ -127,20 +131,22 @@ static void autoload_names(std::unordered_set &names, int get_hidden) } } -static std::map snapshot_vars(const wcstring_list_t &vars) { +static std::map snapshot_vars(const wcstring_list_t &vars, + const environment_t &src) { std::map result; for (const wcstring &name : vars) { - auto var = env_get(name); + auto var = src.get(name); if (var) result[name] = std::move(*var); } return result; } -function_info_t::function_info_t(function_data_t data, const wchar_t *filename, bool autoload) +function_info_t::function_info_t(function_data_t data, const environment_t &vars, + const wchar_t *filename, bool autoload) : props(std::make_shared(std::move(data.props))), description(std::move(data.description)), definition_file(intern(filename)), - inherit_vars(snapshot_vars(data.inherit_vars)), + inherit_vars(snapshot_vars(data.inherit_vars, vars)), is_autoload(autoload) {} function_info_t::function_info_t(const function_info_t &data, const wchar_t *filename, @@ -164,8 +170,8 @@ void function_add(const function_data_t &data, const parser_t &parser) { // Create and store a new function. const wchar_t *filename = reader_current_filename(); - const function_map_t::value_type new_pair(data.name, - function_info_t(data, filename, is_autoload)); + const function_map_t::value_type new_pair( + data.name, function_info_t(data, parser.vars(), filename, is_autoload)); loaded_functions.insert(new_pair); // Add event handlers. diff --git a/src/output.cpp b/src/output.cpp index 4246fd084..585c5fb3f 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -554,7 +554,7 @@ void writembs_check(const char *mbs, const char *mbs_name, bool critical, const if (mbs != NULL) { tputs(mbs, 1, &writeb); } else if (critical) { - auto term = env_get(L"TERM"); + auto term = env_stack_t::globals().get(L"TERM"); const wchar_t *fmt = _(L"Tried to use terminfo string %s on line %ld of %s, which is " L"undefined in terminal of type \"%ls\". Please report this error to %s"); diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index a2d739277..f1821176c 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -385,8 +385,9 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( return ret; } - auto var = env_get(for_var_name, ENV_LOCAL); - if (!var && !is_function_context()) var = env_get(for_var_name, ENV_DEFAULT); + auto &vars = parser->vars(); + auto var = vars.get(for_var_name, ENV_LOCAL); + if (!var && !is_function_context()) var = vars.get(for_var_name, ENV_DEFAULT); if (!var || var->read_only()) { int retval = parser->vars().set_empty(for_var_name, ENV_LOCAL | ENV_USER); if (retval != ENV_OK) { diff --git a/src/path.cpp b/src/path.cpp index 32c4932c9..4117cec41 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -119,12 +119,8 @@ bool path_get_path(const wcstring &cmd, wcstring *out_path, const environment_t return path_get_path_core(cmd, out_path, vars.get(L"PATH")); } -bool path_get_path(const wcstring &cmd, wcstring *out_path) { - return path_get_path_core(cmd, out_path, env_get(L"PATH")); -} - -wcstring_list_t path_get_paths(const wcstring &cmd) { - debug(5, L"path_get_paths('%ls')", cmd.c_str()); +wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars) { + debug(3, L"path_get_paths('%ls')", cmd.c_str()); wcstring_list_t paths; // If the command has a slash, it must be an absolute or relative path and thus we don't bother @@ -138,7 +134,7 @@ wcstring_list_t path_get_paths(const wcstring &cmd) { return paths; } - auto path_var = env_get(L"PATH"); + auto path_var = vars.get(L"PATH"); std::vector pathsv; if (path_var) path_var->to_list(pathsv); for (auto path : pathsv) { @@ -291,7 +287,8 @@ static void path_create(wcstring &path, const wcstring &xdg_var, const wcstring // The vars we fetch must be exported. Allowing them to be universal doesn't make sense and // allowing that creates a lock inversion that deadlocks the shell since we're called before // uvars are available. - const auto xdg_dir = env_get(xdg_var, ENV_GLOBAL | ENV_EXPORT); + const auto &vars = env_stack_t::globals(); + const auto xdg_dir = vars.get(xdg_var, ENV_GLOBAL | ENV_EXPORT); if (!xdg_dir.missing_or_empty()) { using_xdg = true; path = xdg_dir->as_string() + L"/fish"; @@ -301,7 +298,7 @@ static void path_create(wcstring &path, const wcstring &xdg_var, const wcstring saved_errno = errno; } } else { - const auto home = env_get(L"HOME", ENV_GLOBAL | ENV_EXPORT); + const auto home = vars.get(L"HOME", ENV_GLOBAL | ENV_EXPORT); if (!home.missing_or_empty()) { path = home->as_string() + (which_dir == L"config" ? L"/.config/fish" : L"/.local/share/fish"); diff --git a/src/path.h b/src/path.h index 48ac8d654..ae0cc31c0 100644 --- a/src/path.h +++ b/src/path.h @@ -42,7 +42,7 @@ bool path_get_data(wcstring &path); bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL, const environment_t &vars); /// Return all the paths that match the given command. -wcstring_list_t path_get_paths(const wcstring &cmd); +wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars); /// Returns the full path of the specified directory, using the CDPATH variable as a list of base /// directories for relative paths. From b98812dd1a8ae70c034133f92713467f6659f203 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 24 Sep 2018 23:59:55 -0400 Subject: [PATCH 23/24] Remove last vestiges of env_set --- src/builtin_set.cpp | 4 ++-- src/env.cpp | 10 ++-------- src/env.h | 11 ++++++----- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index 0858e5413..017d2f3cc 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -336,13 +336,13 @@ static void handle_env_return(int retval, const wchar_t *cmd, const wchar_t *key break; } default: { - DIE("unexpected env_set() ret val"); + DIE("unexpected vars.set() ret val"); break; } } } -/// Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a +/// Call vars.set. If this is a path variable, e.g. PATH, validate the elements. On error, print a /// description of the problem to stderr. static int env_set_reporting_errors(const wchar_t *cmd, const wchar_t *key, int scope, const wcstring_list_t &list, io_streams_t &streams, diff --git a/src/env.cpp b/src/env.cpp index 52054e97a..f150accc0 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -802,7 +802,7 @@ static void handle_read_limit_change(const wcstring &op, const wcstring &var_nam env_stack_t &vars) { UNUSED(op); UNUSED(var_name); - env_set_read_limit(); + vars.set_read_limit(); } static void handle_fish_history_change(const wcstring &op, const wcstring &var_name, @@ -1241,11 +1241,7 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo ev.arguments.push_back(L"VARIABLE"); ev.arguments.push_back(L"SET"); ev.arguments.push_back(key); - - // debug(1, L"env_set: fire events on variable |%ls|", key); event_fire(&ev); - // debug(1, L"env_set: return from event firing"); - react_to_variable_change(L"SET", key, *this); return ENV_OK; } @@ -1377,7 +1373,7 @@ maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const bool search_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT); // Make the assumption that electric keys can't be shadowed elsewhere, since we currently block - // that in env_set(). + // that in env_stack_t::set(). if (is_electric(key)) { if (!search_global) return none(); if (key == L"history") { @@ -1451,8 +1447,6 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } -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 /// variable. bool var_stack_t::local_scope_exports(const env_node_ref_t &n) const { diff --git a/src/env.h b/src/env.h index f5bcacaa0..8b82cc2ad 100644 --- a/src/env.h +++ b/src/env.h @@ -16,7 +16,11 @@ extern size_t read_byte_limit; extern bool curses_initialized; -// Flags that may be passed as the 'mode' in env_set / env_get. +/// Character for separating two array elements. We use 30, i.e. the ascii record separator since +/// that seems logical. +#define ARRAY_SEP (wchar_t)0x1e + +// Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get(). enum { /// Default mode. Used with `env_get()` to indicate the caller doesn't care what scope the var /// is in or whether it is exported or unexported. @@ -43,7 +47,7 @@ enum { }; typedef uint32_t env_mode_flags_t; -/// Return values for `env_set()`. +/// Return values for `env_stack_t::set()`. enum { ENV_OK, ENV_PERM, ENV_SCOPE, ENV_INVALID, ENV_NOT_FOUND }; /// A struct of configuration directories, determined in main() that fish will optionally pass to @@ -162,9 +166,6 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode = ENV_DEFA /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); -/// Update the read_byte_limit variable. -void env_set_read_limit(); - /// A environment stack of scopes. This is the main class that tracks fish variables. struct var_stack_t; class env_node_t; From 77884bc21a991ead6713f61e2e34c6a8eaf2eb80 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 25 Sep 2018 00:17:05 -0400 Subject: [PATCH 24/24] Instantize env_get This removes env_get(). All fish variable accesses must go through an environment_t. --- src/builtin_pwd.cpp | 3 ++- src/builtin_status.cpp | 2 +- src/env.cpp | 16 +++++----------- src/env.h | 7 ++----- src/expand.cpp | 8 +++----- src/expand.h | 2 +- src/fish_tests.cpp | 37 ++++++++++++++++++++----------------- src/highlight.cpp | 2 +- src/reader.cpp | 13 +++++++------ src/reader.h | 2 +- src/screen.cpp | 12 +----------- 11 files changed, 44 insertions(+), 60 deletions(-) diff --git a/src/builtin_pwd.cpp b/src/builtin_pwd.cpp index f32a783c7..f9d7fda9c 100644 --- a/src/builtin_pwd.cpp +++ b/src/builtin_pwd.cpp @@ -6,6 +6,7 @@ #include "common.h" #include "fallback.h" // IWYU pragma: keep #include "io.h" +#include "parser.h" #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep @@ -48,7 +49,7 @@ int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } wcstring pwd; - if (auto tmp = env_get(L"PWD")) { + if (auto tmp = parser.vars().get(L"PWD")) { pwd = tmp->as_string(); } if (resolve_symlinks) { diff --git a/src/builtin_status.cpp b/src/builtin_status.cpp index 4862a8897..9a6b66c26 100644 --- a/src/builtin_status.cpp +++ b/src/builtin_status.cpp @@ -424,7 +424,7 @@ int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv) { case STATUS_CURRENT_CMD: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) // HACK: Go via the deprecated variable to get the command. - const auto var = env_get(L"_"); + const auto var = parser.vars().get(L"_"); if (!var.missing_or_empty()) { streams.out.append(var->as_string()); streams.out.push_back(L'\n'); diff --git a/src/env.cpp b/src/env.cpp index f150accc0..f48cfde2b 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -349,7 +349,6 @@ static mode_t get_umask() { /// Properly sets all timezone information. static void handle_timezone(const wchar_t *env_var_name, const environment_t &vars) { - // const env_var_t var = env_get(env_var_name, ENV_EXPORT); const auto var = vars.get(env_var_name, ENV_DEFAULT); debug(2, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name, !var ? L"MISSING" : var->as_string().c_str()); @@ -697,7 +696,7 @@ void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported() 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"); + auto pwd_var = get(L"PWD"); wcstring pwd; if (!pwd_var.missing_or_empty()) { pwd = pwd_var->as_string(); @@ -951,8 +950,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { get_hostname_identifier(hostname); vars.set_one(L"hostname", ENV_GLOBAL, hostname); - // Set up SHLVL variable. Not we can't use env_get because SHLVL is read-only, and therefore was - // not inherited from the environment. + // Set up SHLVL variable. Not we can't use vars.get() because SHLVL is read-only, and therefore + // was not inherited from the environment. wcstring nshlvl_str = L"1"; if (const char *shlvl_var = getenv("SHLVL")) { const wchar_t *end; @@ -1049,7 +1048,7 @@ static int set_umask(const wcstring_list_t &list_val) { } if (errno || mask > 0777 || mask < 0) return ENV_INVALID; - // Do not actually create a umask variable. On env_get() it will be calculated. + // Do not actually create a umask variable. On env_stack_t::get() it will be calculated. umask(mask); return ENV_OK; } @@ -1422,7 +1421,7 @@ maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) if (!search_universal) return none(); // Another hack. Only do a universal barrier on the main thread (since it can change variable - // values). Make sure we do this outside the env_lock because it may itself call `env_get()`. + // values). Make sure we do this outside the env_lock because it may itself call `get()`. if (is_main_thread() && !get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); @@ -1440,11 +1439,6 @@ maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) return none(); } -/// Legacy versions. -maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { - return env_stack_t::principal().get(key, mode); -} - void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } /// Returns true if the specified scope or any non-shadowed non-global subscopes contain an exported diff --git a/src/env.h b/src/env.h index 8b82cc2ad..970d05cad 100644 --- a/src/env.h +++ b/src/env.h @@ -22,8 +22,8 @@ extern bool curses_initialized; // Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get(). enum { - /// Default mode. Used with `env_get()` to indicate the caller doesn't care what scope the var - /// is in or whether it is exported or unexported. + /// Default mode. Used with `env_stack_t::get()` to indicate the caller doesn't care what scope + /// the var is in or whether it is exported or unexported. ENV_DEFAULT = 0, /// Flag for local (to the current block) variable. ENV_LOCAL = 1 << 0, @@ -160,9 +160,6 @@ class null_environment_t : public environment_t { wcstring_list_t get_names(int flags) const override; }; -/// Gets the variable with the specified name, or none() if it does not exist. -maybe_t env_get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT); - /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); diff --git a/src/expand.cpp b/src/expand.cpp index 4beb86782..1d0b1601c 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -1010,7 +1010,7 @@ static expand_error_t expand_stage_wildcards(wcstring path_to_expand, std::vecto // Get the PATH/CDPATH and CWD. Perhaps these should be passed in. An empty CDPATH // implies just the current directory, while an empty PATH is left empty. wcstring_list_t paths; - if (auto paths_var = env_get(for_cd ? L"CDPATH" : L"PATH")) { + if (auto paths_var = vars.get(for_cd ? L"CDPATH" : L"PATH")) { paths = paths_var->as_list(); } if (paths.empty()) { @@ -1193,17 +1193,15 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector *cmds, int argc return result; } -maybe_t expand_abbreviation(const wcstring &src) { +maybe_t expand_abbreviation(const wcstring &src, const environment_t &vars) { if (src.empty()) return none(); - const auto &vars = env_stack_t::principal(); wcstring unesc_src; if (!unescape_string(src, &unesc_src, STRING_STYLE_VAR)) { return none(); } wcstring var_name = L"_fish_abbr_" + unesc_src; - auto var_value = vars.get(var_name); - if (var_value) { + if (auto var_value = vars.get(var_name)) { return var_value->as_string(); } return none(); diff --git a/src/expand.h b/src/expand.h index 4cf67e71c..180ed9d2f 100644 --- a/src/expand.h +++ b/src/expand.h @@ -160,7 +160,7 @@ wcstring replace_home_directory_with_tilde(const wcstring &str, const environmen /// Abbreviation support. Expand src as an abbreviation, returning the expanded form if found, /// none() if not. -maybe_t expand_abbreviation(const wcstring &src); +maybe_t expand_abbreviation(const wcstring &src, const environment_t &vars); /// \return a snapshot of all abbreviations as a map abbreviation->expansion. std::map get_abbreviations(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 9efe2c2e7..cd030583b 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1787,59 +1787,62 @@ static void test_abbreviations() { if (ret != 0) err(L"Unable to set abbreviation variable"); } - if (expand_abbreviation(L"")) err(L"Unexpected success with empty abbreviation"); - if (expand_abbreviation(L"nothing")) err(L"Unexpected success with missing abbreviation"); + if (expand_abbreviation(L"", vars)) err(L"Unexpected success with empty abbreviation"); + if (expand_abbreviation(L"nothing", vars)) err(L"Unexpected success with missing abbreviation"); - auto mresult = expand_abbreviation(L"gc"); + auto mresult = expand_abbreviation(L"gc", vars); if (!mresult) err(L"Unexpected failure with gc abbreviation"); if (*mresult != L"git checkout") err(L"Wrong abbreviation result for gc"); - mresult = expand_abbreviation(L"foo"); + mresult = expand_abbreviation(L"foo", vars); if (!mresult) err(L"Unexpected failure with foo abbreviation"); if (*mresult != L"bar") err(L"Wrong abbreviation result for foo"); bool expanded; wcstring result; - expanded = reader_expand_abbreviation_in_command(L"just a command", 3, &result); + expanded = reader_expand_abbreviation_in_command(L"just a command", 3, vars, &result); if (expanded) err(L"Command wrongly expanded on line %ld", (long)__LINE__); - expanded = reader_expand_abbreviation_in_command(L"gc somebranch", 0, &result); + expanded = reader_expand_abbreviation_in_command(L"gc somebranch", 0, vars, &result); if (!expanded) err(L"Command not expanded on line %ld", (long)__LINE__); - expanded = reader_expand_abbreviation_in_command(L"gc somebranch", wcslen(L"gc"), &result); + expanded = + reader_expand_abbreviation_in_command(L"gc somebranch", wcslen(L"gc"), vars, &result); if (!expanded) err(L"gc not expanded"); if (result != L"git checkout somebranch") err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result.c_str()); // Space separation. - expanded = reader_expand_abbreviation_in_command(L"gx somebranch", wcslen(L"gc"), &result); + expanded = + reader_expand_abbreviation_in_command(L"gx somebranch", wcslen(L"gc"), vars, &result); if (!expanded) err(L"gx not expanded"); if (result != L"git checkout somebranch") err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result.c_str()); expanded = reader_expand_abbreviation_in_command(L"echo hi ; gc somebranch", - wcslen(L"echo hi ; g"), &result); + wcslen(L"echo hi ; g"), vars, &result); if (!expanded) err(L"gc not expanded on line %ld", (long)__LINE__); if (result != L"echo hi ; git checkout somebranch") err(L"gc incorrectly expanded on line %ld", (long)__LINE__); expanded = reader_expand_abbreviation_in_command( - L"echo (echo (echo (echo (gc ", wcslen(L"echo (echo (echo (echo (gc"), &result); + L"echo (echo (echo (echo (gc ", wcslen(L"echo (echo (echo (echo (gc"), vars, &result); if (!expanded) err(L"gc not expanded on line %ld", (long)__LINE__); if (result != L"echo (echo (echo (echo (git checkout ") err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result.c_str()); // If commands should be expanded. - expanded = reader_expand_abbreviation_in_command(L"if gc", wcslen(L"if gc"), &result); + expanded = reader_expand_abbreviation_in_command(L"if gc", wcslen(L"if gc"), vars, &result); if (!expanded) err(L"gc not expanded on line %ld", (long)__LINE__); if (result != L"if git checkout") err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result.c_str()); // Others should not be. - expanded = reader_expand_abbreviation_in_command(L"of gc", wcslen(L"of gc"), &result); + expanded = reader_expand_abbreviation_in_command(L"of gc", wcslen(L"of gc"), vars, &result); if (expanded) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); // Others should not be. - expanded = reader_expand_abbreviation_in_command(L"command gc", wcslen(L"command gc"), &result); + expanded = + reader_expand_abbreviation_in_command(L"command gc", wcslen(L"command gc"), vars, &result); if (expanded) err(L"gc incorrectly expanded on line %ld", (long)__LINE__); vars.pop(); @@ -2662,9 +2665,9 @@ static void perform_one_autosuggestion_cd_test(const wcstring &command, const wc } static void perform_one_completion_cd_test(const wcstring &command, const wcstring &expected, - long line) { + const environment_t &vars, long line) { std::vector comps; - complete(command, &comps, COMPLETION_REQUEST_DEFAULT, env_vars_snapshot_t{}); + complete(command, &comps, COMPLETION_REQUEST_DEFAULT, vars); bool expects_error = (expected == L""); @@ -2795,8 +2798,8 @@ static void test_autosuggest_suggest_special() { if (system("mkdir -p '~hahaha/path1/path2/'")) err(L"mkdir failed"); perform_one_autosuggestion_cd_test(L"cd ~haha", L"ha/path1/path2/", vars, __LINE__); perform_one_autosuggestion_cd_test(L"cd ~hahaha/", L"path1/path2/", vars, __LINE__); - perform_one_completion_cd_test(L"cd ~haha", L"ha/", __LINE__); - perform_one_completion_cd_test(L"cd ~hahaha/", L"path1/", __LINE__); + perform_one_completion_cd_test(L"cd ~haha", L"ha/", vars, __LINE__); + perform_one_completion_cd_test(L"cd ~hahaha/", L"path1/", vars, __LINE__); parser_t::principal_parser().vars().remove(L"HOME", ENV_LOCAL | ENV_EXPORT); popd(); diff --git a/src/highlight.cpp b/src/highlight.cpp index 9b844f89f..cda55083b 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -1015,7 +1015,7 @@ static bool command_is_valid(const wcstring &cmd, enum parse_statement_decoratio if (!is_valid && function_ok) is_valid = function_exists_no_autoload(cmd, vars); // Abbreviations - if (!is_valid && abbreviation_ok) is_valid = expand_abbreviation(cmd).has_value(); + if (!is_valid && abbreviation_ok) is_valid = expand_abbreviation(cmd, vars).has_value(); // Regular commands if (!is_valid && command_ok) is_valid = path_get_path(cmd, NULL, vars); diff --git a/src/reader.cpp b/src/reader.cpp index 339e9d360..69a1d2d41 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -395,7 +395,7 @@ class reader_data_t { void pager_selection_changed(); /// Expand abbreviations at the current cursor position, minus backtrack_amt. - bool expand_abbreviation_as_necessary(size_t cursor_backtrack) const; + static bool expand_abbreviation_as_necessary(size_t cursor_backtrack); /// Return the variable set used for e.g. command duration. env_stack_t &vars() { return parser_t::principal_parser().vars(); } @@ -699,7 +699,7 @@ void reader_data_t::pager_selection_changed() { /// Expand abbreviations at the given cursor position. Does NOT inspect 'data'. bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, - wcstring *output) { + const environment_t &vars, wcstring *output) { // See if we are at "command position". Get the surrounding command substitution, and get the // extent of the first token. const wchar_t *const buff = cmdline.c_str(); @@ -750,7 +750,7 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso bool result = false; if (matching_cmd_node) { const wcstring token = matching_cmd_node.get_source(subcmd); - if (auto abbreviation = expand_abbreviation(token)) { + if (auto abbreviation = expand_abbreviation(token, vars)) { // There was an abbreviation! Replace the token in the full command. Maintain the // relative position of the cursor. if (output != NULL) { @@ -767,15 +767,16 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso /// Expand abbreviations at the current cursor position, minus the given cursor backtrack. This may /// change the command line but does NOT repaint it. This is to allow the caller to coalesce /// repaints. -bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) const { +bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) { reader_data_t *data = current_data(); bool result = false; editable_line_t *el = data->active_edit_line(); - if (this->expand_abbreviations && el == &data->command_line) { + if (data->expand_abbreviations && el == &data->command_line) { // Try expanding abbreviations. wcstring new_cmdline; size_t cursor_pos = el->position - mini(el->position, cursor_backtrack); - if (reader_expand_abbreviation_in_command(el->text, cursor_pos, &new_cmdline)) { + if (reader_expand_abbreviation_in_command(el->text, cursor_pos, data->vars(), + &new_cmdline)) { // We expanded an abbreviation! The cursor moves by the difference in the command line // lengths. size_t new_buff_pos = el->position + new_cmdline.size() - el->text.size(); diff --git a/src/reader.h b/src/reader.h index 3854ae4f7..873b8341d 100644 --- a/src/reader.h +++ b/src/reader.h @@ -216,7 +216,7 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, /// Expand abbreviations at the given cursor position. Exposed for testing purposes only. bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, - wcstring *output); + const environment_t &vars, wcstring *output); /// Apply a completion string. Exposed for testing only. wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags, diff --git a/src/screen.cpp b/src/screen.cpp index 63e0906e7..1f4058405 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -110,21 +110,11 @@ static bool allow_soft_wrap() { return auto_right_margin; } -/// Does this look like the escape sequence for setting a screen name. +/// Does this look like the escape sequence for setting a screen name? static bool is_screen_name_escape_seq(const wchar_t *code, size_t *resulting_length) { if (code[1] != L'k') { return false; } - -#if 0 - // TODO: Decide if this should be removed or modified to also test for TERM values that begin - // with "tmux". See issue #3512. - const env_var_t term_name = env_get(L"TERM"); - if (term_name.missing_or_empty() || !string_prefixes_string(L"screen", term_name)) { - return false; - } -#endif - const wchar_t *const screen_name_end_sentinel = L"\x1B\\"; const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel); if (screen_name_end == NULL) {