diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 9462796..d5a4ff6 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -201,6 +201,7 @@ impl StatefulList { } /// Return the current status of the select list, e.g. 2/5, + /// MAYBE add up down arrows, check if at start or end etc pub fn get_state_title(&self) -> String { if self.items.is_empty() { String::new() @@ -597,13 +598,12 @@ impl LogsTz { /// stateful list dependent on whether the timestamp is in the HashSet or not #[derive(Debug, Clone, PartialEq, Eq)] pub struct Logs { - // should just be list of spans? lines: StatefulList>, tz: HashSet, - // could probably be a u16 offset: u16, max_log_len: usize, adjusted_max_width: usize, + adjust_max_width_text_len: usize, } impl Default for Logs { @@ -615,6 +615,7 @@ impl Default for Logs { tz: HashSet::new(), offset: 0, adjusted_max_width: 0, + adjust_max_width_text_len: 0, max_log_len: 0, } } @@ -629,10 +630,24 @@ impl Logs { } } + // TODO test me! /// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y` - pub fn get_scroll_title(&self) -> Option { - if self.offset > 0 { - Some(format!(" {}/{} ", self.offset, self.adjusted_max_width)) + pub fn get_scroll_title(&mut self, width: u16) -> Option { + if self.horizontal_scroll_able(width) { + let text_width = self.adjust_max_width_text_len; + let arrow_left = if self.offset > 0 { " ←" } else { " " }; + let arrow_right = if usize::from(self.offset) < self.adjusted_max_width { + "→ " + } else { + " " + }; + Some(format!( + "{left} {offset:>text_width$}/{adjusted_max_width} {right}", + offset = self.offset, + adjusted_max_width = self.adjusted_max_width, + left = arrow_left, + right = arrow_right, + )) } else { None } @@ -709,13 +724,23 @@ impl Logs { self.lines.get_state_title() } + /// Return true it currently selected cotnainer logs are wide enough to horizontally scroll + pub fn horizontal_scroll_able(&mut self, width: u16) -> bool { + if self.lines.items.is_empty() { + return false; + } + self.adjusted_max_width = self.max_log_len.saturating_sub(width.into()) + 4; + self.adjust_max_width_text_len = self.adjusted_max_width.to_string().chars().count(); + self.max_log_len + 4 > usize::from(width) + } + /// Add a padding so one char will always be visilbe? - /// +6 is to account for borders & the selection triangle and a little bit of padding pub fn forward(&mut self, width: u16) { let offset = usize::from(self.offset); - self.adjusted_max_width = self.max_log_len.saturating_sub(width.into()) + 6; - if self.adjusted_max_width > 0 && offset < self.adjusted_max_width { - self.offset = self.offset.saturating_add(1); + if self.horizontal_scroll_able(width) { + if self.adjusted_max_width > 0 && offset < self.adjusted_max_width { + self.offset = self.offset.saturating_add(1); + } } } @@ -913,7 +938,7 @@ mod tests { text::{Line, Text}, }; - use crate::{ + use crate::{ app_data::{ContainerImage, Logs, LogsTz, RunningState}, ui::log_sanitizer, }; @@ -1153,4 +1178,38 @@ mod tests { result ); } + + #[test] + /// Test the get_scroll_title methods + fn test_scroll_title() { + let mut logs = Logs::default(); + + let result = logs.get_scroll_title(10); + assert!(result.is_none()); + + let input = "short".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let result = logs.get_scroll_title(10); + assert!(result.is_none()); + + let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let result = logs.get_scroll_title(10); + assert_eq!(result, Some(" 0/51 → ".to_owned())); + + logs.forward(10); + + let result = logs.get_scroll_title(10); + assert_eq!(result, Some(" ← 1/51 → ".to_owned())); + + for _ in 0..=49 { + logs.forward(10); + } + let result = logs.get_scroll_title(10); + assert_eq!(result, Some(" ← 51/51 ".to_owned())); + } } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 83c1415..ab7993e 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -638,9 +638,9 @@ impl AppData { } /// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y` - pub fn get_scroll_title(&self) -> Option { - self.get_selected_container() - .and_then(|i| i.logs.get_scroll_title()) + pub fn get_scroll_title(&mut self, width: u16) -> Option { + self.get_mut_selected_container() + .and_then(|i| i.logs.get_scroll_title(width)) } /// Increase the logs offset, basically moving an invisible cursor back diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index a42e5d4..52ecc24 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -168,7 +168,7 @@ impl DockerData { (None, None) }; - // TODO is hardcoded eth0 a good idea here? - Could use first() instead? + // TODO is hardcoded eth0 a good idea here? let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| { i.get("eth0").map_or((0, 0), |x| { ( diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index 72a2bcf..61d501f 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -356,7 +356,6 @@ mod tests { insert_logs(&setup); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); - setup .terminal .draw(|f| { diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index 56f829d..d65a37c 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -158,7 +158,7 @@ pub mod tests { /// Create a FrameData struct from two Arc's, instead of from UI impl From<(&Arc>, &Arc>)> for FrameData { fn from(data: (&Arc>, &Arc>)) -> Self { - let (app_data, gui_data) = (data.0.lock(), data.1.lock()); + let (mut app_data, gui_data) = (data.0.lock(), data.1.lock()); // let container_section_height = app_data.get_container_len(); // let container_section_height = if container_section_height < 12 { @@ -185,7 +185,7 @@ pub mod tests { loading_icon: gui_data.get_loading().to_string(), log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), - scroll_title: app_data.get_scroll_title(), + scroll_title: app_data.get_scroll_title(gui_data.get_screen_width()), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), selected_panel: gui_data.get_selected_panel(), @@ -216,6 +216,7 @@ pub mod tests { let gui_state = Arc::new(Mutex::new(gui_state)); let fd = FrameData::from((&app_data, &gui_state)); let area = Rect::new(0, 0, w, h); + gui_state.lock().set_screen_width(w); TuiTestSetup { app_data, gui_state, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b749aed..3ee8d15 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -312,7 +312,7 @@ pub struct FrameData { impl From<&Ui> for FrameData { fn from(ui: &Ui) -> Self { - let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); + let (mut app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock()); let (filter_by, filter_term) = app_data.get_filter(); Self { @@ -333,7 +333,7 @@ impl From<&Ui> for FrameData { log_title: app_data.get_log_title(), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), - scroll_title: app_data.get_scroll_title(), + scroll_title: app_data.get_scroll_title(gui_data.get_screen_width()), selected_panel: gui_data.get_selected_panel(), sorted_by: app_data.get_sorted(), status: gui_data.get_status(),