From ae1fece1dfac0e3b00729e5590c7a0737b11c6ea Mon Sep 17 00:00:00 2001 From: JEEVITHA KANNAN K S Date: Wed, 11 Sep 2024 09:25:10 +0530 Subject: [PATCH] Add hints Single command execution bug fix --- Cargo.lock | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/hint.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/state.rs | 9 +-- 3 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 src/hint.rs diff --git a/Cargo.lock b/Cargo.lock index a87ca14f7..c8e2b954e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.14" @@ -99,6 +114,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "cassowary" version = "0.3.0" @@ -114,12 +135,35 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cc" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.5", +] + [[package]] name = "clap" version = "4.5.16" @@ -179,6 +223,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crossterm" version = "0.27.0" @@ -280,6 +330,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "include_dir" version = "0.7.4" @@ -339,6 +412,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -423,6 +505,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -703,6 +794,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.17" @@ -871,6 +968,7 @@ dependencies = [ name = "tui" version = "0.1.0" dependencies = [ + "chrono", "clap", "crossterm", "ego-tree", @@ -976,6 +1074,61 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + [[package]] name = "which" version = "6.0.3" @@ -1010,6 +1163,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/src/hint.rs b/src/hint.rs new file mode 100644 index 000000000..76e2e9eab --- /dev/null +++ b/src/hint.rs @@ -0,0 +1,165 @@ +use ratatui::{ + layout::{Margin, Rect}, + style::{Style, Stylize}, + text::{Line, Span}, + widgets::{Block, Borders, Paragraph}, + Frame, +}; + +use crate::state::{AppState, Focus}; + +pub const SHORTCUT_LINES: usize = 2; + +pub struct ShortcutList { + pub scope_name: &'static str, + pub hints: Vec, +} + +pub struct Shortcut { + pub key_sequenses: Vec>, + pub desc: &'static str, +} + +pub fn span_vec_len(span_vec: &[Span]) -> usize { + span_vec.iter().rfold(0, |init, s| init + s.width()) +} +impl ShortcutList { + pub fn draw(&self, frame: &mut Frame, area: Rect) { + let block = Block::default() + .title(self.scope_name) + .borders(Borders::all()); + let inner_area = area.inner(Margin::new(1, 1)); + let mut shortcut_list: Vec> = self.hints.iter().map(|h| h.to_spans()).collect(); + + let mut lines = vec![Line::default(); SHORTCUT_LINES]; + let mut idx = 0; + + while idx < SHORTCUT_LINES - 1 { + let split_idx = shortcut_list + .iter() + .scan(0usize, |total_len, s| { + *total_len += span_vec_len(s); + if *total_len > inner_area.width as usize { + None + } else { + *total_len += 4; + Some(1) + } + }) + .count(); + let new_shortcut_list = shortcut_list.split_off(split_idx); + let line: Vec<_> = shortcut_list + .into_iter() + .flat_map(|mut s| { + s.push(Span::default().content(" ")); + s + }) + .collect(); + shortcut_list = new_shortcut_list; + lines[idx] = line.into(); + idx += 1; + } + lines[idx] = shortcut_list + .into_iter() + .flat_map(|mut s| { + s.push(Span::default().content(" ")); + s + }) + .collect(); + + let p = Paragraph::new(lines).block(block); + frame.render_widget(p, area); + } +} + +impl Shortcut { + pub fn new(key_sequences: Vec<&'static str>, desc: &'static str) -> Self { + Self { + key_sequenses: key_sequences + .iter() + .map(|s| Span::styled(*s, Style::default().bold())) + .collect(), + desc, + } + } + + fn to_spans(&self) -> Vec { + let mut ret: Vec<_> = self + .key_sequenses + .iter() + .flat_map(|seq| { + [ + Span::default().content("["), + seq.clone(), + Span::default().content("] "), + ] + }) + .collect(); + ret.push(Span::styled(self.desc, Style::default().italic())); + ret + } +} + +fn get_list_item_shortcut(state: &AppState) -> Shortcut { + if state.selected_item_is_dir() { + Shortcut::new(vec!["l", "Right", "Enter"], "Go to selected dir") + } else { + Shortcut::new(vec!["l", "Right", "Enter"], "Run selected command") + } +} + +pub fn draw_shortcuts(state: &AppState, frame: &mut Frame, area: Rect) { + match state.focus { + Focus::Search => ShortcutList { + scope_name: "Search bar", + hints: vec![Shortcut::new(vec!["Enter"], "Finish search")], + }, + Focus::List => { + let mut hints = Vec::new(); + hints.push(Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil")); + if state.at_root() { + hints.push(Shortcut::new(vec!["h", "Left", "Tab"], "Focus tab list")); + hints.push(get_list_item_shortcut(state)); + } else { + if state.selected_item_is_up_dir() { + hints.push(Shortcut::new( + vec!["l", "Right", "Enter", "h", "Left"], + "Go to parrent directory", + )); + } else { + hints.push(Shortcut::new(vec!["h", "Left"], "Go to parrent directory")); + hints.push(get_list_item_shortcut(state)); + if state.selected_item_is_cmd() { + hints.push(Shortcut::new(vec!["p"], "Enable preview")); + } + } + hints.push(Shortcut::new(vec!["Tab"], "Focus tab list")); + }; + 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")); + if state.is_current_tab_multi_selectable() { + hints.push(Shortcut::new(vec!["v"], "Toggle Multi selection")); + hints.push(Shortcut::new(vec!["Space"], "Multi select commands")); + } + ShortcutList { + scope_name: "Item list", + hints, + } + } + Focus::TabList => ShortcutList { + scope_name: "Tab list", + hints: vec![ + Shortcut::new(vec!["q", "CTRL-c"], "Exit linutil"), + Shortcut::new(vec!["l", "Right", "Tab", "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"), + ], + }, + Focus::FloatingWindow(ref float) => float.get_shortcut_list(), + } + .draw(frame, area); +} diff --git a/src/state.rs b/src/state.rs index 11e7685b4..ab7256fe4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -246,7 +246,7 @@ impl AppState { } } - fn is_current_tab_multi_selectable(&self) -> bool { + pub fn is_current_tab_multi_selectable(&self) -> bool { let index = self.current_tab.selected().unwrap_or(0); self.tabs .get(index) @@ -313,14 +313,11 @@ impl AppState { if let Some(cmd) = self.get_selected_command(true) { self.selected_commands.push(cmd); } - } - - // Only spawn the floating window if there are selected commands - // This prevents the floating window when changing directories - if !self.selected_commands.is_empty() { let command = RunningCommand::new(self.selected_commands.clone()); self.spawn_float(command, 80, 80); self.selected_commands.clear(); + } else { + self.go_to_selected_dir(); } } fn spawn_float(&mut self, float: T, width: u16, height: u16) {