summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/rust.yml2
-rw-r--r--src/main.rs3
-rw-r--r--src/terminal_pane/scroll.rs108
-rw-r--r--src/terminal_pane/terminal_pane.rs84
-rw-r--r--src/tests/fixtures/htopbin0 -> 16234 bytes
-rw-r--r--src/tests/integration/compatibility.rs23
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop-2.snap32
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__htop.snap32
-rw-r--r--src/utils/logging.rs5
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
new file mode 100644
index 000000000..37ee07771
--- /dev/null
+++ b/src/tests/fixtures/htop
Binary files differ
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)