From 030be79192e81bf3fd3a5bf1b3fac93c5e147dfe Mon Sep 17 00:00:00 2001 From: Penwing Date: Wed, 24 Jan 2024 22:27:52 +0100 Subject: [PATCH] async terminal and better state saves --- Cargo.lock | 65 ++++++------------- Cargo.toml | 10 +-- calcifer_save.json | 2 +- src/calcifer.rs | 3 +- src/calcifer_base.rs | 144 ------------------------------------------ src/main.rs | 4 +- src/tools/mod.rs | 14 +++- src/tools/terminal.rs | 76 ++++++++++++++-------- 8 files changed, 91 insertions(+), 227 deletions(-) delete mode 100644 src/calcifer_base.rs diff --git a/Cargo.lock b/Cargo.lock index cdedca4..c334ea1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -574,8 +574,8 @@ version = "1.0.4" dependencies = [ "eframe", "egui_extras", - "env_logger", "image", + "nix 0.27.1", "rfd", "serde", "serde_json", @@ -1035,18 +1035,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "env_logger" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" -dependencies = [ - "humantime", - "is-terminal", - "log", - "termcolor", -] - [[package]] name = "epaint" version = "0.25.0" @@ -1554,12 +1542,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "icrate" version = "0.0.4" @@ -1629,17 +1611,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "is-terminal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" -dependencies = [ - "hermit-abi", - "rustix 0.38.30", - "windows-sys 0.52.0", -] - [[package]] name = "itoa" version = "1.0.10" @@ -1873,6 +1844,17 @@ dependencies = [ "memoffset 0.7.1", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2642,15 +2624,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.56" @@ -2976,7 +2949,7 @@ checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" dependencies = [ "cc", "downcast-rs", - "nix", + "nix 0.26.4", "scoped-tls", "smallvec", "wayland-sys", @@ -2989,7 +2962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" dependencies = [ "bitflags 2.4.1", - "nix", + "nix 0.26.4", "wayland-backend", "wayland-scanner", ] @@ -3011,7 +2984,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b" dependencies = [ - "nix", + "nix 0.26.4", "wayland-client", "xcursor", ] @@ -3467,7 +3440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ "gethostname 0.3.0", - "nix", + "nix 0.26.4", "winapi", "winapi-wsapoll", "x11rb-protocol 0.12.0", @@ -3494,7 +3467,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" dependencies = [ - "nix", + "nix 0.26.4", ] [[package]] @@ -3515,7 +3488,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ - "nix", + "nix 0.26.4", "winapi", ] @@ -3568,7 +3541,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix", + "nix 0.26.4", "once_cell", "ordered-stream", "rand", diff --git a/Cargo.toml b/Cargo.toml index 2112680..feb57d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,14 +7,14 @@ edition = "2021" [dependencies] eframe = "0.25.0" -env_logger = { version = "0.10.1", default-features = false, features = [ - "auto-color", - "humantime", -] } +# env_logger = { version = "0.10.1", default-features = false, features = [ + # "auto-color", + # "humantime", +# ] } rfd = "0.12.1" egui_extras = "0.25.0" image = "0.24.8" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" - +nix = { version = "0.27.1", features = ["fs"] } diff --git a/calcifer_save.json b/calcifer_save.json index d227408..4174c30 100644 --- a/calcifer_save.json +++ b/calcifer_save.json @@ -1 +1 @@ -{"tabs":["/home/penwing/Documents/notes/victory2.txt"],"theme":6} \ No newline at end of file +{"tabs":["/home/penwing/Documents/projects/rust/calcifer/README.md","/home/penwing/Documents/projects/rust/calcifer/Cargo.toml","/home/penwing/Documents/projects/rust/calcifer/src/main.rs","/home/penwing/Documents/projects/rust/calcifer/src/calcifer.rs","/home/penwing/Documents/projects/rust/calcifer/src/tools/mod.rs","/home/penwing/Documents/projects/rust/calcifer/src/tools/search.rs","/home/penwing/Documents/projects/rust/calcifer/src/calcifer/app_base.rs","/home/penwing/Documents/projects/rust/calcifer/src/tools/tabs.rs","/home/penwing/Documents/projects/rust/calcifer/src/tools/confirm.rs"],"theme":5} \ No newline at end of file diff --git a/src/calcifer.rs b/src/calcifer.rs index eb5bff4..eda52ea 100644 --- a/src/calcifer.rs +++ b/src/calcifer.rs @@ -100,7 +100,8 @@ impl Calcifer { ui.separator(); ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing.y = 0.0; - for entry in &self.command_history { + for entry in &mut self.command_history { + entry.update(); ui.colored_label(command_color, format!("\n{} {}", entry.env, entry.command)); ui.end_row(); if entry.output != "" { diff --git a/src/calcifer_base.rs b/src/calcifer_base.rs deleted file mode 100644 index 4e2bb38..0000000 --- a/src/calcifer_base.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Hello there, Master - -impl super::Calcifer { - pub fn handle_confirm(&mut self) { - if self.close_tab_confirm.proceed { - self.close_tab_confirm.close(); - self.delete_tab(self.tab_to_close); - } - } - pub fn save_tab(&self) -> Option { - if self.tabs[self.selected_tab.to_index()].path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() == "untitled" { - return self.save_tab_as(); - } else { - if let Err(err) = fs::write(&self.tabs[self.selected_tab.to_index()].path, &self.tabs[self.selected_tab.to_index()].code) { - eprintln!("Error writing file: {}", err); - return None; - } - return Some(self.tabs[self.selected_tab.to_index()].path.clone()) - } - } - - pub fn save_tab_as(&self) -> Option { - if let Some(path) = rfd::FileDialog::new().set_directory(Path::new(&PATH_ROOT)).save_file() { - if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab.to_index()].code) { - eprintln!("Error writing file: {}", err); - return None; - } - return Some(path); - } - return None - } - - pub fn handle_save_file(&mut self, path_option : Option) { - if let Some(path) = path_option { - println!("File saved successfully at: {:?}", path); - self.tabs[self.selected_tab.to_index()].path = path; - self.tabs[self.selected_tab.to_index()].saved = true; - } else { - println!("File save failed."); - } - } - - pub fn from_app_state(app_state: tools::AppState) -> Self { - let mut new = Self { - theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], - tabs: Vec::new(), - ..Default::default() - }; - - for path in app_state.tabs { - if path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() != "untitled" { - new.open_file(Some(&path)); - } - } - - if new.tabs == vec![] { - new.open_file(None); - } - - new - } - - pub fn save_state(&self) { - let mut state_theme : usize = 0; - if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { - state_theme = theme; - } - - let mut state_tabs = vec![]; - - for tab in &self.tabs { - state_tabs.push(tab.path.clone()); - } - let app_state = tools::AppState { - tabs: state_tabs, - theme: state_theme, - }; - - let _ = tools::save_state(&app_state, super::SAVE_PATH); - } - - pub fn move_through_tabs(&mut self, forward : bool) { - let new_index = if forward { - (self.selected_tab.to_index() + 1) % self.tabs.len() - } else { - self.selected_tab.to_index().checked_sub(1).unwrap_or(self.tabs.len() - 1) - }; - self.selected_tab = tools::TabNumber::from_index(new_index); - } - - fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> { - if let Some(name) = path.file_name() { - if path.is_dir() { - egui::CollapsingHeader::new(name.to_string_lossy()).show(ui, |ui| { - let mut paths: Vec<_> = fs::read_dir(&path).expect("Failed to read dir").map(|r| r.unwrap()).collect(); - - // Sort the vector using the custom sorting function - paths.sort_by(|a, b| tools::sort_directories_first(a, b)); - - for result in paths { - //let result = path_result.expect("Failed to get path"); - //let full_path = result.path(); - let _ = self.list_files(ui, &result.path()); - } - }); - } else { - //ui.label(name.to_string_lossy()); - if ui.button(name.to_string_lossy()).clicked() { - self.open_file(Some(path)); - } - } - } - Ok(()) - } - - fn open_file(&mut self, path_option: Option<&Path>) { - if self.tabs.len() < MAX_TABS { - if let Some(path) = path_option { - self.tabs.push(tools::Tab::new(path.to_path_buf())); - } else { - self.tabs.push(tools::Tab::default()); - } - self.selected_tab = tools::TabNumber::from_index(self.tabs.len() - 1); - } - } - - fn delete_tab(&mut self, index : usize) { - self.tabs.remove(index); - self.selected_tab = tools::TabNumber::from_index(min(index, self.tabs.len() - 1)); - } - - fn toggle(&self, ui: &mut egui::Ui, display : bool, title : &str) -> bool { - let text = if display.clone() { - format!("hide {}", title) - } else { - format!("show {}", title) - }; - - if ui.add(egui::Button::new(text)).clicked() { - return !display - } - return display - } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7f865e3..6bb11bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use calcifer::code_editor::themes::DEFAULT_THEMES; const TERMINAL_HEIGHT : f32 = 200.0; const RED : egui::Color32 = egui::Color32::from_rgb(235, 108, 99); -const SAVE_PATH : &str = "calcifer_save.json"; +const SAVE_PATH : &str = "/home/penwing/Documents/.saves/calcifer_save.json"; const TIME_LABELS : [&str; 5] = ["settings", "tree", "terminal", "tabs", "content"]; const MAX_FPS : f32 = 30.0; const PATH_ROOT : &str = "/home/penwing/Documents/"; @@ -20,7 +20,7 @@ const MAX_TABS : usize = 20; fn main() -> Result<(), eframe::Error> { let icon_data = tools::load_icon(); - env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + //env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([1200.0, 800.0]) diff --git a/src/tools/mod.rs b/src/tools/mod.rs index af402e1..e93c441 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, path::PathBuf, path::Path, fs::read_to_string, fs::write, path::Component, ffi::OsStr}; +use std::{cmp::Ordering, path::PathBuf, path::Path, fs, fs::read_to_string, io::Write, path::Component, ffi::OsStr, fs::OpenOptions}; use crate::calcifer::code_editor::Syntax; use eframe::egui; use serde::{Serialize, Deserialize}; @@ -24,8 +24,18 @@ pub struct AppState { pub fn save_state(state: &AppState, file_path: &str) -> Result<(), std::io::Error> { let serialized_state = serde_json::to_string(state)?; + + if let Some(parent_dir) = Path::new(file_path).parent() { + fs::create_dir_all(parent_dir)?; + } - write(file_path, serialized_state)?; + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(file_path)?; + + file.write_all(serialized_state.as_bytes())?; Ok(()) } diff --git a/src/tools/terminal.rs b/src/tools/terminal.rs index a63cca2..fb19074 100644 --- a/src/tools/terminal.rs +++ b/src/tools/terminal.rs @@ -1,68 +1,92 @@ use crate::tools::format_path; use std::{process::Command, env, path::Path}; +use std::io::BufReader; +use std::io::BufRead; +use std::process::Stdio; +use nix::fcntl::{fcntl, FcntlArg, OFlag}; +use std::os::unix::io::AsRawFd; + -#[derive(Clone, PartialEq)] pub struct CommandEntry { pub env: String, pub command: String, pub output: String, pub error: String, + pub output_buffer: BufReader, } impl CommandEntry { pub fn new(command: String) -> Self { + let stdout_reader = execute(command.clone()); + CommandEntry { env: format_path(&env::current_dir().expect("Could not find Shell Environnment")), command, output: String::new(), error: String::new(), + output_buffer: stdout_reader, } } - pub fn run(&mut self) -> Self { - let output = Command::new("sh") - .arg("-c") - .arg(self.command.clone()) - .output() - .expect("failed to execute process"); - self.output = (&String::from_utf8_lossy(&output.stdout)).trim_end_matches('\n').to_string(); - self.error = (&String::from_utf8_lossy(&output.stderr)).trim_end_matches('\n').to_string(); - - self.clone() + pub fn update(&mut self) { + let mut output = String::new(); + let _ = self.output_buffer.read_line(&mut output); + if !output.is_empty() { + self.output += &output; + } } } pub fn run_command(command: String) -> CommandEntry { - let mut entry = CommandEntry::new(command); - - if entry.command.len() < 2 { - return entry.run(); - } - - if &entry.command[..2] != "cd" { - return entry.run() + if command.len() < 2 { + return CommandEntry::new(command); } - if entry.command.len() < 4 { - entry.error = "Invalid cd, should provide path".to_string(); + if &command[..2] != "cd" { + return CommandEntry::new(command) + } + + if command.len() < 4 { + let mut entry = CommandEntry::new("echo Invalid cd, should provide path >&2".to_string()); + entry.command = command; return entry } - let path_append = entry.command[3..].replace("~", "/home/penwing"); + let path_append = command[3..].replace("~", "/home/penwing"); let path = Path::new(&path_append); if format!("{}", path.display()) == "/" { - entry.error = "Root access denied".to_string(); + let mut entry = CommandEntry::new("echo Root access denied >&2".to_string()); + entry.command = command; return entry } if env::set_current_dir(path).is_ok() { - entry.output = format!("Moved to : {}", path.display()); + let mut entry = CommandEntry::new(format!("echo Moved to : {}", path.display())); + entry.command = command; + return entry } else { - entry.error = format!("Could not find path : {}", path.display()); + let mut entry = CommandEntry::new(format!("echo Could not find path : {} >&2", path.display())); + entry.command = command; + return entry } +} + + +pub fn execute(command: String) -> BufReader { + let mut child = Command::new("sh") + .arg("-c") + .arg(command.clone()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute process"); - return entry + let stdout = child.stdout.take().unwrap(); + let stdout_fd = stdout.as_raw_fd(); + + fcntl(stdout_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("Failed to set non-blocking mode"); + + return BufReader::new(stdout); } \ No newline at end of file