use unicode_segmentation::UnicodeSegmentation as _;
use unicode_width::UnicodeWidthChar as _;
use super::osc;
use super::{
Charset, CursorStyle, LastPrinted, MODE_ALL_DEC, MODE_ALTERNATE_SCREEN, MODE_ALTERNATE_SCROLL,
MODE_APPLICATION_CURSOR, MODE_BACKSPACE_BS, MODE_BRACKETED_PASTE, MODE_COLOR_SCHEME_UPDATES,
MODE_DEC_ALLOW_80_132, MODE_DECCOLM, MODE_FOCUS_IN_OUT, MODE_GRAPHEME_CLUSTER,
MODE_HIDE_CURSOR, MODE_IN_BAND_RESIZE, MODE_INSERT, MODE_LINE_WRAP, MODE_LNM,
MODE_MOUSE_REPORT_ALL_MOTION, MODE_MOUSE_REPORT_CELL_MOTION, MODE_MOUSE_REPORT_CLICK,
MODE_MOUSE_X10, MODE_REVERSE_VIDEO, MODE_SGR_MOUSE, MODE_SGR_PIXEL_MOUSE, MODE_SYNC_UPDATE,
Screen, ScreenEvent, TitleStackEntry, TitleStackTarget, XtGetTcapEntry, XtWinOpsReport,
u16_to_u8, xtgettcap_is_tn, xtgettcap_lookup,
};
use crate::host_reply::ModeStatus;
fn mode_number_to_save_bit(mode: u16) -> Option<u32> {
match mode {
1 => Some(MODE_APPLICATION_CURSOR),
5 => Some(MODE_REVERSE_VIDEO),
7 => Some(MODE_LINE_WRAP),
9 => Some(MODE_MOUSE_X10),
25 => Some(MODE_HIDE_CURSOR),
67 => Some(MODE_BACKSPACE_BS),
1000 => Some(MODE_MOUSE_REPORT_CLICK),
1002 => Some(MODE_MOUSE_REPORT_CELL_MOTION),
1003 => Some(MODE_MOUSE_REPORT_ALL_MOTION),
1004 => Some(MODE_FOCUS_IN_OUT),
1006 => Some(MODE_SGR_MOUSE),
1007 => Some(MODE_ALTERNATE_SCROLL),
1016 => Some(MODE_SGR_PIXEL_MOUSE),
2004 => Some(MODE_BRACKETED_PASTE),
2026 => Some(MODE_SYNC_UPDATE),
2027 => Some(MODE_GRAPHEME_CLUSTER),
2031 => Some(MODE_COLOR_SCHEME_UPDATES),
2048 => Some(MODE_IN_BAND_RESIZE),
_ => None,
}
}
fn collect_dec_save_bits(params: &vte::Params) -> u32 {
let mut iter = params.iter();
let Some(first) = iter.next() else {
return MODE_ALL_DEC;
};
if iter.next().is_none() && first.first().copied() == Some(0) {
return MODE_ALL_DEC;
}
let mut bits = 0u32;
for param in params {
if let Some(&n) = param.first()
&& let Some(bit) = mode_number_to_save_bit(n)
{
bits |= bit;
}
}
bits
}
impl Screen {
pub(crate) fn text(&mut self, c: char) {
let c = self.charsets[self.active_charset].map(c);
if c == '\u{fe0f}' {
let pos = self.grid().pos();
if pos.col > 0 {
let prev_col = pos.col - 1;
let prev_pos = crate::grid::Pos {
row: pos.row,
col: prev_col,
};
if let Some(prev_cell) = self.grid().drawing_cell(prev_pos)
&& prev_cell.has_contents()
&& !prev_cell.is_wide()
{
let attrs = *prev_cell.attrs();
self.grid_mut()
.drawing_cell_mut(prev_pos)
.expect("pos bounded by Parser::process invariant")
.append(c);
let prev_cell = self
.grid_mut()
.drawing_cell_mut(prev_pos)
.expect("pos bounded by Parser::process invariant");
prev_cell.set_wide(true);
let cur_cell = self
.grid_mut()
.drawing_cell_mut(pos)
.expect("pos bounded by Parser::process invariant");
cur_cell.clear(attrs);
cur_cell.set_wide_continuation(true);
self.grid_mut().col_inc(1);
if let Some(last) = self.last_printed.as_mut() {
last.width = 2;
}
}
}
return;
}
let width = c.width();
if width.is_none() && (u32::from(c)) < 256 {
return;
}
let width: u16 = width
.unwrap_or(1)
.try_into()
.expect("char width fits in u16");
if width == 0 {
self.combine_into_prev_cell(|cell| cell.append(c));
return;
}
self.write_cell_at(c, "", width);
}
pub(crate) fn text_cluster(&mut self, cluster: &str, width: usize) {
let width_u16: u16 = width.try_into().unwrap_or(u16::MAX);
if width == 0 {
self.combine_into_prev_cell(|cell| cell.append_str(cluster));
return;
}
let mut chars = cluster.chars();
let Some(raw_first) = chars.next() else {
return;
};
let first = self.charsets[self.active_charset].map(raw_first);
let rest: String = chars.collect();
self.write_cell_at(first, &rest, width_u16);
}
fn combine_into_prev_cell(&mut self, append: impl FnOnce(&mut crate::cell::Cell)) {
let pos = self.grid().pos();
let size = self.grid().size();
if pos.col > 0 {
let mut prev_cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos {
row: pos.row,
col: pos.col - 1,
})
.expect("pos bounded by Parser::process invariant");
if prev_cell.is_wide_continuation() {
prev_cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos {
row: pos.row,
col: pos.col - 2,
})
.expect("pos bounded by Parser::process invariant");
}
append(prev_cell);
} else if pos.row > 0 {
let prev_row = self
.grid()
.drawing_row(pos.row - 1)
.expect("row bounded by Parser::process invariant");
if prev_row.wrapped() {
let mut prev_cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos {
row: pos.row - 1,
col: size.cols - 1,
})
.expect("pos bounded by Parser::process invariant");
if prev_cell.is_wide_continuation() {
prev_cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos {
row: pos.row - 1,
col: size.cols - 2,
})
.expect("pos bounded by Parser::process invariant");
}
append(prev_cell);
}
}
}
fn write_cell_at(&mut self, first: char, extra: &str, width: u16) {
let pos = self.grid().pos();
let size = self.grid().size();
let attrs = self.attrs;
if self.has_mode_bit(MODE_LINE_WRAP) {
let mut wrap = false;
if pos.col > size.cols - width {
let last_cell = self
.grid()
.drawing_cell(crate::grid::Pos {
row: pos.row,
col: size.cols - 1,
})
.expect("pos bounded by Parser::process invariant");
if last_cell.has_contents() || last_cell.is_wide_continuation() {
wrap = true;
}
}
self.grid_mut().col_wrap(width, wrap);
} else if pos.col > size.cols - width {
self.grid_mut().col_set(size.cols - width);
}
let pos = self.grid().pos();
if self.has_mode_bit(MODE_INSERT) {
self.grid_mut().insert_cells(width);
}
if self
.grid()
.drawing_cell(pos)
.expect("pos bounded by Parser::process invariant")
.is_wide_continuation()
{
let prev_cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos {
row: pos.row,
col: pos.col - 1,
})
.expect("pos bounded by Parser::process invariant");
prev_cell.clear(attrs);
}
if self
.grid()
.drawing_cell(pos)
.expect("pos bounded by Parser::process invariant")
.is_wide()
{
let next_cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos {
row: pos.row,
col: pos.col + 1,
})
.expect("pos bounded by Parser::process invariant");
next_cell.set(' ', attrs);
}
let link = self.hyperlink.clone();
let cell = self
.grid_mut()
.drawing_cell_mut(pos)
.expect("pos bounded by Parser::process invariant");
cell.set(first, attrs);
cell.set_wide(width > 1);
cell.set_hyperlink(link);
if !extra.is_empty() {
self.grid_mut()
.drawing_cell_mut(pos)
.expect("pos bounded by Parser::process invariant")
.append_str(extra);
}
self.grid_mut().col_inc(1);
if width > 1 {
let pos = self.grid().pos();
if self
.grid()
.drawing_cell(pos)
.expect("pos bounded by Parser::process invariant")
.is_wide()
{
let next_next_pos = crate::grid::Pos {
row: pos.row,
col: pos.col + 1,
};
let next_next_cell = self
.grid_mut()
.drawing_cell_mut(next_next_pos)
.expect("pos bounded by Parser::process invariant");
next_next_cell.clear(attrs);
if next_next_pos.col == size.cols - 1 {
self.grid_mut()
.drawing_row_mut(pos.row)
.expect("row bounded by Parser::process invariant")
.wrap(false);
}
}
let next_cell = self
.grid_mut()
.drawing_cell_mut(pos)
.expect("pos bounded by Parser::process invariant");
next_cell.clear(crate::attrs::Attrs::default());
next_cell.set_wide_continuation(true);
self.grid_mut().col_inc(1);
}
if !self.has_mode_bit(MODE_LINE_WRAP) {
self.grid_mut().col_clamp();
}
self.last_printed = Some(LastPrinted { ch: first, width });
}
pub(crate) fn rep(&mut self, count: u16) {
let Some(LastPrinted { ch, width }) = self.last_printed else {
return;
};
let size = self.grid().size();
let cap: u16 = u32::from(size.rows)
.saturating_mul(u32::from(size.cols))
.try_into()
.unwrap_or(u16::MAX);
let n = count.min(cap);
for _ in 0..n {
self.write_cell_at(ch, "", width);
}
}
pub(crate) fn clear_last_printed(&mut self) {
self.last_printed = None;
}
pub(crate) fn flush_print_buffer(&mut self) {
if self.print_buffer.is_empty() {
return;
}
let buf = std::mem::take(&mut self.print_buffer);
for cluster in buf.graphemes(true) {
let width = unicode_width::UnicodeWidthStr::width(cluster);
self.text_cluster(cluster, width);
}
}
pub(crate) fn grapheme_cluster_mode(&self) -> bool {
self.has_mode_bit(MODE_GRAPHEME_CLUSTER)
}
pub(crate) fn push_to_print_buffer(&mut self, c: char) {
self.print_buffer.push(c);
}
pub(crate) fn bs(&mut self) {
self.grid_mut().col_dec(1);
}
pub(crate) fn tab(&mut self) {
self.grid_mut().col_tab();
}
pub(crate) fn cht(&mut self, count: u16) {
self.grid_mut().col_tab_n(count);
}
pub(crate) fn cbt(&mut self, count: u16) {
self.grid_mut().col_tab_backward(count);
}
pub(crate) fn hts(&mut self) {
self.grid_mut().set_tab_stop();
}
pub(crate) fn tbc(&mut self, mode: u16) {
self.grid_mut().clear_tab_stop(mode);
}
pub(crate) fn lf(&mut self) {
if self.has_mode_bit(MODE_LNM) {
self.cr();
}
self.grid_mut().row_inc_scroll(1);
}
pub(crate) fn vt(&mut self) {
self.lf();
}
pub(crate) fn ff(&mut self) {
self.lf();
}
pub(crate) fn bell(&mut self) {
self.pending_events.push(ScreenEvent::Bell);
}
pub(crate) fn cr(&mut self) {
self.grid_mut().col_set(0);
}
pub(crate) fn si(&mut self) {
self.active_charset = 0;
}
pub(crate) fn so(&mut self) {
self.active_charset = 1;
}
pub(crate) fn reset_charset(&mut self) {
self.active_charset = 0;
}
pub(crate) fn designate_charset(&mut self, slot: usize, charset: Charset) {
if slot < 4 {
self.charsets[slot] = charset;
}
}
pub(crate) fn decsc(&mut self) {
self.save_cursor();
}
pub(crate) fn decrc(&mut self) {
self.restore_cursor();
}
pub(crate) fn ri(&mut self) {
self.grid_mut().row_dec_scroll(1);
}
pub(crate) fn nel(&mut self) {
self.cr();
self.lf();
}
pub(crate) fn ris(&mut self) {
let old_cursor_style = self.cursor_style;
let old_kitty_flags = self.kitty_flags.current();
let had_title = !self.title.is_empty() || !self.icon_name.is_empty();
let had_cwd = self.cwd.is_some();
*self = Self::with_profile(
self.grid.size(),
self.grid.scrollback_len(),
self.host_profile.clone(),
);
self.pending_events.push(ScreenEvent::ScreenCleared);
if old_cursor_style != self.cursor_style {
self.pending_events
.push(ScreenEvent::CursorStyleChanged(self.cursor_style));
}
if had_title {
self.pending_events.push(ScreenEvent::TitleChanged);
}
if had_cwd {
self.pending_events
.push(ScreenEvent::WorkingDirectoryChanged);
}
if old_kitty_flags != 0 {
self.pending_events.push(ScreenEvent::KittyFlagsChanged(0));
}
}
pub(crate) fn decaln(&mut self) {
let size = self.grid().size();
let attrs = crate::attrs::Attrs::default();
self.grid_mut().set_scroll_region(0, size.rows - 1);
self.grid_mut().set_pos(crate::grid::Pos { row: 0, col: 0 });
for row in 0..size.rows {
for col in 0..size.cols {
let cell = self
.grid_mut()
.drawing_cell_mut(crate::grid::Pos { row, col })
.expect("pos bounded by Parser::process invariant");
cell.set('E', attrs);
}
}
}
pub(crate) fn ich(&mut self, count: u16) {
self.grid_mut().insert_cells(count);
}
pub(crate) fn cuu(&mut self, offset: u16) {
self.grid_mut().row_dec_clamp(offset);
}
pub(crate) fn cud(&mut self, offset: u16) {
self.grid_mut().row_inc_clamp(offset);
}
pub(crate) fn cuf(&mut self, offset: u16) {
self.grid_mut().col_inc_clamp(offset);
}
pub(crate) fn cub(&mut self, offset: u16) {
self.grid_mut().col_dec(offset);
}
pub(crate) fn cnl(&mut self, offset: u16) {
self.grid_mut().col_set(0);
self.grid_mut().row_inc_clamp(offset);
}
pub(crate) fn cpl(&mut self, offset: u16) {
self.grid_mut().col_set(0);
self.grid_mut().row_dec_clamp(offset);
}
pub(crate) fn cha(&mut self, col: u16) {
self.grid_mut().col_set(col - 1);
}
pub(crate) fn cup(&mut self, (row, col): (u16, u16)) {
self.grid_mut().set_pos(crate::grid::Pos {
row: row - 1,
col: col - 1,
});
}
pub(crate) fn ed(&mut self, mode: u16) {
let attrs = self.attrs;
match mode {
0 => self.grid_mut().erase_all_forward(attrs),
1 => self.grid_mut().erase_all_backward(attrs),
2 => {
self.grid_mut().erase_all(attrs);
self.pending_events.push(ScreenEvent::ScreenCleared);
}
3 => self.grid_mut().clear_scrollback(),
_ => {}
}
}
pub(crate) fn el(&mut self, mode: u16) {
let attrs = self.attrs;
match mode {
0 => self.grid_mut().erase_row_forward(attrs),
1 => self.grid_mut().erase_row_backward(attrs),
2 => self.grid_mut().erase_row(attrs),
_ => {}
}
}
pub(crate) fn il(&mut self, count: u16) {
self.grid_mut().insert_lines(count);
}
pub(crate) fn dl(&mut self, count: u16) {
self.grid_mut().delete_lines(count);
}
pub(crate) fn dch(&mut self, count: u16) {
self.grid_mut().delete_cells(count);
}
pub(crate) fn su(&mut self, count: u16) {
self.grid_mut().scroll_up(count);
}
pub(crate) fn sd(&mut self, count: u16) {
self.grid_mut().scroll_down(count);
}
pub(crate) fn ech(&mut self, count: u16) {
let attrs = self.attrs;
self.grid_mut().erase_cells(count, attrs);
}
pub(crate) fn vpa(&mut self, row: u16) {
self.grid_mut().row_set(row - 1);
}
pub(crate) fn decscusr(&mut self, style: u16) {
let new_style = match style {
0 => CursorStyle::Default,
1 => CursorStyle::BlinkingBlock,
2 => CursorStyle::SteadyBlock,
3 => CursorStyle::BlinkingUnderline,
4 => CursorStyle::SteadyUnderline,
5 => CursorStyle::BlinkingBar,
6 => CursorStyle::SteadyBar,
_ => return,
};
if new_style != self.cursor_style {
self.cursor_style = new_style;
self.pending_events
.push(ScreenEvent::CursorStyleChanged(new_style));
}
}
pub(crate) fn decrqm(&mut self, mode: u16) {
let status = match mode {
1 => self.decrqm_status(MODE_APPLICATION_CURSOR),
5 => self.decrqm_status(MODE_REVERSE_VIDEO),
6 => {
if self.grid().is_origin_mode() {
ModeStatus::Set
} else {
ModeStatus::Reset
}
}
7 => self.decrqm_status(MODE_LINE_WRAP),
9 => self.decrqm_status(MODE_MOUSE_X10),
25 => {
if self.has_mode_bit(MODE_HIDE_CURSOR) {
ModeStatus::Reset
} else {
ModeStatus::Set
}
}
47 | 1047 | 1049 => self.decrqm_status(MODE_ALTERNATE_SCREEN),
67 => self.decrqm_status(MODE_BACKSPACE_BS),
1000 => self.decrqm_status(MODE_MOUSE_REPORT_CLICK),
1002 => self.decrqm_status(MODE_MOUSE_REPORT_CELL_MOTION),
1003 => self.decrqm_status(MODE_MOUSE_REPORT_ALL_MOTION),
1004 => self.decrqm_status(MODE_FOCUS_IN_OUT),
1006 => self.decrqm_status(MODE_SGR_MOUSE),
1016 => self.decrqm_status(MODE_SGR_PIXEL_MOUSE),
2004 => self.decrqm_status(MODE_BRACKETED_PASTE),
2026 => self.decrqm_status(MODE_SYNC_UPDATE),
2027 => self.decrqm_status(MODE_GRAPHEME_CLUSTER),
2031 => self.decrqm_status(MODE_COLOR_SCHEME_UPDATES),
2048 => self.decrqm_status(MODE_IN_BAND_RESIZE),
_ => ModeStatus::NotRecognized,
};
self.pending_events
.push(ScreenEvent::Decrqm { mode, status });
}
fn decrqm_status(&self, flag: u32) -> ModeStatus {
if self.has_mode_bit(flag) {
ModeStatus::Set
} else {
ModeStatus::Reset
}
}
pub(crate) fn ansi_mode_report(&mut self, mode: u16) {
let status = match mode {
4 => {
if self.has_mode_bit(MODE_INSERT) {
ModeStatus::Set
} else {
ModeStatus::Reset
}
}
20 => {
if self.has_mode_bit(MODE_LNM) {
ModeStatus::Set
} else {
ModeStatus::Reset
}
}
_ => ModeStatus::NotRecognized,
};
self.pending_events
.push(ScreenEvent::AnsiModeReport { mode, status });
}
pub(crate) fn decrqss(&mut self, query: &[u8]) {
let response: Option<Vec<u8>> = match query {
b"m" => {
let sgr = self
.attrs
.to_escape_sequence(&crate::attrs::Attrs::default());
let params: &[u8] = if sgr.is_empty() {
b"0"
} else {
&sgr[2..sgr.len() - 1]
};
let mut payload: Vec<u8> = Vec::with_capacity(params.len() + 1);
payload.extend_from_slice(params);
payload.push(b'm');
Some(payload)
}
b" q" => {
let n = match self.cursor_style {
CursorStyle::Default => 0,
CursorStyle::BlinkingBlock => 1,
CursorStyle::SteadyBlock => 2,
CursorStyle::BlinkingUnderline => 3,
CursorStyle::SteadyUnderline => 4,
CursorStyle::BlinkingBar => 5,
CursorStyle::SteadyBar => 6,
};
Some(format!("{n} q").into_bytes())
}
_ => None,
};
self.pending_events.push(ScreenEvent::Decrqss {
query: query.to_vec(),
response,
});
}
pub(crate) fn xtwinops(&mut self, params: &vte::Params) {
let p = params.iter().map(|s| s[0]).collect::<Vec<_>>();
match p.as_slice() {
[14] if self.pixel_cell_size.width > 0 && self.pixel_cell_size.height > 0 => {
let size = self.grid.size();
let height_pixels = u32::from(size.rows) * u32::from(self.pixel_cell_size.height);
let width_pixels = u32::from(size.cols) * u32::from(self.pixel_cell_size.width);
self.pending_events.push(ScreenEvent::XtWinOpsReport(
XtWinOpsReport::TextAreaPixels {
height_pixels,
width_pixels,
},
));
}
[16] if self.pixel_cell_size.width > 0 && self.pixel_cell_size.height > 0 => {
self.pending_events
.push(ScreenEvent::XtWinOpsReport(XtWinOpsReport::CellPixels {
height: self.pixel_cell_size.height,
width: self.pixel_cell_size.width,
}));
}
[18] => {
let size = self.grid.size();
self.pending_events.push(ScreenEvent::XtWinOpsReport(
XtWinOpsReport::TextAreaCells {
rows: size.rows,
cols: size.cols,
},
));
}
[22] | [22, 0] => self.title_stack_push(TitleStackTarget::Both),
[22, 1] => self.title_stack_push(TitleStackTarget::Icon),
[22, 2] => self.title_stack_push(TitleStackTarget::Window),
[23] | [23, 0] => self.title_stack_pop(TitleStackTarget::Both),
[23, 1] => self.title_stack_pop(TitleStackTarget::Icon),
[23, 2] => self.title_stack_pop(TitleStackTarget::Window),
_ => {}
}
}
fn title_stack_push(&mut self, target: TitleStackTarget) {
let cap = usize::from(self.host_profile.title_stack_depth);
if cap == 0 {
return;
}
if self.title_stack.len() >= cap {
self.title_stack.remove(0);
}
let entry = match target {
TitleStackTarget::Both => TitleStackEntry {
title: self.title.clone(),
icon: self.icon_name.clone(),
},
TitleStackTarget::Window => TitleStackEntry {
title: self.title.clone(),
icon: String::new(),
},
TitleStackTarget::Icon => TitleStackEntry {
title: String::new(),
icon: self.icon_name.clone(),
},
};
self.title_stack.push(entry);
}
fn title_stack_pop(&mut self, target: TitleStackTarget) {
let Some(entry) = self.title_stack.pop() else {
return;
};
let mut changed = false;
if matches!(target, TitleStackTarget::Both | TitleStackTarget::Window)
&& self.title != entry.title
{
self.title = entry.title;
changed = true;
}
if matches!(target, TitleStackTarget::Both | TitleStackTarget::Icon)
&& self.icon_name != entry.icon
{
self.icon_name = entry.icon;
changed = true;
}
if changed {
self.pending_events.push(ScreenEvent::TitleChanged);
}
}
pub(crate) fn xtgettcap(&mut self, data: &[u8]) {
let Ok(data_str) = std::str::from_utf8(data) else {
return;
};
let entries: Vec<XtGetTcapEntry> = data_str
.split(';')
.map(|key_hex| {
let key_hex_upper = key_hex.to_ascii_uppercase();
let value: Option<Vec<u8>> = if xtgettcap_is_tn(&key_hex_upper) {
Some(self.host_profile.terminfo_name.as_bytes().to_vec())
} else {
xtgettcap_lookup(&key_hex_upper).map(|v| v.to_vec())
};
let value_hex = value.map(|bytes| {
let mut hex = String::with_capacity(bytes.len() * 2);
for b in &bytes {
hex.push_str(&format!("{b:02X}"));
}
hex
});
XtGetTcapEntry {
key_hex: key_hex_upper,
value_hex,
}
})
.collect();
self.pending_events.push(ScreenEvent::XtGetTcap { entries });
}
pub(crate) fn sm(&mut self, params: &vte::Params) {
for param in params {
match param {
[4] => self.set_mode(MODE_INSERT),
[20] => self.set_mode_with_event(MODE_LNM, true),
_ => {}
}
}
}
pub(crate) fn rm(&mut self, params: &vte::Params) {
for param in params {
match param {
[4] => self.clear_mode(MODE_INSERT),
[20] => self.set_mode_with_event(MODE_LNM, false),
_ => {}
}
}
}
fn deccolm_side_effects(&mut self) {
let attrs = self.attrs;
let size = self.grid().size();
self.grid_mut().erase_all(attrs);
self.grid_mut().set_pos(crate::grid::Pos { row: 0, col: 0 });
self.grid_mut().set_scroll_region(0, size.rows - 1);
self.pending_events.push(ScreenEvent::ScreenCleared);
}
pub(crate) fn decset(&mut self, params: &vte::Params) {
for param in params {
match param {
[1] => self.set_mode(MODE_APPLICATION_CURSOR),
[3] if self.has_mode_bit(MODE_DEC_ALLOW_80_132)
&& !self.has_mode_bit(MODE_DECCOLM) =>
{
self.set_mode(MODE_DECCOLM);
self.deccolm_side_effects();
}
[5] => self.set_mode_with_event(MODE_REVERSE_VIDEO, true),
[6] => self.grid_mut().set_origin_mode(true),
[7] => self.set_mode(MODE_LINE_WRAP),
[9] => self.set_mode_with_event(MODE_MOUSE_X10, true),
[25] => self.clear_mode(MODE_HIDE_CURSOR),
[40] => self.set_mode(MODE_DEC_ALLOW_80_132),
[47] => self.enter_alternate_grid(),
[67] => self.set_mode_with_event(MODE_BACKSPACE_BS, true),
[1047] => self.enter_alternate_grid(),
[1048] => self.decsc(),
[1049] => {
self.decsc();
self.alternate_grid.clear();
self.enter_alternate_grid();
}
[1000] => self.set_mode_with_event(MODE_MOUSE_REPORT_CLICK, true),
[1002] => self.set_mode_with_event(MODE_MOUSE_REPORT_CELL_MOTION, true),
[1003] => self.set_mode_with_event(MODE_MOUSE_REPORT_ALL_MOTION, true),
[1004] => self.set_mode_with_event(MODE_FOCUS_IN_OUT, true),
[1006] => self.set_mode_with_event(MODE_SGR_MOUSE, true),
[1007] => self.set_mode_with_event(MODE_ALTERNATE_SCROLL, true),
[1016] => self.set_mode_with_event(MODE_SGR_PIXEL_MOUSE, true),
[2004] => self.set_mode_with_event(MODE_BRACKETED_PASTE, true),
[2026] => self.set_mode(MODE_SYNC_UPDATE),
[2027] => self.set_mode(MODE_GRAPHEME_CLUSTER),
[2031] => self.set_mode_with_event(MODE_COLOR_SCHEME_UPDATES, true),
[2048] => self.set_mode_with_event(MODE_IN_BAND_RESIZE, true),
_ => {}
}
}
}
pub(crate) fn decrst(&mut self, params: &vte::Params) {
for param in params {
match param {
[1] => self.clear_mode(MODE_APPLICATION_CURSOR),
[3] if self.has_mode_bit(MODE_DEC_ALLOW_80_132)
&& self.has_mode_bit(MODE_DECCOLM) =>
{
self.clear_mode(MODE_DECCOLM);
self.deccolm_side_effects();
}
[5] => self.set_mode_with_event(MODE_REVERSE_VIDEO, false),
[6] => self.grid_mut().set_origin_mode(false),
[7] => self.clear_mode(MODE_LINE_WRAP),
[9] => self.set_mode_with_event(MODE_MOUSE_X10, false),
[25] => self.set_mode(MODE_HIDE_CURSOR),
[40] => self.clear_mode(MODE_DEC_ALLOW_80_132),
[47] => {
self.exit_alternate_grid();
}
[67] => self.set_mode_with_event(MODE_BACKSPACE_BS, false),
[1047] => {
self.alternate_grid.clear();
self.exit_alternate_grid();
}
[1048] => self.decrc(),
[1049] => {
self.exit_alternate_grid();
self.decrc();
}
[1000] => self.set_mode_with_event(MODE_MOUSE_REPORT_CLICK, false),
[1002] => self.set_mode_with_event(MODE_MOUSE_REPORT_CELL_MOTION, false),
[1003] => self.set_mode_with_event(MODE_MOUSE_REPORT_ALL_MOTION, false),
[1004] => self.set_mode_with_event(MODE_FOCUS_IN_OUT, false),
[1006] => self.set_mode_with_event(MODE_SGR_MOUSE, false),
[1007] => self.set_mode_with_event(MODE_ALTERNATE_SCROLL, false),
[1016] => self.set_mode_with_event(MODE_SGR_PIXEL_MOUSE, false),
[2004] => self.set_mode_with_event(MODE_BRACKETED_PASTE, false),
[2026] => self.clear_mode(MODE_SYNC_UPDATE),
[2027] => {
self.flush_print_buffer();
self.clear_mode(MODE_GRAPHEME_CLUSTER);
}
[2031] => self.set_mode_with_event(MODE_COLOR_SCHEME_UPDATES, false),
[2048] => self.set_mode_with_event(MODE_IN_BAND_RESIZE, false),
_ => {}
}
}
}
pub(crate) fn xtsave(&mut self, params: &vte::Params) {
let bits = collect_dec_save_bits(params);
self.saved_modes = (self.saved_modes & !bits) | (self.modes & bits);
self.saved_modes_mask |= bits;
}
pub(crate) fn xtrestore(&mut self, params: &vte::Params) {
let bits = collect_dec_save_bits(params);
let mut remaining = bits & self.saved_modes_mask;
while remaining != 0 {
let bit = remaining & remaining.wrapping_neg();
let enabled = self.saved_modes & bit != 0;
self.set_mode_with_event(bit, enabled);
remaining &= !bit;
}
}
pub(crate) fn sgr(&mut self, params: &vte::Params) {
if params.is_empty() {
self.attrs = crate::attrs::Attrs::default();
return;
}
let mut iter = params.iter();
macro_rules! next_param {
() => {
match iter.next() {
Some(n) => n,
_ => return,
}
};
}
macro_rules! to_u8 {
($n:expr) => {
if let Some(n) = u16_to_u8($n) {
n
} else {
return;
}
};
}
macro_rules! next_param_u8 {
() => {
if let &[n] = next_param!() {
to_u8!(n)
} else {
return;
}
};
}
loop {
match next_param!() {
[0] => self.attrs = crate::attrs::Attrs::default(),
[1] => self.attrs.set_bold(),
[2] => self.attrs.set_dim(),
[3] => self.attrs.set_italic(true),
[4] => self.attrs.set_underline(true),
[4, sub] => {
self.attrs
.set_underline_style(crate::attrs::UnderlineStyle::from_sgr(*sub));
}
[5] => self.attrs.set_blink(true),
[7] => self.attrs.set_inverse(true),
[8] => self.attrs.set_hidden(true),
[9] => self.attrs.set_strikethrough(true),
[22] => self.attrs.set_normal_intensity(),
[23] => self.attrs.set_italic(false),
[21] => self
.attrs
.set_underline_style(crate::attrs::UnderlineStyle::Double),
[24] => self.attrs.set_underline(false),
[25] => self.attrs.set_blink(false),
[27] => self.attrs.set_inverse(false),
[28] => self.attrs.set_hidden(false),
[29] => self.attrs.set_strikethrough(false),
[n] if (30..=37).contains(n) => {
self.attrs.fg_color = crate::attrs::Color::Index(to_u8!(*n) - 30);
}
[38, 2, r, g, b] => {
self.attrs.fg_color =
crate::attrs::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
}
[38, 2, _, r, g, b] => {
self.attrs.fg_color =
crate::attrs::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
}
[38, 5, i] => {
self.attrs.fg_color = crate::attrs::Color::Index(to_u8!(*i));
}
[38] => match next_param!() {
[2] => {
let r = next_param_u8!();
let g = next_param_u8!();
let b = next_param_u8!();
self.attrs.fg_color = crate::attrs::Color::Rgb(r, g, b);
}
[5] => {
self.attrs.fg_color = crate::attrs::Color::Index(next_param_u8!());
}
_ => return,
},
[39] => {
self.attrs.fg_color = crate::attrs::Color::Default;
}
[n] if (40..=47).contains(n) => {
self.attrs.bg_color = crate::attrs::Color::Index(to_u8!(*n) - 40);
}
[48, 2, r, g, b] => {
self.attrs.bg_color =
crate::attrs::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
}
[48, 2, _, r, g, b] => {
self.attrs.bg_color =
crate::attrs::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
}
[48, 5, i] => {
self.attrs.bg_color = crate::attrs::Color::Index(to_u8!(*i));
}
[48] => match next_param!() {
[2] => {
let r = next_param_u8!();
let g = next_param_u8!();
let b = next_param_u8!();
self.attrs.bg_color = crate::attrs::Color::Rgb(r, g, b);
}
[5] => {
self.attrs.bg_color = crate::attrs::Color::Index(next_param_u8!());
}
_ => return,
},
[49] => {
self.attrs.bg_color = crate::attrs::Color::Default;
}
[58, 2, r, g, b] => {
self.attrs.underline_color =
crate::attrs::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
}
[58, 2, _, r, g, b] => {
self.attrs.underline_color =
crate::attrs::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
}
[58, 5, i] => {
self.attrs.underline_color = crate::attrs::Color::Index(to_u8!(*i));
}
[58] => match next_param!() {
[2] => {
let r = next_param_u8!();
let g = next_param_u8!();
let b = next_param_u8!();
self.attrs.underline_color = crate::attrs::Color::Rgb(r, g, b);
}
[5] => {
self.attrs.underline_color = crate::attrs::Color::Index(next_param_u8!());
}
_ => return,
},
[59] => {
self.attrs.underline_color = crate::attrs::Color::Default;
}
[n] if (90..=97).contains(n) => {
self.attrs.fg_color = crate::attrs::Color::Index(to_u8!(*n) - 82);
}
[53] => self.attrs.set_overline(true),
[55] => self.attrs.set_overline(false),
[n] if (100..=107).contains(n) => {
self.attrs.bg_color = crate::attrs::Color::Index(to_u8!(*n) - 92);
}
_ => {}
}
}
}
pub(crate) fn decstbm(&mut self, (top, bottom): (u16, u16)) {
self.grid_mut().set_scroll_region(top - 1, bottom - 1);
}
pub(crate) fn osc(&mut self, params: &[&[u8]]) {
if params.is_empty() {
return;
}
let total_len: usize = params.iter().map(|p| p.len()).sum();
let is_clipboard_class = matches!(params[0], b"52" | b"66" | b"5522");
let cap = if is_clipboard_class {
self.host_profile.osc_max_bytes_clipboard
} else {
self.host_profile.osc_max_bytes_normal
};
if total_len > cap {
if params[0] == b"52" {
let pc = params.get(1).copied().unwrap_or(b"");
let pd = params.get(2).copied().unwrap_or(b"");
let targets = osc::clipboard::parse_targets(pc);
self.pending_events
.push(ScreenEvent::ClipboardWriteRejected {
targets,
decoded_len: pd.len().saturating_mul(3) / 4,
});
}
return;
}
match params[0] {
b"0" => osc::title::handle_set_title_and_icon(self, params),
b"1" => osc::title::handle_set_icon_name(self, params),
b"2" => osc::title::handle_set_window_title(self, params),
b"4" => osc::color::handle_palette_set(self, params),
b"7" => osc::cwd::handle_set(self, params),
b"8" => osc::hyperlink::handle_set(self, params),
b"10" | b"11" | b"12" => osc::color::handle_dynamic_set(self, params),
b"104" => osc::color::handle_palette_reset(self, params),
b"110" => osc::color::handle_foreground_reset(self),
b"111" => osc::color::handle_background_reset(self),
b"112" => osc::color::handle_cursor_color_reset(self),
b"133" => osc::prompt::handle(self, params),
b"22" => osc::pointer::handle_set_shape(self, params),
b"9" => osc::notification::handle_osc9(self, params),
b"777" => osc::notification::handle_osc777(self, params),
b"52" => osc::clipboard::handle_set(self, params),
_ => {}
}
}
}