From 6f4a1a01ff89cb85cbe0bed2821ab5deb9c45e56 Mon Sep 17 00:00:00 2001 From: Jon Bitgood Date: Mon, 17 Nov 2025 11:27:16 -0600 Subject: [PATCH 1/2] Added kitty multicursor support --- src/escape/csi.rs | 15 ++++++++++++++- src/parse.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/escape/csi.rs b/src/escape/csi.rs index 2c49594..04fa2e6 100644 --- a/src/escape/csi.rs +++ b/src/escape/csi.rs @@ -456,7 +456,7 @@ impl Default for SgrModifiers { // Cursor -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Cursor { /// CBT Moves cursor to the Ps tabs backward. The default value of Ps is 1. BackwardTabulation(u32), @@ -592,6 +592,9 @@ pub enum Cursor { }, CursorStyle(CursorStyle), + + /// Response to cursor shape query (kitty multi-cursor protocol). + CursorShapeQueryResponse(Vec), } impl Display for Cursor { @@ -648,6 +651,16 @@ impl Display for Cursor { } } Cursor::CursorStyle(style) => write!(f, "{} q", *style as u8), + Cursor::CursorShapeQueryResponse(shapes) => { + write!(f, ">")?; + for (i, shape) in shapes.iter().enumerate() { + if i > 0 { + write!(f, ";")?; + } + write!(f, "{}", shape)?; + } + write!(f, " q") + } } } } diff --git a/src/parse.rs b/src/parse.rs index 2f4309b..1ec141c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -314,6 +314,10 @@ fn parse_csi(buffer: &[u8]) -> Result> { b'y' => return parse_csi_synchronized_output_mode(buffer), _ => None, }, + b'>' => match buffer[buffer.len() - 2..buffer.len()] { + [b' ', b'q'] => return parse_csi_cursor_shape_query_response(buffer), + _ => None, + }, b'0'..=b'9' => { // Numbered escape code. if buffer.len() == 3 { @@ -905,6 +909,29 @@ fn parse_csi_cursor_position(buffer: &[u8]) -> Result> { )))) } +fn parse_csi_cursor_shape_query_response(buffer: &[u8]) -> Result> { + assert!(buffer.starts_with(b"\x1B[>")); // CSI > + assert!(buffer.ends_with(b" q")); + + if buffer.len() < 5 { + // Minimum: ESC [ > SP q + return Ok(None); + } + + let s = str::from_utf8(&buffer[3..buffer.len() - 2])?; + + let shapes: Vec = s + .split(';') + .filter(|part| !part.is_empty()) + .map(|part| part.parse::()) + .collect::, _>>() + .map_err(|_| MalformedSequenceError)?; + + Ok(Some(Event::Csi(Csi::Cursor( + csi::Cursor::CursorShapeQueryResponse(shapes), + )))) +} + fn parse_csi_keyboard_enhancement_flags(buffer: &[u8]) -> Result> { // CSI ? flags u assert!(buffer.starts_with(b"\x1B[?")); // ESC [ ? From f47c37c4cf6a193907d92bb2b46574b91ba4e499 Mon Sep 17 00:00:00 2001 From: Jon Bitgood Date: Mon, 17 Nov 2025 14:44:29 -0600 Subject: [PATCH 2/2] Add CSI types for kitty multi-cursor operations --- src/escape/csi.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/escape/csi.rs b/src/escape/csi.rs index 04fa2e6..7add8c9 100644 --- a/src/escape/csi.rs +++ b/src/escape/csi.rs @@ -595,6 +595,15 @@ pub enum Cursor { /// Response to cursor shape query (kitty multi-cursor protocol). CursorShapeQueryResponse(Vec), + + SetMultipleCursors { + /// Cursor shape (29 = follow main cursor shape) + shape: u8, + /// List of cursor positions (line, col) 1-indexed + positions: Vec<(u16, u16)>, + }, + + ClearSecondaryCursors, } impl Display for Cursor { @@ -661,6 +670,14 @@ impl Display for Cursor { } write!(f, " q") } + Cursor::SetMultipleCursors { shape, positions } => { + write!(f, ">{}", shape)?; + for (line, col) in positions { + write!(f, ";2:{}:{}", line, col)?; + } + write!(f, " q") + } + Cursor::ClearSecondaryCursors => write!(f, ">0;4 q"), } } }