mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
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:
parent
db98ee13a9
commit
0f7bba5f0e
24 changed files with 337 additions and 238 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
100
src/complete.cpp
100
src/complete.cpp
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
22
src/expand.h
22
src/expand.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
23
src/operation_context.cpp
Normal 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
56
src/operation_context.h
Normal 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
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
16
src/parser.h
16
src/parser.h
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
12
src/reader.h
12
src/reader.h
|
@ -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 &);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
Loading…
Reference in a new issue