diff --git a/Cargo.lock b/Cargo.lock index 266ed837a..5ca326c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,7 +510,9 @@ dependencies = [ "portable-pty", "rand", "ratatui", + "serde", "temp-dir", + "toml", "tree-sitter-bash", "tree-sitter-highlight", "tui-term", diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 40d3c357d..a55746526 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -21,6 +21,8 @@ ego-tree = { workspace = true } oneshot = "0.1.8" portable-pty = "0.8.1" ratatui = "0.28.1" +serde = { version = "1.0.205", features = ["derive"], default-features = false } +toml = { version = "0.8.19", features = ["parse"], default-features = false } tui-term = "0.1.12" temp-dir = "0.1.14" unicode-width = "0.2.0" diff --git a/tui/src/config.rs b/tui/src/config.rs new file mode 100644 index 000000000..84d1b7863 --- /dev/null +++ b/tui/src/config.rs @@ -0,0 +1,28 @@ +use serde::Deserialize; +use std::path::Path; +use std::process; + +#[derive(Deserialize)] +pub struct Config { + pub auto_select: Vec, +} + +impl Config { + pub fn from_file(path: &Path) -> Self { + let content = match std::fs::read_to_string(path) { + Ok(content) => content, + Err(e) => { + eprintln!("Failed to read config file {}: {}", path.display(), e); + process::exit(1); + } + }; + + match toml::from_str(&content) { + Ok(config) => config, + Err(e) => { + eprintln!("Failed to parse config file: {}", e); + process::exit(1); + } + } + } +} diff --git a/tui/src/main.rs b/tui/src/main.rs index 801e3b1d2..b665a408b 100644 --- a/tui/src/main.rs +++ b/tui/src/main.rs @@ -1,3 +1,4 @@ +mod config; mod confirmation; mod filter; mod float; @@ -9,6 +10,7 @@ mod theme; use std::{ io::{self, stdout}, + path::PathBuf, time::Duration, }; @@ -26,6 +28,11 @@ use state::AppState; // Linux utility toolbox #[derive(Debug, Parser)] struct Args { + #[arg( + long, + help = "Path to the configuration file for automatic command selection" + )] + config: Option, #[arg(short, long, value_enum)] #[arg(default_value_t = Theme::Default)] #[arg(help = "Set the theme to use in the application")] @@ -38,7 +45,7 @@ struct Args { fn main() -> io::Result<()> { let args = Args::parse(); - let mut state = AppState::new(args.theme, args.override_validation); + let mut state = AppState::new(args.theme, args.override_validation, args.config); stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; diff --git a/tui/src/state.rs b/tui/src/state.rs index 9ed61771d..85d7cad4a 100644 --- a/tui/src/state.rs +++ b/tui/src/state.rs @@ -1,3 +1,4 @@ +use crate::config::Config; use crate::{ confirmation::{ConfirmPrompt, ConfirmStatus}, filter::{Filter, SearchAction}, @@ -19,6 +20,7 @@ use ratatui::{ widgets::{Block, Borders, List, ListState, Paragraph}, Frame, }; +use std::path::PathBuf; use std::rc::Rc; use temp_dir::TempDir; @@ -60,6 +62,7 @@ pub struct AppState { multi_select: bool, selected_commands: Vec>, drawable: bool, + auto_select: Option>, #[cfg(feature = "tips")] tip: &'static str, } @@ -79,10 +82,12 @@ pub struct ListEntry { } impl AppState { - pub fn new(theme: Theme, override_validation: bool) -> Self { + pub fn new(theme: Theme, override_validation: bool, config_path: Option) -> Self { let (temp_dir, tabs) = linutil_core::get_tabs(!override_validation); let root_id = tabs[0].tree.root().id(); + let auto_select = config_path.map(|path| Config::from_file(&path).auto_select); + let mut state = Self { _temp_dir: temp_dir, theme, @@ -95,14 +100,44 @@ impl AppState { multi_select: false, selected_commands: Vec::new(), drawable: false, + auto_select, #[cfg(feature = "tips")] tip: get_random_tip(), }; state.update_items(); + + if let Some(auto_select) = state.auto_select.clone() { + state.handle_initial_auto_select(&auto_select); + } + state } + fn handle_initial_auto_select(&mut self, auto_select: &[String]) { + let item_list = self.filter.item_list(); + self.selected_commands = auto_select + .iter() + .filter_map(|name| { + item_list + .iter() + .find(|item| item.node.name == *name) + .map(|item| item.node.clone()) + }) + .collect(); + + if !self.selected_commands.is_empty() { + let cmd_names = self + .selected_commands + .iter() + .map(|node| node.name.as_str()) + .collect::>(); + + let prompt = ConfirmPrompt::new(&cmd_names); + self.focus = Focus::ConfirmationPrompt(Float::new(Box::new(prompt), 40, 40)); + } + } + fn get_list_item_shortcut(&self) -> Box<[Shortcut]> { if self.selected_item_is_dir() { Box::new([Shortcut::new("Go to selected dir", ["l", "Right", "Enter"])])