From 375cc7a88f006631bed35eedadabe6362d711aa1 Mon Sep 17 00:00:00 2001 From: rhpidfyre <brandon@rhpidfyre.io> Date: Sun, 19 Jan 2025 02:54:00 -0500 Subject: [PATCH] termianl rendering text, backspace needs fix --- Cargo.lock | 65 ++++++++++++------------ Cargo.toml | 2 +- src/commands.rs | 21 ++++---- src/lib.rs | 1 + src/ps.rs | 36 ++++++------- src/rc.rs | 2 +- src/session.rs | 65 ++++++++---------------- src/terminal.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++ src/vm/mod.rs | 26 +++++----- src/vm/shell.rs | 12 ++--- src/vm/terminal.rs | 4 +- 11 files changed, 227 insertions(+), 129 deletions(-) create mode 100644 src/terminal.rs diff --git a/Cargo.lock b/Cargo.lock index b30e689..f364832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bstr" @@ -32,9 +32,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cc" -version = "1.2.8" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0cf6e91fde44c773c6ee7ec6bba798504641a8bc2eb7e37a04ffbf4dfaa55a" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -169,7 +169,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] -name = "liblambdashell" +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "libpse" version = "0.1.0" dependencies = [ "color-print", @@ -182,16 +192,6 @@ dependencies = [ "whoami", ] -[[package]] -name = "libloading" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" -dependencies = [ - "cfg-if", - "windows-targets", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -210,9 +210,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lua-src" @@ -530,9 +530,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", @@ -541,9 +541,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -555,9 +555,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -565,9 +565,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -578,15 +578,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index b7ad3d7..e64ae29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "liblambdashell" +name = "libpse" version = "0.1.0" edition = "2021" diff --git a/src/commands.rs b/src/commands.rs index 67cb193..05910cd 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -12,7 +12,7 @@ impl PathBufIsValid for PathBuf { } } -trait ChangeDirectory { +trait ChangeDirectory<'a> { fn change_directory(&self, args: SplitWhitespace) -> Option<PathBuf>; fn set_current_dir(&self, new_path: &Path) -> Option<PathBuf>; fn specific_user_dir(&self, user: String) -> Option<PathBuf>; @@ -20,7 +20,7 @@ trait ChangeDirectory { fn previous_dir(&self) -> Option<PathBuf>; fn home_dir(&self) -> Option<PathBuf>; } -impl ChangeDirectory for Command { +impl<'a> ChangeDirectory<'a> for Command<'a> { fn set_current_dir(&self, new_path: &Path) -> Option<PathBuf> { std::env::set_current_dir(new_path).map_or_display_none(|()| Some(new_path.to_path_buf())) } @@ -87,18 +87,19 @@ impl ChangeDirectory for Command { } } -pub struct Command(String); -impl Command { - pub const fn new(input: String) -> Self { +pub struct Command<'a>(&'a String); +impl<'a> Command<'a> { + pub const fn new(input: &'a String) -> Self { Self(input) } pub fn spawn_sys_cmd(&mut self, history: &mut History, command_process: io::Result<process::Child>) { - if let Ok(mut child) = command_process { - history.add(self.0.as_str()); - child.wait().ok(); - } else { - println!("lambdashell: Unknown command: {}", self.0) + match command_process { + Ok(mut child) => { + history.add(self.0.as_str()); + child.wait().ok(); + }, + Err(_) => println!("pse: Unknown command: {}", self.0), } } diff --git a/src/lib.rs b/src/lib.rs index 7a059ed..e7b6da0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub mod session; pub mod commands; pub mod history; +pub mod terminal; pub mod ps; pub mod rc; pub mod vm; diff --git a/src/ps.rs b/src/ps.rs index 5e0f352..ed7753a 100644 --- a/src/ps.rs +++ b/src/ps.rs @@ -1,4 +1,19 @@ -pub const DEFAULT_PS: &str = concat!("lambdashell-", env!("CARGO_PKG_VERSION"), " "); +pub const DEFAULT_PS: &str = concat!("pse-", env!("CARGO_PKG_VERSION"), " "); + +pub trait PsMut { + fn get(&self) -> &str; + fn modify(&mut self, prompt: String); +} +impl PsMut for Ps { + #[inline] + fn get(&self) -> &str { + self.0.as_str() + } + #[inline] + fn modify(&mut self, prompt: String) { + self.0 = prompt + } +} #[derive(Debug)] pub struct Ps(String); @@ -6,23 +21,4 @@ impl Ps { pub const fn set(prompt: String) -> Self { Self(prompt) } - //rustc: `std::string::String::as_str` is not yet stable as a const fn - pub fn get(&self) -> &str { - self.0.as_str() - } - pub fn modify(&mut self, prompt: String) { - self.0 = prompt - } - pub fn display(&self) { - print!("{}", self.0); - } - - pub fn working_dir_name(&self) -> String { - std::env::current_dir().map_or("?".to_owned(), |path| path.file_name().map_or("?".to_owned(), |name| { - match name.to_os_string() == whoami::username_os() { - true => "~".to_owned(), - false => name.to_string_lossy().to_string(), - } - })) - } } \ No newline at end of file diff --git a/src/rc.rs b/src/rc.rs index 120d686..10e21b6 100644 --- a/src/rc.rs +++ b/src/rc.rs @@ -18,7 +18,7 @@ pub fn config_dir() -> Option<PathBuf> { let mut config = home::home_dir()?; config.push(".config"); config.is_valid_dir_or_create()?; - config.push("lambdashell"); + config.push("pse"); config.is_valid_dir_or_create() } diff --git a/src/session.rs b/src/session.rs index 706922e..faf79b9 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,16 +1,11 @@ -use std::{cell::RefCell, fs, io::{self}, rc::Rc}; +use std::{cell::RefCell, fs, rc::Rc}; use core::fmt; use color_print::ceprintln; use crate::{ - commands, history::History, ps::{self, Ps}, rc::{self}, vm::{self, LuauVm} + history::History, ps::{self, Ps}, rc::{self}, terminal, vm::LuauVm }; -#[inline] -pub fn shell_error<E: fmt::Display>(err: E) { - ceprintln!("<bold,r>[!]:</> {err}") -} - pub trait MapDisplay<T, E: fmt::Display> { fn map_or_display<F: FnOnce(T)>(self, f: F); fn map_or_display_none<R, F: FnOnce(T) -> Option<R>>(self, f: F) -> Option<R>; @@ -28,67 +23,47 @@ impl<T, E: fmt::Display> MapDisplay<T, E> for Result<T, E> { } } +pub fn shell_error<E: fmt::Display>(err: E) { + ceprintln!("<bold,r>[!]:</> {err}") +} +pub fn shell_error_none<T, E: fmt::Display>(err: E) -> Option<T> { + shell_error(err); + None +} + #[derive(Debug, Clone)] pub struct Config { - pub norc: bool + pub norc: bool, + pub nojit: bool, + pub nosandbox: bool } pub struct LambdaShell { - terminate: bool, history: History, config: Config, - vm: LuauVm, - ps: Rc<RefCell<Ps>>, + ps: Ps, } impl LambdaShell { pub fn create(config: Config) -> Self { - let ps = Rc::new(RefCell::new(Ps::set(ps::DEFAULT_PS.to_owned()))); Self { - ps: Rc::clone(&ps), - vm: vm::LuauVm::new(ps), + ps: Ps::set(ps::DEFAULT_PS.to_owned()), history: History::init(), - terminate: false, config, } } - pub fn wait(&mut self) -> Result<(), io::Error> { - io::Write::flush(&mut io::stdout()).map(|()| { - let mut input = String::new(); - io::stdin().read_line(&mut input).map_or_display(|_size| match input.trim() { - "exit" => { - self.terminate = true; - self.history.add("exit"); - }, - trim => commands::Command::new(trim.to_owned()).exec(&mut self.history) - }) - }) - } - - pub fn error<E: fmt::Display>(&mut self, err: E) { - shell_error(err); - self.terminate = true; - } - pub fn start(&mut self) { if !self.config.norc { if let Some(conf_file) = rc::config_file() { fs::read_to_string(conf_file).map_or_display(|luau_conf| self.vm_exec(luau_conf)); } } - self.ps.borrow().display(); - - loop { - if self.terminate { break } else { - match self.wait() { - Ok(()) => self.ps.borrow().display(), - Err(flush_err) => self.error(flush_err), - } - } - } - self.history.write_to_file_fallible(); + terminal::Processor::init() + .input_processor(&mut self.history) + .map_or_display(|()| self.history.write_to_file_fallible()); } pub fn vm_exec(&self, source: String) { - self.vm.exec(source); + let p = Rc::new(RefCell::new(&self.ps)); + LuauVm::new(Rc::clone(p)).exec(source); } } diff --git a/src/terminal.rs b/src/terminal.rs new file mode 100644 index 0000000..e8820a5 --- /dev/null +++ b/src/terminal.rs @@ -0,0 +1,122 @@ +use crossterm::{event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, terminal}; +use std::io::{self, Write}; +use thiserror::Error; + +use crate::{commands::Command, history::History, session}; + +#[derive(Debug, Error)] +pub enum InputHandleError { + #[error("UserExit")] + UserExit, + #[error("Sigterm")] + Sigterm, + #[error("Render failure: {0}")] + Write(io::Error), + #[error("Flush failure: {0}")] + Flush(io::Error), + #[error("Disabling the terminal's raw mode failed: {0}")] + EnableRaw(io::Error), + #[error("Enabling the terminal's raw mode failed: {0}")] + DisableRaw(io::Error), + #[error("key input failure: {0}")] + Key(KeyCode), +} +type InputResult<T> = Result<T, InputHandleError>; + +trait SpecificKeybinds { + const TERM_ID_1: &str; + fn key_ctrl(&mut self, input_key: KeyEvent, keycode: KeyCode) -> InputResult<()>; + fn key_enter(&mut self, history: &mut History) -> InputResult<()>; + fn key_backspace(&mut self) -> InputResult<()>; +} +impl SpecificKeybinds for Processor { + const TERM_ID_1: &str = "exit"; + + fn key_ctrl(&mut self, input_key: KeyEvent, keycode: KeyCode) -> InputResult<()> { + if input_key.modifiers.contains(KeyModifiers::CONTROL) { + match keycode { + KeyCode::Char('c') => Err(InputHandleError::Sigterm), + _ => Ok(()) + } + } else { + self.render(Some(keycode.to_string())) + } + } + + fn key_enter(&mut self, history: &mut History) -> InputResult<()> { + if self.0 == Self::TERM_ID_1 { return Err(InputHandleError::UserExit) }; + + terminal::disable_raw_mode().map_err(InputHandleError::DisableRaw)?; + Command::new(&self.0).exec(history); + self.0.clear(); + Ok(()) + } + + fn key_backspace(&mut self) -> InputResult<()> { + match self.0.pop() { + Some(_) => self.render(None), + None => { + //the string is empty, do terminal beep + Ok(()) + }, + } + } +} + +pub struct Processor(String); +impl Processor { + pub const fn init() -> Self { + Self(String::new()) + } + + pub fn render(&mut self, def: Option<String>) -> InputResult<()> { + match def { + Some(def_string) => { + self.0.push_str(&def_string); + write!(io::stdout(), "{def_string}").map_err(InputHandleError::Write)?; + }, + None => { + write!(io::stdout(), "{}", self.0).map_err(InputHandleError::Write)? + } + }; + io::stdout().flush().map_err(InputHandleError::Flush) + } + + pub fn input_handler(&mut self, input_key: KeyEvent, history: &mut History) -> Option<()> { + let input_handle = match input_key.code { + KeyCode::Enter => self.key_enter(history), + KeyCode::Backspace => self.key_backspace(), + KeyCode::Tab => todo!(), + KeyCode::Right => todo!(), + KeyCode::Left => todo!(), + KeyCode::Up => todo!(), + KeyCode::Down => todo!(), + keycode => self.key_ctrl(input_key, keycode) + }; + input_handle.map_or_else(|inp_err| match inp_err { + InputHandleError::UserExit => None, + InputHandleError::Sigterm => self.render(Some("^C".to_owned())).ok(), + InputHandleError::Write(e) => session::shell_error_none(e), + InputHandleError::Flush(e) => session::shell_error_none(e), + InputHandleError::Key(e) => session::shell_error_none(e), + InputHandleError::DisableRaw(e) => session::shell_error_none(e), + InputHandleError::EnableRaw(e) => session::shell_error_none(e) + }, Some) + } + + fn input_mainthread(&mut self, history: &mut History) -> io::Result<()> { + crossterm::execute!(io::stdout(), event::EnableBracketedPaste)?; + loop { + terminal::enable_raw_mode()?; + if let Event::Key(event) = event::read()? { + if self.input_handler(event, history).is_none() { break Ok(()) } + } + } + } + + pub fn input_processor(&mut self, history: &mut History) -> io::Result<()> { + self.input_mainthread(history)?; + terminal::disable_raw_mode()?; + crossterm::execute!(io::stdout(), event::DisableBracketedPaste) + } +} \ No newline at end of file diff --git a/src/vm/mod.rs b/src/vm/mod.rs index e3119b6..667b97d 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,8 +1,8 @@ use mlua::{Function, Lua as Luau, MultiValue, Result as lResult, Table, Value}; use color_print::{cformat, ceprintln}; use terminal::TerminalGlobal; -use std::{cell::RefCell, rc::Rc}; use core::fmt; +use std::{cell::RefCell, rc::Rc}; use shell::ShellGlobal; use crate::{ps::Ps, session::MapDisplay}; @@ -27,14 +27,16 @@ impl<T, E: fmt::Display> LuauRuntimeErr<T> for Result<T, E> { trait Globals { const LIB_VERSION: &str; const CONV_ERROR: &str; - fn global_warn(&self, luau_globals: &Table) -> lResult<()>; - fn global_version(&self, luau_globals: &Table) -> lResult<()>; + const LIB_NAME: &str; + fn glob_warn(&self, luau_globals: &Table) -> lResult<()>; + fn glob_version(&self, luau_globals: &Table) -> lResult<()>; } impl Globals for LuauVm { const LIB_VERSION: &str = env!("CARGO_PKG_VERSION"); + const LIB_NAME: &str = env!("CARGO_PKG_NAME"); const CONV_ERROR: &str = "<SHELL CONVERSION ERROR>"; - fn global_warn(&self, luau_globals: &Table) -> lResult<()> { + fn glob_warn(&self, luau_globals: &Table) -> lResult<()> { let luau_print = luau_globals.get::<Function>("print")?; luau_globals.raw_set("warn", self.vm.create_function(move |this, args: MultiValue| -> lResult<()> { let luau_multi_values = args.into_iter() @@ -46,9 +48,9 @@ impl Globals for LuauVm { })?) } - fn global_version(&self, luau_globals: &Table) -> lResult<()> { + fn glob_version(&self, luau_globals: &Table) -> lResult<()> { let luau_info = luau_globals.get::<String>("_VERSION")?; - luau_globals.raw_set("_VERSION", format!("{luau_info}, liblambdashell {}", Self::LIB_VERSION)) + luau_globals.raw_set("_VERSION", format!("{luau_info}, {} {}", Self::LIB_NAME, Self::LIB_VERSION)) } } @@ -61,12 +63,12 @@ impl LuauVm { Self { vm: Luau::new(), ps } } - fn set_shell_globals(&self) -> lResult<()> { + fn setglobs(&self) -> lResult<()> { let luau_globals = self.vm.globals(); - self.global_shell(&luau_globals)?; - self.global_terminal(&luau_globals)?; - self.global_warn(&luau_globals)?; - self.global_version(&luau_globals)?; + self.glob_shell(&luau_globals)?; + self.glob_terminal(&luau_globals)?; + self.glob_warn(&luau_globals)?; + self.glob_version(&luau_globals)?; luau_globals.raw_set("getfenv", mlua::Nil)?; luau_globals.raw_set("setfenv", mlua::Nil)?; self.vm.sandbox(true)?; @@ -74,6 +76,6 @@ impl LuauVm { } pub fn exec(&self, source: String) { - self.set_shell_globals().map_or_display_none(|()| self.vm.load(source).exec().map_or_luau_rt_err(Some)); + self.setglobs().map_or_display_none(|()| self.vm.load(source).exec().map_or_luau_rt_err(Some)); } } \ No newline at end of file diff --git a/src/vm/shell.rs b/src/vm/shell.rs index c532d17..4accc36 100644 --- a/src/vm/shell.rs +++ b/src/vm/shell.rs @@ -2,7 +2,7 @@ use mlua::{Lua as Luau, MetaMethod, Result as lResult, Table, UserData, UserData use std::{cell::RefCell, rc::Rc}; use whoami::fallible; -use crate::{ps::Ps, vm::LuauVm}; +use crate::{ps::{Ps, PsMut}, vm::LuauVm}; const DEFAULT_HOSTNAME: &str = "hostname"; @@ -27,20 +27,18 @@ impl UserData for Shell { } fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { - methods.add_meta_method_mut(MetaMethod::NewIndex, |_, this, (tindex, tvalue): (String, String)| -> lResult<()> { - if tindex == "PROMPT" { - this.0.borrow_mut().modify(tvalue); - } + methods.add_meta_method_mut(MetaMethod::NewIndex, |_, this, (t_index, t_value): (String, String)| -> lResult<()> { + if t_index == "PROMPT" { this.0.borrow_mut().modify(t_value); } Ok(()) }); } } pub trait ShellGlobal { - fn global_shell(&self, luau_globals: &Table) -> lResult<()>; + fn glob_shell(&self, luau_globals: &Table) -> lResult<()>; } impl ShellGlobal for LuauVm { - fn global_shell(&self, luau_globals: &Table) -> lResult<()> { + fn glob_shell(&self, luau_globals: &Table) -> lResult<()> { luau_globals.raw_set("SHELL", Shell(Rc::clone(&self.ps)))?; Ok(()) } diff --git a/src/vm/terminal.rs b/src/vm/terminal.rs index 7ad38a4..bbd8251 100644 --- a/src/vm/terminal.rs +++ b/src/vm/terminal.rs @@ -84,10 +84,10 @@ impl UserData for Terminal { } pub trait TerminalGlobal { - fn global_terminal(&self, luau_globals: &Table) -> lResult<()>; + fn glob_terminal(&self, luau_globals: &Table) -> lResult<()>; } impl TerminalGlobal for LuauVm { - fn global_terminal(&self, luau_globals: &Table) -> lResult<()> { + fn glob_terminal(&self, luau_globals: &Table) -> lResult<()> { luau_globals.raw_set("TERMINAL", Terminal) } } \ No newline at end of file