diff --git a/src/lib.rs b/src/lib.rs index 19746a94..bd67c030 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,4 +61,6 @@ pub use attrs::Color; pub use callbacks::Callbacks; pub use cell::Cell; pub use parser::Parser; -pub use screen::{MouseProtocolEncoding, MouseProtocolMode, Screen}; +pub use screen::{ + CursorStyle, MouseProtocolEncoding, MouseProtocolMode, Screen, +}; diff --git a/src/perform.rs b/src/perform.rs index e36cfd83..e5615561 100644 --- a/src/perform.rs +++ b/src/perform.rs @@ -164,6 +164,20 @@ impl vte::Perform for WrappedScreen { ); } }, + Some(b' ') => match c { + 'q' => self + .screen + .decscusr(canonicalize_params_1(params, 0), unhandled), + _ => { + self.callbacks.unhandled_csi( + &mut self.screen, + None, + None, + ¶ms.iter().collect::>(), + c, + ); + } + }, Some(b'?') => match c { 'J' => self .screen diff --git a/src/screen.rs b/src/screen.rs index 7dfec97d..a81c0a75 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -50,6 +50,32 @@ pub enum MouseProtocolEncoding { // Urxvt, } +/// The cursor's visual style. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum CursorStyle { + /// The cursor should match the terminal's default. + #[default] + Default, + + /// The cursor should be a blinking block. + BlinkBlock, + + /// The cursor should be a steady block. + SteadyBlock, + + /// The cursor should be a blinking underline. + BlinkUnderline, + + /// The cursor should be a steady underline. + SteadyUnderline, + + /// The cursor should be a blinking vertical bar. + BlinkVerticalBar, + + /// The cursor should be a steady vertical bar. + SteadyVerticalBar, +} + /// Represents the overall terminal state. #[derive(Clone, Debug)] pub struct Screen { @@ -62,6 +88,7 @@ pub struct Screen { modes: u8, mouse_protocol_mode: MouseProtocolMode, mouse_protocol_encoding: MouseProtocolEncoding, + cursor_style: CursorStyle, } impl Screen { @@ -81,6 +108,7 @@ impl Screen { modes: 0, mouse_protocol_mode: MouseProtocolMode::default(), mouse_protocol_encoding: MouseProtocolEncoding::default(), + cursor_style: CursorStyle::default(), } } @@ -403,6 +431,11 @@ impl Screen { MouseProtocolEncoding::Default, ) .write_buf(contents); + crate::term::CursorStyle::new( + self.cursor_style, + CursorStyle::Default, + ) + .write_buf(contents); } /// Returns terminal escape sequences sufficient to change the previous @@ -447,6 +480,8 @@ impl Screen { prev.mouse_protocol_encoding, ) .write_buf(contents); + crate::term::CursorStyle::new(self.cursor_style, prev.cursor_style) + .write_buf(contents); } /// Returns terminal escape sequences sufficient to set the current @@ -585,6 +620,12 @@ impl Screen { self.mouse_protocol_encoding } + /// Returns the currently active [`CursorStyle`]. + #[must_use] + pub fn cursor_style(&self) -> CursorStyle { + self.cursor_style + } + /// Returns the currently active foreground color. #[must_use] pub fn fgcolor(&self) -> crate::Color { @@ -699,6 +740,10 @@ impl Screen { self.mouse_protocol_encoding = MouseProtocolEncoding::default(); } } + + fn set_cursor_style(&mut self, style: CursorStyle) { + self.cursor_style = style; + } } impl Screen { @@ -1001,6 +1046,24 @@ impl Screen { // csi codes + // CSI SP + pub(crate) fn decscusr( + &mut self, + cursor_style: u16, + mut unhandled: impl FnMut(&mut Self), + ) { + match cursor_style { + 0 => self.set_cursor_style(CursorStyle::Default), + 1 => self.set_cursor_style(CursorStyle::BlinkBlock), + 2 => self.set_cursor_style(CursorStyle::SteadyBlock), + 3 => self.set_cursor_style(CursorStyle::BlinkUnderline), + 4 => self.set_cursor_style(CursorStyle::SteadyUnderline), + 5 => self.set_cursor_style(CursorStyle::BlinkVerticalBar), + 6 => self.set_cursor_style(CursorStyle::SteadyVerticalBar), + _ => unhandled(self), + } + } + // CSI @ pub(crate) fn ich(&mut self, count: u16) { self.grid_mut().insert_cells(count); diff --git a/src/term.rs b/src/term.rs index b6b898c6..8617e58a 100644 --- a/src/term.rs +++ b/src/term.rs @@ -545,6 +545,51 @@ impl BufWrite for MouseProtocolEncoding { } } +#[derive(Default, Debug)] +#[must_use = "this struct does nothing unless you call write_buf"] +pub struct CursorStyle { + style: crate::CursorStyle, + prev: crate::CursorStyle, +} + +impl CursorStyle { + pub fn new(style: crate::CursorStyle, prev: crate::CursorStyle) -> Self { + Self { style, prev } + } +} + +impl BufWrite for CursorStyle { + fn write_buf(&self, buf: &mut Vec) { + if self.style == self.prev { + return; + } + + match self.style { + crate::CursorStyle::Default => { + buf.extend_from_slice(b"\x1b[0 q"); + } + crate::CursorStyle::BlinkBlock => { + buf.extend_from_slice(b"\x1b[1 q"); + } + crate::CursorStyle::SteadyBlock => { + buf.extend_from_slice(b"\x1b[2 q"); + } + crate::CursorStyle::BlinkUnderline => { + buf.extend_from_slice(b"\x1b[3 q"); + } + crate::CursorStyle::SteadyUnderline => { + buf.extend_from_slice(b"\x1b[4 q"); + } + crate::CursorStyle::BlinkVerticalBar => { + buf.extend_from_slice(b"\x1b[5 q"); + } + crate::CursorStyle::SteadyVerticalBar => { + buf.extend_from_slice(b"\x1b[6 q"); + } + } + } +} + fn extend_itoa(buf: &mut Vec, i: I) { let mut itoa_buf = itoa::Buffer::new(); buf.extend_from_slice(itoa_buf.format(i).as_bytes()); diff --git a/tests/data/fixtures/modes/21.json b/tests/data/fixtures/modes/21.json new file mode 100644 index 00000000..aa88fc03 --- /dev/null +++ b/tests/data/fixtures/modes/21.json @@ -0,0 +1,9 @@ +{ + "contents": "", + "cells": {}, + "cursor_position": [ + 0, + 0 + ], + "cursor_style": "blinkblock" +} \ No newline at end of file diff --git a/tests/data/fixtures/modes/21.typescript b/tests/data/fixtures/modes/21.typescript new file mode 100644 index 00000000..e833bc25 --- /dev/null +++ b/tests/data/fixtures/modes/21.typescript @@ -0,0 +1 @@ +[1 q \ No newline at end of file diff --git a/tests/helpers/fixtures.rs b/tests/helpers/fixtures.rs index c92cbc1f..a41f227e 100644 --- a/tests/helpers/fixtures.rs +++ b/tests/helpers/fixtures.rs @@ -79,6 +79,13 @@ pub struct FixtureScreen { skip_serializing_if = "is_default" )] mouse_protocol_encoding: vt100::MouseProtocolEncoding, + #[serde( + default, + deserialize_with = "deserialize_cursor_style", + serialize_with = "serialize_cursor_style", + skip_serializing_if = "is_default" + )] + cursor_style: vt100::CursorStyle, } impl FixtureScreen { @@ -113,6 +120,7 @@ impl FixtureScreen { bracketed_paste: screen.bracketed_paste(), mouse_protocol_mode: screen.mouse_protocol_mode(), mouse_protocol_encoding: screen.mouse_protocol_encoding(), + cursor_style: screen.cursor_style(), } } } @@ -228,6 +236,44 @@ where serializer.serialize_str(s) } +fn deserialize_cursor_style<'a, D>( + deserializer: D, +) -> std::result::Result +where + D: serde::de::Deserializer<'a>, +{ + let name = ::deserialize(deserializer)?; + match name.as_ref() { + "default" => Ok(vt100::CursorStyle::Default), + "blinkblock" => Ok(vt100::CursorStyle::BlinkBlock), + "steadyblock" => Ok(vt100::CursorStyle::SteadyBlock), + "blinkunderline" => Ok(vt100::CursorStyle::BlinkUnderline), + "steadyunderline" => Ok(vt100::CursorStyle::SteadyUnderline), + "blinkverticalbar" => Ok(vt100::CursorStyle::BlinkVerticalBar), + "steadyverticalbar" => Ok(vt100::CursorStyle::SteadyVerticalBar), + _ => unimplemented!(), + } +} + +fn serialize_cursor_style( + style: &vt100::CursorStyle, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + let s = match style { + vt100::CursorStyle::Default => "default", + vt100::CursorStyle::BlinkBlock => "blinkblock", + vt100::CursorStyle::SteadyBlock => "steadyblock", + vt100::CursorStyle::BlinkUnderline => "blinkunderline", + vt100::CursorStyle::SteadyUnderline => "steadyunderline", + vt100::CursorStyle::BlinkVerticalBar => "blinkverticalbar", + vt100::CursorStyle::SteadyVerticalBar => "steadyverticalbar", + }; + serializer.serialize_str(s) +} + fn load_input(name: &str, i: usize) -> Option> { let mut file = std::fs::File::open(format!( "tests/data/fixtures/{name}/{i}.typescript" @@ -269,6 +315,7 @@ fn assert_produces(input: &[u8], expected: &FixtureScreen) { parser.screen().mouse_protocol_encoding(), expected.mouse_protocol_encoding ); + assert_eq!(parser.screen().cursor_style(), expected.cursor_style); let (rows, cols) = parser.screen().size(); for row in 0..rows { diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index 7e3c56a1..9baab630 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -111,6 +111,7 @@ pub fn compare_screens( got.mouse_protocol_encoding(), expected.mouse_protocol_encoding() ); + is!(got.cursor_style(), expected.cursor_style()); true } diff --git a/tests/init.rs b/tests/init.rs index caf27130..72ecee45 100644 --- a/tests/init.rs +++ b/tests/init.rs @@ -31,4 +31,5 @@ fn init() { parser.screen().mouse_protocol_encoding(), vt100::MouseProtocolEncoding::Default ); + assert_eq!(parser.screen().cursor_style(), vt100::CursorStyle::Default) }