aboutsummaryrefslogtreecommitdiff
path: root/vendor/vt100/src/screen.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-03-31 20:49:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-03-31 20:49:05 +0300
commit41e009f9b7b46d335d41b365e41f9ad57d571ecb (patch)
tree42b3be90c336ad6d4487e9f86abc56ebb40ba210 /vendor/vt100/src/screen.rs
parentf826fb78dcac89b4ba23e399ac5d141459c14ae9 (diff)
fix alt-screen scrollback handling
Diffstat (limited to 'vendor/vt100/src/screen.rs')
-rw-r--r--vendor/vt100/src/screen.rs1356
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())
+ }
+}