From 073d39705ede937250948892a775865b4cc47e37 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 11 May 2021 11:06:19 +0200 Subject: [PATCH 1/2] Terminal compatibility: various behaviour fixes (#486) * fix(compatibility): various behaviour fixes * style(fmt): rustfmt * style(fmt): thanks clippy for teaching me about matches! --- src/client/panes/grid.rs | 86 +++++++++++++++++-- src/client/panes/terminal_character.rs | 18 ++++ src/client/panes/terminal_pane.rs | 12 ++- src/client/panes/unit/grid_tests.rs | 36 ++++++++ ...lient__panes__grid__grid_tests__csi_b.snap | 8 ++ ...anes__grid__grid_tests__csi_capital_i.snap | 8 ++ ...anes__grid__grid_tests__csi_capital_z.snap | 8 ++ src/client/tab.rs | 9 +- src/tests/fixtures/csi-b | 1 + src/tests/fixtures/csi-capital-i | 1 + src/tests/fixtures/csi-capital-z | 1 + 11 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap create mode 100644 src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap create mode 100644 src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap create mode 100644 src/tests/fixtures/csi-b create mode 100644 src/tests/fixtures/csi-capital-i create mode 100644 src/tests/fixtures/csi-capital-z diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs index 824c83844..0c873be8d 100644 --- a/src/client/panes/grid.rs +++ b/src/client/panes/grid.rs @@ -12,7 +12,7 @@ const SCROLL_BACK: usize = 10_000; use crate::utils::logging::debug_log_to_file; use crate::panes::terminal_character::{ - CharacterStyles, CharsetIndex, Cursor, StandardCharset, TerminalCharacter, + CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; @@ -177,6 +177,7 @@ pub struct Grid { saved_cursor_position: Option, scroll_region: Option<(usize, usize)>, active_charset: CharsetIndex, + preceding_char: Option, pub should_render: bool, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") pub erasure_mode: bool, // ERM @@ -210,6 +211,7 @@ impl Grid { cursor: Cursor::new(0, 0), saved_cursor_position: None, scroll_region: None, + preceding_char: None, width: columns, height: rows, should_render: true, @@ -245,6 +247,26 @@ impl Grid { empty_character.styles = styles; self.pad_current_line_until(self.cursor.x); } + pub fn move_to_previous_tabstop(&mut self) { + let mut previous_tabstop = None; + for tabstop in self.horizontal_tabstops.iter() { + if *tabstop >= self.cursor.x { + break; + } + previous_tabstop = Some(tabstop); + } + match previous_tabstop { + Some(tabstop) => { + self.cursor.x = *tabstop; + } + None => { + self.cursor.x = 0; + } + } + } + pub fn cursor_shape(&self) -> CursorShape { + self.cursor.get_shape() + } fn set_horizontal_tabstop(&mut self) { self.horizontal_tabstops.insert(self.cursor.x); } @@ -925,6 +947,10 @@ impl Grid { self.active_charset = Default::default(); self.erasure_mode = false; self.disable_linewrap = false; + self.cursor.change_shape(CursorShape::Block); + } + fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { + self.preceding_char = Some(terminal_character); } } @@ -937,6 +963,7 @@ impl Perform for Grid { character: c, styles: self.cursor.pending_styles, }; + self.set_preceding_character(terminal_character); self.add_character(terminal_character); } @@ -950,9 +977,10 @@ impl Perform for Grid { // tab self.advance_to_next_tabstop(self.cursor.pending_styles); } - 10 | 11 => { + 10 | 11 | 12 => { // 0a, newline // 0b, vertical tabulation + // 0c, form feed self.add_newline(); } 13 => { @@ -985,7 +1013,7 @@ impl Perform for Grid { // TBD } - fn csi_dispatch(&mut self, params: &Params, _intermediates: &[u8], _ignore: bool, c: char) { + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) { let mut params_iter = params.iter(); let mut next_param_or = |default: u16| { params_iter @@ -998,7 +1026,7 @@ impl Perform for Grid { self.cursor .pending_styles .add_style_from_ansi_params(&mut params_iter); - } else if c == 'C' { + } else if c == 'C' || c == 'a' { // move cursor forward let move_by = next_param_or(1); self.move_cursor_forward_until_edge(move_by); @@ -1031,7 +1059,6 @@ impl Perform for Grid { self.clear_all(char_to_replace); } }; - // TODO: implement 1 } else if c == 'H' || c == 'f' { // goto row/col // we subtract 1 from the row/column because these are 1 indexed @@ -1043,7 +1070,7 @@ impl Perform for Grid { // move cursor up until edge of screen let move_up_count = next_param_or(1); self.move_cursor_up(move_up_count as usize); - } else if c == 'B' { + } else if c == 'B' || c == 'e' { // move cursor down until edge of screen let move_down_count = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; @@ -1052,7 +1079,7 @@ impl Perform for Grid { let move_back_count = next_param_or(1); self.move_cursor_back(move_back_count); } else if c == 'l' { - let first_intermediate_is_questionmark = match _intermediates.get(0) { + let first_intermediate_is_questionmark = match intermediates.get(0) { Some(b'?') => true, None => false, _ => false, @@ -1101,7 +1128,7 @@ impl Perform for Grid { self.insert_mode = false; } } else if c == 'h' { - let first_intermediate_is_questionmark = match _intermediates.get(0) { + let first_intermediate_is_questionmark = match intermediates.get(0) { Some(b'?') => true, None => false, _ => false, @@ -1171,7 +1198,7 @@ impl Perform for Grid { let line_count_to_add = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character); - } else if c == 'G' { + } else if c == 'G' || c == '`' { let column = next_param_or(1).saturating_sub(1); self.move_cursor_to_column(column); } else if c == 'g' { @@ -1216,6 +1243,46 @@ impl Perform for Grid { // TODO: should this be styled? self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER); } + } else if c == 'b' { + if let Some(c) = self.preceding_char { + for _ in 0..next_param_or(1) { + self.add_character(c); + } + } + } else if c == 'E' { + let count = next_param_or(1); + let pad_character = EMPTY_TERMINAL_CHARACTER; + self.move_cursor_down(count, pad_character); + } else if c == 'F' { + let count = next_param_or(1); + self.move_cursor_up(count); + self.move_cursor_to_beginning_of_line(); + } else if c == 'I' { + for _ in 0..next_param_or(1) { + self.advance_to_next_tabstop(self.cursor.pending_styles); + } + } else if c == 'q' { + let first_intermediate_is_space = matches!(intermediates.get(0), Some(b' ')); + if first_intermediate_is_space { + // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. + let cursor_style_id = next_param_or(0); + let shape = match cursor_style_id { + 0 | 2 => Some(CursorShape::Block), + 1 => Some(CursorShape::BlinkingBlock), + 3 => Some(CursorShape::BlinkingUnderline), + 4 => Some(CursorShape::Underline), + 5 => Some(CursorShape::BlinkingBeam), + 6 => Some(CursorShape::Beam), + _ => None, + }; + if let Some(cursor_shape) = shape { + self.cursor.change_shape(cursor_shape); + } + } + } else if c == 'Z' { + for _ in 0..next_param_or(1) { + self.move_to_previous_tabstop(); + } } else { let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); #[cfg(not(test))] @@ -1262,6 +1329,7 @@ impl Perform for Grid { self.move_cursor_to_beginning_of_line(); } (b'M', None) => { + // TODO: if cursor is at the top, it should go down one self.move_cursor_up_with_scrolling(1); } (b'c', None) => { diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs index c7c616c17..d2340e2e4 100644 --- a/src/client/panes/terminal_character.rs +++ b/src/client/panes/terminal_character.rs @@ -701,6 +701,16 @@ impl IndexMut for Charsets { } } +#[derive(Clone, Copy, Debug)] +pub enum CursorShape { + Block, + BlinkingBlock, + Underline, + BlinkingUnderline, + Beam, + BlinkingBeam, +} + #[derive(Clone, Debug)] pub struct Cursor { pub x: usize, @@ -708,6 +718,7 @@ pub struct Cursor { pub is_hidden: bool, pub pending_styles: CharacterStyles, pub charsets: Charsets, + shape: CursorShape, } impl Cursor { @@ -718,8 +729,15 @@ impl Cursor { is_hidden: false, pending_styles: CharacterStyles::new(), charsets: Default::default(), + shape: CursorShape::Block, } } + pub fn change_shape(&mut self, shape: CursorShape) { + self.shape = shape; + } + pub fn get_shape(&self) -> CursorShape { + self.shape + } } #[derive(Clone, Copy)] diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index 5792bba93..98021110b 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::panes::grid::Grid; use crate::panes::terminal_character::{ - CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, + CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; use crate::pty::VteBytes; use crate::tab::Pane; @@ -291,6 +291,16 @@ impl Pane for TerminalPane { fn set_active_at(&mut self, time: Instant) { self.active_at = time; } + fn cursor_shape_csi(&self) -> String { + match self.grid.cursor_shape() { + CursorShape::Block => "\u{1b}[0 q".to_string(), + CursorShape::BlinkingBlock => "\u{1b}[1 q".to_string(), + CursorShape::Underline => "\u{1b}[4 q".to_string(), + CursorShape::BlinkingUnderline => "\u{1b}[3 q".to_string(), + CursorShape::Beam => "\u{1b}[6 q".to_string(), + CursorShape::BlinkingBeam => "\u{1b}[5 q".to_string(), + } + } } impl TerminalPane { diff --git a/src/client/panes/unit/grid_tests.rs b/src/client/panes/unit/grid_tests.rs index b590f404b..b6a2ec6c9 100644 --- a/src/client/panes/unit/grid_tests.rs +++ b/src/client/panes/unit/grid_tests.rs @@ -347,3 +347,39 @@ fn vttest8_5() { } assert_snapshot!(format!("{:?}", grid)); } + +#[test] +fn csi_b() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "csi-b"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +fn csi_capital_i() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "csi-capital-i"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +fn csi_capital_z() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "csi-capital-z"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); +} diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap new file mode 100644 index 000000000..1d0c34e5f --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap @@ -0,0 +1,8 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): ffffff +01 (C): + diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap new file mode 100644 index 000000000..11f7b8f85 --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap @@ -0,0 +1,8 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): foo +01 (C): + diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap new file mode 100644 index 000000000..04f492b86 --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap @@ -0,0 +1,8 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): 12345678foo234567890 +01 (C): + diff --git a/src/client/tab.rs b/src/client/tab.rs index f0ec08690..785a650eb 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -132,6 +132,9 @@ pub trait Pane { fn clear_scroll(&mut self); fn active_at(&self) -> Instant; fn set_active_at(&mut self, instant: Instant); + fn cursor_shape_csi(&self) -> String { + "\u{1b}[0 q".to_string() // default to non blinking block + } fn right_boundary_x_coords(&self) -> usize { self.x() + self.columns() @@ -762,10 +765,12 @@ impl Tab { match self.get_active_terminal_cursor_position() { Some((cursor_position_x, cursor_position_y)) => { let show_cursor = "\u{1b}[?25h"; + let change_cursor_shape = self.get_active_pane().unwrap().cursor_shape_csi(); let goto_cursor_position = &format!( - "\u{1b}[{};{}H\u{1b}[m", + "\u{1b}[{};{}H\u{1b}[m{}", cursor_position_y + 1, - cursor_position_x + 1 + cursor_position_x + 1, + change_cursor_shape ); // goto row/col output.push_str(show_cursor); output.push_str(goto_cursor_position); diff --git a/src/tests/fixtures/csi-b b/src/tests/fixtures/csi-b new file mode 100644 index 000000000..e3fe99975 --- /dev/null +++ b/src/tests/fixtures/csi-b @@ -0,0 +1 @@ +f diff --git a/src/tests/fixtures/csi-capital-i b/src/tests/fixtures/csi-capital-i new file mode 100644 index 000000000..9d658f629 --- /dev/null +++ b/src/tests/fixtures/csi-capital-i @@ -0,0 +1 @@ +foo diff --git a/src/tests/fixtures/csi-capital-z b/src/tests/fixtures/csi-capital-z new file mode 100644 index 000000000..649ca645e --- /dev/null +++ b/src/tests/fixtures/csi-capital-z @@ -0,0 +1 @@ +12345678901234567890foo From 20dcb3f4ca0d18a44cf3fb19cc19e130d8c2f988 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 11 May 2021 11:08:14 +0200 Subject: [PATCH 2/2] docs(changelog): update compatibility fixes --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19105b76c..8fdf304f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) * Fix exit code of `dump-default-config` (https://github.com/zellij-org/zellij/pull/480) -* Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) +* Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) +* Terminal Compatibility: various behaviour fixes (https://github.com/zellij-org/zellij/pull/486) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461)