diff --git a/src/list.rs b/src/list.rs index 8a193897..1b22f762 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,4 +1,4 @@ -use crate::{float::floating_window, state::AppState}; +use crate::{float::floating_window, running_command::Command, state::AppState}; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use ego_tree::{tree, NodeId}; use ratatui::{ @@ -9,18 +9,9 @@ use ratatui::{ Frame, }; -macro_rules! with_common_script { - ($command:expr) => { - concat!( - include_str!("commands/common-script.sh"), - include_str!($command) - ) - }; -} - struct ListNode { name: &'static str, - command: &'static str, + command: Command, } /// This is a data structure that has everything necessary to draw and manage a menu of commands @@ -60,60 +51,74 @@ impl CustomList { // case the tree! macro expands to `ego-tree::tree` data structure let tree = tree!(ListNode { name: "root", - command: "" + command: Command::None, } => { ListNode { name: "Full System Update", - command: with_common_script!("commands/system-update.sh"), + command: Command::LocalFile("system-update.sh"), }, ListNode { name: "Setup Bash Prompt", - command: "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\"" + command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""), }, ListNode { name: "Setup Neovim", - command: "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\"" + command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""), }, - // ListNode { - // name: "Just ls, nothing special, trust me", - // command: include_str!("commands/special_ls.sh"), - // }, ListNode { name: "System Setup", - command: "" + command: Command::None, } => { ListNode { name: "Build Prerequisites", - command: with_common_script!("commands/system-setup/1-compile-setup.sh"), + command: Command::LocalFile("system-setup/1-compile-setup.sh"), }, ListNode { name: "Gaming Dependencies", - command: with_common_script!("commands/system-setup/2-gaming-setup.sh"), + command: Command::LocalFile("system-setup/2-gaming-setup.sh"), }, ListNode { name: "Global Theme", - command: with_common_script!("commands/system-setup/3-global-theme.sh"), + command: Command::LocalFile("system-setup/3-global-theme.sh"), }, - ListNode { - name: "Recursion?", - command: "cargo run" - } }, ListNode { name: "Titus Dotfiles", - command: "" + command: Command::None } => { ListNode { name: "Alacritty Setup", - command: with_common_script!("commands/dotfiles/alacritty-setup.sh"), + command: Command::LocalFile("dotfiles/alacritty-setup.sh"), }, ListNode { name: "Kitty Setup", - command: with_common_script!("commands/dotfiles/kitty-setup.sh"), + command: Command::LocalFile("dotfiles/kitty-setup.sh"), }, ListNode { name: "Rofi Setup", - command: with_common_script!("commands/dotfiles/rofi-setup.sh"), + command: Command::LocalFile("dotfiles/rofi-setup.sh"), + }, + }, + + ListNode { + name: "Testing category", + command: Command::None, + } => { + ListNode { + name: "Complex command", + command: Command::Raw("sleep 1 && ls -la && sleep 1 && ls -la && echo Bonus eza comming... && sleep 1 && ls -la"), + }, + ListNode { + name: "Neovim", + command: Command::Raw("nvim"), + }, + ListNode { + name: "Full bash", + command: Command::Raw("bash"), + }, + ListNode { + name: "Running file with `source`", + command: Command::LocalFile("test/main.sh"), }, } }); @@ -140,9 +145,10 @@ impl CustomList { // If we are not at the root of our filesystem tree, we need to add `..` path, to be able // to go up the tree - // icons:   if !self.at_root() { - items.push(Line::from(format!("{} ..", state.theme.dir_icon)).style(state.theme.dir_color)); + items.push( + Line::from(format!("{} ..", state.theme.dir_icon)).style(state.theme.dir_color), + ); } // Iterate through all the children @@ -204,7 +210,7 @@ impl CustomList { } /// Handle key events, we are only interested in `Press` and `Repeat` events - pub fn handle_key(&mut self, event: KeyEvent) -> Option<&'static str> { + pub fn handle_key(&mut self, event: KeyEvent, state: &AppState) -> Option { if event.kind == KeyEventKind::Release { return None; } @@ -234,14 +240,14 @@ impl CustomList { } // The 'p' key toggles the preview on and off KeyCode::Char('p') => { - self.toggle_preview_window(); + self.toggle_preview_window(state); None } KeyCode::Enter => self.handle_enter(), _ => None, } } - fn toggle_preview_window(&mut self) { + fn toggle_preview_window(&mut self, state: &AppState) { // If the preview window is active, disable it if self.preview_window_state.is_some() { self.preview_window_state = None; @@ -250,17 +256,23 @@ impl CustomList { // Get the selected command if let Some(selected_command) = self.get_selected_command() { - // If command is a folder, we don't display a preview - if selected_command.is_empty() { - return; - } - - // Reconstruct the line breaks and file formatting after the - // 'include_str!()' call in the node - let lines: Vec = selected_command - .lines() - .map(|line| line.to_string()) - .collect(); + let lines = match selected_command { + Command::Raw(cmd) => { + // Reconstruct the line breaks and file formatting after the + // 'include_str!()' call in the node + cmd.lines().map(|line| line.to_string()).collect() + } + Command::LocalFile(file_path) => { + let mut full_path = state.temp_path.clone(); + full_path.push(file_path); + let file_contents = std::fs::read_to_string(&full_path) + .map_err(|_| format!("File not found: {:?}", &full_path)) + .unwrap(); + file_contents.lines().map(|line| line.to_string()).collect() + } + // If command is a folder, we don't display a preview + Command::None => return, + }; // Show the preview window with the text lines self.preview_window_state = Some(PreviewWindowState::new(lines)); @@ -314,7 +326,7 @@ impl CustomList { /// /// This could probably be integrated into the 'handle_enter()' method as to avoid code /// duplication, but I don't want to make too major changes to the codebase. - fn get_selected_command(&self) -> Option<&'static str> { + fn get_selected_command(&self) -> Option { let curr = self .inner_tree .get(*self.visit_stack.last().unwrap()) @@ -331,7 +343,7 @@ impl CustomList { idx += 1; } if idx == selected { - return Some(node.value().command); + return Some(node.value().command.clone()); } } None @@ -341,8 +353,9 @@ impl CustomList { /// - Run a command, if it is the currently selected item, /// - Go up a directory /// - Go down into a directory + /// /// Returns `Some(command)` when command is selected, othervise we returns `None` - fn handle_enter(&mut self) -> Option<&'static str> { + fn handle_enter(&mut self) -> Option { // Get the current node (current directory) let curr = self .inner_tree @@ -370,7 +383,7 @@ impl CustomList { self.list_state.select(Some(0)); return None; } else { - return Some(node.value().command); + return Some(node.value().command.clone()); } } } diff --git a/src/main.rs b/src/main.rs index 0d05f95a..0295336f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, }; +use include_dir::include_dir; use list::CustomList; use ratatui::{ backend::{Backend, CrosstermBackend}, @@ -24,6 +25,7 @@ use ratatui::{ }; use running_command::RunningCommand; use state::AppState; +use tempdir::TempDir; use theme::THEMES; /// This is a binary :), Chris, change this to update the documentation on -h @@ -42,9 +44,15 @@ fn main() -> std::io::Result<()> { } else { THEMES[1].clone() }; + let commands_dir = include_dir!("src/commands"); + let temp_dir: TempDir = TempDir::new("linutil_scripts").unwrap(); + commands_dir + .extract(temp_dir.path()) + .expect("Failed to extract the saved directory"); let state = AppState { theme, + temp_path: temp_dir.path().to_owned(), }; stdout().execute(EnterAlternateScreen)?; @@ -99,8 +107,8 @@ fn run(terminal: &mut Terminal, state: &AppState) -> io::Result<( if key.code == KeyCode::Char('q') { return Ok(()); } - if let Some(cmd) = custom_list.handle_key(key) { - command_opt = Some(RunningCommand::new(cmd)); + if let Some(cmd) = custom_list.handle_key(key, state) { + command_opt = Some(RunningCommand::new(cmd, state)); } } } diff --git a/src/state.rs b/src/state.rs index a7de1ebf..459e7d30 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,9 @@ use crate::theme::Theme; +use std::path::PathBuf; pub struct AppState { /// Selected theme pub theme: Theme, + /// Path to the root of the unpacked files in /tmp + pub temp_path: PathBuf, }