Implemented prompt expansion, and display for errors
This commit is contained in:
171
src/state.rs
171
src/state.rs
@@ -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(¶m).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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user