fic(compatibility): htop scrolling issues (#77)
* fix(compatibility): scrolling inside scroll region with 'M' esc dispatch * style(format): make rustfmt happy * fix(tests): send proper data * fix(logs): do not crash if log file doesn't exist
This commit is contained in:
parent
ea549f151e
commit
c47fed927e
13 changed files with 179 additions and 16 deletions
BIN
htop-debugging/currently-debugging.log
Normal file
BIN
htop-debugging/currently-debugging.log
Normal file
Binary file not shown.
BIN
htop-debugging/mosaic-3.log
Normal file
BIN
htop-debugging/mosaic-3.log
Normal file
Binary file not shown.
|
|
@ -450,10 +450,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
|
||||||
}
|
}
|
||||||
// cleanup();
|
// cleanup();
|
||||||
let reset_style = "\u{1b}[m";
|
let reset_style = "\u{1b}[m";
|
||||||
|
let show_cursor = "\u{1b}[?25h";
|
||||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||||
let goodbye_message = format!(
|
let goodbye_message = format!(
|
||||||
"{}\n{}Bye from Mosaic!",
|
"{}\n{}{}Bye from Mosaic!",
|
||||||
goto_start_of_last_line, reset_style
|
goto_start_of_last_line, reset_style, show_cursor
|
||||||
);
|
);
|
||||||
|
|
||||||
os_input.unset_raw_mode(0);
|
os_input.unset_raw_mode(0);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::os_input_output::OsApi;
|
use crate::os_input_output::OsApi;
|
||||||
use crate::utils::logging::debug_to_file;
|
use crate::utils::logging::{debug_log_to_file, debug_to_file};
|
||||||
use crate::ScreenInstruction;
|
use crate::ScreenInstruction;
|
||||||
|
|
||||||
pub struct ReadFromPid {
|
pub struct ReadFromPid {
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,28 @@ impl CanonicalLine {
|
||||||
|
|
||||||
self.wrapped_fragments.truncate(fragment_index + 1);
|
self.wrapped_fragments.truncate(fragment_index + 1);
|
||||||
}
|
}
|
||||||
|
pub fn replace_with_empty_chars_before_cursor(
|
||||||
|
&mut self,
|
||||||
|
fragment_index: usize,
|
||||||
|
until_col: usize,
|
||||||
|
style_of_empty_space: CharacterStyles,
|
||||||
|
) {
|
||||||
|
let mut empty_char_character = EMPTY_TERMINAL_CHARACTER;
|
||||||
|
empty_char_character.styles = style_of_empty_space;
|
||||||
|
|
||||||
|
if fragment_index > 0 {
|
||||||
|
for i in 0..(fragment_index - 1) {
|
||||||
|
let fragment = self.wrapped_fragments.get_mut(i).unwrap();
|
||||||
|
for i in 0..fragment.characters.len() {
|
||||||
|
fragment.add_character(empty_char_character, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let current_fragment = self.wrapped_fragments.get_mut(fragment_index).unwrap();
|
||||||
|
for i in 0..until_col {
|
||||||
|
current_fragment.add_character(empty_char_character, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for CanonicalLine {
|
impl Debug for CanonicalLine {
|
||||||
|
|
@ -331,21 +353,18 @@ impl Scroll {
|
||||||
pub fn add_canonical_line(&mut self) {
|
pub fn add_canonical_line(&mut self) {
|
||||||
let current_canonical_line_index = self.cursor_position.line_index.0;
|
let current_canonical_line_index = self.cursor_position.line_index.0;
|
||||||
if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
|
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
|
|
||||||
if self.show_cursor {
|
if self.show_cursor {
|
||||||
// scroll region should be ignored if the cursor is hidden
|
// scroll region should be ignored if the cursor is hidden
|
||||||
let scroll_region_top_index = scroll_region_top - 1;
|
if current_canonical_line_index == scroll_region_bottom {
|
||||||
let scroll_region_bottom_index = scroll_region_bottom - 1;
|
|
||||||
if current_canonical_line_index == scroll_region_bottom_index + 1 {
|
|
||||||
// end of scroll region
|
// end of scroll region
|
||||||
// when we have a scroll region set and we're at its bottom
|
// 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
|
// 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
|
// 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
|
// controlling the scroll region (presumably filled by whatever comes next in the
|
||||||
// scroll buffer, but that's not something we control)
|
// scroll buffer, but that's not something we control)
|
||||||
self.canonical_lines.remove(scroll_region_top_index);
|
self.canonical_lines.remove(scroll_region_top);
|
||||||
self.canonical_lines
|
self.canonical_lines
|
||||||
.insert(scroll_region_bottom_index + 1, CanonicalLine::new());
|
.insert(scroll_region_bottom, CanonicalLine::new());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -473,6 +492,20 @@ impl Scroll {
|
||||||
style_of_empty_space,
|
style_of_empty_space,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
pub fn clear_canonical_line_left_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;
|
||||||
|
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_before_cursor(
|
||||||
|
current_line_wrap_position,
|
||||||
|
current_cursor_column_position,
|
||||||
|
style_of_empty_space,
|
||||||
|
);
|
||||||
|
}
|
||||||
pub fn clear_all_after_cursor(&mut self) {
|
pub fn clear_all_after_cursor(&mut self) {
|
||||||
let (current_canonical_line_index, current_line_wrap_position) =
|
let (current_canonical_line_index, current_line_wrap_position) =
|
||||||
self.cursor_position.line_index;
|
self.cursor_position.line_index;
|
||||||
|
|
@ -592,6 +625,25 @@ impl Scroll {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn move_cursor_up_in_scroll_region(&mut self, count: usize) {
|
||||||
|
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
|
||||||
|
for _ in 0..count {
|
||||||
|
let current_canonical_line_index = self.cursor_position.line_index.0;
|
||||||
|
if current_canonical_line_index == scroll_region_top {
|
||||||
|
// if we're at the top line, we create a new line and remove the last line that
|
||||||
|
// would otherwise overflow
|
||||||
|
self.canonical_lines.remove(scroll_region_bottom);
|
||||||
|
self.canonical_lines
|
||||||
|
.insert(current_canonical_line_index, CanonicalLine::new());
|
||||||
|
} else if current_canonical_line_index > scroll_region_top
|
||||||
|
&& current_canonical_line_index <= scroll_region_bottom
|
||||||
|
{
|
||||||
|
self.move_cursor_up(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/// [scroll_up](https://github.com/alacritty/alacritty/blob/ec42b42ce601808070462111c0c28edb0e89babb/alacritty_terminal/src/grid/mod.rs#L261)
|
/// [scroll_up](https://github.com/alacritty/alacritty/blob/ec42b42ce601808070462111c0c28edb0e89babb/alacritty_terminal/src/grid/mod.rs#L261)
|
||||||
/// This function takes the first line of the scroll region and moves it to the bottom (count times)
|
/// This function takes the first line of the scroll region and moves it to the bottom (count times)
|
||||||
pub fn rotate_scroll_region_up(&mut self, count: usize) {
|
pub fn rotate_scroll_region_up(&mut self, count: usize) {
|
||||||
|
|
|
||||||
|
|
@ -687,8 +687,11 @@ impl vte::Perform for TerminalPane {
|
||||||
if params[0] == 0 {
|
if params[0] == 0 {
|
||||||
self.scroll
|
self.scroll
|
||||||
.clear_canonical_line_right_of_cursor(self.pending_styles);
|
.clear_canonical_line_right_of_cursor(self.pending_styles);
|
||||||
|
} else if params[0] == 1 {
|
||||||
|
self.scroll
|
||||||
|
.clear_canonical_line_left_of_cursor(self.pending_styles);
|
||||||
}
|
}
|
||||||
// TODO: implement 1 and 2
|
// TODO: implement 2
|
||||||
} else if c == 'J' {
|
} else if c == 'J' {
|
||||||
// clear all (0 => below, 1 => above, 2 => all, 3 => saved)
|
// clear all (0 => below, 1 => above, 2 => all, 3 => saved)
|
||||||
if params[0] == 0 {
|
if params[0] == 0 {
|
||||||
|
|
@ -826,12 +829,21 @@ impl vte::Perform for TerminalPane {
|
||||||
self.scroll.delete_lines_in_scroll_region(count);
|
self.scroll.delete_lines_in_scroll_region(count);
|
||||||
self.scroll.add_empty_lines_in_scroll_region(count);
|
self.scroll.add_empty_lines_in_scroll_region(count);
|
||||||
} else {
|
} else {
|
||||||
debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)).unwrap();
|
let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
|
||||||
panic!("unhandled csi: {}->{:?}", c, params);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {
|
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
|
||||||
// TBD
|
match (byte, intermediates.get(0)) {
|
||||||
|
(b'M', None) => {
|
||||||
|
self.scroll.move_cursor_up_in_scroll_region(1);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let _ = debug_log_to_file(format!(
|
||||||
|
"Unhandled esc_dispatch: {}->{:?}",
|
||||||
|
byte, intermediates
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
src/tests/fixtures/htop_scrolling
vendored
Normal file
BIN
src/tests/fixtures/htop_scrolling
vendored
Normal file
Binary file not shown.
|
|
@ -214,3 +214,26 @@ pub fn htop() {
|
||||||
assert_snapshot!(snapshot);
|
assert_snapshot!(snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn htop_scrolling() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 116,
|
||||||
|
rows: 28,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let fixture_name = "htop_scrolling";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,4 @@ expression: snapshot
|
||||||
99272 aram 20 0 8456 4348 3320 R 1.3 0.0 0:00.13 htop
|
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
|
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
|
F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit
|
||||||
Bye from Mosaic!
|
Bye from Mosaic!█
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
source: src/tests/integration/compatibility.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
1 [||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 79, 382 thr; 1 running
|
||||||
|
2 [ 0.0%] Load average: 1.40 1.43 1.38
|
||||||
|
3 [ 0.0%] Uptime: 2 days, 07:33:50
|
||||||
|
4 [ 0.0%]
|
||||||
|
Mem[|||||||||||||||||||||||||||||||||||||3.64G/15.3G]
|
||||||
|
Swp[ 0K/16.0G]
|
||||||
|
|
||||||
|
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
|
||||||
|
123934 aram 20 0 8444 4384 3364 R 66.7 0.0 0:00.05 htop --delay=100000000000
|
||||||
|
1 root 20 0 171M 11616 8608 S 0.0 0.1 0:56.91 /sbin/init
|
||||||
|
268 root 20 0 93324 34340 33072 S 0.0 0.2 0:01.05 /usr/lib/systemd/systemd-journald
|
||||||
|
276 root 20 0 32648 10192 7240 S 0.0 0.1 0:01.13 /usr/lib/systemd/systemd-udevd
|
||||||
|
286 root 20 0 78060 1132 996 S 0.0 0.0 0:00.00 /usr/bin/lvmetad -f
|
||||||
|
343 dbus 20 0 6952 4384 3520 S 0.0 0.0 0:13.85 /usr/bin/dbus-daemon --system --address=systemd: --
|
||||||
|
344 root 20 0 14588 7512 6176 S 0.0 0.0 0:03.21 /usr/bin/connmand -n --nodnsproxy
|
||||||
|
345 root 20 0 17696 8372 7128 S 0.0 0.1 0:00.67 /usr/lib/systemd/systemd-logind
|
||||||
|
395 root 20 0 1635M 83324 44976 S 0.0 0.5 0:32.43 /usr/bin/dockerd -H fd://
|
||||||
|
396 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.01 /usr/bin/dockerd -H fd://
|
||||||
|
397 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.01 /usr/bin/dockerd -H fd://
|
||||||
|
398 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
|
||||||
|
399 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
|
||||||
|
412 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
|
||||||
|
441 root 20 0 1635M 83324 44976 S 0.0 0.5 0:41.35 /usr/bin/dockerd -H fd://
|
||||||
|
442 root 20 0 1635M 83324 44976 S 0.0 0.5 0:33.61 /usr/bin/dockerd -H fd://
|
||||||
|
444 root 20 0 1635M 83324 44976 S 0.0 0.5 0:33.13 /usr/bin/dockerd -H fd://
|
||||||
|
449 root 20 0 1635M 83324 44976 S 0.0 0.5 0:41.39 /usr/bin/dockerd -H fd://
|
||||||
|
F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit
|
||||||
|
Bye from Mosaic!█
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
source: src/tests/integration/compatibility.rs
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
1 [||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 79, 382 thr; 1 running
|
||||||
|
2 [ 0.0%] Load average: 1.40 1.43 1.38
|
||||||
|
3 [ 0.0%] Uptime: 2 days, 07:33:50
|
||||||
|
4 [ 0.0%]
|
||||||
|
Mem[|||||||||||||||||||||||||||||||||||||3.64G/15.3G]
|
||||||
|
Swp[ 0K/16.0G]
|
||||||
|
|
||||||
|
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
|
||||||
|
123934 aram 20 0 8444 4384 3364 R 66.7 0.0 0:00.05 htop --delay=100000000000
|
||||||
|
1 root 20 0 171M 11616 8608 S 0.0 0.1 0:56.91 /sbin/init
|
||||||
|
268 root 20 0 93324 34340 33072 S 0.0 0.2 0:01.05 /usr/lib/systemd/systemd-journald
|
||||||
|
276 root 20 0 32648 10192 7240 S 0.0 0.1 0:01.13 /usr/lib/systemd/systemd-udevd
|
||||||
|
286 root 20 0 78060 1132 996 S 0.0 0.0 0:00.00 /usr/bin/lvmetad -f
|
||||||
|
343 dbus 20 0 6952 4384 3520 S 0.0 0.0 0:13.85 /usr/bin/dbus-daemon --system --address=systemd: --
|
||||||
|
344 root 20 0 14588 7512 6176 S 0.0 0.0 0:03.21 /usr/bin/connmand -n --nodnsproxy
|
||||||
|
345 root 20 0 17696 8372 7128 S 0.0 0.1 0:00.67 /usr/lib/systemd/systemd-logind
|
||||||
|
395 root 20 0 1635M 83324 44976 S 0.0 0.5 0:32.43 /usr/bin/dockerd -H fd://
|
||||||
|
396 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.01 /usr/bin/dockerd -H fd://
|
||||||
|
397 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.01 /usr/bin/dockerd -H fd://
|
||||||
|
398 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
|
||||||
|
399 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
|
||||||
|
412 root 20 0 1635M 83324 44976 S 0.0 0.5 0:00.00 /usr/bin/dockerd -H fd://
|
||||||
|
441 root 20 0 1635M 83324 44976 S 0.0 0.5 0:41.35 /usr/bin/dockerd -H fd://
|
||||||
|
442 root 20 0 1635M 83324 44976 S 0.0 0.5 0:33.61 /usr/bin/dockerd -H fd://
|
||||||
|
444 root 20 0 1635M 83324 44976 S 0.0 0.5 0:33.13 /usr/bin/dockerd -H fd://
|
||||||
|
449 root 20 0 1635M 83324 44976 S 0.0 0.5 0:41.39 /usr/bin/dockerd -H fd://
|
||||||
|
F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit
|
||||||
|
█
|
||||||
|
|
@ -29,4 +29,4 @@ expression: snapshot
|
||||||
│ │
|
│ │
|
||||||
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
Press <SPACE> to pause. Use <TAB> to rearrange tables. (DNS queries hidden).
|
Press <SPACE> to pause. Use <TAB> to rearrange tables. (DNS queries hidden).
|
||||||
Bye from Mosaic!
|
Bye from Mosaic!█
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,17 @@ pub fn debug_log_to_file(message: String) -> io::Result<()> {
|
||||||
file.write_all(b"\n")
|
file.write_all(b"\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> {
|
||||||
|
atomic_create_file(MOSAIC_TMP_LOG_FILE);
|
||||||
|
let mut file = fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(MOSAIC_TMP_LOG_FILE)?;
|
||||||
|
file.write_all(message.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn debug_log_to_file_pid_0(message: String, pid: RawFd) -> io::Result<()> {
|
pub fn debug_log_to_file_pid_0(message: String, pid: RawFd) -> io::Result<()> {
|
||||||
if pid == 0 {
|
if pid == 0 {
|
||||||
debug_log_to_file(message)
|
debug_log_to_file(message)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue