diff options
-rw-r--r-- | .github/workflows/rust.yml | 2 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/terminal_pane/scroll.rs | 108 | ||||
-rw-r--r-- | src/terminal_pane/terminal_pane.rs | 84 | ||||
-rw-r--r-- | src/tests/fixtures/htop | bin | 0 -> 16234 bytes | |||
-rw-r--r-- | src/tests/integration/compatibility.rs | 23 | ||||
-rw-r--r-- | src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop-2.snap | 32 | ||||
-rw-r--r-- | src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop.snap | 32 | ||||
-rw-r--r-- | src/utils/logging.rs | 5 |
9 files changed, 257 insertions, 32 deletions
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 64441fc36..f31838a6f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: cargo test -j 1 --verbose fmt: name: Rustfmt runs-on: ubuntu-latest diff --git a/src/main.rs b/src/main.rs index f8f2ca3ba..0126069bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,9 +97,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) { let command_is_executing = CommandIsExecuting::new(); - delete_log_dir().unwrap(); - delete_log_file().unwrap(); - let full_screen_ws = os_input.get_terminal_size_using_fd(0); os_input.into_raw_mode(0); let (send_screen_instructions, receive_screen_instructions): ( diff --git a/src/terminal_pane/scroll.rs b/src/terminal_pane/scroll.rs index 5884ea32a..fd811fa92 100644 --- a/src/terminal_pane/scroll.rs +++ b/src/terminal_pane/scroll.rs @@ -1,7 +1,10 @@ use ::std::collections::VecDeque; use ::std::fmt::{self, Debug, Formatter}; -use crate::terminal_pane::terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACTER}; +use crate::terminal_pane::terminal_character::{ + CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, +}; +use crate::utils::logging::{debug_log_to_file, debug_log_to_file_pid_0}; /* * Scroll @@ -75,6 +78,64 @@ impl CanonicalLine { fragment_to_clear.clear_after_and_including(column_index); self.wrapped_fragments.truncate(fragment_index + 1); } + pub fn replace_with_empty_chars( + &mut self, + fragment_index: usize, + from_col: usize, + count: usize, + style_of_empty_space: CharacterStyles, + ) { + let mut characters_replaced = 0; + let mut column_position_in_fragment = from_col; + let mut current_fragment = fragment_index; + let mut empty_space_character = EMPTY_TERMINAL_CHARACTER; + empty_space_character.styles = style_of_empty_space; + loop { + if characters_replaced == count { + break; + } + match self.wrapped_fragments.get_mut(current_fragment) { + Some(fragment_to_clear) => { + let fragment_characters_count = fragment_to_clear.characters.len(); + if fragment_characters_count >= column_position_in_fragment { + fragment_to_clear + .add_character(empty_space_character, column_position_in_fragment); + column_position_in_fragment += 1; + characters_replaced += 1; + } else { + current_fragment += 1; + column_position_in_fragment = 0; + } + } + None => { + // end of line, nothing more to clear + break; + } + } + } + } + pub fn replace_with_empty_chars_after_cursor( + &mut self, + fragment_index: usize, + from_col: usize, + total_columns: usize, + style_of_empty_space: CharacterStyles, + ) { + let mut empty_char_character = EMPTY_TERMINAL_CHARACTER; + empty_char_character.styles = style_of_empty_space; + let current_fragment = self.wrapped_fragments.get_mut(fragment_index).unwrap(); + let fragment_characters_count = current_fragment.characters.len(); + + for i in from_col..fragment_characters_count { + current_fragment.add_character(empty_char_character, i); + } + + for i in fragment_characters_count..total_columns { + current_fragment.add_character(empty_char_character, i); + } + + self.wrapped_fragments.truncate(fragment_index + 1); + } } impl Debug for CanonicalLine { @@ -283,7 +344,7 @@ impl Scroll { // 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 { + if current_canonical_line_index == scroll_region_bottom_index + 1 { // 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 @@ -292,7 +353,7 @@ impl Scroll { // 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()); + .insert(scroll_region_bottom_index + 1, CanonicalLine::new()); return; } } @@ -405,7 +466,7 @@ impl Scroll { self.lines_in_view = lines; self.total_columns = columns; } - pub fn clear_canonical_line_right_of_cursor(&mut self) { + pub fn clear_canonical_line_right_of_cursor(&mut self, style_of_empty_space: CharacterStyles) { let (current_canonical_line_index, current_line_wrap_position) = self.cursor_position.line_index; let current_cursor_column_position = self.cursor_position.column_index; @@ -413,8 +474,12 @@ impl Scroll { .canonical_lines .get_mut(current_canonical_line_index) .expect("cursor out of bounds"); - current_canonical_line - .clear_after(current_line_wrap_position, current_cursor_column_position); + current_canonical_line.replace_with_empty_chars_after_cursor( + current_line_wrap_position, + current_cursor_column_position, + self.total_columns, + style_of_empty_space, + ); } pub fn clear_all_after_cursor(&mut self) { let (current_canonical_line_index, current_line_wrap_position) = @@ -429,6 +494,25 @@ impl Scroll { self.canonical_lines .truncate(current_canonical_line_index + 1); } + pub fn replace_with_empty_chars( + &mut self, + count: usize, + style_of_empty_space: CharacterStyles, + ) { + let (current_canonical_line_index, current_line_wrap_position) = + self.cursor_position.line_index; + let current_cursor_column_position = self.cursor_position.column_index; + let current_canonical_line = self + .canonical_lines + .get_mut(current_canonical_line_index) + .expect("cursor out of bounds"); + current_canonical_line.replace_with_empty_chars( + current_line_wrap_position, + current_cursor_column_position, + count, + style_of_empty_space, + ); + } pub fn clear_all(&mut self) { self.canonical_lines.clear(); self.canonical_lines.push(CanonicalLine::new()); @@ -458,6 +542,14 @@ impl Scroll { } self.cursor_position.move_to_column(col); } + pub fn move_cursor_to_column(&mut self, col: usize) { + let current_canonical_line = self.cursor_position.line_index.0; + self.move_cursor_to(current_canonical_line, col); + } + pub fn move_cursor_to_line(&mut self, line: usize) { + let current_column = self.cursor_position.column_index; + self.move_cursor_to(line, current_column); + } pub fn set_scroll_region(&mut self, top_line: usize, bottom_line: usize) { self.scroll_region = Some((top_line, bottom_line)); // TODO: clear linewraps in scroll region? @@ -481,7 +573,7 @@ impl Scroll { for _ in 0..count { self.canonical_lines.remove(current_canonical_line_index); self.canonical_lines - .insert(scroll_region_bottom_index, CanonicalLine::new()); + .insert(scroll_region_bottom_index + 1, CanonicalLine::new()); } } } @@ -500,7 +592,7 @@ impl Scroll { // so we add an empty line where the cursor currently is, and delete the last line // of the scroll region for _ in 0..count { - self.canonical_lines.remove(scroll_region_bottom_index); + self.canonical_lines.remove(scroll_region_bottom_index + 1); self.canonical_lines .insert(current_canonical_line_index, CanonicalLine::new()); } diff --git a/src/terminal_pane/terminal_pane.rs b/src/terminal_pane/terminal_pane.rs index eb000550c..dcd067447 100644 --- a/src/terminal_pane/terminal_pane.rs +++ b/src/terminal_pane/terminal_pane.rs @@ -335,8 +335,31 @@ impl vte::Perform for TerminalPane { if params.is_empty() || params[0] == 0 { // reset all self.pending_styles.reset_all(); + if let Some(param1) = params.get(1) { + // TODO: this is a case currently found in eg. htop where we get two different + // csi 'm' codes in one event. + // We should understand why these are happening and then make a more generic + // solution for them + if *param1 == 1 { + // bold + self.pending_styles = self + .pending_styles + .bold(Some(AnsiCode::Code((Some(*param1 as u16), None)))); + } + } } else if params[0] == 39 { self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Reset)); + if let Some(param1) = params.get(1) { + // TODO: this is a case currently found in eg. htop where we get two different + // csi 'm' codes in one event. + // We should understand why these are happening and then make a more generic + // solution for them + if *param1 == 49 { + // TODO: if we need this to fix the bug, we need to make collecting the + // second argument in such cases generic + self.pending_styles = self.pending_styles.background(Some(AnsiCode::Reset)); + } + } } else if params[0] == 49 { self.pending_styles = self.pending_styles.background(Some(AnsiCode::Reset)); } else if params[0] == 21 { @@ -655,7 +678,8 @@ impl vte::Perform for TerminalPane { } else if c == 'K' { // clear line (0 => right, 1 => left, 2 => all) if params[0] == 0 { - self.scroll.clear_canonical_line_right_of_cursor(); + self.scroll + .clear_canonical_line_right_of_cursor(self.pending_styles); } // TODO: implement 1 and 2 } else if c == 'J' { @@ -669,9 +693,11 @@ impl vte::Perform for TerminalPane { } else if c == 'H' { // goto row/col let (row, col) = if params.len() == 1 { - (params[0] as usize, 0) // TODO: is this always correct ? + (params[0] as usize, params[0] as usize) } else { - (params[0] as usize - 1, params[1] as usize - 1) // we subtract 1 here because this csi is 1 indexed and we index from 0 + // we subtract 1 from the column because after we get a cursor goto, the print + // character should be printed on top of the cursor + (params[0] as usize, params[1] as usize - 1) }; self.scroll.move_cursor_to(row, col); } else if c == 'A' { @@ -749,19 +775,39 @@ impl vte::Perform for TerminalPane { }; self.scroll .add_empty_lines_in_scroll_region(line_count_to_add); - } else if c == 'q' || c == 'd' || c == 'X' || c == 'G' { + } else if c == 'q' { // ignore for now to run on mac + } else if c == 'G' { + let column = if params[0] == 0 { + 0 + } else { + // params[0] as usize + params[0] as usize - 1 + }; + self.scroll.move_cursor_to_column(column); + } else if c == 'd' { + // goto line + let line = if params[0] == 0 { + 1 + } else { + params[0] as usize + }; + self.scroll.move_cursor_to_line(line); + } else if c == 'X' || c == 'P' { + // erase characters + let count = if params[0] == 0 { + 1 + } else { + params[0] as usize + }; + self.scroll + .replace_with_empty_chars(count, self.pending_styles); } else if c == 'T' { /* * 124 54 T SD * Scroll down, new lines inserted at top of screen * [4T = Scroll down 4, bring previous lines back into view */ - debug_log_to_file(format!( - "htop (only?) linux csi: {}->{:?} ({:?} - ignore: {})", - c, params, _intermediates, _ignore - )) - .unwrap(); let line_count: i64 = *params.get(0).expect("A number of lines was expected."); if line_count >= 0 { @@ -769,17 +815,15 @@ impl vte::Perform for TerminalPane { } else { self.rotate_scroll_region_down(line_count.abs() as usize); } - } else if c == 'P' { - /* - * 120 50 P * DCH - * Delete Character, from current position to end of field - * [4P = Delete 4 characters, VT102 series - */ - debug_log_to_file(format!( - "htop (only?) linux csi: {}->{:?} (intermediates: {:?}, ignore: {})", - c, params, _intermediates, _ignore - )) - .unwrap(); + } else if c == 'S' { + // move scroll up + let count = if params[0] == 0 { + 1 + } else { + params[0] as usize + }; + self.scroll.delete_lines_in_scroll_region(count); + self.scroll.add_empty_lines_in_scroll_region(count); } else { debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)).unwrap(); panic!("unhandled csi: {}->{:?}", c, params); diff --git a/src/tests/fixtures/htop b/src/tests/fixtures/htop Binary files differnew file mode 100644 index 000000000..37ee07771 --- /dev/null +++ b/src/tests/fixtures/htop diff --git a/src/tests/integration/compatibility.rs b/src/tests/integration/compatibility.rs index a763d5945..8034bfb40 100644 --- a/src/tests/integration/compatibility.rs +++ b/src/tests/integration/compatibility.rs @@ -192,3 +192,26 @@ pub fn vim_ctrl_u() { assert_snapshot!(snapshot); } } + +#[test] +pub fn htop() { + let fake_win_size = PositionAndSize { + columns: 116, + rows: 28, + x: 0, + y: 0, + }; + let fixture_name = "htop"; + let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name); + fake_input_output.add_terminal_input(&[COMMAND_TOGGLE, COMMAND_TOGGLE, QUIT]); + start(Box::new(fake_input_output.clone()), Opt::default()); + let output_frames = fake_input_output + .stdout_writer + .output_frames + .lock() + .unwrap(); + let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size); + for snapshot in snapshots { + assert_snapshot!(snapshot); + } +} diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop-2.snap new file mode 100644 index 000000000..2fa4647b0 --- /dev/null +++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop-2.snap @@ -0,0 +1,32 @@ +--- +source: src/tests/integration/compatibility.rs +expression: snapshot +--- + 1 [||||| 10.1%] Tasks: 73, 413 thr; 1 running + 2 [||||||| 13.5%] Load average: 1.03 1.07 1.30 + 3 [|||||| 10.8%] Uptime: 22:41:15 + 4 [|||||| 10.6%] + Mem[|||||||||||||||||||||||||||||||||||||3.28G/15.3G] + Swp[ 0K/16.0G] + + PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command + 1352 aram 20 0 3776M 581M 238M S 8.7 3.7 2h01:10 /usr/lib/firefox/firefox + 98777 aram 20 0 537M 6184 4240 S 8.1 0.0 0:00.80 target/debug/mosaic --debug + 1669 aram 20 0 2944M 318M 130M S 8.1 2.0 1h01:33 /usr/lib/firefox/firefox -contentproc -childID 6 -i + 826 aram 9 -11 1581M 15092 11244 S 6.1 0.1 42:21.83 /usr/bin/pulseaudio --daemonize=no + 9419 aram 20 0 533M 7392 3344 S 4.7 0.0 22:01.92 /usr/local/bin/mosaic --max-panes 4 + 98913 aram 20 0 537M 6184 4240 S 3.4 0.0 0:00.31 target/debug/mosaic --debug + 1505 aram 20 0 3187M 329M 206M S 3.4 2.1 23:35.90 /usr/lib/firefox/firefox -contentproc -childID 2 -i + 98912 aram 20 0 537M 6184 4240 S 2.7 0.0 0:00.22 target/debug/mosaic --debug + 1164 aram -6 0 1581M 15092 11244 S 2.7 0.1 21:39.80 /usr/bin/pulseaudio --daemonize=no + 1247 aram 20 0 1184M 292M 84828 S 2.7 1.9 38:01.54 /usr/lib/Xorg -nolisten tcp :0 vt1 -keeptty -auth / + 1475 aram -11 0 3776M 581M 238M S 2.0 3.7 14:27.94 /usr/lib/firefox/firefox + 8574 aram 20 0 2944M 318M 130M S 2.0 2.0 14:36.50 /usr/lib/firefox/firefox -contentproc -childID 6 -i + 1364 aram 20 0 3776M 581M 238M S 2.0 3.7 18:01.89 /usr/lib/firefox/firefox + 1870 aram 20 0 3776M 581M 238M S 2.0 3.7 13:27.06 /usr/lib/firefox/firefox + 9427 aram 20 0 533M 7392 3344 S 2.0 0.0 6:53.47 /usr/local/bin/mosaic --max-panes 4 + 98905 aram 20 0 537M 6184 4240 S 2.0 0.0 0:00.17 target/debug/mosaic --debug + 99272 aram 20 0 8456 4348 3320 R 1.3 0.0 0:00.13 htop + 8611 aram 20 0 2944M 318M 130M S 1.3 2.0 8:17.90 /usr/lib/firefox/firefox -contentproc -childID 6 -i +F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit +Bye from Mosaic! diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop.snap new file mode 100644 index 000000000..983518757 --- /dev/null +++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop.snap @@ -0,0 +1,32 @@ +--- +source: src/tests/integration/compatibility.rs +expression: snapshot +--- + + 1 [||||| 10.1%] Tasks: 73, 413 thr; 1 running + 2 [||||||| 13.5%] Load average: 1.03 1.07 1.30 + 3 [|||||| 10.8%] Uptime: 22:41:15 + 4 [|||||| 10.6%] + Mem[|||||||||||||||||||||||||||||||||||||3.28G/15.3G] + Swp[ 0K/16.0G] + + PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command + 1352 aram 20 0 3776M 581M 238M S 8.7 3.7 2h01:10 /usr/lib/firefox/firefox + 98777 aram 20 0 537M 6184 4240 S 8.1 0.0 0:00.80 target/debug/mosaic --debug + 1669 aram 20 0 2944M 318M 130M S 8.1 2.0 1h01:33 /usr/lib/firefox/firefox -contentproc -childID 6 -i + 826 aram 9 -11 1581M 15092 11244 S 6.1 0.1 42:21.83 /usr/bin/pulseaudio --daemonize=no + 9419 aram 20 0 533M 7392 3344 S 4.7 0.0 22:01.92 /usr/local/bin/mosaic --max-panes 4 + 98913 aram 20 0 537M 6184 4240 S 3.4 0.0 0:00.31 target/debug/mosaic --debug + 1505 aram 20 0 3187M 329M 206M S 3.4 2.1 23:35.90 /usr/lib/firefox/firefox -contentproc -childID 2 -i + 98912 aram 20 0 537M 6184 4240 S 2.7 0.0 0:00.22 target/debug/mosaic --debug + 1164 aram -6 0 1581M 15092 11244 S 2.7 0.1 21:39.80 /usr/bin/pulseaudio --daemonize=no + 1247 aram 20 0 1184M 292M 84828 S 2.7 1.9 38:01.54 /usr/lib/Xorg -nolisten tcp :0 vt1 -keeptty -auth / + 1475 aram -11 0 3776M 581M 238M S 2.0 3.7 14:27.94 /usr/lib/firefox/firefox + 8574 aram 20 0 2944M 318M 130M S 2.0 2.0 14:36.50 /usr/lib/firefox/firefox -contentproc -childID 6 -i + 1364 aram 20 0 3776M 581M 238M S 2.0 3.7 18:01.89 /usr/lib/firefox/firefox + 1870 aram 20 0 3776M 581M 238M S 2.0 3.7 13:27.06 /usr/lib/firefox/firefox + 9427 aram 20 0 533M 7392 3344 S 2.0 0.0 6:53.47 /usr/local/bin/mosaic --max-panes 4 + 98905 aram 20 0 537M 6184 4240 S 2.0 0.0 0:00.17 target/debug/mosaic --debug + 99272 aram 20 0 8456 4348 3320 R 1.3 0.0 0:00.13 htop + 8611 aram 20 0 2944M 318M 130M S 1.3 2.0 8:17.90 /usr/lib/firefox/firefox -contentproc -childID 6 -i +F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 38ac57c44..031813eda 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -7,7 +7,12 @@ use std::{ use crate::utils::consts::{MOSAIC_TMP_LOG_DIR, MOSAIC_TMP_LOG_FILE}; +fn atomic_create_file(file_name: &str) { + let _ = fs::OpenOptions::new().create(true).open(file_name); +} + pub fn debug_log_to_file(message: String) -> io::Result<()> { + atomic_create_file(MOSAIC_TMP_LOG_FILE); let mut file = fs::OpenOptions::new() .append(true) .create(true) |