async terminal and better state saves

This commit is contained in:
Penwing 2024-01-24 22:27:52 +01:00
parent 338c5302e3
commit 030be79192
8 changed files with 91 additions and 227 deletions

65
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -1 +1 @@
{"tabs":["/home/penwing/Documents/notes/victory2.txt"],"theme":6}
{"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}

View file

@ -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 != "" {

View file

@ -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<PathBuf> {
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<PathBuf> {
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<PathBuf>) {
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
}
}

View file

@ -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])

View file

@ -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};
@ -25,7 +25,17 @@ pub struct AppState {
pub fn save_state(state: &AppState, file_path: &str) -> Result<(), std::io::Error> {
let serialized_state = serde_json::to_string(state)?;
write(file_path, serialized_state)?;
if let Some(parent_dir) = Path::new(file_path).parent() {
fs::create_dir_all(parent_dir)?;
}
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(file_path)?;
file.write_all(serialized_state.as_bytes())?;
Ok(())
}

View file

@ -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<std::process::ChildStdout>,
}
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 command.len() < 2 {
return CommandEntry::new(command);
}
if &entry.command[..2] != "cd" {
return entry.run()
if &command[..2] != "cd" {
return CommandEntry::new(command)
}
if entry.command.len() < 4 {
entry.error = "Invalid cd, should provide path".to_string();
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<std::process::ChildStdout> {
let mut child = Command::new("sh")
.arg("-c")
.arg(command.clone())
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute process");
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);
}