diff --git a/.gitignore b/.gitignore index 5388e4b..ee5b384 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target /releases +/binaries # Used in the zigbuild for aarch64-apple-darwin .intentionally-empty-file.o \ No newline at end of file diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index 230e982..af38848 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -100,6 +100,13 @@ "up", "k" ], + // Horizontal scroll of the logs + "log_scroll_forward": [ + "right" + ], + "log_scroll_back": [ + "left" + ], // Select next panel "select_next_panel": [ "tab" diff --git a/example_config/example.config.toml b/example_config/example.config.toml index 3c71386..46ff71f 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -86,6 +86,9 @@ scroll_start = ["home"] scroll_up_many = ["pageup"] # scroll up a list by one item scroll_up_one = ["up", "k"] +# Horizontal scroll of the logs +log_scroll_forward = ["right"] +log_scroll_back = ["left"] # Select next panel select_next_panel = ["tab"] # Select previous panel diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index de27aac..97e9f42 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -7,10 +7,7 @@ use std::{ use bollard::service::Port; use jiff::{Timestamp, tz::TimeZone}; -use ratatui::{ - style::Color, - widgets::{ListItem, ListState}, -}; +use ratatui::{layout::Size, style::Color, text::Text, widgets::ListState}; use crate::config::AppColors; @@ -563,81 +560,166 @@ impl LogsTz { /// stateful list dependent on whether the timestamp is in the HashSet or not #[derive(Debug, Clone, PartialEq, Eq)] pub struct Logs { - logs: StatefulList>, + // 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, } impl Default for Logs { fn default() -> Self { - let mut logs = StatefulList::new(vec![]); - logs.end(); + let mut lines = StatefulList::new(vec![]); + lines.end(); Self { - logs, + lines, tz: HashSet::new(), + offset: 0, + adjusted_max_width: 0, + max_log_len: 0, } } } impl Logs { /// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet - pub fn insert(&mut self, line: ListItem<'static>, tz: LogsTz) { + pub fn insert(&mut self, line: Text<'static>, tz: LogsTz) { if self.tz.insert(tz) { - self.logs.items.push(line); + self.max_log_len = self.max_log_len.max(line.width()); + self.lines.items.push(line); } } - /// Get the logs vec, but instead of cloning to whole vec, only clone items with x of the currently selected index + /// 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)) + } else { + None + } + } + + /// Format a log lone. Only return screen width amount of chars + /// If offset set, remove `char_offset` number of chars from a Text + /// `text` *should* only be a single line, so just use the .first() method rather than trying to iterate + fn format_log_line(text: &Text<'static>, char_offset: usize, width: u16) -> Text<'static> { + let mut skipped = 0; + Text::from( + text.lines + .first() + .map(|line| { + ratatui::text::Line::from( + line.spans + .iter() + .filter_map(|span| { + if skipped >= char_offset { + return Some(ratatui::text::Span::styled( + span.content.chars().take(width.into()).collect::(), + span.style, + )); + } + let span_len = span.content.chars().count(); + if skipped + span_len <= char_offset { + skipped += span_len; + None + } else { + let start_index = char_offset - skipped; + skipped = char_offset; + let new_content = span + .content + .chars() + .skip(start_index) + .take(width.into()) + .collect::(); + Some(ratatui::text::Span::styled(new_content, span.style)) + } + }) + .collect::>(), + ) + }) + .into_iter() + .collect::>(), + ) + } + + /// Get the logs vec, but instead of cloning to whole vec, only clone items within x of the currently selected index, as ell as only the current screen widths number of chars /// Where x is the abs different of the index plus the panel height & a padding + /// Take into account the char offset, so that can scroll a line /// The rest can be just empty list items - pub fn to_vec(&self, height: usize, padding: usize) -> Vec> { - let current_index = self.logs.state.selected().unwrap_or_default(); - self.logs + pub fn get_visible_logs(&self, size: Size, padding: usize) -> Vec> { + let current_index = self.lines.state.selected().unwrap_or_default(); + let height_padding = usize::from(size.height) + padding; + let char_offset = if usize::from(self.offset) > self.max_log_len { + self.max_log_len + } else { + self.offset.into() + }; + + self.lines .items .iter() .enumerate() .map(|(index, item)| { - if current_index.abs_diff(index) <= height + padding { - item.clone() + if current_index.abs_diff(index) <= height_padding { + Self::format_log_line(item, char_offset, size.width) } else { - ListItem::from("") + Text::from("") } }) .collect() } + /// The rest of the methods are basically forwarding from the underlying StatefulList pub fn get_state_title(&self) -> String { - self.logs.get_state_title() + self.lines.get_state_title() + } + + /// 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); + } + } + + /// Reduce the char offset + pub const fn back(&mut self) { + self.offset = self.offset.saturating_sub(1); } pub fn next(&mut self) { - self.logs.next(); + self.lines.next(); } pub fn previous(&mut self) { - self.logs.previous(); + self.lines.previous(); } pub fn end(&mut self) { - self.logs.end(); + self.lines.end(); } pub fn start(&mut self) { - self.logs.start(); + self.lines.start(); } - // TODO remove this once zigbuild uses Rust v1.87.0 - #[cfg(target_os = "macos")] - #[allow(clippy::missing_const_for_fn)] - pub fn len(&self) -> usize { - self.logs.items.len() - } + // // TODO remove this once zigbuild uses Rust v1.87.0 + // #[cfg(target_os = "macos")] + // #[allow(clippy::missing_const_for_fn)] + // pub fn len(&self) -> usize { + // self.logs.items.len() + // } - #[cfg(not(target_os = "macos"))] + // #[cfg(not(target_os = "macos"))] pub const fn len(&self) -> usize { - self.logs.items.len() + self.lines.items.len() } pub const fn state(&mut self) -> &mut ListState { - &mut self.logs.state + &mut self.lines.state } } @@ -801,7 +883,10 @@ impl Columns { mod tests { use jiff::tz::TimeZone; - use ratatui::widgets::ListItem; + use ratatui::{ + layout::Size, + text::{Line, Text}, + }; use crate::{ app_data::{ContainerImage, Logs, LogsTz, RunningState}, @@ -941,21 +1026,21 @@ mod tests { let mut logs = Logs::default(); let line = log_sanitizer::remove_ansi(input); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line), tz); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line), tz); - assert_eq!(logs.logs.items.len(), 1); + assert_eq!(logs.lines.items.len(), 1); let input = "2023-01-15T19:13:30.783138328Z Lorem ipsum dolor sit amet"; let (tz, _) = LogsTz::splitter(input); let line = log_sanitizer::remove_ansi(input); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line.clone()), tz.clone()); - logs.insert(ListItem::new(line), tz); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line.clone()), tz.clone()); + logs.insert(Text::from(line), tz); - assert_eq!(logs.logs.items.len(), 2); + assert_eq!(logs.lines.items.len(), 2); } #[test] @@ -1008,4 +1093,39 @@ mod tests { let input = State::from(("oxker", &healthy)); assert_eq!(input, State::Unknown); } + + #[test] + /// Test the format_log_line methods, should ideally check colours are being correct kept as well + fn test_to_vec() { + let mut logs = Logs::default(); + + 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 input = "2023-01-14T19:13:31.783138328Z Hello world some line".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + let input = "2023-01-14T19:13:32.783138328Z Hello world".to_owned(); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz); + + logs.offset = 43; + let result = logs.get_visible_logs( + Size { + width: 14, + height: 10, + }, + 10, + ); + assert_eq!( + vec![ + Text::from(Line::from("some long line")), + Text::from(Line::from("some line")), + Text::from(Line::default()) + ], + result + ); + } } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 3301476..a6e17af 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -1,7 +1,7 @@ use bollard::models::ContainerSummary; use core::fmt; use parking_lot::Mutex; -use ratatui::widgets::{ListItem, ListState}; +use ratatui::{layout::Size, text::Text, widgets::ListState}; use std::{ hash::Hash, sync::Arc, @@ -644,6 +644,28 @@ 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()) + } + + /// Increase the logs offset, basically moving an invisible cursor back + pub fn log_back(&mut self) { + if let Some(i) = self.get_mut_selected_container() { + i.logs.back(); + self.redraw.update(); + } + } + + /// Increase the logs offset, basically moving an invisible cursor forward + pub fn log_forward(&mut self, width: u16) { + if let Some(i) = self.get_mut_selected_container() { + i.logs.forward(width); + self.redraw.update(); + } + } + /// select next selected log line pub fn log_next(&mut self) { if let Some(i) = self.get_mut_selected_container() { @@ -677,12 +699,12 @@ impl AppData { } /// Get mutable Vec of current containers logs - pub fn get_logs(&self, height: u16, padding: usize) -> Vec> { + pub fn get_logs(&self, size: Size, padding: usize) -> Vec> { self.containers .state .selected() .and_then(|i| self.containers.items.get(i)) - .map_or(vec![], |i| i.logs.to_vec(height.into(), padding)) + .map_or(vec![], |i| i.logs.get_visible_logs(size, padding)) } /// Get mutable Option of the currently selected container Logs state @@ -965,7 +987,7 @@ impl AppData { } else { log_sanitizer::remove_ansi(&i) }; - container.logs.insert(ListItem::new(lines), log_tz); + container.logs.insert(Text::from(lines), log_tz); } // Set the logs selected row for each container @@ -1945,14 +1967,19 @@ mod tests { let logs = (1..=3).map(|i| format!("{i} {i}")).collect::>(); app_data.update_log_by_id(logs, &ids[0]); - // app_data.log_start(); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(2)); assert_eq!(result.unwrap().offset(), 0); - let result = app_data.get_logs(4, 1); + let result = app_data.get_logs( + Size { + width: 20, + height: 4, + }, + 1, + ); assert_eq!(result.len(), 3); let result = app_data.get_log_title(); @@ -2340,44 +2367,68 @@ mod tests { app_data.update_log_by_id(logs, &ids[0]); - let result = app_data.get_logs(10, 10); + let result = app_data.get_logs( + Size { + width: 20, + height: 10, + }, + 10, + ); for (index, item) in result.iter().enumerate() { if index < 979 { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } else { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } } - let result = app_data.get_logs(100, 20); + let result = app_data.get_logs( + Size { + width: 20, + height: 100, + }, + 20, + ); for (index, item) in result.iter().enumerate() { if index < 879 { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } else { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } } app_data.log_start(); - let result = app_data.get_logs(10, 10); + + let result = app_data.get_logs( + Size { + width: 20, + height: 10, + }, + 10, + ); for (index, item) in result.iter().enumerate() { if index > 20 { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } else { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } } for _ in 0..=500 { app_data.log_next(); } - - let result = app_data.get_logs(10, 10); + let result = app_data.get_logs( + Size { + width: 20, + height: 10, + }, + 10, + ); for (index, item) in result.iter().enumerate() { if (481..=521).contains(&index) { - assert_eq!(item, &ListItem::new(format!("{index}"))); + assert_eq!(item, &Text::from(format!("{index}"))); } else { - assert_eq!(item, &ListItem::new("")); + assert_eq!(item, &Text::from("")); } } } diff --git a/src/config/config.toml b/src/config/config.toml index 457064a..37dbd13 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -77,6 +77,7 @@ save_logs = ["s"] scroll_down_many = ["pagedown"] # scroll down a list by one item scroll_down_one = ["down", "j"] + # scroll down to the end of a list scroll_end = ["end"] # scroll up to the start of a list @@ -85,6 +86,9 @@ scroll_start = ["home"] scroll_up_many = ["pageup"] # scroll up a list by one item scroll_up_one = ["up", "k"] +# Horizontal scroll of the logs +log_scroll_forward = ["right"] +log_scroll_back = ["left"] # Select next panel select_next_panel = ["tab"] # Select previous panel diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 9847800..e66acef 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -42,6 +42,8 @@ optional_config_struct!( log_section_height_increase, log_section_height_decrease, log_section_toggle, + log_scroll_forward, + log_scroll_back, quit, save_logs, scroll_down_many, @@ -76,6 +78,8 @@ config_struct!( log_section_height_increase, log_section_height_decrease, log_section_toggle, + log_scroll_forward, + log_scroll_back, quit, save_logs, scroll_down_many, @@ -111,6 +115,8 @@ impl Keymap { log_section_height_decrease: (KeyCode::Char('-'), None), log_section_height_increase: (KeyCode::Char('='), None), log_section_toggle: (KeyCode::Char('\\'), None), + log_scroll_back: (KeyCode::Left, None), + log_scroll_forward: (KeyCode::Right, None), quit: (KeyCode::Char('q'), None), save_logs: (KeyCode::Char('s'), None), scroll_down_many: (KeyCode::PageDown, None), @@ -201,6 +207,12 @@ impl From> for Keymap { update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash); update_keymap(ck.scroll_up_many, &mut keymap.scroll_up_many, &mut clash); update_keymap(ck.scroll_up_one, &mut keymap.scroll_up_one, &mut clash); + update_keymap( + ck.log_scroll_forward, + &mut keymap.log_scroll_forward, + &mut clash, + ); + update_keymap(ck.log_scroll_back, &mut keymap.log_scroll_back, &mut clash); update_keymap( ck.select_next_panel, &mut keymap.select_next_panel, @@ -366,6 +378,8 @@ mod tests { exec: None, log_section_height_decrease: None, log_section_height_increase: None, + log_scroll_forward: None, + log_scroll_back: None, filter_mode: None, quit: None, save_logs: None, @@ -410,6 +424,8 @@ mod tests { filter_mode: gen_v(("i", "j")), log_section_height_decrease: gen_v(("-", "Z")), log_section_height_increase: gen_v(("=", "X")), + log_scroll_forward: gen_v(("right", "R")), + log_scroll_back: gen_v(("left", "L")), log_section_toggle: gen_v(("Y", "W")), quit: gen_v(("k", "l")), save_logs: gen_v(("m", "n")), @@ -444,6 +460,8 @@ mod tests { log_section_height_decrease: (KeyCode::Char('-'), Some(KeyCode::Char('Z'))), log_section_height_increase: (KeyCode::Char('='), Some(KeyCode::Char('X'))), log_section_toggle: (KeyCode::Char('Y'), Some(KeyCode::Char('W'))), + log_scroll_forward: (KeyCode::Right, Some(KeyCode::Char('R'))), + log_scroll_back: (KeyCode::Left, Some(KeyCode::Char('L'))), exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 077d5c3..1d5c22b 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -286,6 +286,23 @@ impl InputHandler { } } + /// Advance the "cursor" along the logs + fn logs_forward(&self) { + let panel = self.gui_state.lock().get_selected_panel(); + if panel == SelectablePanel::Logs { + let width = self.gui_state.lock().get_screen_width(); + self.app_data.lock().log_forward(width); + } + } + + /// Retreat the "cursor" along the logs + fn logs_back(&self) { + let panel = self.gui_state.lock().get_selected_panel(); + if panel == SelectablePanel::Logs { + self.app_data.lock().log_back(); + } + } + /// Change the the "next" selectable panel /// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state fn next_panel_key(&self) { @@ -467,6 +484,7 @@ impl InputHandler { } /// Handle button presses in all other scenarios + #[allow(clippy::cognitive_complexity)] async fn handle_others(&mut self, key_code: KeyCode) { self.handle_sort(key_code); // shift key plus arrows @@ -537,28 +555,28 @@ impl InputHandler { _ if self.keymap.scroll_up_one.0 == key_code || self.keymap.scroll_up_one.1 == Some(key_code) => { - self.previous(); + self.scroll_up(); } _ if self.keymap.scroll_up_many.0 == key_code || self.keymap.scroll_up_many.1 == Some(key_code) => { for _ in 0..=6 { - self.previous(); + self.scroll_up(); } } _ if self.keymap.scroll_down_one.0 == key_code || self.keymap.scroll_down_one.1 == Some(key_code) => { - self.next(); + self.scroll_down(); } _ if self.keymap.scroll_down_many.0 == key_code || self.keymap.scroll_down_many.1 == Some(key_code) => { for _ in 0..=6 { - self.next(); + self.scroll_down(); } } @@ -569,6 +587,18 @@ impl InputHandler { self.docker_tx.send(DockerMessage::Update).await.ok(); } + _ if self.keymap.log_scroll_back.0 == key_code + || self.keymap.log_scroll_back.1 == Some(key_code) => + { + self.logs_back(); + } + + _ if self.keymap.log_scroll_forward.0 == key_code + || self.keymap.log_scroll_forward.1 == Some(key_code) => + { + self.logs_forward(); + } + KeyCode::Enter => self.enter_key().await, _ => (), } @@ -638,8 +668,8 @@ impl InputHandler { } } else { match mouse_event.kind { - MouseEventKind::ScrollUp => self.previous(), - MouseEventKind::ScrollDown => self.next(), + MouseEventKind::ScrollUp => self.scroll_up(), + MouseEventKind::ScrollDown => self.scroll_down(), MouseEventKind::Down(MouseButton::Left) => { let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1); let header = self.gui_state.lock().get_intersect_header(mouse_point); @@ -659,7 +689,7 @@ impl InputHandler { } /// Change state to next, depending which panel is currently in focus - fn next(&self) { + fn scroll_down(&self) { let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { SelectablePanel::Containers => self.app_data.lock().containers_next(), @@ -669,7 +699,7 @@ impl InputHandler { } /// Change state to previous, depending which panel is currently in focus - fn previous(&self) { + fn scroll_up(&self) { let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { SelectablePanel::Containers => self.app_data.lock().containers_previous(), diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs index a28cbbe..c861996 100644 --- a/src/ui/draw_blocks/help.rs +++ b/src/ui/draw_blocks/help.rs @@ -84,6 +84,7 @@ impl HelpInfo { } } + // todo ← → for log moving /// Generate the button information span + metadata #[allow(clippy::too_many_lines)] fn gen_keymap_info(colors: AppColors, zone: Option<&TimeZone>, show_timestamp: bool) -> Self { @@ -111,6 +112,11 @@ impl HelpInfo { button_item("Home End"), button_desc("change selected line"), ]), + Line::from(vec![ + space(), + button_item("← →"), + button_desc("horizontal scroll across logs"), + ]), Line::from(vec![ space(), button_item("enter"), @@ -268,6 +274,8 @@ impl HelpInfo { or_secondary(km.scroll_up_many, "scroll list by up many"), or_secondary(km.scroll_end, "scroll list to end"), or_secondary(km.scroll_start, "scroll list to start"), + or_secondary(km.log_scroll_forward, "horizontal scroll logs right"), + or_secondary(km.log_scroll_back, "horizontal scroll logs left"), Line::from(vec![ space(), button_item("enter"), @@ -436,6 +444,8 @@ mod tests { #[test] /// This will cause issues once the version has more than the current 5 chars (0.5.0) + /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); + /// TODO broken wihh the horizonal scrolls! fn test_draw_blocks_help() { let mut setup = test_setup(87, 35, true, true); let tz = setup.app_data.lock().config.timezone.clone(); @@ -463,30 +473,30 @@ mod tests { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } - // border is black on magenta + // border is red on black (1 | 32, _) | (1..=31, 1 | 85) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - // oxker logo && description + // Buttons (2..=10, 2..=85) | (12, 19..=66) | (14, 2..=10 | 13..=27) | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) - | (16 | 23, 2..=12) - | (17..=20 | 22 | 25 | 27, 2..=8) - | (21, 2..=9 | 12..=18) - | (24 | 26, 2..=10) => { + | (16 | 25 | 27, 2..=10) + | (17 | 24, 2..=12) + | (18 | 19 | 20 | 21 | 23 | 26 | 28, 2..=8) + | (22, 2..=9 | 12..=18) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); } - // The URL is white and underlined - (30, 25..=60) => { + // The URL is yellow and underlined + (31, 25..=60) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); } - // The rest is black on magenta + // The rest is red on black _ => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); @@ -498,6 +508,8 @@ mod tests { #[test] /// Test that the help panel gets drawn with custom colors + /// This test is annoying + /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); fn test_draw_blocks_help_custom_colors() { let mut setup = test_setup(87, 35, true, true); let mut colors = AppColors::new(); @@ -535,20 +547,20 @@ mod tests { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Red); } - // oxker logo && description + // Buttons (2..=10, 2..=85) | (12, 19..=66) | (14, 2..=10 | 13..=27) | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) - | (16 | 23, 2..=12) - | (17..=20 | 22 | 25 | 27, 2..=8) - | (21, 2..=9 | 12..=18) - | (24 | 26, 2..=10) => { + | (16 | 25 | 27, 2..=10) + | (17 | 24, 2..=12) + | (18 | 19 | 20 | 21 | 23 | 26 | 28, 2..=8) + | (22, 2..=9 | 12..=18) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); } // The URL is yellow and underlined - (30, 25..=60) => { + (31, 25..=60) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); @@ -566,39 +578,41 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with one definition for each entry fn test_draw_blocks_help_custom_keymap_one_definition() { - let mut setup = test_setup(98, 47, true, true); + let mut setup = test_setup(98, 49, true, true); let input = Keymap { clear: (KeyCode::Char('a'), None), + delete_confirm: (KeyCode::Char('b'), None), delete_deny: (KeyCode::Char('c'), None), - delete_confirm: (KeyCode::Char('e'), None), - exec: (KeyCode::Char('g'), None), - log_section_height_decrease: (KeyCode::Char('z'), None), - log_section_height_increase: (KeyCode::Char('x'), None), - log_section_toggle: (KeyCode::Char('W'), None), - filter_mode: (KeyCode::Char('i'), None), + exec: (KeyCode::Char('d'), None), + filter_mode: (KeyCode::Char('e'), None), + log_scroll_back: (KeyCode::Char('f'), None), + log_scroll_forward: (KeyCode::Char('g'), None), + log_section_height_decrease: (KeyCode::Char('h'), None), + log_section_height_increase: (KeyCode::Char('i'), None), + log_section_toggle: (KeyCode::Char('j'), None), quit: (KeyCode::Char('k'), None), - save_logs: (KeyCode::Char('m'), None), - scroll_down_many: (KeyCode::Char('o'), None), - scroll_down_one: (KeyCode::Char('q'), None), - scroll_end: (KeyCode::Char('s'), None), - scroll_start: (KeyCode::Char('u'), None), - scroll_up_many: (KeyCode::Char('w'), None), - scroll_up_one: (KeyCode::Char('y'), None), - select_next_panel: (KeyCode::Char('0'), None), - select_previous_panel: (KeyCode::Char('2'), None), - sort_by_name: (KeyCode::Char('4'), None), - sort_by_state: (KeyCode::Char('6'), None), - sort_by_status: (KeyCode::Char('8'), None), - sort_by_cpu: (KeyCode::F(1), None), - sort_by_memory: (KeyCode::Char('#'), None), - sort_by_id: (KeyCode::Char('/'), None), - sort_by_image: (KeyCode::Char(','), None), - sort_by_rx: (KeyCode::Char('.'), None), - sort_by_tx: (KeyCode::Insert, None), - sort_reset: (KeyCode::Up, None), - toggle_help: (KeyCode::Home, None), - toggle_mouse_capture: (KeyCode::PageDown, None), + save_logs: (KeyCode::Char('l'), None), + scroll_down_many: (KeyCode::Char('m'), None), + scroll_down_one: (KeyCode::Char('n'), None), + scroll_end: (KeyCode::Char('o'), None), + scroll_start: (KeyCode::Char('p'), None), + scroll_up_many: (KeyCode::Char('q'), None), + scroll_up_one: (KeyCode::Char('r'), None), + select_next_panel: (KeyCode::Char('s'), None), + select_previous_panel: (KeyCode::Char('t'), None), + sort_by_cpu: (KeyCode::Char('u'), None), + sort_by_id: (KeyCode::Char('v'), None), + sort_by_image: (KeyCode::Char('w'), None), + sort_by_memory: (KeyCode::Char('x'), None), + sort_by_name: (KeyCode::Char('y'), None), + sort_by_rx: (KeyCode::Char('z'), None), + sort_by_state: (KeyCode::Char('0'), None), + sort_by_status: (KeyCode::Char('1'), None), + sort_by_tx: (KeyCode::Char('2'), None), + sort_reset: (KeyCode::Char('3'), None), + toggle_help: (KeyCode::Char('4'), None), + toggle_mouse_capture: (KeyCode::Char('5'), None), }; setup @@ -614,39 +628,41 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with two definition for each entry fn test_draw_blocks_help_custom_keymap_two_definitions() { - let mut setup = test_setup(110, 47, true, true); + let mut setup = test_setup(110, 49, true, true); let keymap = Keymap { - clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), - delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), - delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), - exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), - log_section_height_decrease: (KeyCode::Char('A'), Some(KeyCode::Char('Z'))), - log_section_height_increase: (KeyCode::Char('B'), Some(KeyCode::Char('X'))), - log_section_toggle: (KeyCode::Char('C'), Some(KeyCode::Char('W'))), - filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), - quit: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), - save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), - scroll_down_many: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), - scroll_down_one: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), - scroll_end: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), - scroll_start: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), - scroll_up_many: (KeyCode::Char('w'), Some(KeyCode::Char('x'))), - scroll_up_one: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), - select_next_panel: (KeyCode::Char('0'), Some(KeyCode::Char('1'))), - select_previous_panel: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), - sort_by_name: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), - sort_by_state: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), - sort_by_status: (KeyCode::Char('8'), Some(KeyCode::Char('9'))), - sort_by_cpu: (KeyCode::F(1), Some(KeyCode::F(12))), - sort_by_memory: (KeyCode::Char('#'), Some(KeyCode::Char('-'))), - sort_by_id: (KeyCode::Char('/'), Some(KeyCode::Char('='))), - sort_by_image: (KeyCode::Char(','), Some(KeyCode::Char('\\'))), - sort_by_rx: (KeyCode::Char('.'), Some(KeyCode::Char(']'))), - sort_by_tx: (KeyCode::Insert, Some(KeyCode::BackTab)), - sort_reset: (KeyCode::Up, Some(KeyCode::Down)), - toggle_help: (KeyCode::Home, Some(KeyCode::End)), - toggle_mouse_capture: (KeyCode::PageDown, Some(KeyCode::PageUp)), + clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))), + delete_confirm: (KeyCode::Char('b'), Some(KeyCode::Char('B'))), + delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), + exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))), + filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), + log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), + log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), + log_section_height_decrease: (KeyCode::Char('h'), Some(KeyCode::Char('H'))), + log_section_height_increase: (KeyCode::Char('i'), Some(KeyCode::Char('I'))), + log_section_toggle: (KeyCode::Char('j'), Some(KeyCode::Char('J'))), + quit: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), + save_logs: (KeyCode::Char('l'), Some(KeyCode::Char('L'))), + scroll_down_many: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), + scroll_down_one: (KeyCode::Char('n'), Some(KeyCode::Char('N'))), + scroll_end: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), + scroll_start: (KeyCode::Char('p'), Some(KeyCode::Char('P'))), + scroll_up_many: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), + scroll_up_one: (KeyCode::Char('r'), Some(KeyCode::Char('R'))), + select_next_panel: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), + select_previous_panel: (KeyCode::Char('t'), Some(KeyCode::Char('T'))), + sort_by_cpu: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), + sort_by_id: (KeyCode::Char('v'), Some(KeyCode::Char('V'))), + sort_by_image: (KeyCode::Char('w'), Some(KeyCode::Char('W'))), + sort_by_memory: (KeyCode::Char('x'), Some(KeyCode::Char('X'))), + sort_by_name: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))), + sort_by_rx: (KeyCode::Char('z'), Some(KeyCode::Char('Z'))), + sort_by_state: (KeyCode::Char('0'), Some(KeyCode::Char('9'))), + sort_by_status: (KeyCode::Char('1'), Some(KeyCode::Char('8'))), + sort_by_tx: (KeyCode::Char('2'), Some(KeyCode::Char('7'))), + sort_reset: (KeyCode::Char('3'), Some(KeyCode::Char('6'))), + toggle_help: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), + toggle_mouse_capture: (KeyCode::Char('5'), Some(KeyCode::PageDown)), }; setup @@ -662,39 +678,41 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with either one or two definition for each entry fn test_draw_blocks_help_one_and_two_definitions() { - let mut setup = test_setup(110, 47, true, true); + let mut setup = test_setup(110, 49, true, true); let keymap = Keymap { - clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), - delete_deny: (KeyCode::Char('c'), None), - delete_confirm: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), - exec: (KeyCode::Char('g'), None), - filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), - log_section_height_decrease: (KeyCode::Char('A'), Some(KeyCode::Char('Z'))), - log_section_height_increase: (KeyCode::Char('B'), Some(KeyCode::Char('X'))), - log_section_toggle: (KeyCode::Char('C'), Some(KeyCode::Char('W'))), - quit: (KeyCode::Char('k'), None), - save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), - scroll_down_many: (KeyCode::Char('o'), None), - scroll_down_one: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), - scroll_end: (KeyCode::Char('s'), None), - scroll_start: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), - scroll_up_many: (KeyCode::Char('w'), None), - scroll_up_one: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), - select_next_panel: (KeyCode::Char('0'), None), - select_previous_panel: (KeyCode::Char('2'), Some(KeyCode::Char('3'))), - sort_by_name: (KeyCode::Char('4'), None), - sort_by_state: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), - sort_by_status: (KeyCode::Char('8'), None), - sort_by_cpu: (KeyCode::F(1), Some(KeyCode::F(12))), - sort_by_memory: (KeyCode::Char('#'), None), - sort_by_id: (KeyCode::Char('/'), Some(KeyCode::Char('='))), - sort_by_image: (KeyCode::Char(','), None), - sort_by_rx: (KeyCode::Char('.'), Some(KeyCode::Char(']'))), - sort_by_tx: (KeyCode::Insert, None), - sort_reset: (KeyCode::Up, Some(KeyCode::Down)), - toggle_help: (KeyCode::Home, None), - toggle_mouse_capture: (KeyCode::PageDown, Some(KeyCode::PageUp)), + clear: (KeyCode::Char('a'), Some(KeyCode::Char('A'))), + delete_confirm: (KeyCode::Char('b'), None), + delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), + exec: (KeyCode::Char('d'), None), + filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), + log_scroll_back: (KeyCode::Char('f'), None), + log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), + log_section_height_decrease: (KeyCode::Char('h'), None), + log_section_height_increase: (KeyCode::Char('i'), Some(KeyCode::Char('I'))), + log_section_toggle: (KeyCode::Char('j'), None), + quit: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), + save_logs: (KeyCode::Char('l'), None), + scroll_down_many: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), + scroll_down_one: (KeyCode::Char('n'), None), + scroll_end: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), + scroll_start: (KeyCode::Char('p'), None), + scroll_up_many: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), + scroll_up_one: (KeyCode::Char('r'), None), + select_next_panel: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), + select_previous_panel: (KeyCode::Char('t'), None), + sort_by_cpu: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), + sort_by_id: (KeyCode::Char('v'), None), + sort_by_image: (KeyCode::Char('w'), Some(KeyCode::Char('W'))), + sort_by_memory: (KeyCode::Char('x'), None), + sort_by_name: (KeyCode::Char('y'), Some(KeyCode::Char('Y'))), + sort_by_rx: (KeyCode::Char('z'), None), + sort_by_state: (KeyCode::Char('0'), Some(KeyCode::Char('9'))), + sort_by_status: (KeyCode::Char('1'), None), + sort_by_tx: (KeyCode::Char('2'), Some(KeyCode::Char('7'))), + sort_reset: (KeyCode::Char('3'), None), + toggle_help: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), + toggle_mouse_capture: (KeyCode::Char('5'), None), }; let tz = setup.app_data.lock().config.timezone.clone(); diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index 6381e5b..72a2bcf 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -40,7 +40,7 @@ pub fn draw( f.render_widget(paragraph, area); } else { let padding = usize::from(area.height / 5); - let logs = app_data.lock().get_logs(area.height, padding); + let logs = app_data.lock().get_logs(area.as_size(), padding); if logs.is_empty() { let mut paragraph = Paragraph::new("no logs found") .block(block) diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index ec06c61..f97e753 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -72,7 +72,6 @@ pub fn max_line_width(text: &str) -> usize { .max() .unwrap_or_default() } - /// Generate block, add a border if is the selected panel, /// add custom title based on state of each panel fn generate_block<'a>( @@ -101,7 +100,15 @@ fn generate_block<'a>( let mut block = Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) - .title(title); + .title(ratatui::text::Line::from(title).left_aligned()); + + if panel == SelectablePanel::Logs { + if let Some(x) = fd.scroll_title.as_ref() { + block = block + .title_bottom(x.to_owned()) + .title_alignment(ratatui::layout::Alignment::Right); + } + } if !fd.status.contains(&Status::Filter) { if fd.selected_panel == panel { block = block.border_style(Style::default().fg(colors.borders.selected)); @@ -178,6 +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(), port_max_lens: app_data.get_longest_port(), ports: app_data.get_selected_ports(), selected_panel: gui_data.get_selected_panel(), diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap index 747eec0..8b03bdd 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap @@ -1,6 +1,5 @@ --- source: src/ui/draw_blocks/help.rs -assertion_line: 456 expression: setup.terminal.backend() --- " " @@ -19,6 +18,7 @@ expression: setup.terminal.backend() " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " " │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ " +" │ ( ← → ) horizontal scroll across logs │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " " │ ( h ) toggle this help information - or click heading │ " @@ -35,6 +35,5 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap index a0f9ea1..8b03bdd 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap @@ -18,6 +18,7 @@ expression: setup.terminal.backend() " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " " │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ " +" │ ( ← → ) horizontal scroll across logs │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " " │ ( h ) toggle this help information - or click heading │ " @@ -34,6 +35,5 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap index 8cebda0..fe00d6a 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap @@ -2,50 +2,52 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────╮ " -" │ │ " -" │ 88 │ " -" │ 88 │ " -" │ 88 │ " -" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " -" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " -" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " -" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " -" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " -" │ │ " -" │ A simple tui to view & control docker containers │ " -" │ │ " -" │ ( 0 ) select next panel │ " -" │ ( 2 ) select previous panel │ " -" │ ( q ) scroll list down by one │ " -" │ ( y ) scroll list up by one │ " -" │ ( o ) scroll list down by many │ " -" │ ( w ) scroll list by up many │ " -" │ ( s ) scroll list to end │ " -" │ ( u ) scroll list to start │ " -" │ ( enter ) send docker container command │ " -" │ ( g ) exec into a container │ " -" │ ( Home ) toggle this help information - or click heading │ " -" │ ( m ) save logs to file │ " -" │ ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " -" │ ( i ) enter filter mode │ " -" │ ( Up ) reset container sorting │ " -" │ ( 4 ) sort containers by name │ " -" │ ( 6 ) sort containers by state │ " -" │ ( 8 ) sort containers by status │ " -" │ ( F1 ) sort containers by cpu │ " -" │ ( # ) sort containers by memory │ " -" │ ( / ) sort containers by id │ " -" │ ( , ) sort containers by image │ " -" │ ( . ) sort containers by rx │ " -" │ ( Insert ) sort containers by tx │ " -" │ ( z ) decrease log section height │ " -" │ ( x ) increase log section height │ " -" │ ( W ) toggle log section visibility │ " -" │ ( a ) close dialog │ " -" │ ( k ) quit at any time │ " -" │ │ " -" │ currently an early work in progress, all and any input appreciated │ " -" │ https://github.com/mrjackwills/oxker │ " -" │ │ " -" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ " +" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ " +" │ │ " +" │ 88 │ " +" │ 88 │ " +" │ 88 │ " +" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " +" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " +" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " +" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " +" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " +" │ │ " +" │ A simple tui to view & control docker containers │ " +" │ │ " +" │ ( s ) select next panel │ " +" │ ( t ) select previous panel │ " +" │ ( n ) scroll list down by one │ " +" │ ( r ) scroll list up by one │ " +" │ ( m ) scroll list down by many │ " +" │ ( q ) scroll list by up many │ " +" │ ( o ) scroll list to end │ " +" │ ( p ) scroll list to start │ " +" │ ( g ) horizontal scroll logs right │ " +" │ ( f ) horizontal scroll logs left │ " +" │ ( enter ) send docker container command │ " +" │ ( d ) exec into a container │ " +" │ ( 4 ) toggle this help information - or click heading │ " +" │ ( l ) save logs to file │ " +" │ ( 5 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( e ) enter filter mode │ " +" │ ( 3 ) reset container sorting │ " +" │ ( y ) sort containers by name │ " +" │ ( 0 ) sort containers by state │ " +" │ ( 1 ) sort containers by status │ " +" │ ( u ) sort containers by cpu │ " +" │ ( x ) sort containers by memory │ " +" │ ( v ) sort containers by id │ " +" │ ( w ) sort containers by image │ " +" │ ( z ) sort containers by rx │ " +" │ ( 2 ) sort containers by tx │ " +" │ ( h ) decrease log section height │ " +" │ ( i ) increase log section height │ " +" │ ( j ) toggle log section visibility │ " +" │ ( a ) close dialog │ " +" │ ( k ) quit at any time │ " +" │ │ " +" │ currently an early work in progress, all and any input appreciated │ " +" │ https://github.com/mrjackwills/oxker │ " +" │ │ " +" ╰────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap index 818fc99..6ae3453 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap @@ -2,50 +2,52 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " -" │ │ " -" │ 88 │ " -" │ 88 │ " -" │ 88 │ " -" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " -" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " -" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " -" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " -" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " -" │ │ " -" │ A simple tui to view & control docker containers │ " -" │ │ " -" │ ( 0 ) or ( 1 ) select next panel │ " -" │ ( 2 ) or ( 3 ) select previous panel │ " -" │ ( q ) or ( r ) scroll list down by one │ " -" │ ( y ) or ( z ) scroll list up by one │ " -" │ ( o ) or ( p ) scroll list down by many │ " -" │ ( w ) or ( x ) scroll list by up many │ " -" │ ( s ) or ( t ) scroll list to end │ " -" │ ( u ) or ( v ) scroll list to start │ " -" │ ( enter ) send docker container command │ " -" │ ( g ) or ( h ) exec into a container │ " -" │ ( Home ) or ( End ) toggle this help information - or click heading │ " -" │ ( m ) or ( n ) save logs to file │ " -" │ ( Page Down ) or ( Page Up ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " -" │ ( i ) or ( j ) enter filter mode │ " -" │ ( Up ) or ( Down ) reset container sorting │ " -" │ ( 4 ) or ( 5 ) sort containers by name │ " -" │ ( 6 ) or ( 7 ) sort containers by state │ " -" │ ( 8 ) or ( 9 ) sort containers by status │ " -" │ ( F1 ) or ( F12 ) sort containers by cpu │ " -" │ ( # ) or ( - ) sort containers by memory │ " -" │ ( / ) or ( = ) sort containers by id │ " -" │ ( , ) or ( \ ) sort containers by image │ " -" │ ( . ) or ( ] ) sort containers by rx │ " -" │ ( Insert ) or ( Back Tab ) sort containers by tx │ " -" │ ( A ) or ( Z ) decrease log section height │ " -" │ ( B ) or ( X ) increase log section height │ " -" │ ( C ) or ( W ) toggle log section visibility │ " -" │ ( a ) or ( b ) close dialog │ " -" │ ( k ) or ( l ) quit at any time │ " -" │ │ " -" │ currently an early work in progress, all and any input appreciated │ " -" │ https://github.com/mrjackwills/oxker │ " -" │ │ " -" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " +" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────────────────────╮ " +" │ │ " +" │ 88 │ " +" │ 88 │ " +" │ 88 │ " +" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " +" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " +" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " +" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " +" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " +" │ │ " +" │ A simple tui to view & control docker containers │ " +" │ │ " +" │ ( s ) or ( S ) select next panel │ " +" │ ( t ) or ( T ) select previous panel │ " +" │ ( n ) or ( N ) scroll list down by one │ " +" │ ( r ) or ( R ) scroll list up by one │ " +" │ ( m ) or ( M ) scroll list down by many │ " +" │ ( q ) or ( Q ) scroll list by up many │ " +" │ ( o ) or ( O ) scroll list to end │ " +" │ ( p ) or ( P ) scroll list to start │ " +" │ ( g ) or ( G ) horizontal scroll logs right │ " +" │ ( f ) or ( F ) horizontal scroll logs left │ " +" │ ( enter ) send docker container command │ " +" │ ( d ) or ( D ) exec into a container │ " +" │ ( 4 ) or ( 5 ) toggle this help information - or click heading │ " +" │ ( l ) or ( L ) save logs to file │ " +" │ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( e ) or ( E ) enter filter mode │ " +" │ ( 3 ) or ( 6 ) reset container sorting │ " +" │ ( y ) or ( Y ) sort containers by name │ " +" │ ( 0 ) or ( 9 ) sort containers by state │ " +" │ ( 1 ) or ( 8 ) sort containers by status │ " +" │ ( u ) or ( U ) sort containers by cpu │ " +" │ ( x ) or ( X ) sort containers by memory │ " +" │ ( v ) or ( V ) sort containers by id │ " +" │ ( w ) or ( W ) sort containers by image │ " +" │ ( z ) or ( Z ) sort containers by rx │ " +" │ ( 2 ) or ( 7 ) sort containers by tx │ " +" │ ( h ) or ( H ) decrease log section height │ " +" │ ( i ) or ( I ) increase log section height │ " +" │ ( j ) or ( J ) toggle log section visibility │ " +" │ ( a ) or ( A ) close dialog │ " +" │ ( k ) or ( K ) quit at any time │ " +" │ │ " +" │ currently an early work in progress, all and any input appreciated │ " +" │ https://github.com/mrjackwills/oxker │ " +" │ │ " +" ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap index 1778328..1b760e1 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap @@ -2,50 +2,52 @@ source: src/ui/draw_blocks/help.rs expression: setup.terminal.backend() --- -" ╭ 0.00.000 ────────────────────────────────────────────────────────────────────────────────────────────────╮ " -" │ │ " -" │ 88 │ " -" │ 88 │ " -" │ 88 │ " -" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " -" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " -" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " -" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " -" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " -" │ │ " -" │ A simple tui to view & control docker containers │ " -" │ │ " -" │ ( 0 ) select next panel │ " -" │ ( 2 ) or ( 3 ) select previous panel │ " -" │ ( q ) or ( r ) scroll list down by one │ " -" │ ( y ) or ( z ) scroll list up by one │ " -" │ ( o ) scroll list down by many │ " -" │ ( w ) scroll list by up many │ " -" │ ( s ) scroll list to end │ " -" │ ( u ) or ( v ) scroll list to start │ " -" │ ( enter ) send docker container command │ " -" │ ( g ) exec into a container │ " -" │ ( Home ) toggle this help information - or click heading │ " -" │ ( m ) or ( n ) save logs to file │ " -" │ ( Page Down ) or ( Page Up ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " -" │ ( i ) or ( j ) enter filter mode │ " -" │ ( Up ) or ( Down ) reset container sorting │ " -" │ ( 4 ) sort containers by name │ " -" │ ( 6 ) or ( 7 ) sort containers by state │ " -" │ ( 8 ) sort containers by status │ " -" │ ( F1 ) or ( F12 ) sort containers by cpu │ " -" │ ( # ) sort containers by memory │ " -" │ ( / ) or ( = ) sort containers by id │ " -" │ ( , ) sort containers by image │ " -" │ ( . ) or ( ] ) sort containers by rx │ " -" │ ( Insert ) sort containers by tx │ " -" │ ( A ) or ( Z ) decrease log section height │ " -" │ ( B ) or ( X ) increase log section height │ " -" │ ( C ) or ( W ) toggle log section visibility │ " -" │ ( a ) or ( b ) close dialog │ " -" │ ( k ) quit at any time │ " -" │ │ " -" │ currently an early work in progress, all and any input appreciated │ " -" │ https://github.com/mrjackwills/oxker │ " -" │ │ " -" ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ " +" ╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ " +" │ │ " +" │ 88 │ " +" │ 88 │ " +" │ 88 │ " +" │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ " +" │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ " +" │ 8b d8 )888( 8888[ 8PP""""""" 88 │ " +" │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ " +" │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ " +" │ │ " +" │ A simple tui to view & control docker containers │ " +" │ │ " +" │ ( s ) or ( S ) select next panel │ " +" │ ( t ) select previous panel │ " +" │ ( n ) scroll list down by one │ " +" │ ( r ) scroll list up by one │ " +" │ ( m ) or ( M ) scroll list down by many │ " +" │ ( q ) or ( Q ) scroll list by up many │ " +" │ ( o ) or ( O ) scroll list to end │ " +" │ ( p ) scroll list to start │ " +" │ ( g ) or ( G ) horizontal scroll logs right │ " +" │ ( f ) horizontal scroll logs left │ " +" │ ( enter ) send docker container command │ " +" │ ( d ) exec into a container │ " +" │ ( 4 ) or ( 5 ) toggle this help information - or click heading │ " +" │ ( l ) save logs to file │ " +" │ ( 5 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( e ) or ( E ) enter filter mode │ " +" │ ( 3 ) reset container sorting │ " +" │ ( y ) or ( Y ) sort containers by name │ " +" │ ( 0 ) or ( 9 ) sort containers by state │ " +" │ ( 1 ) sort containers by status │ " +" │ ( u ) or ( U ) sort containers by cpu │ " +" │ ( x ) sort containers by memory │ " +" │ ( v ) sort containers by id │ " +" │ ( w ) or ( W ) sort containers by image │ " +" │ ( z ) sort containers by rx │ " +" │ ( 2 ) or ( 7 ) sort containers by tx │ " +" │ ( h ) decrease log section height │ " +" │ ( i ) or ( I ) increase log section height │ " +" │ ( j ) toggle log section visibility │ " +" │ ( a ) or ( A ) close dialog │ " +" │ ( k ) or ( K ) quit at any time │ " +" │ │ " +" │ currently an early work in progress, all and any input appreciated │ " +" │ https://github.com/mrjackwills/oxker │ " +" │ │ " +" ╰────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap index 291cc9f..ceed130 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap @@ -20,6 +20,7 @@ expression: setup.terminal.backend() " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " " │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ " +" │ ( ← → ) horizontal scroll across logs │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " " │ ( h ) toggle this help information - or click heading │ " @@ -36,6 +37,5 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " -" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap index ed89388..ad38e21 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap @@ -5,22 +5,22 @@ expression: setup.terminal.backend() " name state status cpu memory/limit id image ↓ rx ↑ tx ( h ) exit help " "╭ Containers 1/3 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮╭──────────────╮" "│⚪ container_1 ✓ running Up 1 hour 03.00% 30.00 kB / 30.00 kB 1 image_1 0.00 kB 0.00 kB ││▶ pause │" Hidden by multi-width symbols: [(2, " ")] -"│ container_2 ✓ running Up 2 hour 00.00% 0.00 kB / 0.00 kB 2 image_2 0.00 kB 0.00 kB ││ restart │" -"│ container_3 ✓ running Up 3 ho╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ ││ stop │" -"│ │ │ ││ delete │" +"│ container_2 ✓ running Up 2 ho╭ 0.00.000 ──────────────────────────────────────────────────────────────────────────╮ ││ restart │" +"│ container_3 ✓ running Up 3 ho│ │ ││ stop │" +"│ │ 88 │ ││ delete │" "│ │ 88 │ ││ │" "╰────────────────────────────────────│ 88 │────────────────────╯╰──────────────╯" -"╭ Logs 3/3 - container_1 - image_1 ──│ 88 │────────────────────────────────────╮" -"│ line 1 │ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │ │" -"│ line 2 │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ │" -"│▶ line 3 │ 8b d8 )888( 8888[ 8PP""""""" 88 │ │" -"│ │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ │" +"╭ Logs 3/3 - container_1 - image_1 ──│ ,adPPYba, 8b, ,d8 88 ,d8 ,adPPYba, 8b,dPPYba, │────────────────────────────────────╮" +"│ line 1 │ a8" "8a `Y8, ,8P' 88 ,a8" a8P_____88 88P' "Y8 │ │" +"│ line 2 │ 8b d8 )888( 8888[ 8PP""""""" 88 │ │" +"│▶ line 3 │ "8a, ,a8" ,d8" "8b, 88`"Yba, "8b, ,aa 88 │ │" "│ │ `"YbbdP"' 8P' `Y8 88 `Y8a `"Ybbd8"' 88 │ │" "│ │ │ │" "│ │ A simple tui to view & control docker containers │ │" "│ │ │ │" "│ │ ( tab ) or ( shift+tab ) change panels │ │" "│ │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) change selected line │ │" +"│ │ ( ← → ) horizontal scroll across logs │ │" "│ │ ( enter ) send docker container command │ │" "│ │ ( e ) exec into a container │ │" "│ │ ( h ) toggle this help information - or click heading │ │" @@ -37,8 +37,8 @@ expression: setup.terminal.backend() "│ │ • • │ currently an early work in progress, all and any input appreciated │ ││ 8001 │" "│ │ •• • │ https://github.com/mrjackwills/oxker │ ││127.0.0.1 8003 8003│" "│ │ • • │ │ ││ │" -"│ │ •• • • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │" -"│ │• •• ││ │• •• ││ │" +"│ │ •• • • │ │ ││ │" +"│ │• •• ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │" "│ │• • ││ │• • ││ │" "│ │ ││ │ ││ │" "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index e16e720..5915363 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -187,6 +187,7 @@ pub struct GuiState { log_height: u16, rerender: Arc, selected_panel: SelectablePanel, + screen_width: u16, show_logs: bool, status: HashSet, pub info_box_text: Option<(String, Instant)>, @@ -205,6 +206,7 @@ impl GuiState { loading_index: 0, loading_set: HashSet::new(), log_height: 75, + screen_width: 0, rerender: Arc::clone(redraw), selected_panel: SelectablePanel::default(), show_logs, @@ -232,6 +234,16 @@ impl GuiState { } } + /// Set the screen width, used for offset char calculations + pub const fn set_screen_width(&mut self, width: u16) { + self.screen_width = width; + } + + /// Get the screen width, used for offset char calculations + pub const fn get_screen_width(&self) -> u16 { + self.screen_width + } + pub const fn get_show_logs(&self) -> bool { self.show_logs } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index de55e09..412a6be 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -205,6 +205,10 @@ impl Ui { let docker_interval_ms = u128::from(self.app_data.lock().config.docker_interval_ms); let mut drawn_at = std::time::Instant::now(); + if let Ok(size) = self.terminal.size() { + self.gui_state.lock().set_screen_width(size.width); + } + while self.is_running.load(Ordering::SeqCst) { if self.should_redraw(&mut drawn_at, docker_interval_ms) { let fd = FrameData::from(&*self); @@ -243,11 +247,14 @@ impl Ui { } _ => (), } - } else if let Event::Resize(_, _) = event { + } else if let Event::Resize(width, _) = event { self.gui_state.lock().clear_area_map(); + // self.gui_state.lock().set_window_height(row); self.terminal.autoresize().ok(); + // todo set screen width + self.gui_state.lock().set_screen_width(width); } } } @@ -279,7 +286,6 @@ pub struct FrameData { filter_by: FilterBy, filter_term: Option, has_containers: bool, - // container_section_height: u16, log_height: u16, show_logs: bool, has_error: Option, @@ -290,6 +296,7 @@ pub struct FrameData { port_max_lens: (usize, usize, usize), ports: Option<(Vec, State)>, selected_panel: SelectablePanel, + scroll_title: Option, sorted_by: Option<(Header, SortedOrder)>, status: HashSet, } @@ -317,6 +324,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(), selected_panel: gui_data.get_selected_panel(), sorted_by: app_data.get_sorted(), status: gui_data.get_status(),