aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.rs
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-05-09 11:30:28 +0200
committerJonas Maier <jonas@x77.dev>2026-05-09 11:30:28 +0200
commitdd24cc2aec9ece8214ec1a4eff4abd26d00ea083 (patch)
tree354f4cb1c13ba20291fd57680aafaa4df67b99bf /src/main.rs
parentedfc7e48c563a97399d18e3ef44fd595c0fd4e45 (diff)
downloadpish-dd24cc2aec9ece8214ec1a4eff4abd26d00ea083.tar.gz
simple script file test
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs485
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");