Implemented prompt expansion, and display for errors

This commit is contained in:
2025-03-18 15:54:06 -04:00
parent 464caa4517
commit 62c76e70a6
14 changed files with 1022 additions and 288 deletions

View File

@@ -1,23 +1,59 @@
use std::{cell::RefCell, collections::{HashMap, VecDeque}, ops::Range, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
use std::{collections::{HashMap, VecDeque}, ops::{Deref, Range}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration};
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, Node}, prelude::*};
use nix::unistd::{gethostname, getppid, User};
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*};
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
pub static META_TABLE: LazyLock<RwLock<MetaTab>> = LazyLock::new(|| RwLock::new(MetaTab::new()));
thread_local! {
pub static LAST_INPUT: RefCell<String> = RefCell::new(String::new());
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
}
/// A shell function
///
/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to
/// The Node must be stored with the ParsedSrc because the tokens of the node contain an Rc<String>
/// Which refers to the String held in ParsedSrc
///
/// Can be dereferenced to pull out the wrapped Node
#[derive(Clone,Debug)]
pub struct ShFunc(Node);
impl ShFunc {
pub fn new(mut src: ParsedSrc) -> Self {
let body = Self::extract_brc_grp_hack(src.extract_nodes());
Self(body)
}
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
// FIXME: find a better way to do this
let conjunction = tree.pop().unwrap();
let NdRule::Conjunction { mut elements } = conjunction.class else {
unreachable!()
};
let conjunct_node = elements.pop().unwrap();
let ConjunctNode { cmd, operator: _ } = conjunct_node;
*cmd
}
}
impl Deref for ShFunc {
type Target = Node;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// The logic table for the shell
///
/// Contains aliases and functions
pub struct LogTab {
// TODO: Find a way to store actual owned nodes instead of strings that must be re-parsed
functions: HashMap<String,String>,
functions: HashMap<String,ShFunc>,
aliases: HashMap<String,String>
}
@@ -25,10 +61,10 @@ impl LogTab {
pub fn new() -> Self {
Self { functions: HashMap::new(), aliases: HashMap::new() }
}
pub fn insert_func(&mut self, name: &str, body: &str) {
self.functions.insert(name.into(), body.into());
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
self.functions.insert(name.into(), src);
}
pub fn get_func(&self, name: &str) -> Option<String> {
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
self.functions.get(name).cloned()
}
pub fn insert_alias(&mut self, name: &str, body: &str) {
@@ -39,6 +75,7 @@ impl LogTab {
}
}
#[derive(Clone)]
pub struct VarTab {
vars: HashMap<String,String>,
params: HashMap<char,String>,
@@ -49,6 +86,7 @@ impl VarTab {
pub fn new() -> Self {
let vars = HashMap::new();
let params = Self::init_params();
Self::init_env();
let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() };
var_tab.init_sh_argv();
var_tab
@@ -62,6 +100,51 @@ impl VarTab {
params.insert('!', "".into()); // PID of the last background job (if any)
params
}
fn init_env() {
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
// First, inherit any env vars from the parent process
let term = {
if isatty(1).unwrap() {
if let Ok(term) = std::env::var("TERM") {
term
} else {
"linux".to_string()
}
} else {
"xterm-256color".to_string()
}
};
let home;
let username;
let uid;
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
home = user.dir;
username = user.name;
uid = user.uid;
} else {
home = PathBuf::new();
username = "unknown".into();
uid = 0.into();
}
let home = pathbuf_to_string(Ok(home));
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
env::set_var("IFS", " \t\n");
env::set_var("HOSTNAME", hostname);
env::set_var("UID", uid.to_string());
env::set_var("PPID", getppid().to_string());
env::set_var("TMPDIR", "/tmp");
env::set_var("TERM", term);
env::set_var("LANG", "en_US.UTF-8");
env::set_var("USER", username.clone());
env::set_var("LOGNAME", username);
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
env::set_var("HOME", home.clone());
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
env::set_var("FERN_HIST",format!("{}/.fern_hist",home));
env::set_var("FERN_RC",format!("{}/.fernrc",home));
}
pub fn init_sh_argv(&mut self) {
for arg in env::args() {
self.bpush_arg(arg);
@@ -74,11 +157,15 @@ impl VarTab {
&mut self.sh_argv
}
pub fn clear_args(&mut self) {
self.sh_argv.clear()
self.sh_argv.clear();
// Push the current exe again
// This makes sure that $0 is always the current shell, no matter what
// It also updates the arg parameters '@' and '#' as well
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
}
fn update_arg_params(&mut self) {
self.set_param('@', &self.sh_argv.clone().to_vec().join(" "));
self.set_param('#', &self.sh_argv.len().to_string());
self.set_param('@', &self.sh_argv.clone().to_vec()[1..].join(" "));
self.set_param('#', &(self.sh_argv.len() - 1).to_string());
}
/// Push an arg to the front of the arg deque
pub fn fpush_arg(&mut self, arg: String) {
@@ -139,7 +226,8 @@ impl VarTab {
.to_string()
.parse::<usize>()
.unwrap();
return self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default()
let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default();
arg
} else if param == '?' {
self.params.get(&param).map(|s| s.to_string()).unwrap_or("0".into())
} else {
@@ -148,6 +236,25 @@ impl VarTab {
}
}
/// A table of metadata for the shell
pub struct MetaTab {
runtime_start: Option<Instant>
}
impl MetaTab {
pub fn new() -> Self {
Self { runtime_start: None }
}
pub fn start_timer(&mut self) {
self.runtime_start = Some(Instant::now());
}
pub fn stop_timer(&mut self) -> Option<Duration> {
self.runtime_start
.take() // runtime_start returns to None
.map(|start| start.elapsed()) // return the duration, if any
}
}
/// Read from the job table
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
let lock = JOB_TABLE.read().unwrap();
@@ -172,30 +279,30 @@ pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
f(lock)
}
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
let lock = META_TABLE.read().unwrap();
f(lock)
}
/// Write to the variable table
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
let lock = &mut META_TABLE.write().unwrap();
f(lock)
}
/// Read from the logic table
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
let lock = LOGIC_TABLE.read().unwrap();
f(lock)
LOGIC_TABLE.with(|log| {
let lock = log.read().unwrap();
f(lock)
})
}
/// Write to the logic table
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
let lock = &mut LOGIC_TABLE.write().unwrap();
f(lock)
}
pub fn set_last_input(input: &str) {
LAST_INPUT.with(|input_ref| {
let mut last_input = input_ref.borrow_mut();
last_input.clear();
last_input.push_str(input);
})
}
pub fn slice_last_input(range: Range<usize>) -> String {
LAST_INPUT.with(|input_ref| {
let input = input_ref.borrow();
input[range].to_string()
LOGIC_TABLE.with(|log| {
let lock = &mut log.write().unwrap();
f(lock)
})
}
@@ -213,6 +320,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
let mut buf = String::new();
file.read_to_string(&mut buf)?;
exec_input(&buf, None)?;
exec_input(buf)?;
Ok(())
}