diff options
| author | Jonas Maier <jonas@x77.dev> | 2026-05-09 11:30:28 +0200 |
|---|---|---|
| committer | Jonas Maier <jonas@x77.dev> | 2026-05-09 11:30:28 +0200 |
| commit | dd24cc2aec9ece8214ec1a4eff4abd26d00ea083 (patch) | |
| tree | 354f4cb1c13ba20291fd57680aafaa4df67b99bf /src/main.rs | |
| parent | edfc7e48c563a97399d18e3ef44fd595c0fd4e45 (diff) | |
| download | pish-dd24cc2aec9ece8214ec1a4eff4abd26d00ea083.tar.gz | |
simple script file test
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 485 |
1 files changed, 6 insertions, 479 deletions
diff --git a/src/main.rs b/src/main.rs index a4a6f00..fc44fa5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,478 +1,5 @@ -#![feature( - unix_socket_ancillary_data, - peer_credentials_unix_socket, - associated_type_defaults -)] -#![allow(clippy::needless_range_loop)] - -use std::collections::HashMap; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::io::{self, IsTerminal, Read, Write}; -use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::AsRawFd; -use std::path::Path; -use std::process::Command; -use std::sync::{Arc, Mutex}; -use std::thread::sleep; -use std::time::Duration; - -pub mod ansi; -pub mod basedir; -pub mod completion; -pub mod ctrlc; -pub mod cursor; -pub mod date; -pub mod defer; -pub mod export_fun; -pub mod history; -pub mod linebuf; -pub mod panic; -pub mod parse; -pub mod raw; -pub mod reload; -pub mod run; -pub mod rw; -pub mod serialization; -pub mod wait; - -use linebuf::LineBuf; -use raw::*; - -use crate::completion::{PathCache, completion}; -use crate::ctrlc::CtrlC; -use crate::cursor::{Direction, move_cursor}; -use crate::history::HistoryEntry; -use crate::parse::{Block, ExpString, Parse, PostExpansion}; - -macro_rules! print { - ($($x:tt)*) => {{ - let res = write!(io::stdout(), $($x)*); - res.unwrap(); - io::stdout().flush().unwrap(); - }} -} - -macro_rules! println { - () => {{ - println!("") - }}; - ($($x:tt)*) => { - { - let res = write!(io::stdout(), $($x)*); - res.unwrap(); - let res = write!(io::stdout(), "\r\n"); - res.unwrap(); - io::stdout().flush().unwrap(); - } - }; -} - -fn completely_clear_screen() { - print!("\x1B[2J\x1B[1;1H"); -} - -fn clear_screen() { - completely_clear_screen(); -} - -type BString = Vec<u8>; -#[allow(non_camel_case_types)] -type bstr = [u8]; - -pub struct Session { - raw: ScopedRawMode, - line: LineBuf, - history: Vec<HistoryEntry>, - prev_path: BString, - builtins: HashMap<BString, &'static dyn run::BuiltinClone>, - vars: HashMap<BString, BString>, - funs: HashMap<BString, Block>, - aliases: run::Aliases, - socket_running: Option<export_fun::SocketRunning>, - path_cache: PathCache, - ctrlc: CtrlC, - - /// terminfo identifier to command invocation - ti_keybinds: HashMap<BString, parse::Command<PostExpansion>>, - /// byte literals to command invocation - ascii_keybinds: HashMap<BString, parse::Command<PostExpansion>>, - - debug_keystrokes: bool, - loud: bool, - - /// n before end of history.len() - /// 0 == not checking history - history_visit: usize, -} - -/// relative path -- in case it is a proper subpath the result starts with a slash `/` -fn relative_path(root: &Path, target: &Path) -> Option<String> { - let root = root.to_string_lossy(); - let mut target = target.to_string_lossy().to_string(); - if !target.ends_with("/") { - target += "/"; - } - if let Some(("", leaf)) = target.split_once(&*root) { - Some(leaf.into()) - } else { - None - } -} - -impl Session { - fn pretty_cwd_res(&self) -> io::Result<String> { - let dir = std::env::current_dir()?; - let mut s = if let Some(home_dir) = std::env::home_dir() { - if let Some(rela) = relative_path(&home_dir, &dir) { - format!("~{rela}") - } else { - dir.to_string_lossy().to_string() - } - } else { - dir.to_string_lossy().to_string() - }; - while s.ends_with("/") && s.len() > 1 { - s.remove(s.len() - 1); - } - Ok(s) - } - - fn pretty_cwd(&self) -> String { - self.pretty_cwd_res().unwrap_or_else(|_| String::new()) - } - - // TODO: prompt should be BString as well - fn prompt(&self) -> String { - #[cfg(debug_assertions)] - let dev = "dev "; - #[cfg(not(debug_assertions))] - let dev = ""; - format!("{dev}[{}]# ", self.pretty_cwd()) - } - - fn clear_prompt(&mut self) { - cursor::move_cursor(Direction::Right, self.line.distance_from_right_end()); - for _ in 0..self.line.len() { - print!("\x08 \x08"); - } - io::stdout().lock().flush().unwrap(); - self.line.clear(); - } - - fn prompt_clear(&mut self) { - self.clear_prompt(); - self.history_visit = 0; - } - - fn reprint_prompt(&self) { - print!("{}", self.prompt()); - self.line.display_pre(); - self.line.display_post(b""); - } - - fn display_historic_entry(&mut self) { - self.clear_prompt(); - let new = if self.history_visit == 0 { - Vec::new() - } else { - self.history[self.history.len() - self.history_visit] - .cmd - .clone() - }; - io::stdout().write_all(&new).unwrap(); - io::stdout().flush().unwrap(); - self.line.set_content(new); - } - - fn history_up(&mut self) { - if self.history_visit < self.history.len() { - self.history_visit += 1; - self.display_historic_entry(); - } - } - - fn history_down(&mut self) { - if self.history_visit > 0 { - self.history_visit -= 1; - self.display_historic_entry(); - } - } - - fn type_byte(&mut self, b: u8) { - self.line.add(b); - io::stdout().lock().write_all(&[b]).unwrap(); - self.line.display_post(b""); - } - - fn type_bytes(&mut self, bs: &[u8]) { - for b in bs.iter() { - self.type_byte(*b); - } - } - - fn del_left(&mut self) { - if self.line.del_left().is_some() { - cursor::move_cursor(Direction::Left, 1); - self.line.display_post(b" "); - } - } - - fn del_right(&mut self) { - self.line.del_right(); - self.line.display_post(b" "); - } - - fn del_left_or_previous(&mut self) { - if self.line.is_empty() && !self.line.is_dirty() && !self.history.is_empty() { - // take previous command for editing - let cmd = self.history[self.history.len() - 1].cmd.clone(); - self.type_bytes(&cmd); - } else { - self.del_left(); - } - } - - fn move_to_begin(&mut self) { - cursor::move_cursor(Direction::Left, self.line.all_left()); - io::stdout().flush().unwrap(); - } - - fn move_to_end(&mut self) { - cursor::move_cursor(Direction::Right, self.line.all_right()); - io::stdout().flush().unwrap(); - } - - fn del_left_word(&mut self) { - let mut del = 0; - while let Some(x) = self.line.get_left() - && x == b' ' - { - self.line.del_left(); - del += 1; - } - while let Some(x) = self.line.get_left() - && x != b' ' - { - self.line.del_left(); - del += 1; - } - cursor::move_cursor(Direction::Left, del); - self.line.display_post(&vec![b' '; del]); - } - - fn del_right_word(&mut self) { - let mut del = 0; - while let Some(x) = self.line.get_right() - && x == b' ' - { - self.line.del_right(); - del += 1; - } - while let Some(x) = self.line.get_right() - && x != b' ' - { - self.line.del_right(); - del += 1; - } - self.line.display_post(&vec![b' '; del]); - } - - fn cursor_left(&mut self) { - if self.line.left() { - move_cursor(Direction::Left, 1); - io::stdout().lock().flush().unwrap(); - } - } - - fn cursor_right(&mut self) { - if self.line.right() { - move_cursor(Direction::Right, 1); - io::stdout().lock().flush().unwrap(); - } - } - - // move to next - fn cursor_left_word(&mut self) { - let mut i = 0; - - // find word - while let Some(b' ') = self.line.get_left() { - self.line.left(); - i += 1; - } - - // skip it - while let Some(x) = self.line.get_left() - && !x.is_ascii_whitespace() - { - self.line.left(); - i += 1; - } - - cursor::move_cursor(Direction::Left, i); - io::stdout().flush().unwrap(); - } - - fn cursor_right_word(&mut self) { - let mut i = 0; - - // find word - while let Some(b' ') = self.line.get_right() { - self.line.right(); - i += 1; - } - - // skip it - while let Some(x) = self.line.get_right() - && !x.is_ascii_whitespace() - { - self.line.right(); - i += 1; - } - - cursor::move_cursor(Direction::Right, i); - io::stdout().flush().unwrap(); - } - - fn complete(session: Arc<Mutex<Session>>) { - let cmd = session.lock().unwrap().line.pre().to_vec(); - - let comp = completion(session.clone(), &cmd); - - let mut se = session.lock().unwrap(); - - se.type_bytes(&comp.shared_prefix); - - if comp.suggestions.len() > 1 { - print!("\r\n"); - for s in comp.suggestions { - io::stdout().lock().write_all(&s.display).unwrap(); - println!(); - } - se.reprint_prompt(); - } - } - - fn try_submit_command(session: Arc<Mutex<Session>>) { - let mut se = session.lock().unwrap(); - let line = se.line.into_bytes(); - - if !line.is_empty() { - let parsed = match parse::do_parse(&line) { - Ok(p) => p, - Err(_) => { - se.line.add(b'\n'); - print!("\r\n> "); - return; - } - }; - print!("\r\n"); - let entry = HistoryEntry::new(line.clone()); - history::persist(&entry); - se.history.push(entry); - se.history_visit = 0; - se.line.dump(); - drop(se); - run::run(session.clone(), parsed); - } - } - - fn screen_clear(&mut self) { - clear_screen(); - self.reprint_prompt(); - } -} - -fn exec_rc_file(se: Arc<Mutex<Session>>) { - let _ = run::source( - se, - basedir::config_dir().join(".pishrc").as_os_str().as_bytes(), - ); -} - -fn event_loop() { - history::setup(); - ansi::setup(); - - let stdin = io::stdin(); - - let fd = stdin.as_raw_fd(); - let raw = ScopedRawMode::on_fd(fd); - raw.enable(); - - fs::create_dir_all(basedir::config_dir()).unwrap(); - fs::create_dir_all(basedir::data_dir()).unwrap(); - - let se = Session { - raw, - line: LineBuf::new(), - history: Vec::new(), - builtins: run::builtin_map(), - prev_path: vec![b'.'], - history_visit: 0, - socket_running: None, - vars: HashMap::new(), - funs: HashMap::new(), - aliases: run::Aliases::new(), - path_cache: Default::default(), - ctrlc: Default::default(), - debug_keystrokes: false, - loud: false, - ti_keybinds: HashMap::new(), - ascii_keybinds: HashMap::new(), - }; - - let session = Arc::new(Mutex::new(se)); - exec_rc_file(session.clone()); - - session.lock().unwrap().loud = true; - - print!("{}", session.lock().unwrap().prompt()); - - completion::populate_path_cache(session.clone()); - - let _sock_dropper = export_fun::listen(session.clone()); - let _ctrlc = ctrlc::setup(session.clone()); - - 'repl: loop { - let mut se = session.lock().unwrap(); - - let Some(key) = ansi::read(se.debug_keystrokes) else { - break; - }; - - if se.debug_keystrokes { - println!("{key:?}"); - } - - if let Some(cmd) = se.ascii_keybinds.get(key.as_bytes()) { - let cmd = cmd.clone(); - drop(se); - // not sure if/how to report this error - would be strange to print something to console every time a keybind command returns nonzero exit code. - let _ = run::run_quiet(session.clone(), cmd); - continue 'repl; - } - - match key { - ansi::KbInput::Key([x]) => se.type_byte(x), - ansi::KbInput::Escape(escape) => { - for terminfo_key in escape.keys.iter() { - if let Some(cmd) = se.ti_keybinds.get(terminfo_key.as_bytes()) { - let cmd = cmd.clone(); - drop(se); - // not sure if/how to report this error - would be strange to print something to console every time a keybind command returns nonzero exit code. - let _ = run::run_quiet(session.clone(), cmd); - continue 'repl; - } - } - } - ansi::KbInput::InvalidEscape(_) => continue, - } - } - - session.lock().unwrap().raw.disable(); -} +use pish::export_fun; +use std::io::{self, IsTerminal}; fn main() { export_fun::maybe_run_defined_function(); @@ -481,23 +8,23 @@ fn main() { return; } - crate::panic::hook(); + pish::panic::hook(); // it is quite annoying when the terminal window closes due to a crash, so let's just catch all panics loop { - let res = std::panic::catch_unwind(event_loop); + let res = std::panic::catch_unwind(pish::event_loop); match res { Ok(_) => break, Err(_) => { #[cfg(debug_assertions)] unsafe { - reload::continue_reload() + pish::reload::continue_reload() } } } // prevent incredibly fast panic loops - sleep(Duration::from_secs(1)); + std::thread::sleep(std::time::Duration::from_secs(1)); } println!("bye"); |
