fix(compatibility): various htop issues (#66)

* fix(compatibility): various htop issues

* style(format): make rustfmt happy

* fix(logging): do not delete log dir on startup

* fix(tests): update htop with command toggle

* chore(ci): reduce test concurrency to 1
This commit is contained in:
Aram Drevekenin 2020-11-23 18:01:16 +01:00 committed by GitHub
parent fd1f1ce697
commit f88abe6ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 257 additions and 32 deletions

View File

@ -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

View File

@ -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): (

View File

@ -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());
}

View File

@ -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);

BIN
src/tests/fixtures/htop vendored Normal file

Binary file not shown.

View File

@ -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);
}
}

View File

@ -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!

View File

@ -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

View File

@ -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)