Full filesystem, use AppState more

This commit is contained in:
Andrii Dokhniak 2024-07-28 17:31:20 +02:00
parent 38ec32d5de
commit 7ead4369ad
3 changed files with 77 additions and 53 deletions

View File

@ -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 crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ego_tree::{tree, NodeId}; use ego_tree::{tree, NodeId};
use ratatui::{ use ratatui::{
@ -9,18 +9,9 @@ use ratatui::{
Frame, Frame,
}; };
macro_rules! with_common_script {
($command:expr) => {
concat!(
include_str!("commands/common-script.sh"),
include_str!($command)
)
};
}
struct ListNode { struct ListNode {
name: &'static str, 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 /// 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 // case the tree! macro expands to `ego-tree::tree` data structure
let tree = tree!(ListNode { let tree = tree!(ListNode {
name: "root", name: "root",
command: "" command: Command::None,
} => { } => {
ListNode { ListNode {
name: "Full System Update", name: "Full System Update",
command: with_common_script!("commands/system-update.sh"), command: Command::LocalFile("system-update.sh"),
}, },
ListNode { ListNode {
name: "Setup Bash Prompt", 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 { ListNode {
name: "Setup Neovim", 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 { ListNode {
name: "System Setup", name: "System Setup",
command: "" command: Command::None,
} => { } => {
ListNode { ListNode {
name: "Build Prerequisites", name: "Build Prerequisites",
command: with_common_script!("commands/system-setup/1-compile-setup.sh"), command: Command::LocalFile("system-setup/1-compile-setup.sh"),
}, },
ListNode { ListNode {
name: "Gaming Dependencies", name: "Gaming Dependencies",
command: with_common_script!("commands/system-setup/2-gaming-setup.sh"), command: Command::LocalFile("system-setup/2-gaming-setup.sh"),
}, },
ListNode { ListNode {
name: "Global Theme", 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 { ListNode {
name: "Titus Dotfiles", name: "Titus Dotfiles",
command: "" command: Command::None
} => { } => {
ListNode { ListNode {
name: "Alacritty Setup", name: "Alacritty Setup",
command: with_common_script!("commands/dotfiles/alacritty-setup.sh"), command: Command::LocalFile("dotfiles/alacritty-setup.sh"),
}, },
ListNode { ListNode {
name: "Kitty Setup", name: "Kitty Setup",
command: with_common_script!("commands/dotfiles/kitty-setup.sh"), command: Command::LocalFile("dotfiles/kitty-setup.sh"),
}, },
ListNode { ListNode {
name: "Rofi Setup", 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 // If we are not at the root of our filesystem tree, we need to add `..` path, to be able
// to go up the tree // to go up the tree
// icons:  
if !self.at_root() { 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 // Iterate through all the children
@ -204,7 +210,7 @@ impl CustomList {
} }
/// Handle key events, we are only interested in `Press` and `Repeat` events /// 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<Command> {
if event.kind == KeyEventKind::Release { if event.kind == KeyEventKind::Release {
return None; return None;
} }
@ -234,14 +240,14 @@ impl CustomList {
} }
// The 'p' key toggles the preview on and off // The 'p' key toggles the preview on and off
KeyCode::Char('p') => { KeyCode::Char('p') => {
self.toggle_preview_window(); self.toggle_preview_window(state);
None None
} }
KeyCode::Enter => self.handle_enter(), KeyCode::Enter => self.handle_enter(),
_ => None, _ => None,
} }
} }
fn toggle_preview_window(&mut self) { fn toggle_preview_window(&mut self, state: &AppState) {
// If the preview window is active, disable it // If the preview window is active, disable it
if self.preview_window_state.is_some() { if self.preview_window_state.is_some() {
self.preview_window_state = None; self.preview_window_state = None;
@ -250,17 +256,23 @@ impl CustomList {
// Get the selected command // Get the selected command
if let Some(selected_command) = self.get_selected_command() { if let Some(selected_command) = self.get_selected_command() {
// If command is a folder, we don't display a preview let lines = match selected_command {
if selected_command.is_empty() { Command::Raw(cmd) => {
return; // Reconstruct the line breaks and file formatting after the
} // 'include_str!()' call in the node
cmd.lines().map(|line| line.to_string()).collect()
// Reconstruct the line breaks and file formatting after the }
// 'include_str!()' call in the node Command::LocalFile(file_path) => {
let lines: Vec<String> = selected_command let mut full_path = state.temp_path.clone();
.lines() full_path.push(file_path);
.map(|line| line.to_string()) let file_contents = std::fs::read_to_string(&full_path)
.collect(); .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 // Show the preview window with the text lines
self.preview_window_state = Some(PreviewWindowState::new(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 /// 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. /// 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<Command> {
let curr = self let curr = self
.inner_tree .inner_tree
.get(*self.visit_stack.last().unwrap()) .get(*self.visit_stack.last().unwrap())
@ -331,7 +343,7 @@ impl CustomList {
idx += 1; idx += 1;
} }
if idx == selected { if idx == selected {
return Some(node.value().command); return Some(node.value().command.clone());
} }
} }
None None
@ -341,8 +353,9 @@ impl CustomList {
/// - Run a command, if it is the currently selected item, /// - Run a command, if it is the currently selected item,
/// - Go up a directory /// - Go up a directory
/// - Go down into a directory /// - Go down into a directory
///
/// Returns `Some(command)` when command is selected, othervise we returns `None` /// 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<Command> {
// Get the current node (current directory) // Get the current node (current directory)
let curr = self let curr = self
.inner_tree .inner_tree
@ -370,7 +383,7 @@ impl CustomList {
self.list_state.select(Some(0)); self.list_state.select(Some(0));
return None; return None;
} else { } else {
return Some(node.value().command); return Some(node.value().command.clone());
} }
} }
} }

View File

@ -17,6 +17,7 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand, ExecutableCommand,
}; };
use include_dir::include_dir;
use list::CustomList; use list::CustomList;
use ratatui::{ use ratatui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
@ -24,6 +25,7 @@ use ratatui::{
}; };
use running_command::RunningCommand; use running_command::RunningCommand;
use state::AppState; use state::AppState;
use tempdir::TempDir;
use theme::THEMES; use theme::THEMES;
/// This is a binary :), Chris, change this to update the documentation on -h /// This is a binary :), Chris, change this to update the documentation on -h
@ -42,9 +44,15 @@ fn main() -> std::io::Result<()> {
} else { } else {
THEMES[1].clone() 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 { let state = AppState {
theme, theme,
temp_path: temp_dir.path().to_owned(),
}; };
stdout().execute(EnterAlternateScreen)?; stdout().execute(EnterAlternateScreen)?;
@ -99,8 +107,8 @@ fn run<B: Backend>(terminal: &mut Terminal<B>, state: &AppState) -> io::Result<(
if key.code == KeyCode::Char('q') { if key.code == KeyCode::Char('q') {
return Ok(()); return Ok(());
} }
if let Some(cmd) = custom_list.handle_key(key) { if let Some(cmd) = custom_list.handle_key(key, state) {
command_opt = Some(RunningCommand::new(cmd)); command_opt = Some(RunningCommand::new(cmd, state));
} }
} }
} }

View File

@ -1,6 +1,9 @@
use crate::theme::Theme; use crate::theme::Theme;
use std::path::PathBuf;
pub struct AppState { pub struct AppState {
/// Selected theme /// Selected theme
pub theme: Theme, pub theme: Theme,
/// Path to the root of the unpacked files in /tmp
pub temp_path: PathBuf,
} }