From d54a69d74b6bbadac38e1cca30aa76bc8207035c Mon Sep 17 00:00:00 2001 From: Michael Nedokushev Date: Sat, 21 Sep 2024 18:03:54 +0100 Subject: [PATCH 1/3] Rename Tui.process_table_scroll to process_table_scroll_state to make names more consistent --- src/tui/rendering.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tui/rendering.rs b/src/tui/rendering.rs index 41be367..cce956e 100644 --- a/src/tui/rendering.rs +++ b/src/tui/rendering.rs @@ -38,7 +38,7 @@ impl Theme { pub struct Tui { process_table: TableState, - process_table_scroll: ScrollbarState, + process_table_scroll_state: ScrollbarState, theme: Theme, number_of_items: usize, details_scroll_state: ScrollbarState, @@ -53,7 +53,7 @@ impl Tui { search_area.move_cursor(tui_textarea::CursorMove::End); Self { process_table: TableState::default(), - process_table_scroll: ScrollbarState::new(0), + process_table_scroll_state: ScrollbarState::new(0), theme: Theme::new(), number_of_items: 0, process_details_scroll: 0, @@ -73,8 +73,8 @@ impl Tui { i }); self.process_table.select(next_row_index); - self.process_table_scroll = self - .process_table_scroll + self.process_table_scroll_state = self + .process_table_scroll_state .position(next_row_index.unwrap_or(0)); self.reset_process_detals_scroll(); } @@ -85,8 +85,8 @@ impl Tui { i.clamp(0, self.number_of_items.saturating_sub(1)) }); self.process_table.select(previous_index); - self.process_table_scroll = self - .process_table_scroll + self.process_table_scroll_state = self + .process_table_scroll_state .position(previous_index.unwrap_or(0)); self.reset_process_detals_scroll(); } @@ -129,8 +129,8 @@ impl Tui { pub fn update_number_of_items(&mut self, number_of_items: usize) { self.number_of_items = number_of_items; - self.process_table_scroll = self - .process_table_scroll + self.process_table_scroll_state = self + .process_table_scroll_state .content_length(number_of_items.saturating_sub(1)); if number_of_items == 0 { self.process_table.select(None); @@ -246,7 +246,7 @@ impl Tui { vertical: 1, horizontal: 1, }), - &mut self.process_table_scroll, + &mut self.process_table_scroll_state, ); } From cba3d8d5e705acdc26fd8e0c4a1933b3920652e7 Mon Sep 17 00:00:00 2001 From: Michael Nedokushev Date: Sat, 21 Sep 2024 21:29:33 +0100 Subject: [PATCH 2/3] Fix infinite scroll in process details area --- src/tui.rs | 6 +-- src/tui/rendering.rs | 89 ++++++++++++++++++++++++++++++-------------- 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index e7ed8cb..a0b9e34 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -43,7 +43,7 @@ impl App { self.search_results = self .process_manager .find_processes(self.tui.search_input_text(), self.filter_options); - self.tui.update_number_of_items(self.search_results.len()); + self.tui.update_process_table_number_of_items(self.search_results.len()); } fn delete_char(&mut self) { @@ -62,7 +62,7 @@ impl App { self.search_results.remove(pid); //TODO: this must be here because details will show 1/0 when removed! // seems like this can only be fixed by autorefresh! - self.tui.update_number_of_items(self.search_results.len()); + self.tui.update_process_table_number_of_items(self.search_results.len()); } else { self.tui .set_error_message("Failed to kill process, check permissions"); @@ -119,7 +119,7 @@ fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<( app.search_for_processess() } Char('f') if key.modifiers.contains(KeyModifiers::CONTROL) => { - app.tui.process_details_down() + app.tui.process_details_down(&mut terminal.get_frame()) } Char('b') if key.modifiers.contains(KeyModifiers::CONTROL) => { app.tui.process_details_up() diff --git a/src/tui/rendering.rs b/src/tui/rendering.rs index cce956e..d229909 100644 --- a/src/tui/rendering.rs +++ b/src/tui/rendering.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::{borrow::Cow, rc::Rc}; use crossterm::event::KeyEvent; use ratatui::{ @@ -37,12 +37,13 @@ impl Theme { } pub struct Tui { + theme: Theme, process_table: TableState, process_table_scroll_state: ScrollbarState, - theme: Theme, - number_of_items: usize, - details_scroll_state: ScrollbarState, - process_details_scroll: u16, + process_table_number_of_items: usize, + process_details_scroll_state: ScrollbarState, + process_details_scroll_offset: u16, + process_details_number_of_lines: u16, search_area: TextArea<'static>, error_message: Option<&'static str>, } @@ -55,10 +56,11 @@ impl Tui { process_table: TableState::default(), process_table_scroll_state: ScrollbarState::new(0), theme: Theme::new(), - number_of_items: 0, - process_details_scroll: 0, + process_table_number_of_items: 0, + process_details_scroll_offset: 0, + process_details_number_of_lines: 0, //NOTE: we don't update this, value 1 means that this should be rendered - details_scroll_state: ScrollbarState::new(1), + process_details_scroll_state: ScrollbarState::new(1), search_area, error_message: None, } @@ -67,7 +69,7 @@ impl Tui { pub fn select_next_row(&mut self) { let next_row_index = self.process_table.selected().map(|i| { let mut i = i + 1; - if i >= self.number_of_items { + if i >= self.process_table_number_of_items { i = 0 } i @@ -82,7 +84,7 @@ impl Tui { pub fn select_previous_row(&mut self) { let previous_index = self.process_table.selected().map(|i| { let i = i.wrapping_sub(1); - i.clamp(0, self.number_of_items.saturating_sub(1)) + i.clamp(0, self.process_table_number_of_items.saturating_sub(1)) }); self.process_table.select(previous_index); self.process_table_scroll_state = self @@ -99,16 +101,25 @@ impl Tui { self.search_area.insert_char(new_char); } - pub fn process_details_down(&mut self) { - self.process_details_scroll = self.process_details_scroll.saturating_add(1); + pub fn process_details_down(&mut self, frame: &mut Frame) { + let rects = layout_rects(frame); + let process_details_area = rects[2]; + let area_content_height = process_details_area.height - 2; + let content_scrolled = + self.process_details_number_of_lines - self.process_details_scroll_offset; + + if content_scrolled > area_content_height { + self.process_details_scroll_offset = + self.process_details_scroll_offset.saturating_add(1); + } } pub fn process_details_up(&mut self) { - self.process_details_scroll = self.process_details_scroll.saturating_sub(1); + self.process_details_scroll_offset = self.process_details_scroll_offset.saturating_sub(1); } fn reset_process_detals_scroll(&mut self) { - self.process_details_scroll = 0; + self.process_details_scroll_offset = 0; } pub fn set_error_message(&mut self, message: &'static str) { @@ -127,8 +138,8 @@ impl Tui { self.process_table.selected() } - pub fn update_number_of_items(&mut self, number_of_items: usize) { - self.number_of_items = number_of_items; + pub fn update_process_table_number_of_items(&mut self, number_of_items: usize) { + self.process_table_number_of_items = number_of_items; self.process_table_scroll_state = self .process_table_scroll_state .content_length(number_of_items.saturating_sub(1)); @@ -144,18 +155,10 @@ impl Tui { } pub fn render_ui(&mut self, search_results: &ProcessSearchResults, frame: &mut Frame) { - let rects = Layout::vertical([ - Constraint::Length(1), - Constraint::Min(10), - Constraint::Max(7), - Constraint::Length(1), - ]) - .split(frame.area()); + let rects = layout_rects(frame); self.render_search_input(frame, rects[0]); - self.render_process_table(frame, search_results, rects[1]); - self.render_process_details(frame, search_results, rects[2]); render_help(frame, self.error_message, rects[3]); @@ -258,6 +261,9 @@ impl Tui { ) { let selected_process = search_results.nth(self.get_selected_row_index()); let lines = process_details_lines(selected_process); + + self.update_process_details_number_of_lines(area, selected_process); + let info_footer = Paragraph::new(lines) .wrap(Wrap { trim: false }) .left_aligned() @@ -272,7 +278,7 @@ impl Tui { // .border_style(Style::new().fg(app.colors.footer_border_color)) .border_type(BorderType::Rounded), ) - .scroll((self.process_details_scroll, 0)); + .scroll((self.process_details_scroll_offset, 0)); f.render_widget(info_footer, area); f.render_stateful_widget( Scrollbar::default() @@ -282,9 +288,28 @@ impl Tui { .begin_symbol(Some("↑")) .end_symbol(Some("↓")), area, - &mut self.details_scroll_state, + &mut self.process_details_scroll_state, ); } + + fn update_process_details_number_of_lines( + &mut self, + area: Rect, + selected_process: Option<&Process>, + ) { + let content_width = area.width - 2; + + match selected_process { + Some(process) => { + let args_number_of_lines = + (process.args.chars().count() as u16 / content_width) + 1; + self.process_details_number_of_lines = args_number_of_lines + 2; + } + None => { + self.process_details_number_of_lines = 1; + } + } + } } fn dynamic_search_column(search_result: &ProcessSearchResults) -> (&str, fn(&Process) -> &str) { @@ -341,3 +366,13 @@ fn render_help(f: &mut Frame, error_message: Option<&str>, area: Rect) { f.render_widget(error, rects[0]); f.render_widget(help, rects[1]); } + +fn layout_rects(frame: &mut Frame) -> Rc<[Rect]> { + Layout::vertical([ + Constraint::Length(1), + Constraint::Min(10), + Constraint::Max(7), + Constraint::Length(1), + ]) + .split(frame.area()) +} From 99e33239e5a85039e17cd6715b438987513eec4e Mon Sep 17 00:00:00 2001 From: Michael Nedokushev Date: Mon, 23 Sep 2024 09:40:10 +0100 Subject: [PATCH 3/3] Fix formatting --- src/tui.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index a0b9e34..2e45633 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -43,7 +43,8 @@ impl App { self.search_results = self .process_manager .find_processes(self.tui.search_input_text(), self.filter_options); - self.tui.update_process_table_number_of_items(self.search_results.len()); + self.tui + .update_process_table_number_of_items(self.search_results.len()); } fn delete_char(&mut self) { @@ -62,7 +63,8 @@ impl App { self.search_results.remove(pid); //TODO: this must be here because details will show 1/0 when removed! // seems like this can only be fixed by autorefresh! - self.tui.update_process_table_number_of_items(self.search_results.len()); + self.tui + .update_process_table_number_of_items(self.search_results.len()); } else { self.tui .set_error_message("Failed to kill process, check permissions");