Don't match new-style bindings against raw sequences

On Konsole, given

    bind escape,i 'echo escape i'
    bind alt-i 'echo alt-i'

pressing alt-i triggers the wrong binding.  This is because we treat "escape
followed by i" as "alt-i". This is to support raw sequences like "\ei"
which are probably meant as "alt-i" -- we match such inputs to both mappings.

This double matching is not necessary for new-style bindings which
unambiguously describe the key presses, so let's activate this sequence
matching only for bindings specified as raw sequences.

Conversely, we currently fail to match an XTerm raw binding for ctrl-enter:

    echo 'XTerm.vt100.formatOtherKeys: 0' | xrdb
    xterm -e fish
    bind \e\[27\;5\;13~ execute

because we decode this to a single char; we match the leading CSI but not
the entire sequence. So this is a raw binding where we accidentally
match full, modified keys. Fix that too (two birds with one stone).
This commit is contained in:
Johannes Altmanninger 2024-04-12 21:29:08 +02:00
parent 6858f1100a
commit 88d6801720

View file

@ -39,7 +39,7 @@ pub struct InputMappingName {
pub mode: WString, pub mode: WString,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum KeyNameStyle { pub enum KeyNameStyle {
Plain, Plain,
RawEscapeSequence, RawEscapeSequence,
@ -640,7 +640,7 @@ impl EventQueuePeeker<'_> {
/// Check if the next event is the given character. This advances the index on success only. /// Check if the next event is the given character. This advances the index on success only.
/// If \p escaped is set, then return false if this (or any other) character had a timeout. /// If \p escaped is set, then return false if this (or any other) character had a timeout.
fn next_is_char(&mut self, key: Key, escaped: bool) -> bool { fn next_is_char(&mut self, style: &KeyNameStyle, key: Key, escaped: bool) -> bool {
assert!( assert!(
self.idx <= self.peeked.len(), self.idx <= self.peeked.len(),
"Index must not be larger than dequeued event count" "Index must not be larger than dequeued event count"
@ -653,13 +653,16 @@ impl EventQueuePeeker<'_> {
// Use either readch or readch_timed, per our param. // Use either readch or readch_timed, per our param.
if self.idx == self.peeked.len() { if self.idx == self.peeked.len() {
let Some(newevt) = (if escaped { let Some(newevt) = (if escaped {
FLOG!(reader, "reading timed escape");
self.event_queue.readch_timed_esc() self.event_queue.readch_timed_esc()
} else { } else {
FLOG!(reader, "readch timed sequence key");
self.event_queue.readch_timed_sequence_key() self.event_queue.readch_timed_sequence_key()
}) else { }) else {
self.had_timeout = true; self.had_timeout = true;
return false; return false;
}; };
FLOG!(reader, format!("adding peeked {:?}", newevt));
self.peeked.push(newevt); self.peeked.push(newevt);
} }
// Now we have peeked far enough; check the event. // Now we have peeked far enough; check the event.
@ -675,11 +678,16 @@ impl EventQueuePeeker<'_> {
self.idx += 1; self.idx += 1;
self.subidx = 0; self.subidx = 0;
FLOG!(reader, "matched delayed escape prefix in alt sequence"); FLOG!(reader, "matched delayed escape prefix in alt sequence");
return self.next_is_char(Key::from_raw(key.codepoint), true); return self.next_is_char(style, Key::from_raw(key.codepoint), true);
} }
if self.subidx == 0 && kevt.key == key { if *style == KeyNameStyle::Plain {
self.idx += 1; if kevt.key == key {
return true; assert!(self.subidx == 0);
self.idx += 1;
FLOG!(reader, "matched full key", key);
return true;
}
return false;
} }
let actual_seq = kevt.seq.as_char_slice(); let actual_seq = kevt.seq.as_char_slice();
if !actual_seq.is_empty() { if !actual_seq.is_empty() {
@ -690,16 +698,23 @@ impl EventQueuePeeker<'_> {
self.idx += 1; self.idx += 1;
self.subidx = 0; self.subidx = 0;
} }
// These FLOGs are extremely chatty because this is run a bunch of times. FLOG!(
// FLOG!(reader, "matched legacy sequence for", key); reader,
format!(
"matched char {} with offset {} within raw sequence of length {}",
key,
self.subidx,
actual_seq.len()
)
);
return true; return true;
} }
if key.modifiers == Modifiers::ALT && seq_char == '\x1b' { if key.modifiers == Modifiers::ALT && seq_char == '\x1b' {
if self.subidx + 1 == actual_seq.len() { if self.subidx + 1 == actual_seq.len() {
self.idx += 1; self.idx += 1;
self.subidx = 0; self.subidx = 0;
// FLOG!(reader, "matched escape prefix of legacy alt sequence for", key); FLOG!(reader, "matched escape prefix in raw escape sequence");
return self.next_is_char(Key::from_raw(key.codepoint), true); return self.next_is_char(style, Key::from_raw(key.codepoint), true);
} else if actual_seq } else if actual_seq
.get(self.subidx + 1) .get(self.subidx + 1)
.cloned() .cloned()
@ -711,7 +726,7 @@ impl EventQueuePeeker<'_> {
self.idx += 1; self.idx += 1;
self.subidx = 0; self.subidx = 0;
} }
// FLOG!(reader, "matched legacy alt sequence for", key); FLOG!(reader, format!("matched {key} against raw escape sequence"));
return true; return true;
} }
} }
@ -754,7 +769,7 @@ impl Drop for EventQueuePeeker<'_> {
} }
/// \return true if a given \p peeker matches a given sequence of char events given by \p str. /// \return true if a given \p peeker matches a given sequence of char events given by \p str.
fn try_peek_sequence(peeker: &mut EventQueuePeeker, seq: &[Key]) -> bool { fn try_peek_sequence(peeker: &mut EventQueuePeeker, style: &KeyNameStyle, seq: &[Key]) -> bool {
assert!( assert!(
!seq.is_empty(), !seq.is_empty(),
"Empty sequence passed to try_peek_sequence" "Empty sequence passed to try_peek_sequence"
@ -764,7 +779,7 @@ fn try_peek_sequence(peeker: &mut EventQueuePeeker, seq: &[Key]) -> bool {
// If we just read an escape, we need to add a timeout for the next char, // If we just read an escape, we need to add a timeout for the next char,
// to distinguish between the actual escape key and an "alt"-modifier. // to distinguish between the actual escape key and an "alt"-modifier.
let escaped = prev == Key::from_raw(key::Escape); let escaped = prev == Key::from_raw(key::Escape);
if !peeker.next_is_char(*key, escaped) { if !peeker.next_is_char(style, *key, escaped) {
return false; return false;
} }
prev = *key; prev = *key;
@ -803,7 +818,8 @@ impl Inputter {
continue; continue;
} }
if try_peek_sequence(peeker, &m.seq) { // FLOG!(reader, "trying mapping", format!("{:?}", m));
if try_peek_sequence(peeker, &m.key_name_style, &m.seq) {
// A binding for just escape should also be deferred // A binding for just escape should also be deferred
// so escape sequences take precedence. // so escape sequences take precedence.
if m.seq == vec![Key::from_raw(key::Escape)] { if m.seq == vec![Key::from_raw(key::Escape)] {