Refactor how inputter handles script commands

Prior to this change, for bindings which have script commands, the
inputter would execute them directly. However an upcoming fix for #7483
will require more integration with the reader. Switch to a new model where
the reader passes in a function to use for executing script commands.
This commit is contained in:
ridiculousfish 2020-11-15 12:26:06 -08:00
parent 994f95b845
commit e9b683dee1
3 changed files with 36 additions and 20 deletions

View file

@ -356,16 +356,18 @@ void inputter_t::function_push_args(readline_cmd_t code) {
/// Perform the action of the specified binding. allow_commands controls whether fish commands
/// should be executed, or should be deferred until later.
void inputter_t::mapping_execute(const input_mapping_t &m, bool allow_commands) {
void inputter_t::mapping_execute(const input_mapping_t &m,
const command_handler_t &command_handler) {
// has_functions: there are functions that need to be put on the input queue
// has_commands: there are shell commands that need to be evaluated
bool has_commands = false, has_functions = false;
for (const wcstring &cmd : m.commands) {
if (input_function_get_code(cmd))
if (input_function_get_code(cmd)) {
has_functions = true;
else
} else {
has_commands = true;
}
}
// !has_functions && !has_commands: only set bind mode
@ -374,7 +376,7 @@ void inputter_t::mapping_execute(const input_mapping_t &m, bool allow_commands)
return;
}
if (has_commands && !allow_commands) {
if (has_commands && !command_handler) {
// We don't want to run commands yet. Put the characters back and return check_exit.
for (wcstring::const_reverse_iterator it = m.seq.rbegin(), end = m.seq.rend(); it != end;
++it) {
@ -394,11 +396,7 @@ void inputter_t::mapping_execute(const input_mapping_t &m, bool allow_commands)
//
// FIXME(snnw): if commands add stuff to input queue (e.g. commandline -f execute), we won't
// see that until all other commands have also been run.
auto last_statuses = parser_->get_last_statuses();
for (const wcstring &cmd : m.commands) {
parser_->eval(cmd, io_chain_t{});
}
parser_->set_last_statuses(std::move(last_statuses));
command_handler(m.commands);
event_queue_.push_front(char_event_type_t::check_exit);
} else {
// Invalid binding, mixed commands and functions. We would need to execute these one by
@ -467,9 +465,9 @@ maybe_t<input_mapping_t> inputter_t::find_mapping() {
return generic ? maybe_t<input_mapping_t>(*generic) : none();
}
void inputter_t::mapping_execute_matching_or_generic(bool allow_commands) {
void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &command_handler) {
if (auto mapping = find_mapping()) {
mapping_execute(*mapping, allow_commands);
mapping_execute(*mapping, command_handler);
} else {
FLOGF(reader, L"no generic found, ignoring char...");
auto evt = event_queue_.readch();
@ -500,7 +498,7 @@ char_event_t inputter_t::read_characters_no_readline() {
return evt_to_return;
}
char_event_t inputter_t::readch(bool allow_commands) {
char_event_t inputter_t::readch(const command_handler_t &command_handler) {
// Clear the interrupted flag.
reader_reset_interrupted();
// Search for sequence in mapping tables.
@ -552,7 +550,7 @@ char_event_t inputter_t::readch(bool allow_commands) {
return evt;
} else {
event_queue_.push_front(evt);
mapping_execute_matching_or_generic(allow_commands);
mapping_execute_matching_or_generic(command_handler);
// Regarding allow_commands, we're in a loop, but if a fish command is executed,
// check_exit is unread, so the next pass through the loop we'll break out and return
// it.

View file

@ -36,10 +36,11 @@ class inputter_t {
/// to parse it. If no more input follows after the escape key, it is assumed to be an actual
/// escape key press, and is returned as such.
///
/// The argument determines whether fish commands are allowed to be run as bindings. If false,
/// when a character is encountered that would invoke a fish command, it is unread and
/// char_event_type_t::check_exit is returned.
char_event_t readch(bool allow_commands = true);
/// \p command_handler is used to run commands. If empty (in the std::function sense), when a
/// character is encountered that would invoke a fish command, it is unread and
/// char_event_type_t::check_exit is returned. Note the handler is not stored.
using command_handler_t = std::function<void(const wcstring_list_t &)>;
char_event_t readch(const command_handler_t &command_handler = {});
/// Enqueue a char event to the queue of unread characters that input_readch will return before
/// actually reading from fd 0.
@ -64,8 +65,8 @@ class inputter_t {
void function_push_arg(wchar_t arg);
void function_push_args(readline_cmd_t code);
void mapping_execute(const input_mapping_t &m, bool allow_commands);
void mapping_execute_matching_or_generic(bool allow_commands);
void mapping_execute(const input_mapping_t &m, const command_handler_t &command_handler);
void mapping_execute_matching_or_generic(const command_handler_t &command_handler);
bool mapping_is_match(const input_mapping_t &m);
maybe_t<input_mapping_t> find_mapping();
char_event_t read_characters_no_readline();

View file

@ -640,6 +640,7 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
void move_word(editable_line_t *el, bool move_right, bool erase, enum move_word_style_t style,
bool newv);
void run_input_command_scripts(const wcstring_list_t &cmds);
maybe_t<char_event_t> read_normal_chars(readline_loop_state_t &rls);
void handle_readline_command(readline_cmd_t cmd, readline_loop_state_t &rls);
@ -2717,15 +2718,31 @@ struct readline_loop_state_t {
size_t nchars{std::numeric_limits<size_t>::max()};
};
/// Run a sequence of commands from an input binding.
void reader_data_t::run_input_command_scripts(const wcstring_list_t &cmds) {
auto last_statuses = parser().get_last_statuses();
for (const wcstring &cmd : cmds) {
parser().eval(cmd, io_chain_t{});
}
parser().set_last_statuses(std::move(last_statuses));
}
/// Read normal characters, inserting them into the command line.
/// \return the next unhandled event.
maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rls) {
maybe_t<char_event_t> event_needing_handling{};
wcstring accumulated_chars;
size_t limit = std::min(rls.nchars - command_line.size(), READAHEAD_MAX);
using command_handler_t = inputter_t::command_handler_t;
command_handler_t normal_handler = [this](const wcstring_list_t &cmds) {
this->run_input_command_scripts(cmds);
};
command_handler_t empty_handler = {};
while (accumulated_chars.size() < limit) {
bool allow_commands = (accumulated_chars.empty());
auto evt = inputter.readch(allow_commands);
auto evt = inputter.readch(allow_commands ? normal_handler : empty_handler);
if (!event_is_normal_char(evt) || !can_read(conf.in)) {
event_needing_handling = std::move(evt);
break;