aboutsummaryrefslogtreecommitdiff
path: root/src/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'src/protocol')
-rw-r--r--src/protocol/render_ansi.rs93
1 files changed, 88 insertions, 5 deletions
diff --git a/src/protocol/render_ansi.rs b/src/protocol/render_ansi.rs
index 3346bb9c..231aa19f 100644
--- a/src/protocol/render_ansi.rs
+++ b/src/protocol/render_ansi.rs
@@ -33,6 +33,8 @@ use unicode_width::UnicodeWidthStr;
use crate::protocol::{underline_style_from_modifier, CellData, FrameData};
+const REVERSED_MODIFIER: u16 = 1 << 6;
+
/// Bytes produced by a [`BlitEncoder`] for one terminal frame.
pub(crate) struct EncodedBlit {
/// Terminal escape bytes ready to write to the host terminal.
@@ -57,10 +59,23 @@ impl BlitEncoder {
}
pub(crate) fn encode(&self, frame: &FrameData, force_full: bool) -> EncodedBlit {
- self.encode_inner(frame, force_full)
+ self.encode_inner(frame, force_full, false)
+ }
+
+ pub(crate) fn encode_with_suppressed_visible_cursor(
+ &self,
+ frame: &FrameData,
+ force_full: bool,
+ ) -> EncodedBlit {
+ self.encode_inner(frame, force_full, true)
}
- fn encode_inner(&self, frame: &FrameData, force_full: bool) -> EncodedBlit {
+ fn encode_inner(
+ &self,
+ frame: &FrameData,
+ force_full: bool,
+ suppress_visible_cursor: bool,
+ ) -> EncodedBlit {
let prev = if force_full {
None
} else {
@@ -81,7 +96,7 @@ impl BlitEncoder {
prev,
&mut next_last_visible_cursor,
&mut next_last_cursor_shape,
- false,
+ suppress_visible_cursor,
);
if let Some(stats) = prof_stats {
crate::render_prof::duration_since("ansi_encode.total", prof_started);
@@ -118,6 +133,19 @@ impl BlitEncoder {
}
}
+pub(crate) fn frame_with_drawn_cursor(mut frame: FrameData) -> FrameData {
+ if let Some(cursor) = frame.cursor.as_ref().filter(|cursor| cursor.visible) {
+ let (x, y) = clamp_cursor_position(&frame, cursor.x, cursor.y);
+ let idx = (y as usize)
+ .saturating_mul(frame.width as usize)
+ .saturating_add(x as usize);
+ if let Some(cell) = frame.cells.get_mut(idx) {
+ cell.modifier ^= REVERSED_MODIFIER;
+ }
+ }
+ frame
+}
+
#[derive(Clone, Copy, Default)]
struct ProfBlitStats {
scanned_cells: u64,
@@ -280,7 +308,6 @@ fn modifier_to_sgr_parts(val: u16) -> Vec<&'static str> {
const UNDERLINED: u16 = 1 << 3; // 0x08
const SLOW_BLINK: u16 = 1 << 4; // 0x10
const RAPID_BLINK: u16 = 1 << 5; // 0x20
- const REVERSED: u16 = 1 << 6; // 0x40
const HIDDEN: u16 = 1 << 7; // 0x80
const CROSSED_OUT: u16 = 1 << 8; // 0x100
@@ -308,7 +335,7 @@ fn modifier_to_sgr_parts(val: u16) -> Vec<&'static str> {
if val & RAPID_BLINK != 0 {
parts.push("6");
}
- if val & REVERSED != 0 {
+ if val & REVERSED_MODIFIER != 0 {
parts.push("7");
}
if val & HIDDEN != 0 {
@@ -1040,6 +1067,62 @@ mod tests {
}
#[test]
+ fn drawn_cursor_reverses_visible_cursor_cell() {
+ let frame = FrameData {
+ cells: vec![make_cell("A", 0, 0, 0); 9],
+ width: 3,
+ height: 3,
+ cursor: Some(CursorState {
+ x: 2,
+ y: 1,
+ visible: true,
+ shape: 6,
+ }),
+ hyperlinks: Vec::new(),
+ graphics: Vec::new(),
+ };
+ let drawn = frame_with_drawn_cursor(frame.clone());
+
+ assert_eq!(drawn.cells[5].modifier, REVERSED_MODIFIER);
+ assert_eq!(frame.cells[5].modifier, 0);
+
+ let encoded = BlitEncoder::new().encode_with_suppressed_visible_cursor(&drawn, false);
+ let output_str = String::from_utf8(encoded.bytes).unwrap();
+
+ assert!(
+ output_str.contains("\x1b[2;3H\x1b[6 q\x1b[?25l"),
+ "drawn cursor mode should park the host cursor hidden at the focused cursor position"
+ );
+ assert!(
+ !output_str.contains("\x1b[?25h"),
+ "drawn cursor mode should not show the host cursor"
+ );
+ assert!(
+ output_str.contains("\x1b[0;7;39;49mA"),
+ "drawn cursor should be emitted as reverse-video cell content"
+ );
+ }
+
+ #[test]
+ fn drawn_cursor_ignores_hidden_cursor() {
+ let frame = FrameData {
+ cells: vec![make_cell("A", 0, 0, 0)],
+ width: 1,
+ height: 1,
+ cursor: Some(CursorState {
+ x: 0,
+ y: 0,
+ visible: false,
+ shape: 0,
+ }),
+ hyperlinks: Vec::new(),
+ graphics: Vec::new(),
+ };
+
+ assert_eq!(frame_with_drawn_cursor(frame.clone()), frame);
+ }
+
+ #[test]
fn blit_frame_emits_cursor_shape_before_visibility_without_touching_ime_anchor() {
let frame = FrameData {
cells: vec![make_cell("A", 0, 0, 0)],