box drawing fixes
This commit is contained in:
+643
-108
File diff suppressed because it is too large
Load Diff
+42
-7
@@ -294,11 +294,29 @@ impl KeyboardState {
|
||||
/// Encodes a key event according to the Kitty keyboard protocol.
|
||||
pub struct KeyEncoder<'a> {
|
||||
state: &'a KeyboardState,
|
||||
/// Whether application cursor keys mode (DECCKM) is enabled.
|
||||
/// When true, arrow keys send SS3 format (ESC O letter).
|
||||
/// When false, arrow keys send CSI format (ESC [ letter).
|
||||
application_cursor_keys: bool,
|
||||
}
|
||||
|
||||
impl<'a> KeyEncoder<'a> {
|
||||
pub fn new(state: &'a KeyboardState) -> Self {
|
||||
Self { state }
|
||||
Self {
|
||||
state,
|
||||
application_cursor_keys: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new KeyEncoder with application cursor keys mode setting.
|
||||
pub fn with_cursor_mode(
|
||||
state: &'a KeyboardState,
|
||||
application_cursor_keys: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
state,
|
||||
application_cursor_keys,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a functional key press to bytes.
|
||||
@@ -369,7 +387,9 @@ impl<'a> KeyEncoder<'a> {
|
||||
|
||||
if has_event_type {
|
||||
result.push(b':');
|
||||
result.extend_from_slice((event_type as u8).to_string().as_bytes());
|
||||
result.extend_from_slice(
|
||||
(event_type as u8).to_string().as_bytes(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +403,11 @@ impl<'a> KeyEncoder<'a> {
|
||||
}
|
||||
|
||||
/// Encodes functional keys in legacy mode.
|
||||
fn encode_legacy_functional(&self, key: FunctionalKey, modifiers: Modifiers) -> Vec<u8> {
|
||||
fn encode_legacy_functional(
|
||||
&self,
|
||||
key: FunctionalKey,
|
||||
modifiers: Modifiers,
|
||||
) -> Vec<u8> {
|
||||
let mod_param = modifiers.encode();
|
||||
|
||||
match key {
|
||||
@@ -453,12 +477,20 @@ impl<'a> KeyEncoder<'a> {
|
||||
// Other functional keys - encode as CSI u
|
||||
_ => {
|
||||
let key_code = key as u32;
|
||||
self.encode_csi_u(key_code, modifiers, KeyEventType::Press, None)
|
||||
self.encode_csi_u(
|
||||
key_code,
|
||||
modifiers,
|
||||
KeyEventType::Press,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes arrow/home/end keys: CSI 1;mod X (with modifiers) or SS3 X (no modifiers).
|
||||
/// Encodes arrow/home/end keys based on DECCKM mode:
|
||||
/// - Normal mode (application_cursor_keys=false): CSI letter (ESC [ letter)
|
||||
/// - Application mode (application_cursor_keys=true): SS3 letter (ESC O letter)
|
||||
/// With modifiers, always use CSI 1;mod letter format.
|
||||
fn encode_arrow(&self, letter: u8, mod_param: Option<u8>) -> Vec<u8> {
|
||||
if let Some(m) = mod_param {
|
||||
// With modifiers: CSI 1;mod letter
|
||||
@@ -466,9 +498,12 @@ impl<'a> KeyEncoder<'a> {
|
||||
result.extend_from_slice(m.to_string().as_bytes());
|
||||
result.push(letter);
|
||||
result
|
||||
} else {
|
||||
// No modifiers: SS3 letter (application cursor mode)
|
||||
} else if self.application_cursor_keys {
|
||||
// Application cursor mode: SS3 letter (ESC O letter)
|
||||
vec![0x1b, b'O', letter]
|
||||
} else {
|
||||
// Normal cursor mode: CSI letter (ESC [ letter)
|
||||
vec![0x1b, b'[', letter]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+674
-194
File diff suppressed because it is too large
Load Diff
+383
-107
@@ -16,6 +16,9 @@ pub enum TerminalCommand {
|
||||
/// Triggered by OSC 51;statusline;<content> ST
|
||||
/// Empty content clears the statusline (restores default).
|
||||
SetStatusline(Option<String>),
|
||||
/// Set clipboard content via OSC 52.
|
||||
/// Triggered by OSC 52;c;<base64-data> ST
|
||||
SetClipboard(String),
|
||||
}
|
||||
|
||||
/// Direction for pane navigation.
|
||||
@@ -156,7 +159,8 @@ impl Default for ColorPalette {
|
||||
for g in 0..6 {
|
||||
for b in 0..6 {
|
||||
let idx = 16 + r * 36 + g * 6 + b;
|
||||
let to_val = |c: usize| if c == 0 { 0 } else { (55 + c * 40) as u8 };
|
||||
let to_val =
|
||||
|c: usize| if c == 0 { 0 } else { (55 + c * 40) as u8 };
|
||||
colors[idx] = [to_val(r), to_val(g), to_val(b)];
|
||||
}
|
||||
}
|
||||
@@ -196,7 +200,11 @@ impl ColorPalette {
|
||||
let parse_component = |s: &str| -> Option<u8> {
|
||||
let val = u16::from_str_radix(s, 16).ok()?;
|
||||
// Scale to 8-bit if it's a 16-bit value
|
||||
Some(if s.len() > 2 { (val >> 8) as u8 } else { val as u8 })
|
||||
Some(if s.len() > 2 {
|
||||
(val >> 8) as u8
|
||||
} else {
|
||||
val as u8
|
||||
})
|
||||
};
|
||||
let r = parse_component(parts[0])?;
|
||||
let g = parse_component(parts[1])?;
|
||||
@@ -215,7 +223,9 @@ impl ColorPalette {
|
||||
let [r, g, b] = self.default_fg;
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
|
||||
}
|
||||
Color::Rgb(r, g, b) => [*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0, 1.0],
|
||||
Color::Rgb(r, g, b) => {
|
||||
[*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0, 1.0]
|
||||
}
|
||||
Color::Indexed(idx) => {
|
||||
let [r, g, b] = self.colors[*idx as usize];
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
|
||||
@@ -230,7 +240,9 @@ impl ColorPalette {
|
||||
let [r, g, b] = self.default_bg;
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
|
||||
}
|
||||
Color::Rgb(r, g, b) => [*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0, 1.0],
|
||||
Color::Rgb(r, g, b) => {
|
||||
[*r as f32 / 255.0, *g as f32 / 255.0, *b as f32 / 255.0, 1.0]
|
||||
}
|
||||
Color::Indexed(idx) => {
|
||||
let [r, g, b] = self.colors[*idx as usize];
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
|
||||
@@ -320,9 +332,13 @@ impl ProcessingStats {
|
||||
|
||||
#[cfg(feature = "render_timing")]
|
||||
pub fn log_if_slow(&self, threshold_ms: u64) {
|
||||
let total_ms = (self.scroll_up_ns + self.text_handler_ns + self.csi_handler_ns) / 1_000_000;
|
||||
let total_ms =
|
||||
(self.scroll_up_ns + self.text_handler_ns + self.csi_handler_ns)
|
||||
/ 1_000_000;
|
||||
if total_ms >= threshold_ms {
|
||||
let vt_only_ns = self.vt_parser_ns.saturating_sub(self.text_handler_ns + self.csi_handler_ns);
|
||||
let vt_only_ns = self
|
||||
.vt_parser_ns
|
||||
.saturating_sub(self.text_handler_ns + self.csi_handler_ns);
|
||||
log::info!(
|
||||
"[PARSE_DETAIL] text={:.2}ms ({}chars) csi={:.2}ms ({}x) vt_only={:.2}ms ({}calls) scroll={:.2}ms ({}x)",
|
||||
self.text_handler_ns as f64 / 1_000_000.0,
|
||||
@@ -520,6 +536,8 @@ pub struct Terminal {
|
||||
pub application_cursor_keys: bool,
|
||||
/// Auto-wrap mode (DECAWM) - wrap at end of line.
|
||||
auto_wrap: bool,
|
||||
/// Origin mode (DECOM) - cursor positioning relative to scroll region.
|
||||
origin_mode: bool,
|
||||
/// Bracketed paste mode - wrap pasted text with escape sequences.
|
||||
pub bracketed_paste: bool,
|
||||
/// Focus event reporting mode.
|
||||
@@ -545,7 +563,12 @@ impl Terminal {
|
||||
|
||||
/// Creates a new terminal with the given dimensions and scrollback limit.
|
||||
pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self {
|
||||
log::info!("Terminal::new: cols={}, rows={}, scroll_bottom={}", cols, rows, rows.saturating_sub(1));
|
||||
log::info!(
|
||||
"Terminal::new: cols={}, rows={}, scroll_bottom={}",
|
||||
cols,
|
||||
rows,
|
||||
rows.saturating_sub(1)
|
||||
);
|
||||
let grid = vec![vec![Cell::default(); cols]; rows];
|
||||
let line_map: Vec<usize> = (0..rows).collect();
|
||||
|
||||
@@ -579,7 +602,8 @@ impl Terminal {
|
||||
alternate_screen: None,
|
||||
using_alternate_screen: false,
|
||||
application_cursor_keys: false,
|
||||
auto_wrap: true, // Auto-wrap is on by default
|
||||
auto_wrap: true,
|
||||
origin_mode: false,
|
||||
bracketed_paste: false,
|
||||
focus_reporting: false,
|
||||
synchronized_output: false,
|
||||
@@ -736,7 +760,13 @@ impl Terminal {
|
||||
return;
|
||||
}
|
||||
|
||||
log::info!("Terminal::resize: {}x{} -> {}x{}", self.cols, self.rows, cols, rows);
|
||||
log::info!(
|
||||
"Terminal::resize: {}x{} -> {}x{}",
|
||||
self.cols,
|
||||
self.rows,
|
||||
cols,
|
||||
rows
|
||||
);
|
||||
|
||||
let old_cols = self.cols;
|
||||
let old_rows = self.rows;
|
||||
@@ -750,7 +780,8 @@ impl Terminal {
|
||||
// Use actual row length - may differ from self.cols after scrollback swap
|
||||
let old_row_len = self.grid[old_grid_row].len();
|
||||
for col in 0..cols.min(old_row_len) {
|
||||
new_grid[visual_row][col] = self.grid[old_grid_row][col].clone();
|
||||
new_grid[visual_row][col] =
|
||||
self.grid[old_grid_row][col].clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,11 +803,16 @@ impl Terminal {
|
||||
if let Some(ref mut saved) = self.alternate_screen {
|
||||
let mut new_saved_grid = vec![vec![Cell::default(); cols]; rows];
|
||||
for visual_row in 0..rows.min(old_rows) {
|
||||
let old_grid_row = saved.line_map.get(visual_row).copied().unwrap_or(visual_row);
|
||||
let old_grid_row = saved
|
||||
.line_map
|
||||
.get(visual_row)
|
||||
.copied()
|
||||
.unwrap_or(visual_row);
|
||||
if old_grid_row < saved.grid.len() {
|
||||
for col in 0..cols.min(old_cols) {
|
||||
if col < saved.grid[old_grid_row].len() {
|
||||
new_saved_grid[visual_row][col] = saved.grid[old_grid_row][col].clone();
|
||||
new_saved_grid[visual_row][col] =
|
||||
saved.grid[old_grid_row][col].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -859,7 +895,9 @@ impl Terminal {
|
||||
let n = n.min(region_size);
|
||||
|
||||
#[cfg(feature = "render_timing")]
|
||||
{ self.stats.scroll_up_count += n as u32; }
|
||||
{
|
||||
self.stats.scroll_up_count += n as u32;
|
||||
}
|
||||
|
||||
for _ in 0..n {
|
||||
// Save the top line's grid index before rotation
|
||||
@@ -868,7 +906,10 @@ impl Terminal {
|
||||
// Save to scrollback only if scrolling from the very top of the screen
|
||||
// AND not in alternate screen mode (alternate screen never uses scrollback)
|
||||
// AND scrollback is enabled (capacity > 0)
|
||||
if self.scroll_top == 0 && !self.using_alternate_screen && self.scrollback.capacity > 0 {
|
||||
if self.scroll_top == 0
|
||||
&& !self.using_alternate_screen
|
||||
&& self.scrollback.capacity > 0
|
||||
{
|
||||
// Get a slot in the ring buffer - this is O(1) with just modulo arithmetic
|
||||
// If buffer is full, this overwrites the oldest line (perfect for our swap)
|
||||
let cols = self.cols;
|
||||
@@ -884,7 +925,10 @@ impl Terminal {
|
||||
}
|
||||
|
||||
// Rotate line_map: shift all indices up within scroll region using memmove
|
||||
self.line_map.copy_within(self.scroll_top + 1..=self.scroll_bottom, self.scroll_top);
|
||||
self.line_map.copy_within(
|
||||
self.scroll_top + 1..=self.scroll_bottom,
|
||||
self.scroll_top,
|
||||
);
|
||||
self.line_map[self.scroll_bottom] = recycled_grid_row;
|
||||
}
|
||||
|
||||
@@ -909,7 +953,11 @@ impl Terminal {
|
||||
let word_end = word_start + 63;
|
||||
|
||||
// Calculate bit range within this word
|
||||
let bit_start = if start > word_start { start - word_start } else { 0 };
|
||||
let bit_start = if start > word_start {
|
||||
start - word_start
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let bit_end = if end < word_end { end - word_start } else { 63 };
|
||||
|
||||
// Create mask for bits [bit_start, bit_end]
|
||||
@@ -936,7 +984,10 @@ impl Terminal {
|
||||
let recycled_grid_row = self.line_map[self.scroll_bottom];
|
||||
|
||||
// Rotate line_map: shift all indices down within scroll region using memmove
|
||||
self.line_map.copy_within(self.scroll_top..self.scroll_bottom, self.scroll_top + 1);
|
||||
self.line_map.copy_within(
|
||||
self.scroll_top..self.scroll_bottom,
|
||||
self.scroll_top + 1,
|
||||
);
|
||||
self.line_map[self.scroll_top] = recycled_grid_row;
|
||||
|
||||
// Clear the recycled line (now at visual top of scroll region)
|
||||
@@ -1089,7 +1140,8 @@ impl Terminal {
|
||||
// M for press, m for release
|
||||
// Most modern and recommended format
|
||||
let suffix = if pressed { b'M' } else { b'm' };
|
||||
format!("\x1b[<{};{};{}{}", cb, col, row, suffix as char).into_bytes()
|
||||
format!("\x1b[<{};{};{}{}", cb, col, row, suffix as char)
|
||||
.into_bytes()
|
||||
}
|
||||
MouseEncoding::Urxvt => {
|
||||
// URXVT encoding: ESC [ Cb ; Cx ; Cy M
|
||||
@@ -1123,7 +1175,8 @@ impl Terminal {
|
||||
if i < lines_from_scrollback {
|
||||
// This row comes from scrollback
|
||||
// Use ring buffer's get() method with logical index
|
||||
let scrollback_idx = scrollback_len - self.scroll_offset + i;
|
||||
let scrollback_idx =
|
||||
scrollback_len - self.scroll_offset + i;
|
||||
if let Some(line) = self.scrollback.get(scrollback_idx) {
|
||||
rows.push(line);
|
||||
} else {
|
||||
@@ -1161,8 +1214,10 @@ impl Terminal {
|
||||
|
||||
if row_idx < lines_from_scrollback {
|
||||
// This row comes from scrollback
|
||||
let scrollback_idx = scrollback_len - self.scroll_offset + row_idx;
|
||||
self.scrollback.get(scrollback_idx)
|
||||
let scrollback_idx =
|
||||
scrollback_len - self.scroll_offset + row_idx;
|
||||
self.scrollback
|
||||
.get(scrollback_idx)
|
||||
.or_else(|| Some(&self.grid[self.line_map[row_idx]]))
|
||||
} else {
|
||||
// This row comes from the grid
|
||||
@@ -1179,7 +1234,9 @@ impl Terminal {
|
||||
/// Inserts n blank lines at the cursor position, scrolling lines below down.
|
||||
/// Uses line_map rotation for efficiency.
|
||||
fn insert_lines(&mut self, n: usize) {
|
||||
if self.cursor_row < self.scroll_top || self.cursor_row > self.scroll_bottom {
|
||||
if self.cursor_row < self.scroll_top
|
||||
|| self.cursor_row > self.scroll_bottom
|
||||
{
|
||||
return;
|
||||
}
|
||||
let n = n.min(self.scroll_bottom - self.cursor_row + 1);
|
||||
@@ -1208,7 +1265,9 @@ impl Terminal {
|
||||
/// Deletes n lines at the cursor position, scrolling lines below up.
|
||||
/// Uses line_map rotation for efficiency.
|
||||
fn delete_lines(&mut self, n: usize) {
|
||||
if self.cursor_row < self.scroll_top || self.cursor_row > self.scroll_bottom {
|
||||
if self.cursor_row < self.scroll_top
|
||||
|| self.cursor_row > self.scroll_bottom
|
||||
{
|
||||
return;
|
||||
}
|
||||
let n = n.min(self.scroll_bottom - self.cursor_row + 1);
|
||||
@@ -1243,7 +1302,10 @@ impl Terminal {
|
||||
// Truncate n characters from the end
|
||||
row.truncate(self.cols - n);
|
||||
// Insert n blank characters at cursor position (single O(cols) operation)
|
||||
row.splice(self.cursor_col..self.cursor_col, std::iter::repeat(blank).take(n));
|
||||
row.splice(
|
||||
self.cursor_col..self.cursor_col,
|
||||
std::iter::repeat(blank).take(n),
|
||||
);
|
||||
self.mark_line_dirty(self.cursor_row);
|
||||
}
|
||||
|
||||
@@ -1348,7 +1410,11 @@ impl Handler for Terminal {
|
||||
if self.cursor_row > self.scroll_bottom {
|
||||
self.scroll_up(1);
|
||||
self.cursor_row = self.scroll_bottom;
|
||||
log::trace!("LF: scrolled at row {}, now at scroll_bottom {}", old_row, self.cursor_row);
|
||||
log::trace!(
|
||||
"LF: scrolled at row {}, now at scroll_bottom {}",
|
||||
old_row,
|
||||
self.cursor_row
|
||||
);
|
||||
}
|
||||
// Update cache after line change
|
||||
cached_row = self.cursor_row;
|
||||
@@ -1379,7 +1445,8 @@ impl Handler for Terminal {
|
||||
// Write character directly - no wide char handling needed for ASCII
|
||||
// SAFETY: cp is in 0x20..=0x7E which are valid ASCII chars
|
||||
let c = unsafe { char::from_u32_unchecked(cp) };
|
||||
self.grid[grid_row][self.cursor_col] = self.make_cell(c, false);
|
||||
self.grid[grid_row][self.cursor_col] =
|
||||
self.make_cell(c, false);
|
||||
self.cursor_col += 1;
|
||||
}
|
||||
// Slow path for non-ASCII printable characters (including all Unicode)
|
||||
@@ -1453,10 +1520,18 @@ impl Handler for Terminal {
|
||||
if parts.len() >= 3 {
|
||||
if let Ok(index_str) = std::str::from_utf8(parts[1]) {
|
||||
if let Ok(index) = index_str.parse::<u8>() {
|
||||
if let Ok(color_spec) = std::str::from_utf8(parts[2]) {
|
||||
if let Some(rgb) = ColorPalette::parse_color_spec(color_spec) {
|
||||
if let Ok(color_spec) =
|
||||
std::str::from_utf8(parts[2])
|
||||
{
|
||||
if let Some(rgb) =
|
||||
ColorPalette::parse_color_spec(color_spec)
|
||||
{
|
||||
self.palette.colors[index as usize] = rgb;
|
||||
log::debug!("OSC 4: Set color {} to {:?}", index, rgb);
|
||||
log::debug!(
|
||||
"OSC 4: Set color {} to {:?}",
|
||||
index,
|
||||
rgb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1467,9 +1542,14 @@ impl Handler for Terminal {
|
||||
10 => {
|
||||
if parts.len() >= 2 {
|
||||
if let Ok(color_spec) = std::str::from_utf8(parts[1]) {
|
||||
if let Some(rgb) = ColorPalette::parse_color_spec(color_spec) {
|
||||
if let Some(rgb) =
|
||||
ColorPalette::parse_color_spec(color_spec)
|
||||
{
|
||||
self.palette.default_fg = rgb;
|
||||
log::debug!("OSC 10: Set default foreground to {:?}", rgb);
|
||||
log::debug!(
|
||||
"OSC 10: Set default foreground to {:?}",
|
||||
rgb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1478,9 +1558,14 @@ impl Handler for Terminal {
|
||||
11 => {
|
||||
if parts.len() >= 2 {
|
||||
if let Ok(color_spec) = std::str::from_utf8(parts[1]) {
|
||||
if let Some(rgb) = ColorPalette::parse_color_spec(color_spec) {
|
||||
if let Some(rgb) =
|
||||
ColorPalette::parse_color_spec(color_spec)
|
||||
{
|
||||
self.palette.default_bg = rgb;
|
||||
log::debug!("OSC 11: Set default background to {:?}", rgb);
|
||||
log::debug!(
|
||||
"OSC 11: Set default background to {:?}",
|
||||
rgb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1496,7 +1581,9 @@ impl Handler for Terminal {
|
||||
match command {
|
||||
"navigate" => {
|
||||
if parts.len() >= 3 {
|
||||
if let Ok(direction_str) = std::str::from_utf8(parts[2]) {
|
||||
if let Ok(direction_str) =
|
||||
std::str::from_utf8(parts[2])
|
||||
{
|
||||
let direction = match direction_str {
|
||||
"up" => Some(Direction::Up),
|
||||
"down" => Some(Direction::Down),
|
||||
@@ -1505,8 +1592,15 @@ impl Handler for Terminal {
|
||||
_ => None,
|
||||
};
|
||||
if let Some(dir) = direction {
|
||||
log::debug!("OSC 51: Navigate {:?}", dir);
|
||||
self.command_queue.push(TerminalCommand::NavigatePane(dir));
|
||||
log::debug!(
|
||||
"OSC 51: Navigate {:?}",
|
||||
dir
|
||||
);
|
||||
self.command_queue.push(
|
||||
TerminalCommand::NavigatePane(
|
||||
dir,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1517,10 +1611,16 @@ impl Handler for Terminal {
|
||||
// Content may be base64-encoded (prefixed with "b64:") to avoid
|
||||
// escape sequence interpretation issues in the terminal
|
||||
let prefix = b"51;statusline;";
|
||||
let raw_content = if data.len() > prefix.len() && data.starts_with(prefix) {
|
||||
std::str::from_utf8(&data[prefix.len()..]).ok().map(|s| s.to_string())
|
||||
let raw_content = if data.len() > prefix.len()
|
||||
&& data.starts_with(prefix)
|
||||
{
|
||||
std::str::from_utf8(&data[prefix.len()..])
|
||||
.ok()
|
||||
.map(|s| s.to_string())
|
||||
} else if parts.len() >= 3 {
|
||||
std::str::from_utf8(parts[2]).ok().map(|s| s.to_string())
|
||||
std::str::from_utf8(parts[2])
|
||||
.ok()
|
||||
.map(|s| s.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1538,12 +1638,54 @@ impl Handler for Terminal {
|
||||
}
|
||||
});
|
||||
|
||||
let statusline = content.filter(|s| !s.is_empty());
|
||||
log::info!("OSC 51: Set statusline: {:?}", statusline.as_ref().map(|s| format!("{} bytes", s.len())));
|
||||
self.command_queue.push(TerminalCommand::SetStatusline(statusline));
|
||||
let statusline =
|
||||
content.filter(|s| !s.is_empty());
|
||||
log::info!(
|
||||
"OSC 51: Set statusline: {:?}",
|
||||
statusline
|
||||
.as_ref()
|
||||
.map(|s| format!("{} bytes", s.len()))
|
||||
);
|
||||
self.command_queue.push(
|
||||
TerminalCommand::SetStatusline(statusline),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
log::debug!("OSC 51: Unknown command '{}'", command);
|
||||
log::debug!(
|
||||
"OSC 51: Unknown command '{}'",
|
||||
command
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// OSC 52 - Clipboard operations
|
||||
// Format: OSC 52;Pc;Pd ST
|
||||
// Pc = clipboard type ('c' for clipboard, 'p' for primary, 's' for selection)
|
||||
// Pd = base64-encoded data to set, or '?' to query
|
||||
52 => {
|
||||
if parts.len() >= 3 {
|
||||
if let Ok(data_str) = std::str::from_utf8(parts[2]) {
|
||||
if data_str == "?" {
|
||||
log::debug!(
|
||||
"OSC 52: Query clipboard (not implemented)"
|
||||
);
|
||||
} else {
|
||||
use base64::Engine;
|
||||
if let Ok(decoded) =
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.decode(data_str)
|
||||
{
|
||||
if let Ok(text) = String::from_utf8(decoded) {
|
||||
log::debug!(
|
||||
"OSC 52: Set clipboard ({} bytes)",
|
||||
text.len()
|
||||
);
|
||||
self.command_queue.push(
|
||||
TerminalCommand::SetClipboard(text),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1590,8 +1732,10 @@ impl Handler for Terminal {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!("Unhandled DCS sequence: {:?}",
|
||||
std::str::from_utf8(data).unwrap_or("<invalid utf8>"));
|
||||
log::debug!(
|
||||
"Unhandled DCS sequence: {:?}",
|
||||
std::str::from_utf8(data).unwrap_or("<invalid utf8>")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1606,56 +1750,79 @@ impl Handler for Terminal {
|
||||
let secondary = params.secondary;
|
||||
|
||||
match action {
|
||||
// Cursor Up
|
||||
'A' => {
|
||||
let n = params.get(0, 1).max(1) as usize;
|
||||
let old_row = self.cursor_row;
|
||||
self.cursor_row = self.cursor_row.saturating_sub(n);
|
||||
log::trace!("CSI A: cursor up {} from row {} to {}", n, old_row, self.cursor_row);
|
||||
let min_row =
|
||||
if self.origin_mode { self.scroll_top } else { 0 };
|
||||
self.cursor_row =
|
||||
self.cursor_row.saturating_sub(n).max(min_row);
|
||||
}
|
||||
// Cursor Down
|
||||
'B' => {
|
||||
let n = params.get(0, 1).max(1) as usize;
|
||||
let old_row = self.cursor_row;
|
||||
self.cursor_row = (self.cursor_row + n).min(self.rows - 1);
|
||||
log::trace!("CSI B: cursor down {} from row {} to {}", n, old_row, self.cursor_row);
|
||||
let max_row = if self.origin_mode {
|
||||
self.scroll_bottom
|
||||
} else {
|
||||
self.rows - 1
|
||||
};
|
||||
self.cursor_row = (self.cursor_row + n).min(max_row);
|
||||
}
|
||||
// Cursor Forward
|
||||
'C' => {
|
||||
let n = params.get(0, 1).max(1) as usize;
|
||||
let old_col = self.cursor_col;
|
||||
self.cursor_col = (self.cursor_col + n).min(self.cols - 1);
|
||||
log::trace!("CSI C: cursor forward {} from col {} to {}", n, old_col, self.cursor_col);
|
||||
log::trace!(
|
||||
"CSI C: cursor forward {} from col {} to {}",
|
||||
n,
|
||||
old_col,
|
||||
self.cursor_col
|
||||
);
|
||||
}
|
||||
// Cursor Back
|
||||
'D' => {
|
||||
let n = params.get(0, 1).max(1) as usize;
|
||||
self.cursor_col = self.cursor_col.saturating_sub(n);
|
||||
}
|
||||
// Cursor Next Line (CNL)
|
||||
'E' => {
|
||||
let n = params.get(0, 1).max(1) as usize;
|
||||
let max_row = if self.origin_mode {
|
||||
self.scroll_bottom
|
||||
} else {
|
||||
self.rows - 1
|
||||
};
|
||||
self.cursor_col = 0;
|
||||
self.cursor_row = (self.cursor_row + n).min(self.rows - 1);
|
||||
self.cursor_row = (self.cursor_row + n).min(max_row);
|
||||
}
|
||||
// Cursor Previous Line (CPL)
|
||||
'F' => {
|
||||
let n = params.get(0, 1).max(1) as usize;
|
||||
let min_row =
|
||||
if self.origin_mode { self.scroll_top } else { 0 };
|
||||
self.cursor_col = 0;
|
||||
self.cursor_row = self.cursor_row.saturating_sub(n);
|
||||
self.cursor_row =
|
||||
self.cursor_row.saturating_sub(n).max(min_row);
|
||||
}
|
||||
// Cursor Horizontal Absolute (CHA)
|
||||
'G' => {
|
||||
let col = params.get(0, 1).max(1) as usize;
|
||||
let old_col = self.cursor_col;
|
||||
self.cursor_col = (col - 1).min(self.cols - 1);
|
||||
log::trace!("CSI G: cursor to col {} (was {})", self.cursor_col, old_col);
|
||||
log::trace!(
|
||||
"CSI G: cursor to col {} (was {})",
|
||||
self.cursor_col,
|
||||
old_col
|
||||
);
|
||||
}
|
||||
// Cursor Position
|
||||
'H' | 'f' => {
|
||||
let row = params.get(0, 1).max(1) as usize;
|
||||
let col = params.get(1, 1).max(1) as usize;
|
||||
if self.origin_mode {
|
||||
let abs_row =
|
||||
(self.scroll_top + row - 1).min(self.scroll_bottom);
|
||||
self.cursor_row = abs_row;
|
||||
} else {
|
||||
self.cursor_row = (row - 1).min(self.rows - 1);
|
||||
}
|
||||
self.cursor_col = (col - 1).min(self.cols - 1);
|
||||
}
|
||||
// Erase in Display
|
||||
@@ -1754,7 +1921,8 @@ impl Handler for Terminal {
|
||||
let n = (params.get(0, 1).max(1) as usize).min(65535); // Like Kitty's CSI_REP_MAX_REPETITIONS
|
||||
if self.cursor_col > 0 && n > 0 {
|
||||
let grid_row = self.line_map[self.cursor_row];
|
||||
let last_char = self.grid[grid_row][self.cursor_col - 1].character;
|
||||
let last_char =
|
||||
self.grid[grid_row][self.cursor_col - 1].character;
|
||||
let last_cp = last_char as u32;
|
||||
|
||||
// Fast path for ASCII: direct grid write, no width lookup
|
||||
@@ -1799,8 +1967,14 @@ impl Handler for Terminal {
|
||||
// Vertical Position Absolute (VPA)
|
||||
'd' => {
|
||||
let row = params.get(0, 1).max(1) as usize;
|
||||
if self.origin_mode {
|
||||
let abs_row =
|
||||
(self.scroll_top + row - 1).min(self.scroll_bottom);
|
||||
self.cursor_row = abs_row;
|
||||
} else {
|
||||
self.cursor_row = (row - 1).min(self.rows - 1);
|
||||
}
|
||||
}
|
||||
// SGR (Select Graphic Rendition)
|
||||
'm' => {
|
||||
self.handle_sgr(params);
|
||||
@@ -1815,8 +1989,13 @@ impl Handler for Terminal {
|
||||
}
|
||||
6 => {
|
||||
// Cursor position report
|
||||
let response = format!("\x1b[{};{}R", self.cursor_row + 1, self.cursor_col + 1);
|
||||
self.response_queue.extend_from_slice(response.as_bytes());
|
||||
let response = format!(
|
||||
"\x1b[{};{}R",
|
||||
self.cursor_row + 1,
|
||||
self.cursor_col + 1
|
||||
);
|
||||
self.response_queue
|
||||
.extend_from_slice(response.as_bytes());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -1844,7 +2023,10 @@ impl Handler for Terminal {
|
||||
self.scroll_top = (top - 1).min(self.rows - 1);
|
||||
self.scroll_bottom = (bottom - 1).min(self.rows - 1);
|
||||
if self.scroll_top > self.scroll_bottom {
|
||||
std::mem::swap(&mut self.scroll_top, &mut self.scroll_bottom);
|
||||
std::mem::swap(
|
||||
&mut self.scroll_top,
|
||||
&mut self.scroll_bottom,
|
||||
);
|
||||
}
|
||||
// Move cursor to home position
|
||||
self.cursor_row = 0;
|
||||
@@ -1856,25 +2038,44 @@ impl Handler for Terminal {
|
||||
match ps {
|
||||
14 => {
|
||||
// Report text area size in pixels: CSI 4 ; height ; width t
|
||||
let pixel_height = (self.rows as f32 * self.cell_height) as u32;
|
||||
let pixel_width = (self.cols as f32 * self.cell_width) as u32;
|
||||
let response = format!("\x1b[4;{};{}t", pixel_height, pixel_width);
|
||||
self.response_queue.extend_from_slice(response.as_bytes());
|
||||
log::debug!("XTWINOPS 14: Reported text area size {}x{} pixels", pixel_width, pixel_height);
|
||||
let pixel_height =
|
||||
(self.rows as f32 * self.cell_height) as u32;
|
||||
let pixel_width =
|
||||
(self.cols as f32 * self.cell_width) as u32;
|
||||
let response =
|
||||
format!("\x1b[4;{};{}t", pixel_height, pixel_width);
|
||||
self.response_queue
|
||||
.extend_from_slice(response.as_bytes());
|
||||
log::debug!(
|
||||
"XTWINOPS 14: Reported text area size {}x{} pixels",
|
||||
pixel_width,
|
||||
pixel_height
|
||||
);
|
||||
}
|
||||
16 => {
|
||||
// Report cell size in pixels: CSI 6 ; height ; width t
|
||||
let cell_h = self.cell_height as u32;
|
||||
let cell_w = self.cell_width as u32;
|
||||
let response = format!("\x1b[6;{};{}t", cell_h, cell_w);
|
||||
self.response_queue.extend_from_slice(response.as_bytes());
|
||||
log::debug!("XTWINOPS 16: Reported cell size {}x{} pixels", cell_w, cell_h);
|
||||
self.response_queue
|
||||
.extend_from_slice(response.as_bytes());
|
||||
log::debug!(
|
||||
"XTWINOPS 16: Reported cell size {}x{} pixels",
|
||||
cell_w,
|
||||
cell_h
|
||||
);
|
||||
}
|
||||
18 => {
|
||||
// Report text area size in characters: CSI 8 ; rows ; cols t
|
||||
let response = format!("\x1b[8;{};{}t", self.rows, self.cols);
|
||||
self.response_queue.extend_from_slice(response.as_bytes());
|
||||
log::debug!("XTWINOPS 18: Reported text area size {}x{} chars", self.cols, self.rows);
|
||||
let response =
|
||||
format!("\x1b[8;{};{}t", self.rows, self.cols);
|
||||
self.response_queue
|
||||
.extend_from_slice(response.as_bytes());
|
||||
log::debug!(
|
||||
"XTWINOPS 18: Reported text area size {}x{} chars",
|
||||
self.cols,
|
||||
self.rows
|
||||
);
|
||||
}
|
||||
22 | 23 => {
|
||||
// Save/restore window title - ignore
|
||||
@@ -1884,10 +2085,18 @@ impl Handler for Terminal {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Kitty keyboard protocol
|
||||
// ANSI Save Cursor (CSI s) - DECSLRM uses CSI ? s which has primary='?'
|
||||
's' if primary == 0 => {
|
||||
self.save_cursor();
|
||||
}
|
||||
// CSI u: ANSI restore cursor (no params) vs Kitty keyboard protocol (with params)
|
||||
'u' => {
|
||||
if primary == 0 && params.num_params == 0 {
|
||||
self.restore_cursor();
|
||||
} else {
|
||||
self.handle_keyboard_protocol_csi(params);
|
||||
}
|
||||
}
|
||||
// DEC Private Mode Set (CSI ? Ps h)
|
||||
'h' if primary == b'?' => {
|
||||
self.handle_dec_private_mode_set(params);
|
||||
@@ -1922,19 +2131,29 @@ impl Handler for Terminal {
|
||||
underline_style: self.current_underline_style,
|
||||
strikethrough: self.current_strikethrough,
|
||||
};
|
||||
log::debug!("ESC 7: Cursor saved at ({}, {})", self.cursor_col, self.cursor_row);
|
||||
log::debug!(
|
||||
"ESC 7: Cursor saved at ({}, {})",
|
||||
self.cursor_col,
|
||||
self.cursor_row
|
||||
);
|
||||
}
|
||||
|
||||
fn restore_cursor(&mut self) {
|
||||
self.cursor_col = self.saved_cursor.col.min(self.cols.saturating_sub(1));
|
||||
self.cursor_row = self.saved_cursor.row.min(self.rows.saturating_sub(1));
|
||||
self.cursor_col =
|
||||
self.saved_cursor.col.min(self.cols.saturating_sub(1));
|
||||
self.cursor_row =
|
||||
self.saved_cursor.row.min(self.rows.saturating_sub(1));
|
||||
self.current_fg = self.saved_cursor.fg;
|
||||
self.current_bg = self.saved_cursor.bg;
|
||||
self.current_bold = self.saved_cursor.bold;
|
||||
self.current_italic = self.saved_cursor.italic;
|
||||
self.current_underline_style = self.saved_cursor.underline_style;
|
||||
self.current_strikethrough = self.saved_cursor.strikethrough;
|
||||
log::debug!("ESC 8: Cursor restored to ({}, {})", self.cursor_col, self.cursor_row);
|
||||
log::debug!(
|
||||
"ESC 8: Cursor restored to ({}, {})",
|
||||
self.cursor_col,
|
||||
self.cursor_row
|
||||
);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
@@ -1954,6 +2173,7 @@ impl Handler for Terminal {
|
||||
self.mouse_encoding = MouseEncoding::X10;
|
||||
self.application_cursor_keys = false;
|
||||
self.auto_wrap = true;
|
||||
self.origin_mode = false;
|
||||
self.bracketed_paste = false;
|
||||
self.focus_reporting = false;
|
||||
self.synchronized_output = false;
|
||||
@@ -2085,14 +2305,18 @@ impl Terminal {
|
||||
|
||||
// If we're overwriting a wide character's continuation cell,
|
||||
// we need to clear the first cell of that wide character
|
||||
if self.grid[grid_row][self.cursor_col].wide_continuation && self.cursor_col > 0 {
|
||||
if self.grid[grid_row][self.cursor_col].wide_continuation
|
||||
&& self.cursor_col > 0
|
||||
{
|
||||
self.grid[grid_row][self.cursor_col - 1] = Cell::default();
|
||||
}
|
||||
|
||||
// If we're overwriting the first cell of a wide character,
|
||||
// we need to clear its continuation cell
|
||||
if char_width == 1 && self.cursor_col + 1 < self.cols
|
||||
&& self.grid[grid_row][self.cursor_col + 1].wide_continuation {
|
||||
if char_width == 1
|
||||
&& self.cursor_col + 1 < self.cols
|
||||
&& self.grid[grid_row][self.cursor_col + 1].wide_continuation
|
||||
{
|
||||
self.grid[grid_row][self.cursor_col + 1] = Cell::default();
|
||||
}
|
||||
|
||||
@@ -2106,7 +2330,8 @@ impl Terminal {
|
||||
// If the next cell is the first cell of another wide character,
|
||||
// clear its continuation cell
|
||||
if self.cursor_col + 1 < self.cols
|
||||
&& self.grid[grid_row][self.cursor_col + 1].wide_continuation {
|
||||
&& self.grid[grid_row][self.cursor_col + 1].wide_continuation
|
||||
{
|
||||
self.grid[grid_row][self.cursor_col + 1] = Cell::default();
|
||||
}
|
||||
|
||||
@@ -2120,7 +2345,10 @@ impl Terminal {
|
||||
///
|
||||
/// SAFETY: Caller must ensure i < params.num_params
|
||||
#[inline(always)]
|
||||
fn parse_extended_color(params: &CsiParams, i: usize) -> Option<(Color, usize)> {
|
||||
fn parse_extended_color(
|
||||
params: &CsiParams,
|
||||
i: usize,
|
||||
) -> Option<(Color, usize)> {
|
||||
let num = params.num_params;
|
||||
let p = ¶ms.params;
|
||||
let is_sub = ¶ms.is_sub_param;
|
||||
@@ -2131,11 +2359,10 @@ impl Terminal {
|
||||
if mode == 5 && i + 2 < num {
|
||||
return Some((Color::Indexed(p[i + 2] as u8), 2));
|
||||
} else if mode == 2 && i + 4 < num {
|
||||
return Some((Color::Rgb(
|
||||
p[i + 2] as u8,
|
||||
p[i + 3] as u8,
|
||||
p[i + 4] as u8,
|
||||
), 4));
|
||||
return Some((
|
||||
Color::Rgb(p[i + 2] as u8, p[i + 3] as u8, p[i + 4] as u8),
|
||||
4,
|
||||
));
|
||||
}
|
||||
} else if i + 2 < num {
|
||||
// Regular format (38;2;r;g;b or 38;5;idx)
|
||||
@@ -2143,11 +2370,10 @@ impl Terminal {
|
||||
if mode == 5 {
|
||||
return Some((Color::Indexed(p[i + 2] as u8), 2));
|
||||
} else if mode == 2 && i + 4 < num {
|
||||
return Some((Color::Rgb(
|
||||
p[i + 2] as u8,
|
||||
p[i + 3] as u8,
|
||||
p[i + 4] as u8,
|
||||
), 4));
|
||||
return Some((
|
||||
Color::Rgb(p[i + 2] as u8, p[i + 3] as u8, p[i + 4] as u8),
|
||||
4,
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -2195,13 +2421,17 @@ impl Terminal {
|
||||
22 => self.current_bold = false,
|
||||
23 => self.current_italic = false,
|
||||
24 => self.current_underline_style = 0,
|
||||
27 => std::mem::swap(&mut self.current_fg, &mut self.current_bg),
|
||||
27 => {
|
||||
std::mem::swap(&mut self.current_fg, &mut self.current_bg)
|
||||
}
|
||||
29 => self.current_strikethrough = false,
|
||||
// Standard foreground colors (30-37)
|
||||
30..=37 => self.current_fg = Color::Indexed((code - 30) as u8),
|
||||
38 => {
|
||||
// Extended foreground color
|
||||
if let Some((color, consumed)) = Self::parse_extended_color(params, i) {
|
||||
if let Some((color, consumed)) =
|
||||
Self::parse_extended_color(params, i)
|
||||
{
|
||||
self.current_fg = color;
|
||||
i += consumed;
|
||||
}
|
||||
@@ -2211,16 +2441,22 @@ impl Terminal {
|
||||
40..=47 => self.current_bg = Color::Indexed((code - 40) as u8),
|
||||
48 => {
|
||||
// Extended background color
|
||||
if let Some((color, consumed)) = Self::parse_extended_color(params, i) {
|
||||
if let Some((color, consumed)) =
|
||||
Self::parse_extended_color(params, i)
|
||||
{
|
||||
self.current_bg = color;
|
||||
i += consumed;
|
||||
}
|
||||
}
|
||||
49 => self.current_bg = Color::Default,
|
||||
// Bright foreground colors (90-97)
|
||||
90..=97 => self.current_fg = Color::Indexed((code - 90 + 8) as u8),
|
||||
90..=97 => {
|
||||
self.current_fg = Color::Indexed((code - 90 + 8) as u8)
|
||||
}
|
||||
// Bright background colors (100-107)
|
||||
100..=107 => self.current_bg = Color::Indexed((code - 100 + 8) as u8),
|
||||
100..=107 => {
|
||||
self.current_bg = Color::Indexed((code - 100 + 8) as u8)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
@@ -2250,7 +2486,11 @@ impl Terminal {
|
||||
let flags = params.get(0, 0) as u8;
|
||||
let mode = params.get(1, 1) as u8;
|
||||
self.keyboard.set_flags(flags, mode);
|
||||
log::debug!("Keyboard flags set to {:?} (mode {})", self.keyboard.flags(), mode);
|
||||
log::debug!(
|
||||
"Keyboard flags set to {:?} (mode {})",
|
||||
self.keyboard.flags(),
|
||||
mode
|
||||
);
|
||||
}
|
||||
b'>' => {
|
||||
let flags = if params.num_params == 0 {
|
||||
@@ -2259,12 +2499,18 @@ impl Terminal {
|
||||
Some(params.params[0] as u8)
|
||||
};
|
||||
self.keyboard.push(flags);
|
||||
log::debug!("Keyboard flags pushed: {:?}", self.keyboard.flags());
|
||||
log::debug!(
|
||||
"Keyboard flags pushed: {:?}",
|
||||
self.keyboard.flags()
|
||||
);
|
||||
}
|
||||
b'<' => {
|
||||
let count = params.get(0, 1) as usize;
|
||||
self.keyboard.pop(count);
|
||||
log::debug!("Keyboard flags popped: {:?}", self.keyboard.flags());
|
||||
log::debug!(
|
||||
"Keyboard flags popped: {:?}",
|
||||
self.keyboard.flags()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -2279,6 +2525,16 @@ impl Terminal {
|
||||
self.application_cursor_keys = true;
|
||||
log::debug!("DECCKM: Application cursor keys enabled");
|
||||
}
|
||||
6 => {
|
||||
self.origin_mode = true;
|
||||
self.cursor_row = self.scroll_top;
|
||||
self.cursor_col = 0;
|
||||
log::debug!(
|
||||
"DECOM: Origin mode enabled, cursor at ({}, {})",
|
||||
self.cursor_col,
|
||||
self.cursor_row
|
||||
);
|
||||
}
|
||||
7 => {
|
||||
self.auto_wrap = true;
|
||||
log::debug!("DECAWM: Auto-wrap enabled");
|
||||
@@ -2334,7 +2590,10 @@ impl Terminal {
|
||||
self.synchronized_output = true;
|
||||
log::trace!("Synchronized output enabled");
|
||||
}
|
||||
_ => log::debug!("Unhandled DEC private mode set: {}", params.params[i]),
|
||||
_ => log::debug!(
|
||||
"Unhandled DEC private mode set: {}",
|
||||
params.params[i]
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2348,6 +2607,14 @@ impl Terminal {
|
||||
self.application_cursor_keys = false;
|
||||
log::debug!("DECCKM: Normal cursor keys enabled");
|
||||
}
|
||||
6 => {
|
||||
self.origin_mode = false;
|
||||
self.cursor_row = 0;
|
||||
self.cursor_col = 0;
|
||||
log::debug!(
|
||||
"DECOM: Origin mode disabled, cursor at (0, 0)"
|
||||
);
|
||||
}
|
||||
7 => {
|
||||
self.auto_wrap = false;
|
||||
log::debug!("DECAWM: Auto-wrap disabled");
|
||||
@@ -2372,7 +2639,9 @@ impl Terminal {
|
||||
1002 => {
|
||||
if self.mouse_tracking == MouseTrackingMode::ButtonEvent {
|
||||
self.mouse_tracking = MouseTrackingMode::None;
|
||||
log::debug!("Mouse tracking: Button-event mode disabled");
|
||||
log::debug!(
|
||||
"Mouse tracking: Button-event mode disabled"
|
||||
);
|
||||
}
|
||||
}
|
||||
1003 => {
|
||||
@@ -2417,7 +2686,10 @@ impl Terminal {
|
||||
self.synchronized_output = false;
|
||||
log::trace!("Synchronized output disabled");
|
||||
}
|
||||
_ => log::debug!("Unhandled DEC private mode reset: {}", params.params[i]),
|
||||
_ => log::debug!(
|
||||
"Unhandled DEC private mode reset: {}",
|
||||
params.params[i]
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2449,7 +2721,8 @@ impl Terminal {
|
||||
let absolute_row = self.scrollback.len() + self.cursor_row;
|
||||
|
||||
// Process the command
|
||||
let (response, placement_result) = self.image_storage.process_command(
|
||||
let (response, placement_result) =
|
||||
self.image_storage.process_command(
|
||||
cmd,
|
||||
self.cursor_col,
|
||||
absolute_row,
|
||||
@@ -2468,10 +2741,13 @@ impl Terminal {
|
||||
// by the number of rows in the image placement rectangle."
|
||||
// However, if C=1 was specified, don't move the cursor.
|
||||
if let Some(placement) = placement_result {
|
||||
if !placement.suppress_cursor_move && !placement.virtual_placement {
|
||||
if !placement.suppress_cursor_move
|
||||
&& !placement.virtual_placement
|
||||
{
|
||||
// Move cursor down by (rows - 1) since we're already on the first row
|
||||
// Then set cursor to the column after the image
|
||||
let new_row = self.cursor_row + placement.rows.saturating_sub(1);
|
||||
let new_row =
|
||||
self.cursor_row + placement.rows.saturating_sub(1);
|
||||
if new_row >= self.rows {
|
||||
// Need to scroll
|
||||
let scroll_amount = new_row - self.rows + 1;
|
||||
|
||||
Reference in New Issue
Block a user