Prevent signals from tearing multi-char bindings

Say the user has a multi-char binding (typically an escape sequence), and a
signal arrives partway through the binding. The signal has an event handler
which enques some readline event, for example, `repaint`. Prior to this
change, the readline event would cause the multi-char binding to fail. This
would cause bits of the escape sequence to be printed to the screen.

Fix this by noticing when a sequence was "interrupted" by a non-char event,
and then rotating a sequence of such interruptions to the front of the
queue.

Fixes #8628
This commit is contained in:
ridiculousfish
2022-01-30 13:15:50 -08:00
parent 43e5004b6e
commit 1bdd629326
5 changed files with 120 additions and 1 deletions

View File

@@ -504,6 +504,14 @@ class event_queue_peeker_t {
idx_ = 0;
}
/// Test if any of our peeked events are readline or check_exit.
bool char_sequence_interrupted() const {
for (const auto &evt : peeked_) {
if (evt.is_readline() || evt.is_check_exit()) return true;
}
return false;
}
/// Reset our index back to 0.
void restart() { idx_ = 0; }
@@ -594,7 +602,9 @@ static bool try_peek_sequence(event_queue_peeker_t *peeker, const wcstring &str)
}
/// \return the first mapping that matches, walking first over the user's mapping list, then the
/// preset list. \return null if nothing matches.
/// preset list.
/// \return none if nothing matches, or if we may have matched a longer sequence but it was
/// interrupted by a readline event.
maybe_t<input_mapping_t> inputter_t::find_mapping(event_queue_peeker_t *peeker) {
const input_mapping_t *generic = nullptr;
const auto &vars = parser_->vars();
@@ -627,6 +637,12 @@ maybe_t<input_mapping_t> inputter_t::find_mapping(event_queue_peeker_t *peeker)
peeker->restart();
}
if (peeker->char_sequence_interrupted()) {
// We might have matched a longer sequence, but we were interrupted, e.g. by a signal.
FLOG(reader, "torn sequence, rearranging events");
return none();
}
if (escape) {
// We need to reconsume the escape.
peeker->next();
@@ -671,6 +687,15 @@ void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &co
}
peeker.restart();
if (peeker.char_sequence_interrupted()) {
// This may happen if we received a signal in the middle of an escape sequence or other
// multi-char binding. Move these non-char events to the front of the queue, handle them
// first, and then later we'll return and try the sequence again. See #8628.
peeker.consume();
this->promote_interruptions_to_front();
return;
}
FLOGF(reader, L"no generic found, ignoring char...");
auto evt = peeker.next();
peeker.consume();