mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-23 01:36:39 +08:00
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:
parent
939aba02de
commit
3684c91ad2
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
18
src/input.h
18
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<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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user