From 13af16b3360dddc8aede1e7c429696b115fadd6d Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 6 Nov 2020 18:12:03 +0100 Subject: [PATCH] fix(scroll): support show/hide cursor and fix vim scrolling behaviour (#27) --- src/main.rs | 2 +- src/screen.rs | 30 ++++++++----- src/terminal_pane/scroll.rs | 43 +++++++++++++------ src/terminal_pane/terminal_pane.rs | 37 +++++++++++++--- src/tests/integration/basic.rs | 2 +- ...lity__run_bandwhich_from_fish_shell-2.snap | 2 +- ...bility__run_bandwhich_from_fish_shell.snap | 2 +- src/tests/utils.rs | 12 +++--- 8 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/main.rs b/src/main.rs index c4bf547d..87250ec2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -269,7 +269,7 @@ pub fn start(mut os_input: Box, opts: Opt) { send_screen_instructions.send(ScreenInstruction::ScrollDown).unwrap(); } else if buffer[0] == 24 { // ctrl-x send_screen_instructions.send(ScreenInstruction::CloseFocusedPane).unwrap(); - } else if buffer[0] == 6 { // ctrl-f + } else if buffer[0] == 5 { // ctrl-e send_screen_instructions.send(ScreenInstruction::ToggleActiveTerminalFullscreen).unwrap(); } else { // println!("\r buffer {:?} ", buffer[0]); diff --git a/src/screen.rs b/src/screen.rs index 5acec8d8..e02f8e8f 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -264,13 +264,13 @@ impl Screen { self.os_api.tcdrain(*active_terminal_id).expect("failed to drain terminal"); } } - fn get_active_terminal_cursor_position(&self) -> (usize, usize) { // (x, y) + fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> { // (x, y) let active_terminal = &self.get_active_terminal().unwrap(); - let (x_in_terminal, y_in_terminal) = active_terminal.cursor_coordinates(); - - let x = active_terminal.get_x() + x_in_terminal; - let y = active_terminal.get_y() + y_in_terminal; - (x, y) + active_terminal.cursor_coordinates().and_then(|(x_in_terminal, y_in_terminal)| { + let x = active_terminal.get_x() + x_in_terminal; + let y = active_terminal.get_y() + y_in_terminal; + Some((x, y)) + }) } pub fn toggle_active_terminal_fullscreen(&mut self) { if let Some(active_terminal_id) = self.get_active_terminal_id() { @@ -312,10 +312,20 @@ impl Screen { let vte_output = boundaries.vte_output(); stdout.write_all(&vte_output.as_bytes()).expect("cannot write to stdout"); - let (cursor_position_x, cursor_position_y) = self.get_active_terminal_cursor_position(); - let goto_cursor_position = format!("\u{1b}[{};{}H\u{1b}[m", cursor_position_y + 1, cursor_position_x + 1); // goto row/col - stdout.write_all(&goto_cursor_position.as_bytes()).expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); + match self.get_active_terminal_cursor_position() { + Some((cursor_position_x, cursor_position_y)) => { + let show_cursor = "\u{1b}[?25h"; + let goto_cursor_position = format!("\u{1b}[{};{}H\u{1b}[m", cursor_position_y + 1, cursor_position_x + 1); // goto row/col + stdout.write_all(&show_cursor.as_bytes()).expect("cannot write to stdout"); + stdout.write_all(&goto_cursor_position.as_bytes()).expect("cannot write to stdout"); + stdout.flush().expect("could not flush"); + }, + None => { + let hide_cursor = "\u{1b}[?25l"; + stdout.write_all(&hide_cursor.as_bytes()).expect("cannot write to stdout"); + stdout.flush().expect("could not flush"); + } + } } fn terminal_ids_directly_left_of(&self, id: &RawFd) -> Option> { let mut ids = vec![]; diff --git a/src/terminal_pane/scroll.rs b/src/terminal_pane/scroll.rs index 13bdaf37..2867f122 100644 --- a/src/terminal_pane/scroll.rs +++ b/src/terminal_pane/scroll.rs @@ -172,7 +172,8 @@ pub struct Scroll { total_columns: usize, lines_in_view: usize, viewport_bottom_offset: Option, - scroll_region: Option<(usize, usize)> // start line, end line (if set, this is the area the will scroll) + scroll_region: Option<(usize, usize)>, // start line, end line (if set, this is the area the will scroll) + show_cursor: bool, } impl Scroll { @@ -187,6 +188,7 @@ impl Scroll { cursor_position, viewport_bottom_offset: None, scroll_region: None, + show_cursor: true, } } pub fn as_character_lines(&self) -> Vec> { @@ -243,21 +245,29 @@ impl Scroll { self.cursor_position.move_to_beginning_of_linewrap(); } } + pub fn show_cursor (&mut self) { + self.show_cursor = true; + } + pub fn hide_cursor (&mut self) { + self.show_cursor = false; + } pub fn add_canonical_line(&mut self) { let current_canonical_line_index = self.cursor_position.line_index.0; if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { // the scroll region indices start at 1, so we need to adjust them - let scroll_region_top_index = scroll_region_top - 1; - let scroll_region_bottom_index = scroll_region_bottom - 1; - if current_canonical_line_index == scroll_region_bottom_index { // end of scroll region - // when we have a scroll region set and we're at its bottom - // we need to delete its first line, thus shifting all lines in it upwards - // then we add an empty line at its end which will be filled by the application - // controlling the scroll region (presumably filled by whatever comes next in the - // scroll buffer, but that's not something we control) - self.canonical_lines.remove(scroll_region_top_index); - self.canonical_lines.insert(scroll_region_bottom_index, CanonicalLine::new()); - return; + if self.show_cursor { // scroll region should be ignored if the cursor is hidden + let scroll_region_top_index = scroll_region_top - 1; + let scroll_region_bottom_index = scroll_region_bottom - 1; + if current_canonical_line_index == scroll_region_bottom_index { // end of scroll region + // when we have a scroll region set and we're at its bottom + // we need to delete its first line, thus shifting all lines in it upwards + // then we add an empty line at its end which will be filled by the application + // controlling the scroll region (presumably filled by whatever comes next in the + // scroll buffer, but that's not something we control) + self.canonical_lines.remove(scroll_region_top_index); + self.canonical_lines.insert(scroll_region_bottom_index, CanonicalLine::new()); + return; + } } } if current_canonical_line_index == self.canonical_lines.len() - 1 { @@ -272,7 +282,10 @@ impl Scroll { panic!("cursor out of bounds, cannot add_canonical_line"); } } - pub fn cursor_coordinates_on_screen(&self) -> (usize, usize) { // (x, y) + pub fn cursor_coordinates_on_screen(&self) -> Option<(usize, usize)> { // (x, y) + if !self.show_cursor { + return None + } let (canonical_line_cursor_position, line_wrap_cursor_position) = self.cursor_position.line_index; let x = self.cursor_position.column_index; let mut y = 0; @@ -294,10 +307,12 @@ impl Scroll { let total_lines = self.canonical_lines.iter().fold(0, |total_lines, current_line| total_lines + current_line.wrapped_fragments.len()); // TODO: is this performant enough? should it be cached or kept track of? let y = if total_lines < self.lines_in_view { total_lines - y + } else if y > self.lines_in_view { + self.lines_in_view } else { self.lines_in_view - y }; - (x, y) + Some((x, y)) } pub fn move_cursor_forward(&mut self, count: usize) { let (current_canonical_line_index, current_line_wrap_position) = self.cursor_position.line_index; diff --git a/src/terminal_pane/terminal_pane.rs b/src/terminal_pane/terminal_pane.rs index ce5198b1..6107f403 100644 --- a/src/terminal_pane/terminal_pane.rs +++ b/src/terminal_pane/terminal_pane.rs @@ -223,7 +223,7 @@ impl TerminalPane { pub fn read_buffer_as_lines (&self) -> Vec> { self.scroll.as_character_lines() } - pub fn cursor_coordinates (&self) -> (usize, usize) { // (x, y) + pub fn cursor_coordinates (&self) -> Option<(usize, usize)> { // (x, y) self.scroll.cursor_coordinates_on_screen() } pub fn scroll_up(&mut self, count: usize) { @@ -255,8 +255,8 @@ impl TerminalPane { self.should_render = true; } fn add_newline (&mut self) { - self.scroll.add_canonical_line(); // TODO: handle scroll region - self.reset_all_ansi_codes(); + self.scroll.add_canonical_line(); + // self.reset_all_ansi_codes(); // TODO: find out if we should be resetting here or not self.should_render = true; } fn move_to_beginning_of_line (&mut self) { @@ -557,14 +557,41 @@ impl vte::Perform for TerminalPane { let move_back_count = if params[0] == 0 { 1 } else { params[0] as usize }; self.scroll.move_cursor_back(move_back_count); } else if c == 'l' { - // TBD + let first_intermediate_is_questionmark = match _intermediates.get(0) { + Some(b'?') => true, + None => false, + _ => false + }; + if first_intermediate_is_questionmark { + match params.get(0) { + Some(25) => { + self.scroll.hide_cursor(); + self.should_render = true; + }, + _ => {} + }; + } } else if c == 'h' { - // TBD + let first_intermediate_is_questionmark = match _intermediates.get(0) { + Some(b'?') => true, + None => false, + _ => false + }; + if first_intermediate_is_questionmark { + match params.get(0) { + Some(25) => { + self.scroll.show_cursor(); + self.should_render = true; + }, + _ => {} + }; + } } else if c == 'r' { if params.len() > 1 { let top_line_index = params[0] as usize; let bottom_line_index = params[1] as usize; self.scroll.set_scroll_region(top_line_index, bottom_line_index); + self.scroll.show_cursor(); } else { self.scroll.clear_scroll_region(); } diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs index 7067c8f6..eb542f95 100644 --- a/src/tests/integration/basic.rs +++ b/src/tests/integration/basic.rs @@ -170,7 +170,7 @@ pub fn toggle_focused_pane_fullscreen () { // split-largest_terminal * 3, toggle-fullscreen * 2, ctrl-p, toggle-fullscreen * 2, ctrl-p, toggle-fullscreen * 2, ctrl-p, toggle-fullscreen * 2 and quit // (ctrl-z + ctrl-z + ctrl-z + ctrl-z + ctrl-f, ctrl-f, ctrl-p, ctrl-f, ctrl-f, ctrl-p, ctrl-f, // ctrl-f, ctrl-p, ctrl-f, ctrl-f, ctrl-q) - fake_input_output.add_terminal_input(&[26, 26, 26, 6, 6, 16, 6, 6, 16, 6, 6, 16, 6, 6, 17]); + fake_input_output.add_terminal_input(&[26, 26, 26, 5, 5, 16, 5, 5, 16, 5, 5, 16, 5, 5, 17]); let mut opts = Opt::default(); opts.max_panes = Some(4); start(Box::new(fake_input_output.clone()), opts); diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell-2.snap index 9c90df81..8ca0fe37 100644 --- a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell-2.snap +++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell-2.snap @@ -29,4 +29,4 @@ expression: snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Press to pause. Use to rearrange tables. (DNS queries hidden). -Bye from Mosaic!█ +Bye from Mosaic! diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap index f8f4a539..5d056ac2 100644 --- a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap +++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__run_bandwhich_from_fish_shell.snap @@ -6,7 +6,7 @@ expression: snapshot ┌Utilization by process name───────────────────────────────────────────────────────────────────────────────────────┐ │Process Connections Up / Down │ │ │ -│firefox 3 46Bps / 5█Bps │ +│firefox 3 46Bps / 57Bps │ │ │ │ │ │ │ diff --git a/src/tests/utils.rs b/src/tests/utils.rs index 1e76b31f..779c72a4 100644 --- a/src/tests/utils.rs +++ b/src/tests/utils.rs @@ -14,15 +14,17 @@ pub fn get_output_frame_snapshots(output_frames: &[Vec], win_size: &Winsize) vte_parser.advance(&mut terminal_output, *byte); } let output_lines = terminal_output.read_buffer_as_lines(); - let (cursor_x, cursor_y) = terminal_output.cursor_coordinates(); + let cursor_coordinates = terminal_output.cursor_coordinates(); let mut snapshot = String::new(); for (line_index, line) in output_lines.iter().enumerate() { for (character_index, terminal_character) in line.iter().enumerate() { - if line_index == cursor_y && character_index == cursor_x { - snapshot.push('█'); - } else { - snapshot.push(terminal_character.character); + if let Some((cursor_x, cursor_y)) = cursor_coordinates { + if line_index == cursor_y && character_index == cursor_x { + snapshot.push('█'); + continue; + } } + snapshot.push(terminal_character.character); } if line_index != output_lines.len() - 1 { snapshot.push('\n');