Instantize env_set

Switch env_set to an instance method on environmnet_t.
This commit is contained in:
ridiculousfish 2018-09-14 00:36:26 -07:00
parent 421fbdd52a
commit c1dd284b3e
19 changed files with 193 additions and 130 deletions

View File

@ -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;
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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)

View File

@ -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<std::basic_string<CharType_t>> string_list_t;

View File

@ -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<wcstring, void (*)(const wcstring &, const wcstring &)>
typedef std::unordered_map<wcstring, void (*)(const wcstring &, const wcstring &, env_stack_t &)>
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<char> export_array;
maybe_t<null_terminated_array_t<char>> 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<var_stack_t>()) {}
env_stack_t::env_stack_t(std::unique_ptr<var_stack_t> 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<long>(getpid()));
vars.set_one(L"fish_pid", ENV_GLOBAL, to_string<long>(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<long>(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_var_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<std::string> 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<env_var_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<var_stack_t> dup_stack = make_unique<var_stack_t>(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;

View File

@ -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_var_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<env_node_t> n, const wchar_t *key, int var_mode);
std::shared_ptr<env_node_t> 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<var_stack_t> 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<env_var_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 {

View File

@ -377,7 +377,7 @@ void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &all_ios) {
shlvl_str = to_string<long>(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<job_t> 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) {

View File

@ -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);

View File

@ -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, &ltime);

View File

@ -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());
}
}

View File

@ -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<bool> 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();
}

View File

@ -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) {

View File

@ -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);

View File

@ -53,6 +53,14 @@ class maybe_t {
}
}
// Construct a value in-place.
template <class... Args>
void emplace(Args &&... args) {
reset();
filled = true;
new (storage) T(std::forward<Args>(args)...);
}
// Access the value.
T &value() {
assert(filled && "maybe_t does not have a value");

View File

@ -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;

View File

@ -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()) {

View File

@ -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();