diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 7d726e9e3..cd45d4902 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -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) { diff --git a/src/input.cpp b/src/input.cpp index 1ec0f1a59..13c6a6172 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -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 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 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 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 diff --git a/src/input.h b/src/input.h index 6f62ec37e..44593d677 100644 --- a/src/input.h +++ b/src/input.h @@ -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; - 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_; - input_event_queue_t event_queue_; std::vector input_function_args_{}; bool function_status_{false}; - // A function called when select() is interrupted by a signal. - // See interrupt_handler_t. - maybe_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); diff --git a/src/input_common.cpp b/src/input_common.cpp index 593968f7a..5a1f636f2 100644 --- a/src/input_common.cpp +++ b/src/input_common.cpp @@ -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 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; diff --git a/src/input_common.h b/src/input_common.h index 1146f4175..c696df60c 100644 --- a/src/input_common.h +++ b/src/input_common.h @@ -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()>; - /// 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 try_pop(); int in_{0}; - const interrupt_handler_t interrupt_handler_; std::deque queue_; }; diff --git a/src/reader.cpp b/src/reader.cpp index 1a40ea677..0f6b991b3 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -2856,7 +2856,7 @@ maybe_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); } }