Make input_event_queue_t a base class

This concerns the problem of "injecting" fancy fish bits like job reaping
into the "common" input stuff which is also used by fish_key_reader.
Instead of providing a callback, make the input event queue a base class
with virtual functions. This allows for a richer interface and simplifies
some memory management issues.
This commit is contained in:
ridiculousfish 2021-04-10 19:49:56 -07:00
parent 939aba02de
commit 3684c91ad2
6 changed files with 49 additions and 66 deletions

View File

@ -3669,11 +3669,11 @@ static void test_input() {
// Push the desired binding to the queue.
for (wchar_t c : desired_binding) {
input.queue_ch(c);
input.queue_char(c);
}
// Now test.
auto evt = input.readch();
auto evt = input.read_char();
if (!evt.is_readline()) {
err(L"Event is not a readline");
} else if (evt.get_readline() != readline_cmd_t::down_line) {

View File

@ -316,11 +316,11 @@ void init_input() {
}
inputter_t::inputter_t(parser_t &parser, int in)
: parser_(parser.shared()), event_queue_(in, get_interrupt_handler()) {}
: input_event_queue_t(in), parser_(parser.shared()) {}
/// Handle interruptions to key reading by reaping finished jobs and propagating the interrupt to
/// the reader.
maybe_t<char_event_t> inputter_t::handle_interrupt() {
void inputter_t::select_interrupted() /* override */ {
// Fire any pending events.
auto &parser = *this->parser_;
event_fire_delayed(parser);
@ -329,20 +329,12 @@ maybe_t<char_event_t> inputter_t::handle_interrupt() {
// Tell the reader an event occurred.
if (reader_reading_interrupted()) {
auto vintr = shell_modes.c_cc[VINTR];
if (vintr == 0) {
return none();
if (vintr != 0) {
this->push_front(char_event_t{vintr});
}
return char_event_t{vintr};
return;
}
return char_event_t{char_event_type_t::check_exit};
}
interrupt_handler_t inputter_t::get_interrupt_handler() {
// It's OK to capture 'this' by value because we use this to populate one of our instance
// variables.
interrupt_handler_t func = [this] { return this->handle_interrupt(); };
return func;
this->push_front(char_event_t{char_event_type_t::check_exit});
}
void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); }
@ -364,7 +356,7 @@ void inputter_t::function_push_args(readline_cmd_t code) {
// Skip and queue up any function codes. See issue #2357.
wchar_t arg{};
for (;;) {
auto evt = event_queue_.readch();
auto evt = this->readch();
if (evt.is_char()) {
arg = evt.get_char();
break;
@ -375,7 +367,7 @@ void inputter_t::function_push_args(readline_cmd_t code) {
}
// Push the function codes back into the input stream.
event_queue_.insert_front(skipped.begin(), skipped.end());
this->insert_front(skipped.begin(), skipped.end());
}
/// Perform the action of the specified binding. allow_commands controls whether fish commands
@ -406,15 +398,15 @@ void inputter_t::mapping_execute(const input_mapping_t &m,
if (has_commands && !command_handler) {
// We don't want to run commands yet. Put the characters back and return check_exit.
event_queue_.insert_front(m.seq.cbegin(), m.seq.cend());
event_queue_.push_front(char_event_type_t::check_exit);
this->insert_front(m.seq.cbegin(), m.seq.cend());
this->push_front(char_event_type_t::check_exit);
return; // skip the input_set_bind_mode
} else if (has_functions && !has_commands) {
// Functions are added at the head of the input queue.
for (auto it = m.commands.rbegin(), end = m.commands.rend(); it != end; ++it) {
readline_cmd_t code = input_function_get_code(*it).value();
function_push_args(code);
event_queue_.push_front(char_event_t(code, m.seq));
this->push_front(char_event_t(code, m.seq));
}
} else if (has_commands && !has_functions) {
// Execute all commands.
@ -422,26 +414,24 @@ void inputter_t::mapping_execute(const input_mapping_t &m,
// 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.
command_handler(m.commands);
event_queue_.push_front(char_event_type_t::check_exit);
this->push_front(char_event_type_t::check_exit);
} else {
// Invalid binding, mixed commands and functions. We would need to execute these one by
// one.
event_queue_.push_front(char_event_type_t::check_exit);
this->push_front(char_event_type_t::check_exit);
}
// Empty bind mode indicates to not reset the mode (#2871)
if (!m.sets_mode.empty()) input_set_bind_mode(*parser_, m.sets_mode);
}
void inputter_t::queue_ch(const char_event_t &ch) {
void inputter_t::queue_char(const char_event_t &ch) {
if (ch.is_readline()) {
function_push_args(ch.get_readline());
}
event_queue_.push_back(ch);
this->push_back(ch);
}
void inputter_t::push_front(const char_event_t &ch) { event_queue_.push_front(ch); }
/// A class which allows accumulating input events, or returns them to the queue.
/// This contains a list of events which have been dequeued, and a current index into that list.
class event_queue_peeker_t {
@ -617,7 +607,7 @@ maybe_t<input_mapping_t> inputter_t::find_mapping(event_queue_peeker_t *peeker)
}
void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &command_handler) {
event_queue_peeker_t peeker(event_queue_);
event_queue_peeker_t peeker(*this);
// Check for mouse-tracking CSI before mappings to prevent the generic mapping handler from
// taking over.
@ -638,7 +628,7 @@ void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &co
// of a helper function to disable mouse tracking.
// writembs(outputter_t::stdoutput(), "\x1B[?1000l");
peeker.consume();
event_queue_.push_front(char_event_t(readline_cmd_t::disable_mouse_tracking, L""));
this->push_front(char_event_t(readline_cmd_t::disable_mouse_tracking, L""));
return;
}
peeker.restart();
@ -654,7 +644,7 @@ void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &co
FLOGF(reader, L"no generic found, ignoring char...");
auto evt = peeker.next();
if (evt.is_eof()) {
event_queue_.push_front(evt);
this->push_front(evt);
}
peeker.consume();
}
@ -669,7 +659,7 @@ char_event_t inputter_t::read_characters_no_readline() {
char_event_t evt_to_return{0};
for (;;) {
auto evt = event_queue_.readch();
auto evt = this->readch();
if (evt.is_readline()) {
saved_events.push_back(evt);
} else {
@ -679,16 +669,16 @@ char_event_t inputter_t::read_characters_no_readline() {
}
// Restore any readline functions
event_queue_.insert_front(saved_events.cbegin(), saved_events.cend());
this->insert_front(saved_events.cbegin(), saved_events.cend());
return evt_to_return;
}
char_event_t inputter_t::readch(const command_handler_t &command_handler) {
char_event_t inputter_t::read_char(const command_handler_t &command_handler) {
// Clear the interrupted flag.
reader_reset_interrupted();
// Search for sequence in mapping tables.
while (true) {
auto evt = event_queue_.readch();
auto evt = this->readch();
if (evt.is_readline()) {
switch (evt.get_readline()) {
@ -696,7 +686,7 @@ char_event_t inputter_t::readch(const command_handler_t &command_handler) {
case readline_cmd_t::self_insert_notfirst: {
// Typically self-insert is generated by the generic (empty) binding.
// However if it is generated by a real sequence, then insert that sequence.
event_queue_.insert_front(evt.seq.cbegin(), evt.seq.cend());
this->insert_front(evt.seq.cbegin(), evt.seq.cend());
// Issue #1595: ensure we only insert characters, not readline functions. The
// common case is that this will be empty.
char_event_t res = read_characters_no_readline();
@ -718,9 +708,9 @@ char_event_t inputter_t::readch(const command_handler_t &command_handler) {
}
// Else we flush remaining tokens
do {
evt = event_queue_.readch();
evt = this->readch();
} while (evt.is_readline());
event_queue_.push_front(evt);
this->push_front(evt);
return readch();
}
default: {
@ -732,7 +722,7 @@ char_event_t inputter_t::readch(const command_handler_t &command_handler) {
// There's no need to go through the input functions.
return evt;
} else {
event_queue_.push_front(evt);
this->push_front(evt);
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

View File

@ -23,7 +23,7 @@ wcstring describe_char(wint_t c);
void init_input();
struct input_mapping_t;
class inputter_t {
class inputter_t final : private input_event_queue_t {
public:
/// Construct from a parser, and the fd from which to read.
explicit inputter_t(parser_t &parser, int in = STDIN_FILENO);
@ -41,14 +41,11 @@ class inputter_t {
/// 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 = {});
char_event_t read_char(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.
void queue_ch(const char_event_t &ch);
/// Enqueue a char event to the front of the queue; this will be the next event returned.
void push_front(const char_event_t &ch);
void queue_char(const char_event_t &ch);
/// Sets the return status of the most recently executed input function.
void function_set_status(bool status) { function_status_ = status; }
@ -57,18 +54,15 @@ class inputter_t {
wchar_t function_pop_arg();
private:
// Called when select() is interrupted by a signal.
void select_interrupted() override;
// We need a parser to evaluate bindings.
const std::shared_ptr<parser_t> parser_;
input_event_queue_t event_queue_;
std::vector<wchar_t> input_function_args_{};
bool function_status_{false};
// A function called when select() is interrupted by a signal.
// See interrupt_handler_t.
maybe_t<char_event_t> handle_interrupt();
interrupt_handler_t get_interrupt_handler();
void function_push_arg(wchar_t arg);
void function_push_args(readline_cmd_t code);
void mapping_execute(const input_mapping_t &m, const command_handler_t &command_handler);

View File

@ -36,8 +36,7 @@
#define WAIT_ON_ESCAPE_DEFAULT 30
static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
input_event_queue_t::input_event_queue_t(int in, interrupt_handler_t handler)
: in_(in), interrupt_handler_(std::move(handler)) {}
input_event_queue_t::input_event_queue_t(int in) : in_(in) {}
/// Internal function used by readch to read one byte.
/// This calls select() on three fds: input (e.g. stdin), the ioport notifier fd (for main thread
@ -169,11 +168,7 @@ char_event_t input_event_queue_t::readch() {
case readb_interrupted:
// FIXME: here signals may break multibyte sequences.
if (interrupt_handler_) {
if (auto interrupt_evt = interrupt_handler_()) {
return interrupt_evt.acquire();
}
}
this->select_interrupted();
break;
case readb_uvar_notified:
@ -233,3 +228,6 @@ maybe_t<char_event_t> input_event_queue_t::readch_timed() {
void input_event_queue_t::push_back(const char_event_t& ch) { queue_.push_back(ch); }
void input_event_queue_t::push_front(const char_event_t& ch) { queue_.push_front(ch); }
void input_event_queue_t::select_interrupted() {}
input_event_queue_t::~input_event_queue_t() = default;

View File

@ -180,15 +180,12 @@ class char_event_t {
class environment_t;
void update_wait_on_escape_ms(const environment_t &vars);
/// A function type called when select() is interrupted by a signal.
/// The function maybe returns an event which is propagated back to the caller.
using interrupt_handler_t = std::function<maybe_t<char_event_t>()>;
/// A class which knows how to produce a stream of input events.
/// This is a base class; you may subclass it for its override points.
class input_event_queue_t {
public:
/// Construct from a file descriptor \p in, and an interrupt handler \p handler.
explicit input_event_queue_t(int in = STDIN_FILENO, interrupt_handler_t handler = {});
explicit input_event_queue_t(int in = STDIN_FILENO);
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously
@ -216,6 +213,11 @@ class input_event_queue_t {
queue_.insert(queue_.begin(), begin, end);
}
/// Override point for when when select() is interrupted by a signal. The default does nothing.
virtual void select_interrupted();
virtual ~input_event_queue_t();
private:
/// \return if we have any lookahead.
bool has_lookahead() const { return !queue_.empty(); }
@ -224,7 +226,6 @@ class input_event_queue_t {
maybe_t<char_event_t> try_pop();
int in_{0};
const interrupt_handler_t interrupt_handler_;
std::deque<char_event_t> queue_;
};

View File

@ -2856,7 +2856,7 @@ maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rl
while (accumulated_chars.size() < limit) {
bool allow_commands = (accumulated_chars.empty());
auto evt = inputter.readch(allow_commands ? normal_handler : empty_handler);
auto evt = inputter.read_char(allow_commands ? normal_handler : empty_handler);
if (!event_is_normal_char(evt) || !select_wrapper_t::poll_fd_readable(conf.in)) {
event_needing_handling = std::move(evt);
break;
@ -4114,7 +4114,7 @@ void reader_schedule_prompt_repaint() {
reader_data_t *data = current_data_or_null();
if (data && !data->force_exec_prompt_and_repaint) {
data->force_exec_prompt_and_repaint = true;
data->inputter.queue_ch(readline_cmd_t::repaint);
data->inputter.queue_char(readline_cmd_t::repaint);
}
}
@ -4127,7 +4127,7 @@ void reader_handle_command(readline_cmd_t cmd) {
void reader_queue_ch(const char_event_t &ch) {
if (reader_data_t *data = current_data_or_null()) {
data->inputter.queue_ch(ch);
data->inputter.queue_char(ch);
}
}