diff --git a/tui/src/floating_text.rs b/tui/src/floating_text.rs index 83eb9ce2e..fefe9b9ad 100644 --- a/tui/src/floating_text.rs +++ b/tui/src/floating_text.rs @@ -294,11 +294,11 @@ impl FloatContent for FloatingText { ( self.mode_title, Box::new([ - Shortcut::new(vec!["j", "Down"], "Scroll down"), - Shortcut::new(vec!["k", "Up"], "Scroll up"), - Shortcut::new(vec!["h", "Left"], "Scroll left"), - Shortcut::new(vec!["l", "Right"], "Scroll right"), - Shortcut::new(vec!["Enter", "p", "d", "g"], "Close window"), + Shortcut::new("Scroll down", ["j", "Down"]), + Shortcut::new("Scroll up", ["k", "Up"]), + Shortcut::new("Scroll left", ["h", "Left"]), + Shortcut::new("Scroll right", ["l", "Right"]), + Shortcut::new("Close window", ["Enter", "p", "d", "g"]), ]), ) } diff --git a/tui/src/hint.rs b/tui/src/hint.rs index c98afe9d0..338ce6748 100644 --- a/tui/src/hint.rs +++ b/tui/src/hint.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use ratatui::{ style::{Style, Stylize}, text::{Line, Span}, @@ -31,16 +33,23 @@ pub fn create_shortcut_list( let mut lines: Vec> = vec![]; - for _ in 0.. { + loop { let split_idx = shortcut_spans .iter() .scan(0usize, |total_len, s| { - *total_len += span_vec_len(s); - if *total_len > render_width as usize { - None + // take at least one so that we guarantee that we drain the list + // otherwise, this might lock up the + if *total_len == 0 { + *total_len += span_vec_len(s) + 4; + Some(()) } else { - *total_len += 4; - Some(1) + *total_len += span_vec_len(s); + if *total_len > render_width as usize { + None + } else { + *total_len += 4; + Some(()) + } } }) .count(); @@ -59,11 +68,11 @@ pub fn create_shortcut_list( } impl Shortcut { - pub fn new(key_sequences: Vec<&'static str>, desc: &'static str) -> Self { + pub fn new(desc: &'static str, key_sequences: [&'static str; LEN]) -> Self { Self { key_sequences: key_sequences .iter() - .map(|s| Span::styled(*s, Style::default().bold())) + .map(|s| Span::styled(Cow::<'static, str>::Borrowed(s), Style::default().bold())) .collect(), desc, } diff --git a/tui/src/running_command.rs b/tui/src/running_command.rs index 1ae27ff12..366a3dc36 100644 --- a/tui/src/running_command.rs +++ b/tui/src/running_command.rs @@ -121,12 +121,12 @@ impl FloatContent for RunningCommand { if self.is_finished() { ( "Finished command", - Box::new([Shortcut::new(vec!["Enter", "q"], "Close window")]), + Box::new([Shortcut::new("Close window", ["Enter", "q"])]), ) } else { ( "Running command", - Box::new([Shortcut::new(vec!["CTRL-c"], "Kill the command")]), + Box::new([Shortcut::new("Kill the command", ["CTRL-c"])]), ) } } diff --git a/tui/src/state.rs b/tui/src/state.rs index c1c58d683..3448a3af4 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -76,6 +76,7 @@ impl AppState { pub fn new(theme: Theme, override_validation: bool) -> Self { let tabs = linutil_core::get_tabs(!override_validation); let root_id = tabs[0].tree.root().id(); + let mut state = Self { theme, focus: Focus::List, @@ -90,22 +91,20 @@ impl AppState { #[cfg(feature = "tips")] tip: get_random_tip(), }; + state.update_items(); state } - fn get_list_item_shortcut(&self) -> Vec { + fn get_list_item_shortcut(&self) -> Box<[Shortcut]> { if self.selected_item_is_dir() { - vec![Shortcut::new( - vec!["l", "Right", "Enter"], - "Go to selected dir", - )] + Box::new([Shortcut::new("Go to selected dir", ["l", "Right", "Enter"])]) } else { - vec![ - Shortcut::new(vec!["l", "Right", "Enter"], "Run selected command"), - Shortcut::new(vec!["p"], "Enable preview"), - Shortcut::new(vec!["d"], "Command Description"), - ] + Box::new([ + Shortcut::new("Run selected command", ["l", "Right", "Enter"]), + Shortcut::new("Enable preview", ["p"]), + Shortcut::new("Command Description", ["d"]), + ]) } } @@ -113,39 +112,39 @@ impl AppState { match self.focus { Focus::Search => ( "Search bar", - Box::new([Shortcut::new(vec!["Enter"], "Finish search")]), + Box::new([Shortcut::new("Finish search", ["Enter"])]), ), Focus::List => { let mut hints = Vec::new(); - hints.push(Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil")); + hints.push(Shortcut::new("Exit linutil", ["q", "CTRL-c"])); if self.at_root() { - hints.push(Shortcut::new(vec!["h", "Left"], "Focus tab list")); + hints.push(Shortcut::new("Focus tab list", ["h", "Left"])); hints.extend(self.get_list_item_shortcut()); } else if self.selected_item_is_up_dir() { hints.push(Shortcut::new( - vec!["l", "Right", "Enter", "h", "Left"], "Go to parent directory", + ["l", "Right", "Enter", "h", "Left"], )); } else { - hints.push(Shortcut::new(vec!["h", "Left"], "Go to parent directory")); + hints.push(Shortcut::new("Go to parent directory", ["h", "Left"])); hints.extend(self.get_list_item_shortcut()); } - hints.push(Shortcut::new(vec!["k", "Up"], "Select item above")); - hints.push(Shortcut::new(vec!["j", "Down"], "Select item below")); - hints.push(Shortcut::new(vec!["t"], "Next theme")); - hints.push(Shortcut::new(vec!["T"], "Previous theme")); + hints.push(Shortcut::new("Select item above", ["k", "Up"])); + hints.push(Shortcut::new("Select item below", ["j", "Down"])); + hints.push(Shortcut::new("Next theme", ["t"])); + hints.push(Shortcut::new("Previous theme", ["T"])); if self.is_current_tab_multi_selectable() { - hints.push(Shortcut::new(vec!["v"], "Toggle multi-selection mode")); - hints.push(Shortcut::new(vec!["Space"], "Select multiple commands")); + hints.push(Shortcut::new("Toggle multi-selection mode", ["v"])); + hints.push(Shortcut::new("Select multiple commands", ["Space"])); } - hints.push(Shortcut::new(vec!["Tab"], "Next tab")); - hints.push(Shortcut::new(vec!["Shift-Tab"], "Previous tab")); - hints.push(Shortcut::new(vec!["g"], "Important actions guide")); + hints.push(Shortcut::new("Next tab", ["Tab"])); + hints.push(Shortcut::new("Previous tab", ["Shift-Tab"])); + hints.push(Shortcut::new("Important actions guide", ["g"])); ("Command list", hints.into_boxed_slice()) } @@ -153,14 +152,14 @@ impl AppState { Focus::TabList => ( "Tab list", Box::new([ - Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"), - Shortcut::new(vec!["l", "Right", "Enter"], "Focus action list"), - Shortcut::new(vec!["k", "Up"], "Select item above"), - Shortcut::new(vec!["j", "Down"], "Select item below"), - Shortcut::new(vec!["t"], "Next theme"), - Shortcut::new(vec!["T"], "Previous theme"), - Shortcut::new(vec!["Tab"], "Next tab"), - Shortcut::new(vec!["Shift-Tab"], "Previous tab"), + Shortcut::new("Exit linutil", ["q", "CTRL-c"]), + Shortcut::new("Focus action list", ["l", "Right", "Enter"]), + Shortcut::new("Select item above", ["k", "Up"]), + Shortcut::new("Select item below", ["j", "Down"]), + Shortcut::new("Next theme", ["t"]), + Shortcut::new("Previous theme", ["T"]), + Shortcut::new("Next tab", ["Tab"]), + Shortcut::new("Previous tab", ["Shift-Tab"]), ]), ), @@ -444,11 +443,13 @@ impl AppState { self.focus = Focus::List; } } + Focus::Search => match self.filter.handle_key(key) { SearchAction::Exit => self.exit_search(), SearchAction::Update => self.update_items(), SearchAction::None => {} }, + Focus::TabList => match key.code { KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.focus = Focus::List, @@ -470,19 +471,14 @@ impl AppState { KeyCode::Char('g') => self.toggle_task_list_guide(), _ => {} }, + Focus::List if key.kind != KeyEventKind::Release => match key.code { KeyCode::Char('j') | KeyCode::Down => self.selection.select_next(), KeyCode::Char('k') | KeyCode::Up => self.selection.select_previous(), KeyCode::Char('p') | KeyCode::Char('P') => self.enable_preview(), KeyCode::Char('d') | KeyCode::Char('D') => self.enable_description(), KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => self.handle_enter(), - KeyCode::Char('h') | KeyCode::Left => { - if self.at_root() { - self.focus = Focus::TabList; - } else { - self.enter_parent_directory(); - } - } + KeyCode::Char('h') | KeyCode::Left => self.go_back(), KeyCode::Char('/') => self.enter_search(), KeyCode::Char('t') => self.theme.next(), KeyCode::Char('T') => self.theme.prev(), @@ -491,10 +487,12 @@ impl AppState { KeyCode::Char(' ') if self.multi_select => self.toggle_selection(), _ => {} }, + _ => (), }; true } + fn toggle_multi_select(&mut self) { if self.is_current_tab_multi_selectable() { self.multi_select = !self.multi_select; @@ -503,6 +501,7 @@ impl AppState { } } } + fn toggle_selection(&mut self) { if let Some(command) = self.get_selected_command() { if self.selected_commands.contains(&command) { @@ -512,12 +511,14 @@ impl AppState { } } } + pub fn is_current_tab_multi_selectable(&self) -> bool { let index = self.current_tab.selected().unwrap_or(0); self.tabs .get(index) .map_or(false, |tab| tab.multi_selectable) } + fn update_items(&mut self) { self.filter.update_items( &self.tabs, @@ -537,11 +538,20 @@ impl AppState { self.visit_stack.len() == 1 } + fn go_back(&mut self) { + if self.at_root() { + self.focus = Focus::TabList; + } else { + self.enter_parent_directory(); + } + } + fn enter_parent_directory(&mut self) { self.visit_stack.pop(); self.selection.select(Some(0)); self.update_items(); } + fn get_selected_node(&self) -> Option<&ListNode> { let mut selected_index = self.selection.selected().unwrap_or(0);