diff --git a/src/escape/csi.rs b/src/escape/csi.rs index 2c49594..7add8c9 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,18 @@ pub enum Cursor { }, CursorStyle(CursorStyle), + + /// 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 { @@ -648,6 +660,24 @@ 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") + } + 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"), } } } 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 [ ?