aboutsummaryrefslogtreecommitdiffstats
path: root/src
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
parentedfc7e48c563a97399d18e3ef44fd595c0fd4e45 (diff)
downloadpish-dd24cc2aec9ece8214ec1a4eff4abd26d00ea083.tar.gz
simple script file test
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs509
-rw-r--r--src/main.rs485
-rw-r--r--src/run/builtin.rs4
-rw-r--r--src/run/mod.rs37
4 files changed, 542 insertions, 493 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..cf57cee
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,509 @@
+#![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::hash::Hash;
+use std::io::{self, 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};
+
+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: Option<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,
+}
+
+impl Session {
+ pub fn new_noninteractive() -> Self {
+ Self {
+ raw: None,
+ line: LineBuf::new(),
+ history: Vec::new(),
+ prev_path: b".".into(),
+ builtins: HashMap::new(),
+ vars: HashMap::new(),
+ funs: HashMap::new(),
+ aliases: run::Aliases::new(),
+ socket_running: None,
+ path_cache: Default::default(),
+ ctrlc: Default::default(),
+ ti_keybinds: HashMap::new(),
+ ascii_keybinds: HashMap::new(),
+ debug_keystrokes: false,
+ loud: false,
+ history_visit: 0,
+ }
+ }
+}
+
+/// 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 raw_enable(&self) {
+ if let Some(r) = &self.raw {
+ r.enable();
+ }
+ }
+
+ fn raw_disable(&self) {
+ if let Some(r) = &self.raw {
+ r.disable();
+ }
+ }
+}
+
+fn exec_rc_file(se: Arc<Mutex<Session>>) {
+ let _ = run::source(
+ se,
+ basedir::config_dir().join(".pishrc").as_os_str().as_bytes(),
+ );
+}
+
+pub 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: Some(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();
+}
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");
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
index cfaef1e..f085005 100644
--- a/src/run/builtin.rs
+++ b/src/run/builtin.rs
@@ -120,9 +120,9 @@ impl Builtin for re {
}
fn special(&mut self, session: Arc<Mutex<Session>>, _args: &[BString]) {
- session.lock().unwrap().raw.disable();
+ session.lock().unwrap().raw_disable();
crate::reload::begin_reload();
- session.lock().unwrap().raw.enable(); // something went wrong, let's restore raw mode
+ session.lock().unwrap().raw_enable(); // something went wrong, let's restore raw mode
}
fn io(
diff --git a/src/run/mod.rs b/src/run/mod.rs
index 885261b..7e575ce 100644
--- a/src/run/mod.rs
+++ b/src/run/mod.rs
@@ -95,12 +95,12 @@ pub struct Executor {
expand_commands: bool,
}
-struct SpawnedPipeline {
+pub struct SpawnedPipeline {
executors: Vec<SpawnedCmd>,
cancel: Vec<Canceler>,
}
-enum SpawnedCmd {
+pub enum SpawnedCmd {
Builtin(ThreadWaiter<Result<(), BuiltinError>>),
Fun(ThreadWaiter<Result<(), ExecError>>),
Child(ChildWaiter),
@@ -135,7 +135,7 @@ impl IsSuccessful for Result<(), ExecError> {
}
impl SpawnedCmd {
- fn join(self) -> Result<(), ExecError> {
+ pub fn join(self) -> Result<(), ExecError> {
match self {
SpawnedCmd::Builtin(handle) => {
handle.into_inner().join().map_err(|_| ExecError::Panic)??
@@ -171,7 +171,7 @@ impl SpawnedCmd {
}
/// returns whether the spawned command is already joined
- fn join_timeout(&mut self, timeout_ms: u16) -> bool {
+ pub fn join_timeout(&mut self, timeout_ms: u16) -> bool {
match self {
SpawnedCmd::Builtin(tw) => tw.try_join(timeout_ms),
SpawnedCmd::Fun(tw) => tw.try_join(timeout_ms),
@@ -185,7 +185,7 @@ impl SpawnedCmd {
}
}
- fn cancel(&mut self) {
+ pub fn cancel(&mut self) {
match self {
SpawnedCmd::Pipeline(pipes) => {
for c in pipes.cancel.iter_mut() {
@@ -226,6 +226,14 @@ impl Executor {
}
}
+ pub fn new(se: Arc<Mutex<Session>>) -> Self {
+ Self {
+ se,
+ args: None,
+ expand_commands: true,
+ }
+ }
+
fn spawn_cmd(
&mut self,
cmd: CommandKind,
@@ -436,6 +444,15 @@ impl Executor {
});
SpawnedCmd::Fun(handle)
}
+
+ pub fn execute_script(
+ &mut self,
+ s: parse::Script,
+ stdin: InputReader,
+ stdout: OutputWriter,
+ ) -> SpawnedCmd {
+ self.execute_block(parse::Block { commands: s.stmts }, stdin, stdout)
+ }
}
impl parse::Expander for Executor {
@@ -559,11 +576,7 @@ impl Aliases {
}
fn exec(se: Arc<Mutex<Session>>, ast: Ast<PreExpansion>) -> Result<(), ExecError> {
- let mut exec = Executor {
- se: se.clone(),
- args: None,
- expand_commands: true,
- };
+ let mut exec = Executor::new(se.clone());
let ast = ast.expand(&mut exec)?;
let (stdin, c1) = InputReader::new(Input::Stdin);
let (stdout, c2) = OutputWriter::new(Output::Stdout);
@@ -587,9 +600,9 @@ pub fn run_quiet(
}
pub fn run(se: Arc<Mutex<Session>>, parsed: Ast<PreExpansion>) {
- se.lock().unwrap().raw.disable();
+ se.lock().unwrap().raw_disable();
let result = exec(se.clone(), parsed);
- se.lock().unwrap().raw.enable();
+ se.lock().unwrap().raw_enable();
if se.lock().unwrap().loud {
let status_string = match result {