mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-02-23 03:35:42 +08:00
Merge branch 'instance_env'
This merges a bunch of changes that eliminate fish variables as a global concept. Instead fish variables are tied to an instance of environment_t (read-only) or env_stack_t (read/write), which is explicitly threaded through every site. This is nice cleanup and also preparation for concurrent execution, where multiple independent threads may need to see different variables.
This commit is contained in:
commit
92d3f5f548
@ -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.
|
||||
@ -65,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();
|
||||
}
|
||||
@ -96,7 +101,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;
|
||||
|
||||
@ -256,7 +261,9 @@ 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 */);
|
||||
// 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 */);
|
||||
}
|
||||
|
||||
if (really_load) {
|
||||
|
@ -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<autoload_t, autoload_function_t> {
|
||||
@ -88,7 +88,7 @@ class autoload_t : public lru_cache_t<autoload_t, autoload_function_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();
|
||||
|
@ -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');
|
||||
|
@ -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,29 +434,31 @@ 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);
|
||||
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);
|
||||
} else {
|
||||
env_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);
|
||||
auto &vars = parser.vars();
|
||||
|
||||
int retval = exec_subshell(opt_spec->validation_command, cmd_output, false);
|
||||
vars.push(true);
|
||||
vars.set_one(L"_argparse_cmd", ENV_LOCAL, opts.name);
|
||||
if (is_long_flag) {
|
||||
vars.set_one(var_name_prefix + L"name", ENV_LOCAL, opt_spec->long_flag);
|
||||
} else {
|
||||
vars.set_one(var_name_prefix + L"name", ENV_LOCAL,
|
||||
wcstring(1, opt_spec->short_flag).c_str());
|
||||
}
|
||||
vars.set_one(var_name_prefix + L"value", ENV_LOCAL, woptarg);
|
||||
|
||||
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');
|
||||
}
|
||||
env_pop();
|
||||
vars.pop();
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -473,13 +476,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 +494,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 +510,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 +530,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 +548,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 +563,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 +595,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);
|
||||
@ -620,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
|
||||
@ -635,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
|
||||
@ -676,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;
|
||||
}
|
||||
|
@ -34,12 +34,10 @@ 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 {
|
||||
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;
|
||||
@ -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, env_get_pwd_slash())) {
|
||||
wcstring pwd = parser.vars().get_pwd_slash();
|
||||
maybe_t<wcstring> 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);
|
||||
|
||||
@ -86,6 +87,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;
|
||||
}
|
||||
|
@ -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
|
||||
@ -95,14 +96,14 @@ 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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
recursion_level++;
|
||||
|
||||
std::vector<completion_t> 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);
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
@ -203,7 +204,9 @@ 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();
|
||||
// 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);
|
||||
|
||||
@ -414,6 +417,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;
|
||||
@ -452,8 +456,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);
|
||||
// env_set_one(*var_ptr, opts.place, L"");
|
||||
parser.vars().set_empty(*var_ptr, opts.place);
|
||||
++var_ptr;
|
||||
}
|
||||
};
|
||||
@ -488,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();
|
||||
}
|
||||
|
||||
@ -512,13 +515,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) {
|
||||
@ -534,14 +537,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.
|
||||
@ -555,7 +558,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.
|
||||
@ -566,7 +569,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -235,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);
|
||||
}
|
||||
@ -250,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;
|
||||
|
||||
@ -266,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) {
|
||||
@ -336,21 +336,22 @@ 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,
|
||||
wcstring_list_t &list, io_streams_t &streams) {
|
||||
if (is_path_variable(key) && !validate_path_warning_on_colons(cmd, key, list, 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, vars)) {
|
||||
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;
|
||||
@ -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<long> &indexes, wchar_t *src, int scope, io_streams_t &streams) {
|
||||
static int parse_index(std::vector<long> &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);
|
||||
|
||||
@ -475,7 +477,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++) {
|
||||
@ -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<long> 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 = 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);
|
||||
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<long> 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;
|
||||
@ -653,23 +656,23 @@ 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) {
|
||||
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);
|
||||
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;
|
||||
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<long> 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;
|
||||
@ -774,9 +776,9 @@ 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);
|
||||
return check_global_scope_exists(cmd, opts, varname, streams, parser.vars());
|
||||
}
|
||||
|
||||
/// The set builtin creates, updates, and erases (removes, deletes) variables.
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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<env_var_t> col_var = env_get(L"COLUMNS");
|
||||
maybe_t<env_var_t> row_var = env_get(L"LINES");
|
||||
maybe_t<env_var_t> col_var = vars.get(L"COLUMNS");
|
||||
maybe_t<env_var_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,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) {
|
||||
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);
|
||||
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);
|
||||
auto lines = vars.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)
|
||||
@ -1791,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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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() {
|
||||
if (s_override_variable_names != NULL) {
|
||||
return *s_override_variable_names;
|
||||
}
|
||||
return env_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
|
||||
@ -301,8 +287,16 @@ void completions_sort_and_prioritize(std::vector<completion_t> *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<completion_t> completions;
|
||||
|
||||
/// Table of completions conditions that have already been tested and the corresponding test
|
||||
@ -372,7 +366,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();
|
||||
|
||||
@ -415,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 principal_parser.
|
||||
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.
|
||||
@ -500,18 +497,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -540,7 +538,7 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description
|
||||
const std::vector<completion_t> &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);
|
||||
@ -595,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: 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<wcstring, wcstring> lookup;
|
||||
lookup.reserve(list.size());
|
||||
|
||||
@ -661,7 +661,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);
|
||||
}
|
||||
@ -673,7 +673,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);
|
||||
}
|
||||
|
||||
@ -745,7 +746,7 @@ void completer_t::complete_from_args(const wcstring &str, const wcstring &args,
|
||||
}
|
||||
|
||||
std::vector<completion_t> 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();
|
||||
@ -869,7 +870,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
|
||||
@ -886,28 +887,17 @@ 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 <TAB> 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.
|
||||
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) {
|
||||
@ -1095,7 +1085,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<completion_t> 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());
|
||||
}
|
||||
|
||||
@ -1114,7 +1104,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());
|
||||
}
|
||||
}
|
||||
@ -1127,10 +1117,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();
|
||||
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) {
|
||||
@ -1152,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);
|
||||
@ -1578,14 +1566,14 @@ void completer_t::perform() {
|
||||
}
|
||||
|
||||
void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *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();
|
||||
}
|
||||
|
@ -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<completion_t> *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();
|
||||
@ -194,8 +196,6 @@ void append_completion(std::vector<completion_t> *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);
|
||||
|
552
src/env.cpp
552
src/env.cpp
File diff suppressed because it is too large
Load Diff
165
src/env.h
165
src/env.h
@ -16,10 +16,14 @@
|
||||
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.
|
||||
/// 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,
|
||||
@ -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
|
||||
@ -131,70 +135,135 @@ class env_var_t {
|
||||
bool operator!=(const env_var_t &rhs) const { return ! (*this == rhs); }
|
||||
};
|
||||
|
||||
/// 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);
|
||||
/// An environment is read-only access to variable values.
|
||||
class environment_t {
|
||||
protected:
|
||||
environment_t() = 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);
|
||||
public:
|
||||
virtual maybe_t<env_var_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();
|
||||
|
||||
/// 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);
|
||||
/// Returns the PWD with a terminating slash.
|
||||
wcstring get_pwd_slash() const;
|
||||
};
|
||||
|
||||
/// Sets the variable with the specified name to no values.
|
||||
int env_set_empty(const wcstring &key, env_mode_flags_t mode);
|
||||
/// The null environment contains nothing.
|
||||
class null_environment_t : public environment_t {
|
||||
public:
|
||||
null_environment_t();
|
||||
~null_environment_t() override;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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();
|
||||
maybe_t<env_var_t> get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override;
|
||||
wcstring_list_t get_names(int flags) const override;
|
||||
};
|
||||
|
||||
/// 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();
|
||||
/// 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 final : public environment_t {
|
||||
friend class parser_t;
|
||||
std::unique_ptr<var_stack_t> vars_;
|
||||
|
||||
/// Sets up argv as the given null terminated array of strings.
|
||||
void env_set_argv(const wchar_t *const *argv);
|
||||
int set_internal(const wcstring &key, env_mode_flags_t var_mode, wcstring_list_t val);
|
||||
|
||||
/// Returns all variable names.
|
||||
wcstring_list_t env_get_names(int flags);
|
||||
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);
|
||||
|
||||
/// Update the PWD variable based on the result of getcwd.
|
||||
void env_set_pwd_from_getcwd();
|
||||
static env_stack_t make_principal();
|
||||
|
||||
/// Returns the PWD with a terminating slash.
|
||||
wcstring env_get_pwd_slash();
|
||||
var_stack_t &vars_stack();
|
||||
const var_stack_t &vars_stack() const;
|
||||
|
||||
/// Update the read_byte_limit variable.
|
||||
void env_set_read_limit();
|
||||
explicit env_stack_t(std::unique_ptr<var_stack_t> vars_);
|
||||
env_stack_t();
|
||||
~env_stack_t() override;
|
||||
|
||||
class env_vars_snapshot_t {
|
||||
std::map<wcstring, env_var_t> vars;
|
||||
bool is_current() const;
|
||||
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;
|
||||
|
||||
/// 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) 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);
|
||||
|
||||
/// 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();
|
||||
|
||||
// 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 {
|
||||
std::map<wcstring, env_var_t> vars;
|
||||
wcstring_list_t names;
|
||||
|
||||
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 environment_t &source, const wchar_t *const *keys);
|
||||
~env_vars_snapshot_t() override;
|
||||
|
||||
env_vars_snapshot_t(const wchar_t *const *keys);
|
||||
env_vars_snapshot_t();
|
||||
maybe_t<env_var_t> get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override;
|
||||
|
||||
maybe_t<env_var_t> get(const wcstring &key) const;
|
||||
|
||||
// Returns the fake snapshot representing the live variables array.
|
||||
static const env_vars_snapshot_t ¤t();
|
||||
wcstring_list_t get_names(int flags) const override;
|
||||
|
||||
// Vars necessary for highlighting.
|
||||
static const wchar_t *const highlighting_keys[];
|
||||
|
44
src/exec.cpp
44
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<char> 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_t> &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.
|
||||
@ -369,7 +369,7 @@ void internal_exec(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());
|
||||
@ -377,10 +377,10 @@ void internal_exec(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(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<job_t> &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<job_t> &j, process_t *p,
|
||||
const io_chain_t &proc_io_chain) {
|
||||
static bool exec_external_command(env_stack_t &vars, const std::shared_ptr<job_t> &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<char> argv_array;
|
||||
@ -663,7 +663,7 @@ static bool exec_external_command(const std::shared_ptr<job_t> &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();
|
||||
@ -787,7 +787,7 @@ static bool exec_block_or_func_process(parser_t &parser, std::shared_ptr<job_t>
|
||||
|
||||
function_block_t *fb =
|
||||
parser.push_block<function_block_t>(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);
|
||||
@ -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<job_t> 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");
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
@ -1085,14 +1085,14 @@ bool exec_job(parser_t &parser, shared_ptr<job_t> 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<wcstring> &outputs, bool apply_exit_status,
|
||||
bool is_subcmd) {
|
||||
int exec_subshell(const wcstring &cmd, parser_t &parser, std::vector<wcstring> &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);
|
||||
}
|
||||
|
@ -23,9 +23,10 @@ bool exec_job(parser_t &parser, std::shared_ptr<job_t> 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<wcstring> &outputs, bool preserve_exit_status,
|
||||
int exec_subshell(const wcstring &cmd, parser_t &parser, std::vector<wcstring> &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);
|
||||
|
134
src/expand.cpp
134
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<long
|
||||
/// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
|
||||
/// as last_idx instead of string.size()-1.
|
||||
static bool expand_variables(wcstring instr, std::vector<completion_t> *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.
|
||||
@ -353,10 +354,10 @@ static bool expand_variables(wcstring instr, std::vector<completion_t> *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);
|
||||
var = vars.get(var_name);
|
||||
}
|
||||
|
||||
// Parse out any following slice.
|
||||
@ -406,7 +407,7 @@ static bool expand_variables(wcstring instr, std::vector<completion_t> *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<completion_t> *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<completion_t> *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<completion_t> *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<wcstring> 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<completion_t> *completions) {
|
||||
static void unexpand_tildes(const wcstring &input, const environment_t &vars,
|
||||
std::vector<completion_t> *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<completion_t> *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<completion_t> *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'/');
|
||||
}
|
||||
@ -883,15 +888,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<completion_t> *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<completion_t> *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 +917,8 @@ static expand_error_t expand_stage_cmdsubst(wcstring input, std::vector<completi
|
||||
}
|
||||
|
||||
static expand_error_t expand_stage_variables(wcstring input, std::vector<completion_t> *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;
|
||||
@ -925,7 +933,7 @@ static expand_error_t expand_stage_variables(wcstring input, std::vector<complet
|
||||
append_completion(out, std::move(next));
|
||||
} else {
|
||||
size_t size = next.size();
|
||||
if (!expand_variables(std::move(next), out, size, errors)) {
|
||||
if (!expand_variables(std::move(next), out, size, vars, errors)) {
|
||||
return EXPAND_ERROR;
|
||||
}
|
||||
}
|
||||
@ -933,23 +941,25 @@ static expand_error_t expand_stage_variables(wcstring input, std::vector<complet
|
||||
}
|
||||
|
||||
static expand_error_t expand_stage_braces(wcstring input, std::vector<completion_t> *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<completion_t> *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);
|
||||
expand_home_directory(input, vars);
|
||||
}
|
||||
expand_percent_self(input);
|
||||
append_completion(out, std::move(input));
|
||||
return EXPAND_OK;
|
||||
}
|
||||
|
||||
static expand_error_t expand_stage_wildcards(wcstring path_to_expand,
|
||||
std::vector<completion_t> *out, expand_flags_t flags,
|
||||
static expand_error_t expand_stage_wildcards(wcstring path_to_expand, std::vector<completion_t> *out,
|
||||
expand_flags_t flags, const environment_t &vars,
|
||||
parse_error_list_t *errors) {
|
||||
UNUSED(errors);
|
||||
expand_error_t result = EXPAND_OK;
|
||||
@ -968,7 +978,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<bool>(flags & EXPAND_SPECIAL_FOR_CD);
|
||||
bool for_command = static_cast<bool>(flags & EXPAND_SPECIAL_FOR_COMMAND);
|
||||
@ -1000,7 +1010,7 @@ static expand_error_t expand_stage_wildcards(wcstring path_to_expand,
|
||||
// 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()) {
|
||||
@ -1042,7 +1052,8 @@ static expand_error_t expand_stage_wildcards(wcstring path_to_expand,
|
||||
}
|
||||
|
||||
expand_error_t expand_string(wcstring input, std::vector<completion_t> *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 +1075,7 @@ expand_error_t expand_string(wcstring input, std::vector<completion_t> *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 &&
|
||||
@ -1081,7 +1092,7 @@ expand_error_t expand_string(wcstring input, std::vector<completion_t> *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()),
|
||||
@ -1090,14 +1101,15 @@ expand_error_t expand_string(wcstring input, std::vector<completion_t> *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<completion_t> 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 +1117,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 +1127,9 @@ expand_error_t expand_to_command_and_args(const wcstring &instr, wcstring *out_c
|
||||
}
|
||||
|
||||
std::vector<completion_t> 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;
|
||||
@ -1180,34 +1193,31 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc
|
||||
return result;
|
||||
}
|
||||
|
||||
static owning_lock<std::map<wcstring, wcstring>> 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<wcstring> expand_abbreviation(const wcstring &src, const environment_t &vars) {
|
||||
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;
|
||||
wcstring unesc_src;
|
||||
if (!unescape_string(src, &unesc_src, STRING_STYLE_VAR)) {
|
||||
return none();
|
||||
}
|
||||
wcstring var_name = L"_fish_abbr_" + unesc_src;
|
||||
if (auto var_value = vars.get(var_name)) {
|
||||
return var_value->as_string();
|
||||
}
|
||||
return none();
|
||||
}
|
||||
|
||||
std::map<wcstring, wcstring> 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<wcstring, wcstring> 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;
|
||||
}
|
||||
|
||||
|
25
src/expand.h
25
src/expand.h
@ -14,9 +14,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common.h"
|
||||
#include "maybe.h"
|
||||
#include "parse_constants.h"
|
||||
|
||||
class environment_t;
|
||||
class env_var_t;
|
||||
class environment_t;
|
||||
|
||||
enum {
|
||||
/// Flag specifying that cmdsubst expansion should be skipped.
|
||||
@ -111,13 +114,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<completion_t> *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 +133,8 @@ __warn_unused expand_error_t expand_string(wcstring input, std::vector<completio
|
||||
/// \param errors Resulting errors, or NULL to ignore
|
||||
///
|
||||
/// \return Whether expansion succeded
|
||||
bool expand_one(wcstring &inout_str, expand_flags_t flags, parse_error_list_t *errors = NULL);
|
||||
bool expand_one(wcstring &inout_str, expand_flags_t flags, const environment_t &vars,
|
||||
parse_error_list_t *errors = NULL);
|
||||
|
||||
/// Expand a command string like $HOME/bin/cmd into a command and list of arguments.
|
||||
/// Return the command and arguments by reference.
|
||||
@ -136,8 +142,8 @@ bool expand_one(wcstring &inout_str, expand_flags_t flags, parse_error_list_t *e
|
||||
/// that API does not distinguish between expansion resulting in an empty command (''), and
|
||||
/// expansion resulting in no command (e.g. unset variable).
|
||||
// \return an expand error.
|
||||
expand_error_t expand_to_command_and_args(const wcstring &instr, wcstring *out_cmd,
|
||||
wcstring_list_t *out_args,
|
||||
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 = NULL);
|
||||
|
||||
/// Convert the variable value to a human readable form, i.e. escape things, handle arrays, etc.
|
||||
@ -147,15 +153,14 @@ 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 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<wcstring> expand_abbreviation(const wcstring &src, const environment_t &vars);
|
||||
|
||||
/// \return a snapshot of all abbreviations as a map abbreviation->expansion.
|
||||
std::map<wcstring, wcstring> get_abbreviations();
|
||||
|
@ -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].
|
||||
@ -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);
|
||||
}
|
||||
@ -417,7 +418,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);
|
||||
|
||||
|
@ -529,7 +529,7 @@ int main(int argc, char *argv[]) {
|
||||
std::vector<highlight_spec_t> 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;
|
||||
|
@ -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
|
||||
@ -911,7 +911,7 @@ static void test_parser() {
|
||||
|
||||
say(L"Testing eval_args");
|
||||
completion_list_t comps;
|
||||
parser_t::expand_argument_list(L"alpha 'beta gamma' delta", 0, &comps);
|
||||
parser_t::expand_argument_list(L"alpha 'beta gamma' delta", 0, null_environment_t{}, &comps);
|
||||
do_test(comps.size() == 3);
|
||||
do_test(comps.at(0).completion == L"alpha");
|
||||
do_test(comps.at(1).completion == L"beta gamma");
|
||||
@ -1500,6 +1500,25 @@ static void test_lru() {
|
||||
do_test(cache.evicted.size() == size_t(total_nodes));
|
||||
}
|
||||
|
||||
/// A crappy environment_t that only knows about PWD.
|
||||
struct pwd_environment_t : public environment_t {
|
||||
std::map<wcstring, wcstring> extras;
|
||||
|
||||
virtual maybe_t<env_var_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 {};
|
||||
}
|
||||
|
||||
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 +1534,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 {
|
||||
@ -1755,7 +1774,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<std::pair<const wcstring, const wcstring>> abbreviations = {
|
||||
{L"gc", L"git checkout"},
|
||||
@ -1763,66 +1783,69 @@ 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");
|
||||
}
|
||||
|
||||
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"", vars)) err(L"Unexpected success with empty abbreviation");
|
||||
if (expand_abbreviation(L"nothing", vars)) 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", vars);
|
||||
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", vars);
|
||||
if (!mresult) err(L"Unexpected failure with foo abbreviation");
|
||||
if (*mresult != L"bar") err(L"Wrong abbreviation result for foo");
|
||||
|
||||
bool expanded;
|
||||
expanded = reader_expand_abbreviation_in_command(L"just a command", 3, &result);
|
||||
wcstring 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__);
|
||||
|
||||
env_pop();
|
||||
vars.pop();
|
||||
}
|
||||
|
||||
/// Test path functions.
|
||||
@ -2119,30 +2142,32 @@ 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.
|
||||
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");
|
||||
@ -2172,7 +2197,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);
|
||||
}
|
||||
@ -2185,7 +2210,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<wchar_t> args;
|
||||
@ -2333,13 +2358,23 @@ 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<completion_t> completions;
|
||||
complete_set_variable_names(&names);
|
||||
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"};
|
||||
}
|
||||
|
||||
complete(L"$", &completions, COMPLETION_REQUEST_DEFAULT);
|
||||
maybe_t<env_var_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 {};
|
||||
}
|
||||
};
|
||||
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);
|
||||
do_test(completions.at(0).completion == L"Bar1");
|
||||
@ -2350,7 +2385,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 +2393,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 +2411,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,116 +2442,116 @@ 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();
|
||||
completions.clear();
|
||||
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);
|
||||
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);
|
||||
do_test(completions.at(0).completion == L"four");
|
||||
@ -2592,9 +2629,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<completion_t> comps;
|
||||
complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION);
|
||||
complete(command, &comps, COMPLETION_REQUEST_AUTOSUGGESTION, vars);
|
||||
|
||||
bool expects_error = (expected == L"<error>");
|
||||
|
||||
@ -2628,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<completion_t> comps;
|
||||
complete(command, &comps, COMPLETION_REQUEST_DEFAULT);
|
||||
complete(command, &comps, COMPLETION_REQUEST_DEFAULT, vars);
|
||||
|
||||
bool expects_error = (expected == L"<error>");
|
||||
|
||||
@ -2679,7 +2716,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");
|
||||
}
|
||||
@ -2695,70 +2732,82 @@ 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();
|
||||
|
||||
env_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"<error>",
|
||||
perform_one_autosuggestion_cd_test(L"cd '~/test_autosuggest_suggest_specia'", L"<error>", 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_completion_cd_test(L"cd ~haha", L"ha/", __LINE__);
|
||||
perform_one_completion_cd_test(L"cd ~hahaha/", L"path1/", __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/", vars, __LINE__);
|
||||
perform_one_completion_cd_test(L"cd ~hahaha/", L"path1/", vars, __LINE__);
|
||||
|
||||
env_remove(L"HOME", ENV_LOCAL | ENV_EXPORT);
|
||||
parser_t::principal_parser().vars().remove(L"HOME", ENV_LOCAL | ENV_EXPORT);
|
||||
popd();
|
||||
}
|
||||
|
||||
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;
|
||||
@ -4274,9 +4323,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}});
|
||||
|
||||
@ -4309,7 +4359,7 @@ static void test_highlighting() {
|
||||
do_test(expected_colors.size() == text.size());
|
||||
|
||||
std::vector<highlight_spec_t> 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(),
|
||||
@ -4328,8 +4378,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() {
|
||||
@ -4389,7 +4439,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<wchar_t **>(argv));
|
||||
@ -4728,14 +4778,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);
|
||||
|
@ -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<const function_properties_t> 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<wcstring, env_var_t> inherit_vars;
|
||||
/// Flag for specifying that this function was automatically loaded.
|
||||
const bool is_autoload;
|
||||
public:
|
||||
/// Immutable properties of the function.
|
||||
std::shared_ptr<const function_properties_t> 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<wcstring, env_var_t> 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<wcstring> &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<wcstring> &names, int get_hidden)
|
||||
}
|
||||
}
|
||||
|
||||
static std::map<wcstring, env_var_t> snapshot_vars(const wcstring_list_t &vars) {
|
||||
static std::map<wcstring, env_var_t> snapshot_vars(const wcstring_list_t &vars,
|
||||
const environment_t &src) {
|
||||
std::map<wcstring, env_var_t> 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<const function_properties_t>(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.
|
||||
@ -198,7 +204,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() ||
|
||||
@ -344,23 +350,24 @@ 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<wcstring, env_var_t> &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;
|
||||
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 {
|
||||
env_set_empty(named_arg, ENV_LOCAL | ENV_USER);
|
||||
vars.set_empty(named_arg, ENV_LOCAL | ENV_USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
@ -109,7 +109,8 @@ std::map<wcstring, env_var_t> 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<wcstring, env_var_t> &inherited_vars);
|
||||
|
||||
/// Observes that fish_function_path has changed.
|
||||
|
@ -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<bool>(flags & PATH_REQUIRE_DIR);
|
||||
@ -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 );
|
||||
|
||||
@ -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<wcstring> pathsv =
|
||||
cdpath.missing_or_empty() ? wcstring_list_t{L"."} : cdpath->as_list();
|
||||
|
||||
@ -236,22 +236,24 @@ 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
|
||||
// appropriately for commands. If we succeed, return true.
|
||||
static bool plain_statement_get_expanded_command(const wcstring &src,
|
||||
tnode_t<g::plain_statement> 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<wcstring> 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;
|
||||
}
|
||||
|
||||
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())
|
||||
@ -310,8 +312,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 +323,7 @@ static bool autosuggest_parse_command(const wcstring &buff, wcstring *out_expand
|
||||
// Find the last statement.
|
||||
auto last_statement = parse_tree.find_last_node<g::plain_statement>();
|
||||
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<g::argument>(last_statement)) {
|
||||
*out_last_arg = last_arg.get_source(buff);
|
||||
@ -333,7 +335,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;
|
||||
@ -341,18 +343,17 @@ 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");
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -365,7 +366,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)) {
|
||||
@ -667,7 +668,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 +699,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),
|
||||
@ -801,7 +802,7 @@ void highlighter_t::color_argument(tnode_t<g::tok_string> 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
|
||||
@ -814,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;
|
||||
}
|
||||
@ -823,7 +824,7 @@ bool highlighter_t::is_cd(tnode_t<g::plain_statement> 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,11 +841,11 @@ void highlighter_t::color_arguments(const std::vector<tnode_t<g::argument>> &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 &&
|
||||
!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);
|
||||
}
|
||||
}
|
||||
@ -883,7 +884,7 @@ void highlighter_t::color_redirection(tnode_t<g::redirection> 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 {
|
||||
@ -985,7 +986,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;
|
||||
@ -1014,14 +1015,14 @@ 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, vars).has_value();
|
||||
|
||||
// Regular commands
|
||||
if (!is_valid && command_ok) is_valid = path_get_path(cmd, NULL, vars);
|
||||
|
||||
// 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.
|
||||
@ -1117,7 +1118,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);
|
||||
@ -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,
|
||||
@ -1196,11 +1198,11 @@ const highlighter_t::color_array_t &highlighter_t::highlight() {
|
||||
}
|
||||
|
||||
void highlight_shell(const wcstring &buff, std::vector<highlight_spec_t> &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.
|
||||
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 */);
|
||||
@ -1208,11 +1210,11 @@ void highlight_shell(const wcstring &buff, std::vector<highlight_spec_t> &color,
|
||||
}
|
||||
|
||||
void highlight_shell_no_io(const wcstring &buff, std::vector<highlight_spec_t> &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.
|
||||
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 */);
|
||||
@ -1306,7 +1308,7 @@ static void highlight_universal_internal(const wcstring &buffstr,
|
||||
}
|
||||
|
||||
void highlight_universal(const wcstring &buff, std::vector<highlight_spec_t> &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());
|
||||
|
@ -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<highlight_spec_t> &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<highlight_spec_t> &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<highlight_spec_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_universal(const wcstring &buffstr, std::vector<highlight_spec_t> &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).
|
||||
@ -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
|
||||
|
@ -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"
|
||||
@ -1693,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);
|
||||
|
||||
@ -1803,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) {
|
||||
@ -1866,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()) {
|
||||
@ -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();
|
||||
@ -1992,13 +1992,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();
|
||||
}
|
||||
|
@ -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();
|
||||
@ -229,7 +233,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();
|
||||
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -121,13 +121,14 @@ tnode_t<g::plain_statement> parse_execution_context_t::infinite_recursive_statem
|
||||
// are not infinite recursion. In particular that is what enables 'wrapper functions'.
|
||||
tnode_t<g::statement> statement = first_job.child<0>();
|
||||
tnode_t<g::job_continuation> continuation = first_job.child<1>();
|
||||
const null_environment_t nullenv{};
|
||||
while (statement) {
|
||||
tnode_t<g::plain_statement> plain_statement =
|
||||
statement.try_get_child<g::decorated_statement, 0>()
|
||||
.try_get_child<g::plain_statement, 0>();
|
||||
if (plain_statement) {
|
||||
maybe_t<wcstring> 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<g::tok_string> 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;
|
||||
}
|
||||
@ -384,10 +385,11 @@ 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 = 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());
|
||||
@ -404,7 +406,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;
|
||||
|
||||
@ -438,8 +440,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<completion_t> 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 +724,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);
|
||||
@ -798,7 +801,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.
|
||||
@ -811,9 +814,9 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
|
||||
if (args_from_cmd_expansion.empty() && !args.try_get_child<g::argument, 0>() &&
|
||||
!args.try_get_child<g::redirection, 0>()) {
|
||||
// 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_as_implicit_cd(cmd, parser->vars().get_pwd_slash(), parser->vars())
|
||||
.has_value();
|
||||
}
|
||||
}
|
||||
|
||||
@ -883,7 +886,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 +935,8 @@ bool parse_execution_context_t::determine_io_chain(tnode_t<g::arguments_or_redir
|
||||
auto redirect_type = redirection_type(redirect_node, pstree->src, &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 =
|
||||
|
@ -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<wcstring> 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;
|
||||
}
|
||||
|
||||
|
@ -96,30 +96,26 @@ 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() : cancellation_requested(false), is_within_fish_initialization(false) {}
|
||||
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;
|
||||
|
||||
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
|
||||
@ -158,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;
|
||||
}
|
||||
}
|
||||
@ -176,7 +172,7 @@ void parser_t::pop_block(const block_t *expected) {
|
||||
std::unique_ptr<block_t> 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;
|
||||
@ -318,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<completion_t> *output_arg_list) {
|
||||
assert(output_arg_list != NULL);
|
||||
|
||||
@ -334,7 +331,8 @@ void parser_t::expand_argument_list(const wcstring &arg_list_src, expand_flags_t
|
||||
tnode_t<grammar::freestanding_argument_list> arg_list(&tree, &tree.at(0));
|
||||
while (auto arg = arg_list.next_in_list<grammar::argument>()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -399,7 +397,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 +534,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);
|
||||
|
27
src/parser.h
27
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<parse_execution_context_t> execution_context;
|
||||
/// List of called functions, used to help prevent infinite recursion.
|
||||
@ -175,7 +173,8 @@ class parser_t {
|
||||
std::vector<std::unique_ptr<block_t>> 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.
|
||||
@ -200,12 +199,21 @@ 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;
|
||||
|
||||
/// 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 +222,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;
|
||||
|
||||
@ -245,7 +250,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<completion_t> *output);
|
||||
const environment_t &vars, std::vector<completion_t> *output);
|
||||
|
||||
/// Returns a string describing the current parser position in the format 'FILENAME (line
|
||||
/// LINE_NUMBER): LINE'. Example:
|
||||
@ -270,9 +275,9 @@ 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);
|
||||
/// 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
|
||||
|
59
src/path.cpp
59
src/path.cpp
@ -115,16 +115,12 @@ 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"));
|
||||
}
|
||||
|
||||
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<wcstring> pathsv;
|
||||
if (path_var) path_var->to_list(pathsv);
|
||||
for (auto path : pathsv) {
|
||||
@ -157,13 +153,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 env_vars_snapshot_t &env_vars) {
|
||||
maybe_t<wcstring> 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.
|
||||
@ -188,7 +183,7 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, 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;
|
||||
@ -197,36 +192,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 env_vars_snapshot_t &vars) {
|
||||
maybe_t<wcstring> 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;
|
||||
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"..") {
|
||||
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
|
||||
@ -266,10 +257,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()) {
|
||||
@ -295,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";
|
||||
@ -305,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");
|
||||
|
32
src/path.h
32
src/path.h
@ -34,40 +34,36 @@ 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 env_vars_snapshot_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);
|
||||
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. 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 env_vars_snapshot_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<wcstring> 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 env_vars_snapshot_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<wcstring> 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.
|
||||
|
@ -6,6 +6,7 @@
|
||||
// IWYU pragma: no_include <__bit_reference>
|
||||
#include "config.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
@ -1289,3 +1290,9 @@ void hup_background_jobs() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::atomic<bool> 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(); }
|
||||
|
@ -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();
|
||||
|
||||
|
@ -395,7 +395,15 @@ 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(); }
|
||||
|
||||
/// 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
|
||||
reader_data_t(history_t *hist) : history(hist) {}
|
||||
@ -691,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();
|
||||
@ -742,14 +750,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, vars)) {
|
||||
// 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;
|
||||
}
|
||||
@ -760,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();
|
||||
@ -825,7 +833,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++) {
|
||||
@ -863,7 +872,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++) {
|
||||
@ -874,7 +884,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);
|
||||
@ -884,7 +894,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);
|
||||
@ -903,10 +913,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);
|
||||
@ -1277,9 +1289,10 @@ struct autosuggestion_result_t {
|
||||
// on a background thread) to determine the autosuggestion
|
||||
static std::function<autosuggestion_result_t(void)> 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(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
|
||||
@ -1329,7 +1342,7 @@ static std::function<autosuggestion_result_t(void)> get_autosuggestion_performer
|
||||
// Try normal completions.
|
||||
completion_request_flags_t complete_flags = COMPLETION_REQUEST_AUTOSUGGESTION;
|
||||
std::vector<completion_t> 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);
|
||||
@ -1824,7 +1837,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.
|
||||
@ -1997,7 +2010,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];
|
||||
@ -2008,7 +2021,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) {
|
||||
@ -2017,7 +2030,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);
|
||||
|
||||
@ -2032,12 +2045,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();
|
||||
@ -2142,14 +2155,15 @@ 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.
|
||||
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);
|
||||
@ -2201,7 +2215,8 @@ static void highlight_complete(highlight_result_t result) {
|
||||
static std::function<highlight_result_t(void)> 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;
|
||||
@ -2337,7 +2352,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);
|
||||
@ -2345,7 +2361,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;
|
||||
|
||||
@ -2471,6 +2486,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.
|
||||
@ -2683,7 +2700,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);
|
||||
data->complete_func(buffcpy, &comp, complete_flags, vars);
|
||||
|
||||
// Munge our completions.
|
||||
completions_sort_and_prioritize(&comp);
|
||||
@ -2890,7 +2907,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());
|
||||
|
14
src/reader.h
14
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.
|
||||
@ -149,18 +149,14 @@ 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_t> *,
|
||||
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.
|
||||
typedef void (*highlight_function_t)(const wcstring &, std::vector<highlight_spec_t> &, 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 &);
|
||||
@ -220,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,
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user