Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/raw_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,9 @@ fn extract_one_event(buffer: &[u8]) -> Option<(RawInputEvent, usize)> {

if buffer[0] == ESC {
let seq_len = complete_escape_sequence_len(buffer)?;
if let Some(mouse) = parse_default_mouse(&buffer[..seq_len]) {
return Some((RawInputEvent::Mouse(mouse), seq_len));
}
let seq = std::str::from_utf8(&buffer[..seq_len]).ok()?;

if let Some((kind, color)) = parse_default_color_response(seq) {
Expand Down Expand Up @@ -980,6 +983,13 @@ fn complete_escape_sequence_len(buffer: &[u8]) -> Option<usize> {
}
}

if buffer.len() >= 7
&& buffer.starts_with(b"\x1b\x1b[M")
&& parse_default_mouse(&buffer[1..7]).is_some()
{
return Some(1);
}

if buffer.starts_with(b"\x1b\x1b") {
return complete_escape_sequence_len(&buffer[1..]).map(|len| len + 1);
}
Expand All @@ -988,6 +998,9 @@ fn complete_escape_sequence_len(buffer: &[u8]) -> Option<usize> {
if buffer.starts_with(b"\x1b[<") {
return find_csi_final(buffer, b"Mm");
}
if buffer.starts_with(b"\x1b[M") {
return (buffer.len() >= 6).then_some(6);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return find_csi_final(
buffer,
b"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
Expand Down Expand Up @@ -1166,6 +1179,23 @@ fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
.position(|window| window == needle)
}

fn parse_default_mouse(sequence: &[u8]) -> Option<MouseEvent> {
let &[ESC, b'[', b'M', encoded_cb, encoded_column, encoded_row] = sequence else {
return None;
};
let cb = encoded_cb.checked_sub(32)?;
let column = u16::from(encoded_column).checked_sub(33)?;
let row = u16::from(encoded_row).checked_sub(33)?;
let (kind, modifiers) = parse_mouse_cb(cb)?;

Some(MouseEvent {
kind,
column,
row,
modifiers,
})
}

fn parse_sgr_mouse(sequence: &str) -> Option<MouseEvent> {
let body = sequence.strip_prefix("\x1b[<")?;
let final_char = body.chars().last()?;
Expand Down Expand Up @@ -1361,6 +1391,17 @@ mod tests {
assert_eq!(mouse.modifiers, KeyModifiers::empty());
}

#[test]
fn parses_default_mouse_encoding() {
let events = parse_raw_input_bytes_sync(b"\x1b[MCN1");
let [RawInputEvent::Mouse(mouse)] = events.as_slice() else {
panic!("expected one mouse event");
};
assert_eq!(mouse.kind, MouseEventKind::Moved);
assert_eq!((mouse.column, mouse.row), (45, 16));
assert_eq!(mouse.modifiers, KeyModifiers::empty());
}

#[test]
fn parses_extended_button_drag_as_mouse_motion() {
for input in [
Expand Down Expand Up @@ -1916,6 +1957,28 @@ mod tests {
}
}

#[test]
fn lone_escape_then_default_mouse_report_emits_both_events() {
let mut framer = RawInputFramer::default();

assert!(framer.push(b"\x1b").is_empty());
let events = framer.push(b"\x1b[MCN1");

assert_eq!(events.len(), 2);
let mut events = events.into_iter();
assert_raw_key(events.next().unwrap(), KeyCode::Esc, KeyModifiers::empty());
assert!(matches!(
events.next().unwrap(),
RawInputEvent::Mouse(MouseEvent {
kind: MouseEventKind::Moved,
column: 45,
row: 16,
..
})
));
assert!(framer.flush_timeout().is_empty());
}

#[test]
fn legacy_doubled_escape_alt_arrow_remains_one_event() {
let mut framer = RawInputFramer::default();
Expand Down
Loading