Introduce operation_context_t

This commit recognizes an existing pattern: many operations need some
combination of a set of variables, a way to detect cancellation, and
sometimes a parser. For example, tab completion needs a parser to execute
custom completions, the variable set, should cancel on SIGINT. Background
autosuggestions don't need a parser, but they do need the variables and
should cancel if the user types something new. Etc.

This introduces a new triple operation_context_t that wraps these concepts
up. This simplifies many method signatures and argument passing.
This commit is contained in:
ridiculousfish 2020-01-15 17:14:47 -08:00
parent db98ee13a9
commit 0f7bba5f0e
24 changed files with 337 additions and 238 deletions

View file

@ -118,6 +118,7 @@ SET(FISH_SRCS
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp src/wutil.cpp
src/future_feature_flags.cpp src/redirection.cpp src/topic_monitor.cpp
src/flog.cpp src/trace.cpp src/timer.cpp src/null_terminated_array.cpp
src/operation_context.cpp
)
# Header files are just globbed.

View file

@ -347,8 +347,8 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (!have_do_complete_param)
parser.libdata().builtin_complete_current_commandline = true;
completion_list_t comp = complete(do_complete_param, completion_request_t::fuzzy_match,
parser.vars(), parser.shared());
completion_list_t comp =
complete(do_complete_param, completion_request_t::fuzzy_match, parser.context());
for (const auto &next : comp) {
// Make a fake commandline, and then apply the completion to it.
@ -392,7 +392,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) {
std::vector<highlight_spec_t> colors;
size_t len = repr.size();
highlight_shell_no_io(repr, colors, len, nullptr, env_stack_t::globals());
highlight_shell_no_io(repr, colors, len, operation_context_t::globals());
streams.out.append(str2wcstring(colorize(repr, colors)));
} else {
streams.out.append(repr);

View file

@ -23,6 +23,7 @@
#include "function.h"
#include "highlight.h"
#include "io.h"
#include "parser.h"
#include "parser_keywords.h"
#include "proc.h"
#include "signal.h"
@ -263,8 +264,9 @@ static int report_function_metadata(const wchar_t *funcname, bool verbose, io_st
append_format(comment, L"# Defined in %ls @ line %d\n", path, line_number);
if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) {
std::vector<highlight_spec_t> colors;
highlight_shell_no_io(comment, colors, comment.size(), nullptr,
env_stack_t::globals());
highlight_shell_no_io(
comment, colors, comment.size(),
operation_context_t{nullptr, env_stack_t::globals(), no_cancel});
streams.out.append(str2wcstring(colorize(comment, colors)));
} else {
streams.out.append(comment);
@ -437,7 +439,7 @@ int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) {
std::vector<highlight_spec_t> colors;
highlight_shell_no_io(def, colors, def.size(), nullptr, env_stack_t::globals());
highlight_shell_no_io(def, colors, def.size(), operation_context_t::globals());
streams.out.append(str2wcstring(colorize(def, colors)));
} else {
streams.out.append(def);

View file

@ -208,7 +208,7 @@ static int read_interactive(parser_t &parser, wcstring &buff, int nchars, bool s
reader_set_left_prompt(prompt);
reader_set_right_prompt(right_prompt);
if (shell) {
reader_set_complete_function(&complete);
reader_set_complete_ok(true);
reader_set_highlight_function(&highlight_shell);
reader_set_test_function(&reader_shell_test);
}

View file

@ -266,6 +266,10 @@ std::shared_ptr<T> move_to_sharedptr(T &&v) {
return std::make_shared<T>(std::move(v));
}
/// A function type to check for cancellation.
/// \return true if execution should cancel.
using cancel_checker_t = std::function<bool()>;
/// Print a stack trace to stderr.
void show_stackframe(const wchar_t msg_level, int frame_count = 100, int skip_levels = 0);

View file

@ -311,12 +311,8 @@ void completions_sort_and_prioritize(completion_list_t *comps, completion_reques
/// Class representing an attempt to compute completions.
class completer_t {
/// Environment inside which we are completing.
const environment_t &vars;
/// The parser used for condition testing and subshell expansion.
/// If null, these features are disabled.
std::shared_ptr<parser_t> parser;
/// The operation context for this completion.
const operation_context_t &ctx;
/// The command to complete.
const wcstring cmd;
@ -395,9 +391,8 @@ class completer_t {
const std::vector<tok_t> &args);
public:
completer_t(const environment_t &vars, std::shared_ptr<parser_t> parser, wcstring c,
completion_request_flags_t f)
: vars(vars), parser(std::move(parser)), cmd(std::move(c)), flags(f) {}
completer_t(const operation_context_t &ctx, wcstring c, completion_request_flags_t f)
: ctx(ctx), cmd(std::move(c)), flags(f) {}
void perform();
@ -421,7 +416,7 @@ bool completer_t::condition_test(const wcstring &condition) {
// std::fwprintf( stderr, L"No condition specified\n" );
return true;
}
if (!parser) {
if (!ctx.parser) {
return false;
}
@ -430,7 +425,8 @@ 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, *parser, false /* don't apply exit status */));
test_res =
(0 == exec_subshell(condition, *ctx.parser, false /* don't apply exit status */));
condition_cache[condition] = test_res;
} else {
// Use the old value.
@ -558,7 +554,7 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description
wcstring tmp = wc_escaped;
if (!expand_one(tmp,
this->expand_flags() | expand_flag::skip_cmdsubst | expand_flag::skip_wildcards,
vars, parser))
ctx))
return;
const wcstring wc = parse_util_unescape_wildcards(tmp);
@ -576,7 +572,7 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description
/// for the executable.
void completer_t::complete_cmd_desc(const wcstring &str) {
ASSERT_IS_MAIN_THREAD();
if (!parser) return;
if (!ctx.parser) return;
wcstring cmd;
size_t pos = str.find_last_of(L'/');
@ -619,7 +615,7 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
// systems with a large set of manuals, but it should be ok since apropos is only called once.
lookup.clear();
list.clear();
if (exec_subshell(lookup_cmd, *parser, list, false /* don't apply exit status */) != -1) {
if (exec_subshell(lookup_cmd, *ctx.parser, list, false /* don't apply exit status */) != -1) {
// Then discard anything that is not a possible completion and put the result into a
// hashtable with the completion as key and the description as value.
//
@ -683,7 +679,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd) {
expand_string(str_cmd, &this->completions,
this->expand_flags() | expand_flag::special_for_command |
expand_flag::for_completions | expand_flag::executables_only,
vars, parser, nullptr);
ctx);
if (result != expand_result_t::error && this->wants_descriptions()) {
this->complete_cmd_desc(str_cmd);
}
@ -695,7 +691,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd) {
expand_string(
str_cmd, &this->completions,
this->expand_flags() | expand_flag::for_completions | expand_flag::directories_only,
vars, parser, nullptr);
ctx);
UNUSED(ignore);
if (str_cmd.empty() || (str_cmd.find(L'/') == wcstring::npos && str_cmd.at(0) != L'~')) {
@ -717,7 +713,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd) {
}
void completer_t::complete_abbr(const wcstring &cmd) {
std::map<wcstring, wcstring> abbrs = get_abbreviations(vars);
std::map<wcstring, wcstring> abbrs = get_abbreviations(ctx.vars);
completion_list_t possible_comp;
possible_comp.reserve(abbrs.size());
for (const auto &kv : abbrs) {
@ -752,9 +748,9 @@ void completer_t::complete_from_args(const wcstring &str, const wcstring &args,
bool is_autosuggest = (this->type() == COMPLETE_AUTOSUGGEST);
bool saved_interactive = false;
if (parser) {
saved_interactive = parser->libdata().is_interactive;
parser->libdata().is_interactive = false;
if (ctx.parser) {
saved_interactive = ctx.parser->libdata().is_interactive;
ctx.parser->libdata().is_interactive = false;
}
expand_flags_t eflags{};
@ -763,10 +759,10 @@ void completer_t::complete_from_args(const wcstring &str, const wcstring &args,
eflags |= expand_flag::skip_cmdsubst;
}
completion_list_t possible_comp = parser_t::expand_argument_list(args, eflags, vars, parser);
completion_list_t possible_comp = parser_t::expand_argument_list(args, eflags, ctx);
if (parser) {
parser->libdata().is_interactive = saved_interactive;
if (ctx.parser) {
ctx.parser->libdata().is_interactive = saved_interactive;
}
this->complete_strings(escape_string(str, ESCAPE_ALL), const_desc(desc), possible_comp, flags);
@ -883,7 +879,7 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
bool use_common = true, use_files = true, has_force = false;
wcstring cmd, path;
parse_cmd_string(cmd_orig, &path, &cmd, vars);
parse_cmd_string(cmd_orig, &path, &cmd, ctx.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
@ -909,7 +905,7 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
// may be faster, path_get_path can potentially do a lot of FS/IO access, so env.get() +
// function_exists() should still be faster.
// Use cmd_orig here as it is potentially pathed.
head_exists = head_exists || path_get_path(cmd_orig, nullptr, vars);
head_exists = head_exists || path_get_path(cmd_orig, nullptr, ctx.vars);
}
if (!head_exists) {
@ -1103,7 +1099,7 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
/// Perform generic (not command-specific) expansions on the specified string.
void completer_t::complete_param_expand(const wcstring &str, bool do_file,
bool handle_as_special_cd) {
if (reader_test_should_cancel()) return;
if (ctx.check_cancel()) return;
expand_flags_t flags =
this->expand_flags() | expand_flag::skip_cmdsubst | expand_flag::for_completions;
@ -1137,8 +1133,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file,
// See #4954.
const wcstring sep_string = wcstring(str, sep_index + 1);
completion_list_t local_completions;
if (expand_string(sep_string, &local_completions, flags, vars, parser, nullptr) ==
expand_result_t::error) {
if (expand_string(sep_string, &local_completions, flags, ctx) == expand_result_t::error) {
debug(3, L"Error while expanding string '%ls'", sep_string.c_str());
}
@ -1157,8 +1152,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.clear(expand_flag::fuzzy_match);
if (expand_string(str, &this->completions, flags, vars, parser, nullptr) ==
expand_result_t::error) {
if (expand_string(str, &this->completions, flags, ctx) == expand_result_t::error) {
debug(3, L"Error while expanding string '%ls'", str.c_str());
}
}
@ -1171,7 +1165,7 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) {
size_t varlen = str.length() - start_offset;
bool res = false;
for (const wcstring &env_name : vars.get_names(0)) {
for (const wcstring &env_name : ctx.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) {
@ -1197,14 +1191,14 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) {
// #6288.
if (env_name == L"history") {
history_t *history =
&history_t::history_with_name(history_session_id(parser->vars()));
&history_t::history_with_name(history_session_id(ctx.vars));
for (size_t i = 1; i < history->size() && desc.size() < 64; i++) {
if (i > 1) desc += L' ';
desc += expand_escape_string(history->item_at_index(i).str());
}
} else {
// Can't use this->vars here, it could be any variable.
auto var = vars.get(env_name);
// Can't use ctx.vars here, it could be any variable.
auto var = ctx.vars.get(env_name);
if (!var) continue;
wcstring value = expand_escape_variable(*var);
@ -1311,7 +1305,7 @@ bool completer_t::try_complete_user(const wcstring &str) {
setpwent();
// cppcheck-suppress getpwentCalled
while (struct passwd *pw = getpwent()) {
if (reader_test_should_cancel()) {
if (ctx.check_cancel()) {
break;
}
const wcstring pw_name_str = str2wcstring(pw->pw_name);
@ -1349,6 +1343,7 @@ using wrap_chain_visited_set_t = std::set<std::pair<wcstring, wcstring>>;
// Recursive implementation of walk_wrap_chain().
static void walk_wrap_chain_recursive(const wcstring &command_line, source_range_t command_range,
const wrap_chain_visitor_t &visitor,
const cancel_checker_t &cancel_checker,
wrap_chain_visited_set_t *visited, size_t depth) {
// Limit our recursion depth. This prevents cycles in the wrap chain graph from overflowing.
if (depth > 24) return;
@ -1377,8 +1372,8 @@ static void walk_wrap_chain_recursive(const wcstring &command_line, source_range
// Recurse with our new command and command line.
source_range_t faux_source_range{uint32_t(where),
uint32_t(wrapped_command.size())};
walk_wrap_chain_recursive(faux_commandline, faux_source_range, visitor, visited,
depth + 1);
walk_wrap_chain_recursive(faux_commandline, faux_source_range, visitor,
cancel_checker, visited, depth + 1);
}
}
}
@ -1391,9 +1386,10 @@ static void walk_wrap_chain_recursive(const wcstring &command_line, source_range
// target wrapped by the given command, update the command line with that target and invoke this
// recursively.
static void walk_wrap_chain(const wcstring &command_line, source_range_t command_range,
const wrap_chain_visitor_t &visitor) {
const wrap_chain_visitor_t &visitor,
const cancel_checker_t &cancel_checker) {
wrap_chain_visited_set_t visited;
walk_wrap_chain_recursive(command_line, command_range, visitor, &visited, 0);
walk_wrap_chain_recursive(command_line, command_range, visitor, cancel_checker, &visited, 0);
}
/// If the argument contains a '[' typed by the user, completion by appending to the argument might
@ -1574,31 +1570,29 @@ void completer_t::perform() {
// we're doing autosuggestions.
bool wants_transient = depth > 0 && !(flags & completion_request_t::autosuggestion);
if (wants_transient) {
parser->libdata().transient_commandlines.push_back(cmdline);
ctx.parser->libdata().transient_commandlines.push_back(cmdline);
}
bool is_variable_assignment = bool(variable_assignment_equals_pos(cmd));
if (is_variable_assignment && parser) {
if (is_variable_assignment && ctx.parser) {
// To avoid issues like #2705 we complete commands starting with variable
// assignments by recursively calling complete for the command suffix
// without the first variable assignment token.
wcstring unaliased_cmd;
if (parser->libdata().transient_commandlines.empty()) {
if (ctx.parser->libdata().transient_commandlines.empty()) {
unaliased_cmd = cmdline;
} else {
unaliased_cmd = parser->libdata().transient_commandlines.back();
unaliased_cmd = ctx.parser->libdata().transient_commandlines.back();
}
tokenizer_t tok(unaliased_cmd.c_str(), TOK_ACCEPT_UNFINISHED);
maybe_t<tok_t> cmd_tok = tok.next();
assert(cmd_tok);
unaliased_cmd =
unaliased_cmd.replace(0, cmd_tok->offset + cmd_tok->length, L"");
parser->libdata().transient_commandlines.push_back(unaliased_cmd);
ctx.parser->libdata().transient_commandlines.push_back(unaliased_cmd);
cleanup_t remove_transient(
[&] { parser->libdata().transient_commandlines.pop_back(); });
completion_list_t comp =
complete(unaliased_cmd, completion_request_t::fuzzy_match, parser->vars(),
parser->shared());
this->completions.insert(completions.end(), comp.begin(), comp.end());
[&] { ctx.parser->libdata().transient_commandlines.pop_back(); });
vec_append(this->completions,
complete(unaliased_cmd, completion_request_t::fuzzy_match, ctx));
do_file = false;
} else if (!complete_param(
cmd, previous_argument_unescape, current_argument_unescape,
@ -1606,14 +1600,14 @@ void completer_t::perform() {
do_file = false;
}
if (wants_transient) {
parser->libdata().transient_commandlines.pop_back();
ctx.parser->libdata().transient_commandlines.pop_back();
}
};
assert(cmd_tok.offset < std::numeric_limits<uint32_t>::max());
assert(cmd_tok.length < std::numeric_limits<uint32_t>::max());
source_range_t range = {static_cast<uint32_t>(cmd_tok.offset),
static_cast<uint32_t>(cmd_tok.length)};
walk_wrap_chain(cmd, range, receiver);
walk_wrap_chain(cmd, range, receiver, ctx.cancel_checker);
}
// Hack. If we're cd, handle it specially (issue #1059, others).
@ -1636,14 +1630,14 @@ void completer_t::perform() {
}
completion_list_t complete(const wcstring &cmd_with_subcmds, completion_request_flags_t flags,
const environment_t &vars, const std::shared_ptr<parser_t> &parser) {
const operation_context_t &ctx) {
// 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 != nullptr && cmdsubst_end != nullptr && cmdsubst_end >= cmdsubst_begin);
wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin);
completer_t completer(vars, parser, std::move(cmd), flags);
completer_t completer(ctx, std::move(cmd), flags);
completer.perform();
return completer.acquire_completions();
}

View file

@ -117,6 +117,9 @@ struct enum_info_t<completion_request_t> {
using completion_request_flags_t = enum_set_t<completion_request_t>;
class completion_t;
using completion_list_t = std::vector<completion_t>;
enum complete_option_type_t {
option_type_args_only, // no option
option_type_short, // -x
@ -172,9 +175,9 @@ void complete_remove(const wcstring &cmd, bool cmd_is_path, const wcstring &opti
void complete_remove_all(const wcstring &cmd, bool cmd_is_path);
/// \return all completions of the command cmd.
class parser_t;
class operation_context_t;
completion_list_t complete(const wcstring &cmd, completion_request_flags_t flags,
const environment_t &vars, const std::shared_ptr<parser_t> &parser);
const operation_context_t &ctx);
/// Return a list of all current completions.
wcstring complete_print();

View file

@ -850,11 +850,8 @@ static void remove_internal_separator(wcstring *str, bool conv) {
namespace {
/// A type that knows how to perform expansions.
class expander_t {
/// Variables to use in expansion.
const environment_t &vars;
/// The parser for expanding command substitutions, or nullptr if not enabled.
std::shared_ptr<parser_t> parser;
/// Operation context for this expansion.
const operation_context_t &ctx;
/// Flags to use during expansion.
const expand_flags_t flags;
@ -873,14 +870,12 @@ class expander_t {
expand_result_t stage_home_and_self(wcstring input, completion_list_t *out);
expand_result_t stage_wildcards(wcstring path_to_expand, completion_list_t *out);
expander_t(const environment_t &vars, std::shared_ptr<parser_t> parser, expand_flags_t flags,
parse_error_list_t *errors)
: vars(vars), parser(std::move(parser)), flags(flags), errors(errors) {}
expander_t(const operation_context_t &ctx, expand_flags_t flags, parse_error_list_t *errors)
: ctx(ctx), flags(flags), errors(errors) {}
public:
static expand_result_t expand_string(wcstring input, completion_list_t *out_completions,
expand_flags_t flags, const environment_t &vars,
const std::shared_ptr<parser_t> &parser,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors);
};
@ -899,8 +894,8 @@ expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_list_t *ou
return expand_result_t::error;
}
} else {
assert(parser && "Must have a parser to expand command substitutions");
bool cmdsubst_ok = expand_cmdsubst(std::move(input), *parser, out, errors);
assert(ctx.parser && "Must have a parser to expand command substitutions");
bool cmdsubst_ok = expand_cmdsubst(std::move(input), *ctx.parser, out, errors);
if (!cmdsubst_ok) return expand_result_t::error;
}
@ -922,7 +917,7 @@ expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *o
append_completion(out, std::move(next));
} else {
size_t size = next.size();
if (!expand_variables(std::move(next), out, size, vars, errors)) {
if (!expand_variables(std::move(next), out, size, ctx.vars, errors)) {
return expand_result_t::error;
}
}
@ -930,13 +925,12 @@ expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *o
}
expand_result_t expander_t::stage_braces(wcstring input, completion_list_t *out) {
UNUSED(vars);
return expand_braces(input, flags, out, errors);
}
expand_result_t expander_t::stage_home_and_self(wcstring input, completion_list_t *out) {
if (!(flags & expand_flag::skip_home_directories)) {
expand_home_directory(input, vars);
expand_home_directory(input, ctx.vars);
}
expand_percent_self(input);
append_completion(out, std::move(input));
@ -961,7 +955,7 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
//
// 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 = vars.get_pwd_slash();
const wcstring working_dir = ctx.vars.get_pwd_slash();
wcstring_list_t effective_working_dirs;
bool for_cd = flags & expand_flag::special_for_cd;
bool for_command = flags & expand_flag::special_for_command;
@ -993,7 +987,7 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
// 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 = vars.get(for_cd ? L"CDPATH" : L"PATH")) {
if (auto paths_var = ctx.vars.get(for_cd ? L"CDPATH" : L"PATH")) {
paths = paths_var->as_list();
}
if (paths.empty()) {
@ -1009,8 +1003,8 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
result = expand_result_t::wildcard_no_match;
completion_list_t expanded;
for (const auto &effective_working_dir : effective_working_dirs) {
wildcard_expand_result_t expand_res =
wildcard_expand_string(path_to_expand, effective_working_dir, flags, &expanded);
wildcard_expand_result_t expand_res = wildcard_expand_string(
path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded);
switch (expand_res) {
case wildcard_expand_result_t::match:
// Something matched,so overall we matched.
@ -1038,10 +1032,9 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
}
expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out_completions,
expand_flags_t flags, const environment_t &vars,
const std::shared_ptr<parser_t> &parser,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors) {
assert(((flags & expand_flag::skip_cmdsubst) || parser) &&
assert(((flags & expand_flag::skip_cmdsubst) || ctx.parser) &&
"Must have a parser if not skipping command substitutions");
// Early out. If we're not completing, and there's no magic in the input, we're done.
if (!(flags & expand_flag::for_completions) && expand_is_clean(input)) {
@ -1049,7 +1042,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
return expand_result_t::ok;
}
expander_t expand(vars, parser, flags, errors);
expander_t expand(ctx, flags, errors);
// Our expansion stages.
const stage_t stages[] = {&expander_t::stage_cmdsubst, &expander_t::stage_variables,
@ -1087,7 +1080,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
if (total_result != expand_result_t::error) {
// Hack to un-expand tildes (see #647).
if (!(flags & expand_flag::skip_home_directories)) {
unexpand_tildes(input, vars, &completions);
unexpand_tildes(input, ctx.vars, &completions);
}
out_completions->insert(out_completions->end(),
std::make_move_iterator(completions.begin()),
@ -1098,21 +1091,20 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
} // namespace
expand_result_t expand_string(wcstring input, completion_list_t *out_completions,
expand_flags_t flags, const environment_t &vars,
const shared_ptr<parser_t> &parser, parse_error_list_t *errors) {
return expander_t::expand_string(std::move(input), out_completions, flags, vars, parser,
errors);
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors) {
return expander_t::expand_string(std::move(input), out_completions, flags, ctx, errors);
}
bool expand_one(wcstring &string, expand_flags_t flags, const environment_t &vars,
const shared_ptr<parser_t> &parser, parse_error_list_t *errors) {
bool expand_one(wcstring &string, expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors) {
completion_list_t completions;
if (!flags.get(expand_flag::for_completions) && expand_is_clean(string)) {
return true;
}
if (expand_string(string, &completions, flags | expand_flag::no_descriptions, vars, parser,
if (expand_string(std::move(string), &completions, flags | expand_flag::no_descriptions, ctx,
errors) != expand_result_t::error &&
completions.size() == 1) {
string = std::move(completions.at(0).completion);
@ -1121,7 +1113,7 @@ bool expand_one(wcstring &string, expand_flags_t flags, const environment_t &var
return false;
}
expand_result_t expand_to_command_and_args(const wcstring &instr, const environment_t &vars,
expand_result_t expand_to_command_and_args(const wcstring &instr, const operation_context_t &ctx,
wcstring *out_cmd, wcstring_list_t *out_args,
parse_error_list_t *errors) {
// Fast path.
@ -1133,8 +1125,8 @@ expand_result_t expand_to_command_and_args(const wcstring &instr, const environm
completion_list_t completions;
expand_result_t expand_err = expand_string(
instr, &completions,
{expand_flag::skip_cmdsubst, expand_flag::no_descriptions, expand_flag::skip_jobs}, vars,
nullptr, errors);
{expand_flag::skip_cmdsubst, expand_flag::no_descriptions, expand_flag::skip_jobs}, ctx,
errors);
if (expand_err == expand_result_t::ok || expand_err == expand_result_t::wildcard_match) {
// The first completion is the command, any remaning are arguments.
bool first = true;

View file

@ -21,6 +21,7 @@
class environment_t;
class env_var_t;
class environment_t;
class operation_context_t;
/// Set of flags controlling expansions.
enum class expand_flag {
@ -125,18 +126,15 @@ enum class expand_result_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 skip_cmdsubst skip_variables and skip_wildcards
/// \param vars variables used during expansion.
/// \param parser the parser to use for command substitutions, or nullptr to disable.
/// \param errors Resulting errors, or NULL to ignore
/// \param ctx The parser, variables, and cancellation checker for this operation. The parser may
/// be null. \param errors Resulting errors, or nullptr to ignore
///
/// \return An expand_result_t.
/// wildcard_no_match and wildcard_match are normal exit conditions used only on
/// strings containing wildcards to tell if the wildcard produced any matches.
class parser_t;
__warn_unused expand_result_t expand_string(wcstring input, completion_list_t *output,
expand_flags_t flags, const environment_t &vars,
const std::shared_ptr<parser_t> &parser,
parse_error_list_t *errors);
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors = nullptr);
/// 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.
@ -144,12 +142,12 @@ __warn_unused expand_result_t expand_string(wcstring input, completion_list_t *o
/// \param inout_str The parameter to expand in-place
/// \param flags Specifies if any expansion pass should be skipped. Legal values are any combination
/// of skip_cmdsubst skip_variables and skip_wildcards
/// \param parser the parser to use for command substitutions, or nullptr to disable.
/// \param errors Resulting errors, or NULL to ignore
/// \param ctx The parser, variables, and cancellation checker for this operation. The parser may be
/// null. \param errors Resulting errors, or nullptr to ignore
///
/// \return Whether expansion succeeded.
bool expand_one(wcstring &string, expand_flags_t flags, const environment_t &vars,
const std::shared_ptr<parser_t> &parser, parse_error_list_t *errors = nullptr);
bool expand_one(wcstring &string, expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors = nullptr);
/// Expand a command string like $HOME/bin/cmd into a command and list of arguments.
/// Return the command and arguments by reference.
@ -157,7 +155,7 @@ bool expand_one(wcstring &string, expand_flags_t flags, const environment_t &var
/// 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_result_t expand_to_command_and_args(const wcstring &instr, const environment_t &vars,
expand_result_t expand_to_command_and_args(const wcstring &instr, const operation_context_t &ctx,
wcstring *out_cmd, wcstring_list_t *out_args,
parse_error_list_t *errors = nullptr);

View file

@ -38,6 +38,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#include "env.h"
#include "fish_version.h"
#include "highlight.h"
#include "operation_context.h"
#include "output.h"
#include "parse_constants.h"
#include "print_help.h"
@ -313,7 +314,7 @@ static const char *highlight_role_to_string(highlight_role_t role) {
static std::string make_pygments_csv(const wcstring &src) {
const size_t len = src.size();
std::vector<highlight_spec_t> colors;
highlight_shell_no_io(src, colors, src.size(), nullptr, env_stack_t::globals());
highlight_shell_no_io(src, colors, src.size(), operation_context_t::globals());
assert(colors.size() == len && "Colors and src should have same size");
struct token_range_t {
@ -633,8 +634,8 @@ int main(int argc, char *argv[]) {
// Maybe colorize.
std::vector<highlight_spec_t> colors;
if (output_type != output_type_plain_text) {
highlight_shell_no_io(output_wtext, colors, output_wtext.size(), nullptr,
env_stack_t::globals());
highlight_shell_no_io(output_wtext, colors, output_wtext.size(),
operation_context_t::globals());
}
std::string colored_output;

View file

@ -59,6 +59,7 @@
#include "iothread.h"
#include "lru.h"
#include "maybe.h"
#include "operation_context.h"
#include "pager.h"
#include "parse_constants.h"
#include "parse_tree.h"
@ -1015,8 +1016,8 @@ static void test_parser() {
parser->eval(L"function '' ; echo fail; exit 42 ; end ; ''", io_chain_t());
say(L"Testing eval_args");
completion_list_t comps = parser_t::expand_argument_list(
L"alpha 'beta gamma' delta", expand_flags_t{}, parser->vars(), parser);
completion_list_t comps = parser_t::expand_argument_list(L"alpha 'beta gamma' delta",
expand_flags_t{}, parser->context());
do_test(comps.size() == 3);
do_test(comps.at(0).completion == L"alpha");
do_test(comps.at(1).completion == L"beta gamma");
@ -1083,6 +1084,7 @@ static void test_cancellation() {
// Ensure that we don't think we should cancel.
reader_reset_interrupted();
parser.clear_cancel();
}
static void test_indents() {
@ -1644,14 +1646,14 @@ static bool expand_test(const wchar_t *in, expand_flags_t flags, ...) {
bool res = true;
wchar_t *arg;
parse_error_list_t errors;
auto parser = parser_t::principal_parser().shared();
pwd_environment_t pwd{};
operation_context_t ctx{parser_t::principal_parser().shared(), pwd, no_cancel};
if (expand_string(in, &output, flags, pwd_environment_t{}, parser, &errors) ==
expand_result_t::error) {
if (expand_string(in, &output, flags, ctx, &errors) == expand_result_t::error) {
if (errors.empty()) {
err(L"Bug: Parse error reported but no error text found.");
} else {
err(L"%ls", errors.at(0).describe(in, parser->is_interactive()).c_str());
err(L"%ls", errors.at(0).describe(in, ctx.parser->is_interactive()).c_str());
}
return false;
}
@ -2311,8 +2313,9 @@ 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.
auto parser = parser_t::principal_parser().shared();
completion_list_t comps =
parser_t::expand_argument_list(str, expand_flags_t{}, null_environment_t{}, parser);
null_environment_t nullenv{};
operation_context_t ctx{parser, nullenv, no_cancel};
completion_list_t comps = parser_t::expand_argument_list(str, expand_flags_t{}, ctx);
wcstring_list_t argv;
for (const auto &c : comps) {
@ -2613,7 +2616,7 @@ static void test_complete() {
auto parser = parser_t::principal_parser().shared();
auto do_complete = [&](const wcstring &cmd, completion_request_flags_t flags) {
return complete(cmd, flags, vars, parser);
return complete(cmd, flags, operation_context_t{parser, vars, no_cancel});
};
completion_list_t completions;
@ -2787,7 +2790,7 @@ static void test_complete() {
auto &pvars = parser_t::principal_parser().vars();
function_add(L"testabbrsonetwothreefour", {}, nullptr, {});
int ret = pvars.set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion");
completions = complete(L"testabbrsonetwothree", {}, pvars, parser);
completions = complete(L"testabbrsonetwothree", {}, parser->context());
do_test(ret == 0);
do_test(completions.size() == 2);
do_test(completions.at(0).completion == L"four");
@ -2872,7 +2875,7 @@ static void test_completion_insertions() {
static void perform_one_autosuggestion_cd_test(const wcstring &command, const wcstring &expected,
const environment_t &vars, long line) {
completion_list_t comps =
complete(command, completion_request_t::autosuggestion, vars, nullptr);
complete(command, completion_request_t::autosuggestion, operation_context_t{vars});
bool expects_error = (expected == L"<error>");
@ -2907,7 +2910,7 @@ static void perform_one_autosuggestion_cd_test(const wcstring &command, const wc
static void perform_one_completion_cd_test(const wcstring &command, const wcstring &expected,
const environment_t &vars, long line) {
completion_list_t comps = complete(command, {}, vars, nullptr);
completion_list_t comps = complete(command, {}, operation_context_t{vars});
bool expects_error = (expected == L"<error>");
@ -3047,7 +3050,7 @@ static void test_autosuggest_suggest_special() {
static void perform_one_autosuggestion_should_ignore_test(const wcstring &command, long line) {
completion_list_t comps =
complete(command, completion_request_t::autosuggestion, null_environment_t{}, nullptr);
complete(command, completion_request_t::autosuggestion, operation_context_t::empty());
do_test(comps.empty());
if (!comps.empty()) {
const wcstring &suggestion = comps.front().completion;
@ -4647,7 +4650,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, vars);
highlight_shell(text, colors, 20, operation_context_t{vars});
if (expected_colors.size() != colors.size()) {
err(L"Color vector has wrong size! Expected %lu, actual %lu", expected_colors.size(),

View file

@ -29,6 +29,7 @@
#include "output.h"
#include "parse_constants.h"
#include "parse_util.h"
#include "parser.h"
#include "path.h"
#include "tnode.h"
#include "tokenizer.h"
@ -340,11 +341,12 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d
// appropriately for commands. If we succeed, return true.
static bool plain_statement_get_expanded_command(const wcstring &src,
tnode_t<g::plain_statement> stmt,
const environment_t &vars, wcstring *out_cmd) {
const operation_context_t &ctx,
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_result_t err = expand_to_command_and_args(*cmd, vars, out_cmd, nullptr);
expand_result_t err = expand_to_command_and_args(*cmd, ctx, out_cmd, nullptr);
return err == expand_result_t::ok || err == expand_result_t::wildcard_match;
}
@ -396,7 +398,7 @@ static bool has_expand_reserved(const wcstring &str) {
// Parse a command line. Return by reference the first command, and the first argument to that
// command (as a string), if any. This is used to validate autosuggestions.
static bool autosuggest_parse_command(const wcstring &buff, const environment_t &vars,
static bool autosuggest_parse_command(const wcstring &buff, const operation_context_t &ctx,
wcstring *out_expanded_command, wcstring *out_arg) {
// Parse the buffer.
parse_node_tree_t parse_tree;
@ -414,7 +416,7 @@ static bool autosuggest_parse_command(const wcstring &buff, const environment_t
}
if (first_statement &&
plain_statement_get_expanded_command(buff, first_statement, vars, out_expanded_command)) {
plain_statement_get_expanded_command(buff, first_statement, ctx, out_expanded_command)) {
// Find the first argument.
auto args_and_redirs = first_statement.child<1>();
if (auto arg = args_and_redirs.next_in_list<grammar::argument>()) {
@ -427,7 +429,7 @@ static bool autosuggest_parse_command(const wcstring &buff, const environment_t
bool autosuggest_validate_from_history(const history_item_t &item,
const wcstring &working_directory,
const environment_t &vars) {
const operation_context_t &ctx) {
ASSERT_IS_BACKGROUND_THREAD();
bool handled = false, suggestionOK = false;
@ -435,16 +437,16 @@ 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(), vars, &parsed_command, &cd_dir)) return false;
if (!autosuggest_parse_command(item.str(), ctx, &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_flag::skip_cmdsubst, vars, nullptr)) {
if (expand_one(cd_dir, expand_flag::skip_cmdsubst, ctx)) {
handled = true;
bool is_help =
string_prefixes_string(cd_dir, L"--help") || string_prefixes_string(cd_dir, L"-h");
if (!is_help) {
auto path = path_get_cdpath(cd_dir, working_directory, vars);
auto path = path_get_cdpath(cd_dir, working_directory, ctx.vars);
if (path && !paths_are_same_file(working_directory, *path)) {
suggestionOK = true;
}
@ -458,7 +460,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, nullptr, vars)) {
if (path_get_path(parsed_command, nullptr, ctx.vars)) {
cmd_ok = true;
} else if (builtin_exists(parsed_command) || function_exists_no_autoload(parsed_command)) {
cmd_ok = true;
@ -768,8 +770,8 @@ class highlighter_t {
const wcstring &buff;
// Cursor position.
const size_t cursor_pos;
// Environment variables. Again, a reference member variable!
const environment_t &vars;
// The operation context. Again, a reference member variable!
const operation_context_t &ctx;
// Whether it's OK to do I/O.
const bool io_ok;
// Working directory.
@ -800,11 +802,11 @@ class highlighter_t {
public:
// Constructor
highlighter_t(const wcstring &str, size_t pos, const environment_t &ev, wcstring wd,
highlighter_t(const wcstring &str, size_t pos, const operation_context_t &ctx, wcstring wd,
bool can_do_io)
: buff(str),
cursor_pos(pos),
vars(ev),
ctx(ctx),
io_ok(can_do_io),
working_directory(std::move(wd)),
color_array(str.size()) {
@ -891,7 +893,7 @@ void highlighter_t::color_argument(tnode_t<g::tok_string> node) {
}
// Highlight it recursively.
highlighter_t cmdsub_highlighter(cmdsub_contents, cursor_subpos, this->vars,
highlighter_t cmdsub_highlighter(cmdsub_contents, cursor_subpos, this->ctx,
this->working_directory, this->io_ok);
const color_array_t &subcolors = cmdsub_highlighter.highlight();
@ -927,7 +929,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, vars, &cmd_str)) {
if (plain_statement_get_expanded_command(this->buff, stmt, ctx, &cmd_str)) {
cmd_is_cd = (cmd_str == L"cd");
}
}
@ -944,11 +946,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_flag::skip_cmdsubst, vars, nullptr)) {
if (expand_one(param, expand_flag::skip_cmdsubst, ctx)) {
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, vars, PATH_EXPAND_TILDE)) {
!is_potential_cd_path(param, working_directory, ctx.vars, PATH_EXPAND_TILDE)) {
this->color_node(arg, highlight_role_t::error);
}
}
@ -988,7 +990,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_flag::skip_cmdsubst, vars, nullptr)) {
} else if (!expand_one(target, expand_flag::skip_cmdsubst, ctx)) {
// Could not be expanded.
target_is_valid = false;
} else {
@ -1239,10 +1241,10 @@ highlighter_t::color_array_t highlighter_t::highlight() {
// 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, vars, &expanded_cmd);
plain_statement_get_expanded_command(buff, stmt, ctx, &expanded_cmd);
if (expanded && !has_expand_reserved(expanded_cmd)) {
is_valid_cmd =
command_is_valid(expanded_cmd, decoration, working_directory, vars);
command_is_valid(expanded_cmd, decoration, working_directory, ctx.vars);
}
}
if (!is_valid_cmd) {
@ -1298,7 +1300,7 @@ highlighter_t::color_array_t highlighter_t::highlight() {
if (node.type != symbol_argument || !node.has_source()) continue;
// Underline every valid path.
if (node_is_potential_path(buff, node, vars, working_directory)) {
if (node_is_potential_path(buff, node, ctx.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_role_t::error because it looks dorky. For example,
@ -1333,26 +1335,16 @@ std::string colorize(const wcstring &text, const std::vector<highlight_spec_t> &
}
void highlight_shell(const wcstring &buff, std::vector<highlight_spec_t> &color, size_t pos,
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 = vars.get_pwd_slash();
// Highlight it!
highlighter_t highlighter(buff, pos, vars, working_directory, true /* can do IO */);
const operation_context_t &ctx) {
const wcstring working_directory = ctx.vars.get_pwd_slash();
highlighter_t highlighter(buff, pos, ctx, working_directory, true /* can do IO */);
color = highlighter.highlight();
}
void highlight_shell_no_io(const wcstring &buff, std::vector<highlight_spec_t> &color, size_t pos,
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 = vars.get_pwd_slash();
// Highlight it!
highlighter_t highlighter(buff, pos, vars, working_directory, false /* no IO allowed */);
const operation_context_t &ctx) {
const wcstring working_directory = ctx.vars.get_pwd_slash();
highlighter_t highlighter(buff, pos, ctx, working_directory, false /* no IO allowed */);
color = highlighter.highlight();
}
@ -1442,9 +1434,8 @@ 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 environment_t &vars) {
UNUSED(error);
UNUSED(vars);
const operation_context_t &ctx) {
UNUSED(ctx);
assert(buff.size() == color.size());
std::fill(color.begin(), color.end(), highlight_spec_t{});
highlight_universal_internal(buff, color, pos);

View file

@ -71,6 +71,7 @@ struct highlight_spec_t {
};
class history_item_t;
class operation_context_t;
std::string colorize(const wcstring &text, const std::vector<highlight_spec_t> &colors);
@ -81,15 +82,14 @@ std::string colorize(const wcstring &text, const std::vector<highlight_spec_t> &
/// \param color The array in which to store the color codes. The first 8 bits are used for fg
/// color, the next 8 bits for bg color.
/// \param pos the cursor position. Used for quote matching, etc.
/// \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.
/// \param ctx The variables and cancellation check for this operation.
void highlight_shell(const wcstring &buffstr, std::vector<highlight_spec_t> &color, size_t pos,
wcstring_list_t *error, const environment_t &vars);
const operation_context_t &ctx);
/// 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 environment_t &vars);
size_t pos, const operation_context_t &ctx);
/// Perform syntax highlighting for the text in buff. Matching quotes and parenthesis are
/// highlighted. The result is stored in the color array as a color_code from the HIGHLIGHT_ enum
@ -99,10 +99,9 @@ void highlight_shell_no_io(const wcstring &buffstr, std::vector<highlight_spec_t
/// \param color The array in which to store the color codes. The first 8 bits are used for fg
/// color, the next 8 bits for bg color.
/// \param pos the cursor position. Used for quote matching, etc.
/// \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.
/// \param ctx The cancellation and other environment for this operation. This is unused.
void highlight_universal(const wcstring &buffstr, std::vector<highlight_spec_t> &color, size_t pos,
wcstring_list_t *error, const environment_t &vars);
const operation_context_t &ctx);
/// \return an RGB color for a given highlight spec.
rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_background);
@ -112,7 +111,7 @@ rgb_color_t highlight_get_color(const highlight_spec_t &highlight, bool is_backg
/// reference whether the suggestion is valid or not.
bool autosuggest_validate_from_history(const history_item_t &item,
const wcstring &working_directory,
const environment_t &vars);
const operation_context_t &ctx);
// 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).

23
src/operation_context.cpp Normal file
View file

@ -0,0 +1,23 @@
// Utilities for io redirection.
#include "config.h" // IWYU pragma: keep
#include "operation_context.h"
#include "env.h"
bool no_cancel() { return false; }
operation_context_t::operation_context_t(std::shared_ptr<parser_t> parser,
const environment_t &vars, cancel_checker_t cancel_checker)
: parser(std::move(parser)), vars(vars), cancel_checker(std::move(cancel_checker)) {}
operation_context_t operation_context_t::empty() {
static const null_environment_t nullenv{};
return operation_context_t{nullenv};
}
operation_context_t operation_context_t::globals() {
return operation_context_t{env_stack_t::globals()};
}
operation_context_t::~operation_context_t() = default;

56
src/operation_context.h Normal file
View file

@ -0,0 +1,56 @@
#ifndef FISH_OPERATION_CONTEXT_H
#define FISH_OPERATION_CONTEXT_H
#include <memory>
#include "common.h"
class environment_t;
class parser_t;
/// A common helper which always returns false.
bool no_cancel();
/// A operation_context_t is a simple property bag which wraps up data needed for highlighting,
/// expansion, completion, and more.
/// It contains the following triple:
/// 1. A parser. This is often null. If not null, it may be used to execute fish script. If null,
/// then this is a background operation and fish script must not be executed.
/// 2. A variable set. This is never null. This may differ from the variables in the parser.
/// 3. A cancellation checker. This is a function which you may call to detect that the operation
/// is no longer necessary and should be cancelled.
class operation_context_t {
public:
// The parser, if this is a foreground operation. If this is a background operation, this may be
// nullptr.
std::shared_ptr<parser_t> parser;
// The set of variables. It is the creator's responsibility to ensure this lives as log as the
// context itself.
const environment_t &vars;
// A function which may be used to poll for cancellation.
cancel_checker_t cancel_checker;
// Invoke the cancel checker. \return if we should cancel.
bool check_cancel() const { return cancel_checker(); }
// \return an "empty" context which contains no variables, no parser, and never cancels.
static operation_context_t empty();
// \return an operation context that contains only global variables, no parser, and never
// cancels.
static operation_context_t globals();
/// Construct from the full triple of a parser, vars, and cancel checker.
operation_context_t(std::shared_ptr<parser_t> parser, const environment_t &vars,
cancel_checker_t cancel_checker);
/// Construct from vars alone.
explicit operation_context_t(const environment_t &vars)
: operation_context_t(nullptr, vars, no_cancel) {}
~operation_context_t();
};
#endif

View file

@ -89,8 +89,9 @@ static redirection_spec_t get_stderr_merge() {
}
parse_execution_context_t::parse_execution_context_t(parsed_source_ref_t pstree, parser_t *p,
const operation_context_t &ctx,
job_lineage_t lineage)
: pstree(std::move(pstree)), parser(p), lineage(std::move(lineage)) {}
: pstree(std::move(pstree)), parser(p), ctx(ctx), lineage(std::move(lineage)) {}
// Utilities
@ -136,8 +137,7 @@ tnode_t<g::plain_statement> parse_execution_context_t::infinite_recursive_statem
if (plain_statement) {
maybe_t<wcstring> cmd = command_for_plain_statement(plain_statement, pstree->src);
if (cmd &&
expand_one(*cmd, {expand_flag::skip_cmdsubst, expand_flag::skip_variables}, nullenv,
nullptr) &&
expand_one(*cmd, {expand_flag::skip_cmdsubst, expand_flag::skip_variables}, ctx) &&
cmd == forbidden_function_name) {
// This is it.
infinite_recursive_statement = plain_statement;
@ -382,7 +382,7 @@ eval_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, expand_flags_t{}, parser->vars(), parser->shared())) {
if (!expand_one(for_var_name, expand_flags_t{}, ctx)) {
report_error(var_name_node, FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str());
return eval_result_t::error;
}
@ -458,9 +458,8 @@ eval_result_t parse_execution_context_t::run_switch_statement(
// Expand it. We need to offset any errors by the position of the string.
completion_list_t switch_values_expanded;
parse_error_list_t errors;
auto expand_ret =
expand_string(switch_value, &switch_values_expanded, expand_flag::no_descriptions,
parser->vars(), parser->shared(), &errors);
auto expand_ret = expand_string(switch_value, &switch_values_expanded,
expand_flag::no_descriptions, ctx, &errors);
parse_error_offset_source_start(&errors, switch_value_n.source_range()->start);
switch (expand_ret) {
@ -720,7 +719,7 @@ eval_result_t parse_execution_context_t::expand_command(tnode_t<grammar::plain_s
// Expand the string to produce completions, and report errors.
expand_result_t expand_err =
expand_to_command_and_args(unexp_cmd, parser->vars(), out_cmd, out_args, &errors);
expand_to_command_and_args(unexp_cmd, ctx, out_cmd, out_args, &errors);
if (expand_err == expand_result_t::error) {
parser->set_last_statuses(statuses_t::just(STATUS_ILLEGAL_CMD));
// Issue #5812 - the expansions were done on the command token,
@ -877,8 +876,8 @@ eval_result_t parse_execution_context_t::expand_arguments_from_nodes(
// Expand this string.
parse_error_list_t errors;
arg_expanded.clear();
auto expand_ret = expand_string(arg_str, &arg_expanded, expand_flag::no_descriptions,
parser->vars(), parser->shared(), &errors);
auto expand_ret =
expand_string(arg_str, &arg_expanded, expand_flag::no_descriptions, ctx, &errors);
parse_error_offset_source_start(&errors, arg_node.source_range()->start);
switch (expand_ret) {
case expand_result_t::error: {
@ -934,8 +933,7 @@ bool parse_execution_context_t::determine_redirections(
// PCA: I can't justify this skip_variables flag. It was like this when I got here.
bool target_expanded =
expand_one(target, no_exec() ? expand_flag::skip_variables : expand_flags_t{},
parser->vars(), parser->shared());
expand_one(target, no_exec() ? expand_flag::skip_variables : expand_flags_t{}, ctx);
if (!target_expanded || target.empty()) {
// TODO: Improve this error message.
report_error(redirect_node, _(L"Invalid redirection target: %ls"), target.c_str());
@ -1026,9 +1024,8 @@ eval_result_t parse_execution_context_t::apply_variable_assignments(
completion_list_t expression_expanded;
parse_error_list_t errors;
// TODO this is mostly copied from expand_arguments_from_nodes, maybe extract to function
auto expand_ret =
expand_string(expression, &expression_expanded, expand_flag::no_descriptions,
parser->vars(), parser->shared(), &errors);
auto expand_ret = expand_string(expression, &expression_expanded,
expand_flag::no_descriptions, ctx, &errors);
parse_error_offset_source_start(
&errors, variable_assignment.source_range()->start + *equals_pos + 1);
switch (expand_ret) {

View file

@ -11,6 +11,7 @@
#include "proc.h"
class block_t;
class operation_context_t;
class parser_t;
/// An eval_result represents evaluation errors including wildcards which failed to match, syntax
@ -34,6 +35,7 @@ class parse_execution_context_t {
private:
parsed_source_ref_t pstree;
parser_t *const parser;
const operation_context_t &ctx;
// The currently executing job node, used to indicate the line number.
tnode_t<grammar::job> executing_job_node{};
// Cached line number information.
@ -137,7 +139,8 @@ class parse_execution_context_t {
int line_offset_of_character_at_offset(size_t offset);
public:
parse_execution_context_t(parsed_source_ref_t pstree, parser_t *p, job_lineage_t lineage);
parse_execution_context_t(parsed_source_ref_t pstree, parser_t *p,
const operation_context_t &ctx, job_lineage_t lineage);
/// Returns the current line number, indexed from 1. Not const since it touches
/// cached_lineno_offset.

View file

@ -1129,8 +1129,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, null_environment_t{}, &command, nullptr,
parse_errors) == expand_result_t::error) {
if (expand_to_command_and_args(*unexp_command, operation_context_t::empty(), &command,
nullptr, parse_errors) == expand_result_t::error) {
errored = true;
parse_error_offset_source_start(parse_errors, source_start);
}
@ -1193,7 +1193,7 @@ static bool detect_errors_in_plain_statement(const wcstring &buff_src,
// Check that we don't do an invalid builtin (issue #1252).
if (!errored && decoration == parse_statement_decoration_builtin &&
expand_one(*unexp_command, expand_flag::skip_cmdsubst, null_environment_t{}, nullptr,
expand_one(*unexp_command, expand_flag::skip_cmdsubst, operation_context_t::empty(),
parse_errors) &&
!builtin_exists(*unexp_command)) {
errored = append_syntax_error(parse_errors, source_start, UNKNOWN_BUILTIN_ERR_MSG,

View file

@ -306,8 +306,8 @@ void parser_t::emit_profiling(const char *path) const {
}
completion_list_t parser_t::expand_argument_list(const wcstring &arg_list_src,
expand_flags_t eflags, const environment_t &vars,
const std::shared_ptr<parser_t> &parser) {
expand_flags_t eflags,
const operation_context_t &ctx) {
// Parse the string as an argument list.
parse_node_tree_t tree;
if (!parse_tree_from_string(arg_list_src, parse_flag_none, &tree, nullptr /* errors */,
@ -322,8 +322,7 @@ completion_list_t parser_t::expand_argument_list(const wcstring &arg_list_src,
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, &result, eflags, vars, parser, nullptr /* errors */) ==
expand_result_t::error) {
if (expand_string(arg_src, &result, eflags, ctx) == expand_result_t::error) {
break; // failed to expand a string
}
}
@ -332,6 +331,14 @@ completion_list_t parser_t::expand_argument_list(const wcstring &arg_list_src,
std::shared_ptr<parser_t> parser_t::shared() { return shared_from_this(); }
cancel_checker_t parser_t::cancel_checker() const {
return [this]() { return this->cancellation_signal != 0; };
}
operation_context_t parser_t::context() {
return operation_context_t{this->shared(), this->vars(), this->cancel_checker()};
}
/// Append stack trace info for the block \p b to \p trace.
static void append_block_description_to_stack_trace(const block_t &b, wcstring &trace,
const environment_t &vars) {
@ -660,12 +667,13 @@ eval_result_t parser_t::eval_node(const parsed_source_ref_t &ps, tnode_t<T> node
job_reap(*this, false); // not sure why we reap jobs here
// Start it up
operation_context_t op_ctx = this->context();
block_t *scope_block = this->push_block(block_t::scope_block(block_type));
// Create and set a new execution context.
using exc_ctx_ref_t = std::unique_ptr<parse_execution_context_t>;
scoped_push<exc_ctx_ref_t> exc(
&execution_context, make_unique<parse_execution_context_t>(ps, this, std::move(lineage)));
scoped_push<exc_ctx_ref_t> exc(&execution_context, make_unique<parse_execution_context_t>(
ps, this, op_ctx, std::move(lineage)));
eval_result_t res = execution_context->eval_node(node, scope_block);
exc.restore();
this->pop_block(scope_block);

View file

@ -14,6 +14,7 @@
#include "common.h"
#include "event.h"
#include "expand.h"
#include "operation_context.h"
#include "parse_constants.h"
#include "parse_execution.h"
#include "parse_tree.h"
@ -197,6 +198,8 @@ struct library_data_t {
std::shared_ptr<const autoclose_fd_t> cwd_fd{};
};
class operation_context_t;
class parser_t : public std::enable_shared_from_this<parser_t> {
friend class parse_execution_context_t;
@ -251,6 +254,9 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
/// Indicates that we should stop execution due to the given signal.
static void cancel_requested(int sig);
/// Clear any cancel.
void clear_cancel() { cancellation_signal = 0; }
/// Global event blocks.
event_blockage_list_t global_event_blocks;
@ -280,8 +286,8 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
/// cmdsubst execution on the tokens. Errors are ignored. If a parser is provided, it is used
/// for command substitution expansion.
static completion_list_t expand_argument_list(const wcstring &arg_list_src,
expand_flags_t flags, const environment_t &vars,
const std::shared_ptr<parser_t> &parser);
expand_flags_t flags,
const operation_context_t &ctx);
/// Returns a string describing the current parser position in the format 'FILENAME (line
/// LINE_NUMBER): LINE'. Example:
@ -370,6 +376,12 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
/// \return a shared pointer reference to this parser.
std::shared_ptr<parser_t> shared();
/// \return a cancel poller for checking if this parser has been signalled.
cancel_checker_t cancel_checker() const;
/// \return the operation context for this parser.
operation_context_t context();
~parser_t();
};

View file

@ -136,6 +136,18 @@ static inline unsigned read_generation_count() {
return s_generation.load(std::memory_order_relaxed);
}
/// \return an operation context for a background operation..
/// Crucially the operation context itself does not contain a parser.
/// It is the caller's responsibility to ensure the environment lives as long as the result.
operation_context_t get_bg_context(const std::shared_ptr<environment_t> &env,
unsigned int generation_count) {
cancel_checker_t cancel_checker = [generation_count] {
// Cancel if the generation count changed.
return generation_count != read_generation_count();
};
return operation_context_t{nullptr, *env, std::move(cancel_checker)};
}
void editable_line_t::insert_string(const wcstring &str, size_t start, size_t len) {
// Clamp the range to something valid.
size_t string_length = str.size();
@ -380,8 +392,8 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
std::vector<highlight_spec_t> colors;
/// An array defining the block level at each character.
std::vector<int> indents;
/// Function for tab completion.
complete_function_t complete_func{nullptr};
/// Whether tab completion is allowed.
bool complete_ok{false};
/// Function for syntax highlighting.
highlight_function_t highlight_func{highlight_universal};
/// Function for testing if the string can be returned.
@ -1283,11 +1295,9 @@ static std::function<autosuggestion_result_t(void)> get_autosuggestion_performer
// this should use shared_ptr
return [=]() -> autosuggestion_result_t {
ASSERT_IS_BACKGROUND_THREAD();
const autosuggestion_result_t nothing = {};
// If the main thread has moved on, skip all the work.
// Otherwise record the generation.
if (generation_count != read_generation_count()) {
operation_context_t ctx = get_bg_context(vars, generation_count);
if (ctx.check_cancel()) {
return nothing;
}
s_thread_generation = generation_count;
@ -1297,21 +1307,22 @@ static std::function<autosuggestion_result_t(void)> get_autosuggestion_performer
return nothing;
}
history_search_t searcher(*history, search_string, history_search_type_t::prefix);
while (!reader_test_should_cancel() && searcher.go_backwards()) {
history_search_t searcher(*history, search_string, history_search_type_t::prefix,
history_search_flags_t{});
while (!ctx.check_cancel() && searcher.go_backwards()) {
const history_item_t &item = searcher.current_item();
// Skip items with newlines because they make terrible autosuggestions.
if (item.str().find(L'\n') != wcstring::npos) continue;
if (autosuggest_validate_from_history(item, working_directory, *vars)) {
if (autosuggest_validate_from_history(item, working_directory, ctx)) {
// The command autosuggestion was handled specially, so we're done.
return {searcher.current_string(), search_string};
}
}
// Maybe cancel here.
if (reader_test_should_cancel()) return nothing;
if (ctx.check_cancel()) return nothing;
// Here we do something a little funny. If the line ends with a space, and the cursor is not
// at the end, don't use completion autosuggestions. It ends up being pretty weird seeing
@ -1325,7 +1336,7 @@ static std::function<autosuggestion_result_t(void)> get_autosuggestion_performer
// Try normal completions.
completion_request_flags_t complete_flags = completion_request_t::autosuggestion;
completion_list_t completions = complete(search_string, complete_flags, *vars, nullptr);
completion_list_t completions = complete(search_string, complete_flags, ctx);
completions_sort_and_prioritize(&completions, complete_flags);
if (!completions.empty()) {
const completion_t &comp = completions.at(0);
@ -2036,16 +2047,12 @@ static std::function<highlight_result_t(void)> get_highlight_performer(
parser_t &parser, const wcstring &text, long match_highlight_pos,
highlight_function_t highlight_func) {
auto vars = parser.vars().snapshot();
unsigned int generation_count = read_generation_count();
unsigned generation_count = read_generation_count();
return [=]() -> highlight_result_t {
if (text.empty()) return {};
if (generation_count != read_generation_count()) {
// The gen count has changed, so don't do anything.
return {};
}
s_thread_generation = generation_count;
operation_context_t ctx = get_bg_context(vars, generation_count);
std::vector<highlight_spec_t> colors(text.size(), highlight_spec_t{});
highlight_func(text, colors, match_highlight_pos, nullptr /* error */, *vars);
highlight_func(text, colors, match_highlight_pos, ctx);
return {std::move(colors), text};
};
}
@ -2152,7 +2159,7 @@ void reader_set_allow_autosuggesting(bool flag) { current_data()->allow_autosugg
void reader_set_expand_abbreviations(bool flag) { current_data()->expand_abbreviations = flag; }
void reader_set_complete_function(complete_function_t f) { current_data()->complete_func = f; }
void reader_set_complete_ok(bool flag) { current_data()->complete_ok = flag; }
void reader_set_highlight_function(highlight_function_t func) {
current_data()->highlight_func = func;
@ -2255,7 +2262,7 @@ uint64_t reader_run_count() { return run_count; }
/// Read interactively. Read input from stdin while providing editing facilities.
static int read_i(parser_t &parser) {
reader_push(parser, history_session_id(parser.vars()));
reader_set_complete_function(&complete);
reader_set_complete_ok(true);
reader_set_highlight_function(&highlight_shell);
reader_set_test_function(&reader_shell_test);
reader_set_allow_autosuggesting(true);
@ -2514,7 +2521,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
case rl::complete:
case rl::complete_and_search: {
if (!complete_func) break;
if (!complete_ok) break;
// Use the command line only; it doesn't make sense to complete in any other line.
editable_line_t *el = &command_line;
@ -2540,9 +2547,6 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
// Get the string; we have to do this after removing any trailing backslash.
const wchar_t *const buff = el->text.c_str();
// Clear the completion list.
rls.comp.clear();
// Figure out the extent of the command substitution surrounding the cursor.
// This is because we only look at the current command substitution to form
// completions - stuff happening outside of it is not interesting.
@ -2566,7 +2570,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
// std::fwprintf(stderr, L"Complete (%ls)\n", buffcpy.c_str());
completion_request_flags_t complete_flags = {completion_request_t::descriptions,
completion_request_t::fuzzy_match};
rls.comp = complete_func(buffcpy, complete_flags, vars, parser_ref);
rls.comp = complete(buffcpy, complete_flags, parser_ref->context());
// User-supplied completions may have changed the commandline - prevent buffer
// overflow.

View file

@ -17,6 +17,8 @@
class environment_t;
class history_t;
class io_chain_t;
class operation_context_t;
class parser_t;
/// Helper class for storing a command line.
class editable_line_t {
@ -146,16 +148,12 @@ void reader_push(parser_t &parser, const wcstring &name);
/// Return to previous reader environment.
void reader_pop();
/// Specify function to use for finding possible tab completions.
typedef completion_list_t (*complete_function_t)(const wcstring &, completion_request_flags_t,
const environment_t &,
const std::shared_ptr<parser_t> &parser);
void reader_set_complete_function(complete_function_t);
/// Mark whether tab completion is enabled.
void reader_set_complete_ok(bool flag);
/// The type of a highlight function.
using highlight_function_t = void (*)(const wcstring &, std::vector<highlight_spec_t> &, size_t,
wcstring_list_t *, const environment_t &vars);
const operation_context_t &ctx);
/// Function type for testing if a string is valid for the reader to return.
using test_function_t = parser_test_error_bits_t (*)(parser_t &, const wcstring &);

View file

@ -456,6 +456,8 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc
}
class wildcard_expander_t {
// A function to call to check cancellation.
cancel_checker_t cancel_checker;
// The working directory to resolve paths against
const wcstring working_directory;
// The set of items we have resolved, used to efficiently avoid duplication.
@ -502,7 +504,7 @@ class wildcard_expander_t {
/// Indicate whether we should cancel wildcard expansion. This latches 'interrupt'.
bool interrupted() {
did_interrupt = did_interrupt || reader_test_should_cancel();
did_interrupt = did_interrupt || cancel_checker();
return did_interrupt;
}
@ -629,8 +631,12 @@ class wildcard_expander_t {
}
public:
wildcard_expander_t(wcstring wd, expand_flags_t f, completion_list_t *r)
: working_directory(std::move(wd)), flags(f), resolved_completions(r) {
wildcard_expander_t(wcstring wd, expand_flags_t f, const cancel_checker_t &cancel_checker,
completion_list_t *r)
: cancel_checker(cancel_checker),
working_directory(std::move(wd)),
flags(f),
resolved_completions(r) {
assert(resolved_completions != nullptr);
// Insert initial completions into our set to avoid duplicates.
@ -897,7 +903,9 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc,
wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
const wcstring &working_directory,
expand_flags_t flags, completion_list_t *output) {
expand_flags_t flags,
const cancel_checker_t &cancel_checker,
completion_list_t *output) {
assert(output != nullptr);
// Fuzzy matching only if we're doing completions.
assert(flags.get(expand_flag::for_completions) || !flags.get(expand_flag::fuzzy_match));
@ -934,7 +942,7 @@ wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
effective_wc = wc;
}
wildcard_expander_t expander(prefix, flags, output);
wildcard_expander_t expander(prefix, flags, cancel_checker, output);
expander.expand(base_dir, effective_wc.c_str(), base_dir);
return expander.status_code();
}

View file

@ -48,7 +48,9 @@ enum class wildcard_expand_result_t {
};
wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
const wcstring &working_directory,
expand_flags_t flags, completion_list_t *out);
expand_flags_t flags,
const cancel_checker_t &cancel_checker,
completion_list_t *out);
/// Test whether the given wildcard matches the string. Does not perform any I/O.
///