diff options
Diffstat (limited to 'vendor/vt100/src/screen.rs')
| -rw-r--r-- | vendor/vt100/src/screen.rs | 1356 |
1 files changed, 1356 insertions, 0 deletions
diff --git a/vendor/vt100/src/screen.rs b/vendor/vt100/src/screen.rs new file mode 100644 index 00000000..a11ed776 --- /dev/null +++ b/vendor/vt100/src/screen.rs @@ -0,0 +1,1356 @@ +use crate::term::BufWrite as _; +use unicode_width::UnicodeWidthChar as _; + +const MODE_APPLICATION_KEYPAD: u8 = 0b0000_0001; +const MODE_APPLICATION_CURSOR: u8 = 0b0000_0010; +const MODE_HIDE_CURSOR: u8 = 0b0000_0100; +const MODE_ALTERNATE_SCREEN: u8 = 0b0000_1000; +const MODE_BRACKETED_PASTE: u8 = 0b0001_0000; + +/// The xterm mouse handling mode currently in use. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum MouseProtocolMode { + /// Mouse handling is disabled. + #[default] + None, + + /// Mouse button events should be reported on button press. Also known as + /// X10 mouse mode. + Press, + + /// Mouse button events should be reported on button press and release. + /// Also known as VT200 mouse mode. + PressRelease, + + // Highlight, + /// Mouse button events should be reported on button press and release, as + /// well as when the mouse moves between cells while a button is held + /// down. + ButtonMotion, + + /// Mouse button events should be reported on button press and release, + /// and mouse motion events should be reported when the mouse moves + /// between cells regardless of whether a button is held down or not. + AnyMotion, + // DecLocator, +} + +/// The encoding to use for the enabled [`MouseProtocolMode`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub enum MouseProtocolEncoding { + /// Default single-printable-byte encoding. + #[default] + Default, + + /// UTF-8-based encoding. + Utf8, + + /// SGR-like encoding. + Sgr, + // Urxvt, +} + +/// Represents the overall terminal state. +#[derive(Clone, Debug)] +pub struct Screen { + grid: crate::grid::Grid, + alternate_grid: crate::grid::Grid, + + attrs: crate::attrs::Attrs, + saved_attrs: crate::attrs::Attrs, + + modes: u8, + mouse_protocol_mode: MouseProtocolMode, + mouse_protocol_encoding: MouseProtocolEncoding, +} + +impl Screen { + pub(crate) fn new( + size: crate::grid::Size, + scrollback_len: usize, + ) -> Self { + let mut grid = crate::grid::Grid::new(size, scrollback_len); + grid.allocate_rows(); + Self { + grid, + alternate_grid: crate::grid::Grid::new(size, scrollback_len), + + attrs: crate::attrs::Attrs::default(), + saved_attrs: crate::attrs::Attrs::default(), + + modes: 0, + mouse_protocol_mode: MouseProtocolMode::default(), + mouse_protocol_encoding: MouseProtocolEncoding::default(), + } + } + + /// Resizes the terminal. + pub fn set_size(&mut self, rows: u16, cols: u16) { + self.grid.set_size(crate::grid::Size { rows, cols }); + self.alternate_grid + .set_size(crate::grid::Size { rows, cols }); + } + + /// Returns the current size of the terminal. + /// + /// The return value will be (rows, cols). + #[must_use] + pub fn size(&self) -> (u16, u16) { + let size = self.grid().size(); + (size.rows, size.cols) + } + + /// Scrolls to the given position in the scrollback. + /// + /// This position indicates the offset from the top of the screen, and + /// should be `0` to put the normal screen in view. + /// + /// This affects the return values of methods called on the screen: for + /// instance, `screen.cell(0, 0)` will return the top left corner of the + /// screen after taking the scrollback offset into account. + /// + /// The value given will be clamped to the actual size of the scrollback. + pub fn set_scrollback(&mut self, rows: usize) { + self.grid_mut().set_scrollback(rows); + } + + /// Returns the current position in the scrollback. + /// + /// This position indicates the offset from the top of the screen, and is + /// `0` when the normal screen is in view. + #[must_use] + pub fn scrollback(&self) -> usize { + self.grid().scrollback() + } + + /// Returns the text contents of the terminal. + /// + /// This will not include any formatting information, and will be in plain + /// text format. + #[must_use] + pub fn contents(&self) -> String { + let mut contents = String::new(); + self.write_contents(&mut contents); + contents + } + + fn write_contents(&self, contents: &mut String) { + self.grid().write_contents(contents); + } + + /// Returns the text contents of the terminal by row, restricted to the + /// given subset of columns. + /// + /// This will not include any formatting information, and will be in plain + /// text format. + /// + /// Newlines will not be included. + pub fn rows( + &self, + start: u16, + width: u16, + ) -> impl Iterator<Item = String> + '_ { + self.grid().visible_rows().map(move |row| { + let mut contents = String::new(); + row.write_contents(&mut contents, start, width, false); + contents + }) + } + + /// Returns the text contents of the terminal logically between two cells. + /// This will include the remainder of the starting row after `start_col`, + /// followed by the entire contents of the rows between `start_row` and + /// `end_row`, followed by the beginning of the `end_row` up until + /// `end_col`. This is useful for things like determining the contents of + /// a clipboard selection. + #[must_use] + pub fn contents_between( + &self, + start_row: u16, + start_col: u16, + end_row: u16, + end_col: u16, + ) -> String { + match start_row.cmp(&end_row) { + std::cmp::Ordering::Less => { + let (_, cols) = self.size(); + let mut contents = String::new(); + for (i, row) in self + .grid() + .visible_rows() + .enumerate() + .skip(usize::from(start_row)) + .take(usize::from(end_row) - usize::from(start_row) + 1) + { + if i == usize::from(start_row) { + row.write_contents( + &mut contents, + start_col, + cols - start_col, + false, + ); + if !row.wrapped() { + contents.push('\n'); + } + } else if i == usize::from(end_row) { + row.write_contents(&mut contents, 0, end_col, false); + } else { + row.write_contents(&mut contents, 0, cols, false); + if !row.wrapped() { + contents.push('\n'); + } + } + } + contents + } + std::cmp::Ordering::Equal => { + if start_col < end_col { + self.rows(start_col, end_col - start_col) + .nth(usize::from(start_row)) + .unwrap_or_default() + } else { + String::new() + } + } + std::cmp::Ordering::Greater => String::new(), + } + } + + /// Return escape codes sufficient to reproduce the entire contents of the + /// current terminal state. This is a convenience wrapper around + /// [`contents_formatted`](Self::contents_formatted) and + /// [`input_mode_formatted`](Self::input_mode_formatted). + #[must_use] + pub fn state_formatted(&self) -> Vec<u8> { + let mut contents = vec![]; + self.write_contents_formatted(&mut contents); + self.write_input_mode_formatted(&mut contents); + contents + } + + /// Return escape codes sufficient to turn the terminal state of the + /// screen `prev` into the current terminal state. This is a convenience + /// wrapper around [`contents_diff`](Self::contents_diff) and + /// [`input_mode_diff`](Self::input_mode_diff). + #[must_use] + pub fn state_diff(&self, prev: &Self) -> Vec<u8> { + let mut contents = vec![]; + self.write_contents_diff(&mut contents, prev); + self.write_input_mode_diff(&mut contents, prev); + contents + } + + /// Returns the formatted visible contents of the terminal. + /// + /// Formatting information will be included inline as terminal escape + /// codes. The result will be suitable for feeding directly to a raw + /// terminal parser, and will result in the same visual output. + #[must_use] + pub fn contents_formatted(&self) -> Vec<u8> { + let mut contents = vec![]; + self.write_contents_formatted(&mut contents); + contents + } + + fn write_contents_formatted(&self, contents: &mut Vec<u8>) { + crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents); + let prev_attrs = self.grid().write_contents_formatted(contents); + self.attrs.write_escape_code_diff(contents, &prev_attrs); + } + + /// Returns the formatted visible contents of the terminal by row, + /// restricted to the given subset of columns. + /// + /// Formatting information will be included inline as terminal escape + /// codes. The result will be suitable for feeding directly to a raw + /// terminal parser, and will result in the same visual output. + /// + /// You are responsible for positioning the cursor before printing each + /// row, and the final cursor position after displaying each row is + /// unspecified. + // the unwraps in this method shouldn't be reachable + #[allow(clippy::missing_panics_doc)] + pub fn rows_formatted( + &self, + start: u16, + width: u16, + ) -> impl Iterator<Item = Vec<u8>> + '_ { + let mut wrapping = false; + self.grid().visible_rows().enumerate().map(move |(i, row)| { + // number of rows in a grid is stored in a u16 (see Size), so + // visible_rows can never return enough rows to overflow here + let i = i.try_into().unwrap(); + let mut contents = vec![]; + row.write_contents_formatted( + &mut contents, + start, + width, + i, + wrapping, + None, + None, + ); + if start == 0 && width == self.grid.size().cols { + wrapping = row.wrapped(); + } + contents + }) + } + + /// Returns a terminal byte stream sufficient to turn the visible contents + /// of the screen described by `prev` into the visible contents of the + /// screen described by `self`. + /// + /// The result of rendering `prev.contents_formatted()` followed by + /// `self.contents_diff(prev)` should be equivalent to the result of + /// rendering `self.contents_formatted()`. This is primarily useful when + /// you already have a terminal parser whose state is described by `prev`, + /// since the diff will likely require less memory and cause less + /// flickering than redrawing the entire screen contents. + #[must_use] + pub fn contents_diff(&self, prev: &Self) -> Vec<u8> { + let mut contents = vec![]; + self.write_contents_diff(&mut contents, prev); + contents + } + + fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) { + if self.hide_cursor() != prev.hide_cursor() { + crate::term::HideCursor::new(self.hide_cursor()) + .write_buf(contents); + } + let prev_attrs = self.grid().write_contents_diff( + contents, + prev.grid(), + prev.attrs, + ); + self.attrs.write_escape_code_diff(contents, &prev_attrs); + } + + /// Returns a sequence of terminal byte streams sufficient to turn the + /// visible contents of the subset of each row from `prev` (as described + /// by `start` and `width`) into the visible contents of the corresponding + /// row subset in `self`. + /// + /// You are responsible for positioning the cursor before printing each + /// row, and the final cursor position after displaying each row is + /// unspecified. + // the unwraps in this method shouldn't be reachable + #[allow(clippy::missing_panics_doc)] + pub fn rows_diff<'a>( + &'a self, + prev: &'a Self, + start: u16, + width: u16, + ) -> impl Iterator<Item = Vec<u8>> + 'a { + self.grid() + .visible_rows() + .zip(prev.grid().visible_rows()) + .enumerate() + .map(move |(i, (row, prev_row))| { + // number of rows in a grid is stored in a u16 (see Size), so + // visible_rows can never return enough rows to overflow here + let i = i.try_into().unwrap(); + let mut contents = vec![]; + row.write_contents_diff( + &mut contents, + prev_row, + start, + width, + i, + false, + false, + crate::grid::Pos { row: i, col: start }, + crate::attrs::Attrs::default(), + ); + contents + }) + } + + /// Returns terminal escape sequences sufficient to set the current + /// terminal's input modes. + /// + /// Supported modes are: + /// * application keypad + /// * application cursor + /// * bracketed paste + /// * xterm mouse support + #[must_use] + pub fn input_mode_formatted(&self) -> Vec<u8> { + let mut contents = vec![]; + self.write_input_mode_formatted(&mut contents); + contents + } + + fn write_input_mode_formatted(&self, contents: &mut Vec<u8>) { + crate::term::ApplicationKeypad::new( + self.mode(MODE_APPLICATION_KEYPAD), + ) + .write_buf(contents); + crate::term::ApplicationCursor::new( + self.mode(MODE_APPLICATION_CURSOR), + ) + .write_buf(contents); + crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE)) + .write_buf(contents); + crate::term::MouseProtocolMode::new( + self.mouse_protocol_mode, + MouseProtocolMode::None, + ) + .write_buf(contents); + crate::term::MouseProtocolEncoding::new( + self.mouse_protocol_encoding, + MouseProtocolEncoding::Default, + ) + .write_buf(contents); + } + + /// Returns terminal escape sequences sufficient to change the previous + /// terminal's input modes to the input modes enabled in the current + /// terminal. + #[must_use] + pub fn input_mode_diff(&self, prev: &Self) -> Vec<u8> { + let mut contents = vec![]; + self.write_input_mode_diff(&mut contents, prev); + contents + } + + fn write_input_mode_diff(&self, contents: &mut Vec<u8>, prev: &Self) { + if self.mode(MODE_APPLICATION_KEYPAD) + != prev.mode(MODE_APPLICATION_KEYPAD) + { + crate::term::ApplicationKeypad::new( + self.mode(MODE_APPLICATION_KEYPAD), + ) + .write_buf(contents); + } + if self.mode(MODE_APPLICATION_CURSOR) + != prev.mode(MODE_APPLICATION_CURSOR) + { + crate::term::ApplicationCursor::new( + self.mode(MODE_APPLICATION_CURSOR), + ) + .write_buf(contents); + } + if self.mode(MODE_BRACKETED_PASTE) != prev.mode(MODE_BRACKETED_PASTE) + { + crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE)) + .write_buf(contents); + } + crate::term::MouseProtocolMode::new( + self.mouse_protocol_mode, + prev.mouse_protocol_mode, + ) + .write_buf(contents); + crate::term::MouseProtocolEncoding::new( + self.mouse_protocol_encoding, + prev.mouse_protocol_encoding, + ) + .write_buf(contents); + } + + /// Returns terminal escape sequences sufficient to set the current + /// terminal's drawing attributes. + /// + /// Supported drawing attributes are: + /// * fgcolor + /// * bgcolor + /// * bold + /// * dim + /// * italic + /// * underline + /// * inverse + /// + /// This is not typically necessary, since + /// [`contents_formatted`](Self::contents_formatted) will leave + /// the current active drawing attributes in the correct state, but this + /// can be useful in the case of drawing additional things on top of a + /// terminal output, since you will need to restore the terminal state + /// without the terminal contents necessarily being the same. + #[must_use] + pub fn attributes_formatted(&self) -> Vec<u8> { + let mut contents = vec![]; + self.write_attributes_formatted(&mut contents); + contents + } + + fn write_attributes_formatted(&self, contents: &mut Vec<u8>) { + crate::term::ClearAttrs.write_buf(contents); + self.attrs.write_escape_code_diff( + contents, + &crate::attrs::Attrs::default(), + ); + } + + /// Returns the current cursor position of the terminal. + /// + /// The return value will be (row, col). + #[must_use] + pub fn cursor_position(&self) -> (u16, u16) { + let pos = self.grid().pos(); + (pos.row, pos.col) + } + + /// Returns terminal escape sequences sufficient to set the current + /// cursor state of the terminal. + /// + /// This is not typically necessary, since + /// [`contents_formatted`](Self::contents_formatted) will leave + /// the cursor in the correct state, but this can be useful in the case of + /// drawing additional things on top of a terminal output, since you will + /// need to restore the terminal state without the terminal contents + /// necessarily being the same. + /// + /// Note that the bytes returned by this function may alter the active + /// drawing attributes, because it may require redrawing existing cells in + /// order to position the cursor correctly (for instance, in the case + /// where the cursor is past the end of a row). Therefore, you should + /// ensure to reset the active drawing attributes if necessary after + /// processing this data, for instance by using + /// [`attributes_formatted`](Self::attributes_formatted). + #[must_use] + pub fn cursor_state_formatted(&self) -> Vec<u8> { + let mut contents = vec![]; + self.write_cursor_state_formatted(&mut contents); + contents + } + + fn write_cursor_state_formatted(&self, contents: &mut Vec<u8>) { + crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents); + self.grid() + .write_cursor_position_formatted(contents, None, None); + + // we don't just call write_attributes_formatted here, because that + // would still be confusing - consider the case where the user sets + // their own unrelated drawing attributes (on a different parser + // instance) and then calls cursor_state_formatted. just documenting + // it and letting the user handle it on their own is more + // straightforward. + } + + /// Returns the [`Cell`](crate::Cell) object at the given location in the + /// terminal, if it exists. + #[must_use] + pub fn cell(&self, row: u16, col: u16) -> Option<&crate::Cell> { + self.grid().visible_cell(crate::grid::Pos { row, col }) + } + + /// Returns whether the text in row `row` should wrap to the next line. + #[must_use] + pub fn row_wrapped(&self, row: u16) -> bool { + self.grid() + .visible_row(row) + .is_some_and(crate::row::Row::wrapped) + } + + /// Returns whether the alternate screen is currently in use. + #[must_use] + pub fn alternate_screen(&self) -> bool { + self.mode(MODE_ALTERNATE_SCREEN) + } + + /// Returns whether the terminal should be in application keypad mode. + #[must_use] + pub fn application_keypad(&self) -> bool { + self.mode(MODE_APPLICATION_KEYPAD) + } + + /// Returns whether the terminal should be in application cursor mode. + #[must_use] + pub fn application_cursor(&self) -> bool { + self.mode(MODE_APPLICATION_CURSOR) + } + + /// Returns whether the terminal should be in hide cursor mode. + #[must_use] + pub fn hide_cursor(&self) -> bool { + self.mode(MODE_HIDE_CURSOR) + } + + /// Returns whether the terminal should be in bracketed paste mode. + #[must_use] + pub fn bracketed_paste(&self) -> bool { + self.mode(MODE_BRACKETED_PASTE) + } + + /// Returns the currently active [`MouseProtocolMode`]. + #[must_use] + pub fn mouse_protocol_mode(&self) -> MouseProtocolMode { + self.mouse_protocol_mode + } + + /// Returns the currently active [`MouseProtocolEncoding`]. + #[must_use] + pub fn mouse_protocol_encoding(&self) -> MouseProtocolEncoding { + self.mouse_protocol_encoding + } + + /// Returns the currently active foreground color. + #[must_use] + pub fn fgcolor(&self) -> crate::Color { + self.attrs.fgcolor + } + + /// Returns the currently active background color. + #[must_use] + pub fn bgcolor(&self) -> crate::Color { + self.attrs.bgcolor + } + + /// Returns whether newly drawn text should be rendered with the bold text + /// attribute. + #[must_use] + pub fn bold(&self) -> bool { + self.attrs.bold() + } + + /// Returns whether newly drawn text should be rendered with the dim text + /// attribute. + #[must_use] + pub fn dim(&self) -> bool { + self.attrs.dim() + } + + /// Returns whether newly drawn text should be rendered with the italic + /// text attribute. + #[must_use] + pub fn italic(&self) -> bool { + self.attrs.italic() + } + + /// Returns whether newly drawn text should be rendered with the + /// underlined text attribute. + #[must_use] + pub fn underline(&self) -> bool { + self.attrs.underline() + } + + /// Returns whether newly drawn text should be rendered with the inverse + /// text attribute. + #[must_use] + pub fn inverse(&self) -> bool { + self.attrs.inverse() + } + + pub(crate) fn grid(&self) -> &crate::grid::Grid { + if self.mode(MODE_ALTERNATE_SCREEN) { + &self.alternate_grid + } else { + &self.grid + } + } + + fn grid_mut(&mut self) -> &mut crate::grid::Grid { + if self.mode(MODE_ALTERNATE_SCREEN) { + &mut self.alternate_grid + } else { + &mut self.grid + } + } + + fn enter_alternate_grid(&mut self) { + self.grid_mut().set_scrollback(0); + self.set_mode(MODE_ALTERNATE_SCREEN); + self.alternate_grid.allocate_rows(); + self.grid_mut().set_scrollback(0); + } + + fn exit_alternate_grid(&mut self) { + self.clear_mode(MODE_ALTERNATE_SCREEN); + } + + fn save_cursor(&mut self) { + self.grid_mut().save_cursor(); + self.saved_attrs = self.attrs; + } + + fn restore_cursor(&mut self) { + self.grid_mut().restore_cursor(); + self.attrs = self.saved_attrs; + } + + fn set_mode(&mut self, mode: u8) { + self.modes |= mode; + } + + fn clear_mode(&mut self, mode: u8) { + self.modes &= !mode; + } + + fn mode(&self, mode: u8) -> bool { + self.modes & mode != 0 + } + + fn set_mouse_mode(&mut self, mode: MouseProtocolMode) { + self.mouse_protocol_mode = mode; + } + + fn clear_mouse_mode(&mut self, mode: MouseProtocolMode) { + if self.mouse_protocol_mode == mode { + self.mouse_protocol_mode = MouseProtocolMode::default(); + } + } + + fn set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) { + self.mouse_protocol_encoding = encoding; + } + + fn clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) { + if self.mouse_protocol_encoding == encoding { + self.mouse_protocol_encoding = MouseProtocolEncoding::default(); + } + } +} + +impl Screen { + pub(crate) fn text(&mut self, c: char) { + let pos = self.grid().pos(); + let size = self.grid().size(); + let attrs = self.attrs; + + let width = c.width(); + if width.is_none() && (u32::from(c)) < 256 { + // don't even try to draw control characters + return; + } + let width = width + .unwrap_or(1) + .try_into() + // width() can only return 0, 1, or 2 + .unwrap(); + + // it doesn't make any sense to wrap if the last column in a row + // didn't already have contents. don't try to handle the case where a + // character wraps because there was only one column left in the + // previous row - literally everything handles this case differently, + // and this is tmux behavior (and also the simplest). i'm open to + // reconsidering this behavior, but only with a really good reason + // (xterm handles this by introducing the concept of triple width + // cells, which i really don't want to do). + 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, + }) + // pos.row is valid, since it comes directly from + // self.grid().pos() which we assume to always have a valid + // row value. size.cols - 1 is also always a valid column. + .unwrap(); + if last_cell.has_contents() || last_cell.is_wide_continuation() { + wrap = true; + } + } + self.grid_mut().col_wrap(width, wrap); + let pos = self.grid().pos(); + + if width == 0 { + if pos.col > 0 { + let mut prev_cell = self + .grid_mut() + .drawing_cell_mut(crate::grid::Pos { + row: pos.row, + col: pos.col - 1, + }) + // pos.row is valid, since it comes directly from + // self.grid().pos() which we assume to always have a + // valid row value. pos.col - 1 is valid because we just + // checked for pos.col > 0. + .unwrap(); + if prev_cell.is_wide_continuation() { + prev_cell = self + .grid_mut() + .drawing_cell_mut(crate::grid::Pos { + row: pos.row, + col: pos.col - 2, + }) + // pos.row is valid, since it comes directly from + // self.grid().pos() which we assume to always have a + // valid row value. we know pos.col - 2 is valid + // because the cell at pos.col - 1 is a wide + // continuation character, which means there must be + // the first half of the wide character before it. + .unwrap(); + } + prev_cell.append(c); + } else if pos.row > 0 { + let prev_row = self + .grid() + .drawing_row(pos.row - 1) + // pos.row is valid, since it comes directly from + // self.grid().pos() which we assume to always have a + // valid row value. pos.row - 1 is valid because we just + // checked for pos.row > 0. + .unwrap(); + 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, + }) + // pos.row is valid, since it comes directly from + // self.grid().pos() which we assume to always have a + // valid row value. pos.row - 1 is valid because we + // just checked for pos.row > 0. col of size.cols - 1 + // is always valid. + .unwrap(); + 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, + }) + // pos.row is valid, since it comes directly from + // self.grid().pos() which we assume to always + // have a valid row value. pos.row - 1 is valid + // because we just checked for pos.row > 0. col of + // size.cols - 2 is valid because the cell at + // size.cols - 1 is a wide continuation character, + // so it must have the first half of the wide + // character before it. + .unwrap(); + } + prev_cell.append(c); + } + } + } else { + if self + .grid() + .drawing_cell(pos) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because we + // called col_wrap() immediately before this, which ensures + // that self.grid().pos().col has a valid value. + .unwrap() + .is_wide_continuation() + { + let prev_cell = self + .grid_mut() + .drawing_cell_mut(crate::grid::Pos { + row: pos.row, + col: pos.col - 1, + }) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because + // we called col_wrap() immediately before this, which + // ensures that self.grid().pos().col has a valid value. + // pos.col - 1 is valid because the cell at pos.col is a + // wide continuation character, so it must have the first + // half of the wide character before it. + .unwrap(); + prev_cell.clear(attrs); + } + + if self + .grid() + .drawing_cell(pos) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because we + // called col_wrap() immediately before this, which ensures + // that self.grid().pos().col has a valid value. + .unwrap() + .is_wide() + { + let next_cell = self + .grid_mut() + .drawing_cell_mut(crate::grid::Pos { + row: pos.row, + col: pos.col + 1, + }) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because + // we called col_wrap() immediately before this, which + // ensures that self.grid().pos().col has a valid value. + // pos.col + 1 is valid because the cell at pos.col is a + // wide character, so it must have the second half of the + // wide character after it. + .unwrap(); + next_cell.set(' ', attrs); + } + + let cell = self + .grid_mut() + .drawing_cell_mut(pos) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because we + // called col_wrap() immediately before this, which ensures + // that self.grid().pos().col has a valid value. + .unwrap(); + cell.set(c, attrs); + self.grid_mut().col_inc(1); + if width > 1 { + let pos = self.grid().pos(); + if self + .grid() + .drawing_cell(pos) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because + // we called col_wrap() earlier, which ensures that + // self.grid().pos().col has a valid value. this is true + // even though we just called col_inc, because this branch + // only happens if width > 1, and col_wrap takes width + // into account. + .unwrap() + .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) + // pos.row is valid because we assume + // self.grid().pos() to always have a valid row value. + // pos.col is valid because we called col_wrap() + // earlier, which ensures that self.grid().pos().col + // has a valid value. this is true even though we just + // called col_inc, because this branch only happens if + // width > 1, and col_wrap takes width into account. + // pos.col + 1 is valid because the cell at pos.col is + // wide, and so it must have the second half of the + // wide character after it. + .unwrap(); + next_next_cell.clear(attrs); + if next_next_pos.col == size.cols - 1 { + self.grid_mut() + .drawing_row_mut(pos.row) + // we assume self.grid().pos().row is always valid + .unwrap() + .wrap(false); + } + } + let next_cell = self + .grid_mut() + .drawing_cell_mut(pos) + // pos.row is valid because we assume self.grid().pos() to + // always have a valid row value. pos.col is valid because + // we called col_wrap() earlier, which ensures that + // self.grid().pos().col has a valid value. this is true + // even though we just called col_inc, because this branch + // only happens if width > 1, and col_wrap takes width + // into account. + .unwrap(); + next_cell.clear(crate::attrs::Attrs::default()); + next_cell.set_wide_continuation(true); + self.grid_mut().col_inc(1); + } + } + } + + // control codes + + 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 lf(&mut self) { + 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 cr(&mut self) { + self.grid_mut().col_set(0); + } + + // escape codes + + // ESC 7 + pub(crate) fn decsc(&mut self) { + self.save_cursor(); + } + + // ESC 8 + pub(crate) fn decrc(&mut self) { + self.restore_cursor(); + } + + // ESC = + pub(crate) fn deckpam(&mut self) { + self.set_mode(MODE_APPLICATION_KEYPAD); + } + + // ESC > + pub(crate) fn deckpnm(&mut self) { + self.clear_mode(MODE_APPLICATION_KEYPAD); + } + + // ESC M + pub(crate) fn ri(&mut self) { + self.grid_mut().row_dec_scroll(1); + } + + // ESC c + pub(crate) fn ris(&mut self) { + *self = Self::new(self.grid.size(), self.grid.scrollback_len()); + } + + // csi codes + + // CSI @ + pub(crate) fn ich(&mut self, count: u16) { + self.grid_mut().insert_cells(count); + } + + // CSI A + pub(crate) fn cuu(&mut self, offset: u16) { + self.grid_mut().row_dec_clamp(offset); + } + + // CSI B + pub(crate) fn cud(&mut self, offset: u16) { + self.grid_mut().row_inc_clamp(offset); + } + + // CSI C + pub(crate) fn cuf(&mut self, offset: u16) { + self.grid_mut().col_inc_clamp(offset); + } + + // CSI D + pub(crate) fn cub(&mut self, offset: u16) { + self.grid_mut().col_dec(offset); + } + + // CSI E + pub(crate) fn cnl(&mut self, offset: u16) { + self.grid_mut().col_set(0); + self.grid_mut().row_inc_clamp(offset); + } + + // CSI F + pub(crate) fn cpl(&mut self, offset: u16) { + self.grid_mut().col_set(0); + self.grid_mut().row_dec_clamp(offset); + } + + // CSI G + pub(crate) fn cha(&mut self, col: u16) { + self.grid_mut().col_set(col - 1); + } + + // CSI H + pub(crate) fn cup(&mut self, (row, col): (u16, u16)) { + self.grid_mut().set_pos(crate::grid::Pos { + row: row - 1, + col: col - 1, + }); + } + + // CSI J + pub(crate) fn ed( + &mut self, + mode: u16, + mut unhandled: impl FnMut(&mut Self), + ) { + 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), + _ => unhandled(self), + } + } + + // CSI ? J + pub(crate) fn decsed( + &mut self, + mode: u16, + unhandled: impl FnMut(&mut Self), + ) { + self.ed(mode, unhandled); + } + + // CSI K + pub(crate) fn el( + &mut self, + mode: u16, + mut unhandled: impl FnMut(&mut Self), + ) { + 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), + _ => unhandled(self), + } + } + + // CSI ? K + pub(crate) fn decsel( + &mut self, + mode: u16, + unhandled: impl FnMut(&mut Self), + ) { + self.el(mode, unhandled); + } + + // CSI L + pub(crate) fn il(&mut self, count: u16) { + self.grid_mut().insert_lines(count); + } + + // CSI M + pub(crate) fn dl(&mut self, count: u16) { + self.grid_mut().delete_lines(count); + } + + // CSI P + pub(crate) fn dch(&mut self, count: u16) { + self.grid_mut().delete_cells(count); + } + + // CSI S + pub(crate) fn su(&mut self, count: u16) { + self.grid_mut().scroll_up(count); + } + + // CSI T + pub(crate) fn sd(&mut self, count: u16) { + self.grid_mut().scroll_down(count); + } + + // CSI X + pub(crate) fn ech(&mut self, count: u16) { + let attrs = self.attrs; + self.grid_mut().erase_cells(count, attrs); + } + + // CSI d + pub(crate) fn vpa(&mut self, row: u16) { + self.grid_mut().row_set(row - 1); + } + + // CSI ? h + pub(crate) fn decset( + &mut self, + params: &vte::Params, + mut unhandled: impl FnMut(&mut Self), + ) { + for param in params { + match param { + [1] => self.set_mode(MODE_APPLICATION_CURSOR), + [6] => self.grid_mut().set_origin_mode(true), + [9] => self.set_mouse_mode(MouseProtocolMode::Press), + [25] => self.clear_mode(MODE_HIDE_CURSOR), + [47] => self.enter_alternate_grid(), + [1000] => { + self.set_mouse_mode(MouseProtocolMode::PressRelease); + } + [1002] => { + self.set_mouse_mode(MouseProtocolMode::ButtonMotion); + } + [1003] => self.set_mouse_mode(MouseProtocolMode::AnyMotion), + [1005] => { + self.set_mouse_encoding(MouseProtocolEncoding::Utf8); + } + [1006] => { + self.set_mouse_encoding(MouseProtocolEncoding::Sgr); + } + [1049] => { + self.decsc(); + self.alternate_grid.clear(); + self.alternate_grid.clear_scrollback(); + self.enter_alternate_grid(); + } + [2004] => self.set_mode(MODE_BRACKETED_PASTE), + _ => unhandled(self), + } + } + } + + // CSI ? l + pub(crate) fn decrst( + &mut self, + params: &vte::Params, + mut unhandled: impl FnMut(&mut Self), + ) { + for param in params { + match param { + [1] => self.clear_mode(MODE_APPLICATION_CURSOR), + [6] => self.grid_mut().set_origin_mode(false), + [9] => self.clear_mouse_mode(MouseProtocolMode::Press), + [25] => self.set_mode(MODE_HIDE_CURSOR), + [47] => { + self.exit_alternate_grid(); + } + [1000] => { + self.clear_mouse_mode(MouseProtocolMode::PressRelease); + } + [1002] => { + self.clear_mouse_mode(MouseProtocolMode::ButtonMotion); + } + [1003] => { + self.clear_mouse_mode(MouseProtocolMode::AnyMotion); + } + [1005] => { + self.clear_mouse_encoding(MouseProtocolEncoding::Utf8); + } + [1006] => { + self.clear_mouse_encoding(MouseProtocolEncoding::Sgr); + } + [1049] => { + self.exit_alternate_grid(); + self.decrc(); + } + [2004] => self.clear_mode(MODE_BRACKETED_PASTE), + _ => unhandled(self), + } + } + } + + // CSI m + pub(crate) fn sgr( + &mut self, + params: &vte::Params, + mut unhandled: impl FnMut(&mut Self), + ) { + // XXX really i want to just be able to pass in a default Params + // instance with a 0 in it, but vte doesn't allow creating new Params + // instances + 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), + [7] => self.attrs.set_inverse(true), + [22] => self.attrs.set_normal_intensity(), + [23] => self.attrs.set_italic(false), + [24] => self.attrs.set_underline(false), + [27] => self.attrs.set_inverse(false), + [n] if (30..=37).contains(n) => { + self.attrs.fgcolor = crate::Color::Idx(to_u8!(*n) - 30); + } + [38, 2, r, g, b] => { + self.attrs.fgcolor = + crate::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b)); + } + [38, 5, i] => { + self.attrs.fgcolor = crate::Color::Idx(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.fgcolor = crate::Color::Rgb(r, g, b); + } + [5] => { + self.attrs.fgcolor = + crate::Color::Idx(next_param_u8!()); + } + _ => { + unhandled(self); + return; + } + }, + [39] => { + self.attrs.fgcolor = crate::Color::Default; + } + [n] if (40..=47).contains(n) => { + self.attrs.bgcolor = crate::Color::Idx(to_u8!(*n) - 40); + } + [48, 2, r, g, b] => { + self.attrs.bgcolor = + crate::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b)); + } + [48, 5, i] => { + self.attrs.bgcolor = crate::Color::Idx(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.bgcolor = crate::Color::Rgb(r, g, b); + } + [5] => { + self.attrs.bgcolor = + crate::Color::Idx(next_param_u8!()); + } + _ => { + unhandled(self); + return; + } + }, + [49] => { + self.attrs.bgcolor = crate::Color::Default; + } + [n] if (90..=97).contains(n) => { + self.attrs.fgcolor = crate::Color::Idx(to_u8!(*n) - 82); + } + [n] if (100..=107).contains(n) => { + self.attrs.bgcolor = crate::Color::Idx(to_u8!(*n) - 92); + } + _ => unhandled(self), + } + } + } + + // CSI r + pub(crate) fn decstbm(&mut self, (top, bottom): (u16, u16)) { + self.grid_mut().set_scroll_region(top - 1, bottom - 1); + } +} + +fn u16_to_u8(i: u16) -> Option<u8> { + if i > u16::from(u8::MAX) { + None + } else { + // safe because we just ensured that the value fits in a u8 + Some(i.try_into().unwrap()) + } +} |
