- fixed 'I' command in normal mode not moving to exact start of line
- Added disown builtin - Fixed job table not hanging up child processes on exit - Added target architecture and os to --version output - Added local builtin for creating variables scoped to functions
This commit is contained in:
34
src/builtin/eval.rs
Normal file
34
src/builtin/eval.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use nix::{errno::Errno, unistd::execvpe};
|
||||
|
||||
use crate::{
|
||||
builtin::setup_builtin,
|
||||
jobs::JobBldr,
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node, execute::exec_input},
|
||||
procio::IoStack,
|
||||
state,
|
||||
};
|
||||
|
||||
pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (expanded_argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if expanded_argv.is_empty() {
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let joined_argv = expanded_argv.into_iter()
|
||||
.map(|(s, _)| s)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
exec_input(joined_argv, None, false)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
state::{self, VarFlags, write_vars},
|
||||
state::{self, VarFlags, read_vars, write_vars},
|
||||
};
|
||||
|
||||
use super::setup_builtin;
|
||||
@@ -45,3 +45,42 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
let vars_output = read_vars(|v| {
|
||||
let mut vars = v.flatten_vars()
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<String>>();
|
||||
vars.sort();
|
||||
let mut vars_joined = vars.join("\n");
|
||||
vars_joined.push('\n');
|
||||
vars_joined
|
||||
});
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, vars_output.as_bytes())?; // Write it
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
if let Some((var, val)) = arg.split_once('=') {
|
||||
write_vars(|v| v.set_var(var, val, VarFlags::LOCAL));
|
||||
} else {
|
||||
write_vars(|v| v.set_var(&arg, "", VarFlags::LOCAL)); // Create an uninitialized local variable
|
||||
}
|
||||
}
|
||||
}
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -179,3 +179,46 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||
let mut argv = argv.into_iter();
|
||||
|
||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::ExecFail, "disown: No jobs to disown", blame));
|
||||
};
|
||||
|
||||
let mut tabid = curr_job_id;
|
||||
let mut nohup = false;
|
||||
let mut disown_all = false;
|
||||
|
||||
while let Some((arg, span)) = argv.next() {
|
||||
match arg.as_str() {
|
||||
"-h" => nohup = true,
|
||||
"-a" => disown_all = true,
|
||||
_ => {
|
||||
tabid = parse_job_id(&arg, span.clone())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disown_all {
|
||||
write_jobs(|j| j.disown_all(nohup))?;
|
||||
} else {
|
||||
write_jobs(|j| j.disown(JobID::TableID(tabid), nohup))?;
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
1
src/builtin/local.rs
Normal file
1
src/builtin/local.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -27,11 +27,12 @@ pub mod trap;
|
||||
pub mod zoltraak;
|
||||
pub mod dirstack;
|
||||
pub mod exec;
|
||||
pub mod eval;
|
||||
|
||||
pub const BUILTINS: [&str; 25] = [
|
||||
"echo", "cd", "read", "export", "pwd", "source", "shift", "jobs", "fg", "bg", "alias", "unalias",
|
||||
pub const BUILTINS: [&str; 28] = [
|
||||
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown", "alias", "unalias",
|
||||
"return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command", "trap",
|
||||
"pushd", "popd", "dirs", "exec",
|
||||
"pushd", "popd", "dirs", "exec", "eval"
|
||||
];
|
||||
|
||||
/// Sets up a builtin command
|
||||
|
||||
54
src/jobs.rs
54
src/jobs.rs
@@ -373,7 +373,7 @@ impl JobTab {
|
||||
if job
|
||||
.get_stats()
|
||||
.iter()
|
||||
.all(|stat| matches!(stat, WtStat::Exited(_, _)))
|
||||
.all(|stat| matches!(stat, WtStat::Exited(_, _) | WtStat::Signaled(_, _, _)))
|
||||
{
|
||||
jobs_to_remove.push(JobID::TableID(id));
|
||||
}
|
||||
@@ -383,6 +383,41 @@ impl JobTab {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hang_up(&mut self) {
|
||||
for job in self.jobs_mut().iter_mut().flatten() {
|
||||
if job.send_hup {
|
||||
job.killpg(Signal::SIGHUP).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disown(&mut self, id: JobID, nohup: bool) -> ShResult<()> {
|
||||
if let Some(job) = self.query_mut(id.clone()) {
|
||||
if nohup {
|
||||
job.no_hup();
|
||||
} else {
|
||||
self.remove_job(id);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disown_all(&mut self, nohup: bool) -> ShResult<()> {
|
||||
let mut ids_to_remove = vec![];
|
||||
for job in self.jobs_mut().iter_mut().flatten() {
|
||||
if nohup {
|
||||
job.no_hup();
|
||||
} else {
|
||||
ids_to_remove.push(JobID::TableID(job.tabid().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
for id in ids_to_remove {
|
||||
self.remove_job(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -390,6 +425,7 @@ pub struct JobBldr {
|
||||
table_id: Option<usize>,
|
||||
pgid: Option<Pid>,
|
||||
children: Vec<ChildProc>,
|
||||
send_hup: bool,
|
||||
}
|
||||
|
||||
impl Default for JobBldr {
|
||||
@@ -404,6 +440,7 @@ impl JobBldr {
|
||||
table_id: None,
|
||||
pgid: None,
|
||||
children: vec![],
|
||||
send_hup: true,
|
||||
}
|
||||
}
|
||||
pub fn with_id(self, id: usize) -> Self {
|
||||
@@ -411,6 +448,7 @@ impl JobBldr {
|
||||
table_id: Some(id),
|
||||
pgid: self.pgid,
|
||||
children: self.children,
|
||||
send_hup: self.send_hup,
|
||||
}
|
||||
}
|
||||
pub fn with_pgid(self, pgid: Pid) -> Self {
|
||||
@@ -418,6 +456,7 @@ impl JobBldr {
|
||||
table_id: self.table_id,
|
||||
pgid: Some(pgid),
|
||||
children: self.children,
|
||||
send_hup: self.send_hup,
|
||||
}
|
||||
}
|
||||
pub fn set_pgid(&mut self, pgid: Pid) {
|
||||
@@ -426,11 +465,16 @@ impl JobBldr {
|
||||
pub fn pgid(&self) -> Option<Pid> {
|
||||
self.pgid
|
||||
}
|
||||
pub fn no_hup(mut self) -> Self {
|
||||
self.send_hup = false;
|
||||
self
|
||||
}
|
||||
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid,
|
||||
children,
|
||||
send_hup: self.send_hup,
|
||||
}
|
||||
}
|
||||
pub fn push_child(&mut self, child: ChildProc) {
|
||||
@@ -441,6 +485,7 @@ impl JobBldr {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
||||
children: self.children,
|
||||
send_hup: self.send_hup,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,12 +514,16 @@ pub struct Job {
|
||||
table_id: Option<usize>,
|
||||
pgid: Pid,
|
||||
children: Vec<ChildProc>,
|
||||
send_hup: bool,
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn set_tabid(&mut self, id: usize) {
|
||||
self.table_id = Some(id)
|
||||
}
|
||||
pub fn no_hup(&mut self) {
|
||||
self.send_hup = false;
|
||||
}
|
||||
pub fn running(&self) -> bool {
|
||||
!self.children.iter().all(|chld| chld.exited())
|
||||
}
|
||||
@@ -520,8 +569,7 @@ impl Job {
|
||||
let stat = match sig {
|
||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||
Signal::SIGCONT => WtStat::Continued(self.pgid),
|
||||
Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false),
|
||||
_ => unimplemented!("{}", sig),
|
||||
sig => WtStat::Signaled(self.pgid, sig, false),
|
||||
};
|
||||
self.set_stats(stat);
|
||||
Ok(killpg(self.pgid, sig)?)
|
||||
|
||||
21
src/main.rs
21
src/main.rs
@@ -36,7 +36,7 @@ use crate::prompt::get_prompt;
|
||||
use crate::prompt::readline::term::raw_mode;
|
||||
use crate::prompt::readline::{FernVi, ReadlineEvent};
|
||||
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||
use crate::state::{read_logic, source_rc, write_meta};
|
||||
use crate::state::{read_logic, source_rc, write_jobs, write_meta};
|
||||
use clap::Parser;
|
||||
use state::{read_vars, write_vars};
|
||||
|
||||
@@ -74,9 +74,25 @@ fn kickstart_lazy_evals() {
|
||||
read_vars(|_| {});
|
||||
}
|
||||
|
||||
/// We need to make sure that even if we panic, our child processes get sighup
|
||||
fn setup_panic_handler() {
|
||||
let default_panic_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let _ = state::FERN.try_with(|fern| {
|
||||
if let Ok(mut jobs) = fern.jobs.try_borrow_mut() {
|
||||
jobs.hang_up();
|
||||
}
|
||||
});
|
||||
|
||||
default_panic_hook(info);
|
||||
}));
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
env_logger::init();
|
||||
kickstart_lazy_evals();
|
||||
setup_panic_handler();
|
||||
|
||||
let mut args = FernArgs::parse();
|
||||
if env::args().next().is_some_and(|a| a.starts_with('-')) {
|
||||
// first arg is '-fern'
|
||||
@@ -84,7 +100,7 @@ fn main() -> ExitCode {
|
||||
args.login_shell = true;
|
||||
}
|
||||
if args.version {
|
||||
println!("fern {}", env!("CARGO_PKG_VERSION"));
|
||||
println!("fern {} ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::ARCH, std::env::consts::OS);
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -104,6 +120,7 @@ fn main() -> ExitCode {
|
||||
eprintln!("fern: error running EXIT trap: {e}");
|
||||
}
|
||||
|
||||
write_jobs(|j| j.hang_up());
|
||||
ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::{HashSet, VecDeque};
|
||||
|
||||
use crate::{
|
||||
builtin::{
|
||||
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, exec, export::export, flowctl::flowctl, jobctl::{JobBehavior, continue_job, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak
|
||||
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, export::{export, local}, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak
|
||||
},
|
||||
expand::expand_aliases,
|
||||
jobs::{ChildProc, JobStack, dispatch_job},
|
||||
@@ -588,11 +588,13 @@ impl Dispatcher {
|
||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
||||
"cd" => cd(cmd, curr_job_mut),
|
||||
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
||||
"local" => local(cmd, io_stack_mut, curr_job_mut),
|
||||
"pwd" => pwd(cmd, io_stack_mut, curr_job_mut),
|
||||
"source" => source(cmd, curr_job_mut),
|
||||
"shift" => shift(cmd, curr_job_mut),
|
||||
"fg" => continue_job(cmd, curr_job_mut, JobBehavior::Foregound),
|
||||
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
||||
"disown" => disown(cmd, io_stack_mut, curr_job_mut),
|
||||
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
||||
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
||||
"unalias" => unalias(cmd, io_stack_mut, curr_job_mut),
|
||||
@@ -608,6 +610,7 @@ impl Dispatcher {
|
||||
"popd" => popd(cmd, io_stack_mut, curr_job_mut),
|
||||
"dirs" => dirs(cmd, io_stack_mut, curr_job_mut),
|
||||
"exec" => exec::exec_builtin(cmd, io_stack_mut, curr_job_mut),
|
||||
"eval" => eval::eval(cmd, io_stack_mut, curr_job_mut),
|
||||
_ => unimplemented!(
|
||||
"Have not yet added support for builtin '{}'",
|
||||
cmd_raw.span.as_str()
|
||||
|
||||
@@ -677,15 +677,19 @@ pub fn get_insertions(input: &str) -> Vec<(usize, Marker)> {
|
||||
/// - Unimplemented features (comments, brace groups)
|
||||
pub fn marker_for(class: &TkRule) -> Option<Marker> {
|
||||
match class {
|
||||
TkRule::Pipe | TkRule::ErrPipe | TkRule::And | TkRule::Or | TkRule::Bg => {
|
||||
TkRule::Pipe |
|
||||
TkRule::ErrPipe |
|
||||
TkRule::And |
|
||||
TkRule::Or |
|
||||
TkRule::Bg |
|
||||
TkRule::BraceGrpStart |
|
||||
TkRule::BraceGrpEnd => {
|
||||
Some(markers::OPERATOR)
|
||||
}
|
||||
TkRule::Sep => Some(markers::CMD_SEP),
|
||||
TkRule::Redir => Some(markers::REDIRECT),
|
||||
TkRule::CasePattern => Some(markers::CASE_PAT),
|
||||
TkRule::BraceGrpStart => todo!(),
|
||||
TkRule::BraceGrpEnd => todo!(),
|
||||
TkRule::Comment => todo!(),
|
||||
TkRule::Comment => Some(markers::COMMENT),
|
||||
TkRule::Expanded { exp: _ } | TkRule::EOI | TkRule::SOI | TkRule::Null | TkRule::Str => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,7 @@ pub fn hang_up(_: libc::c_int) {
|
||||
SHOULD_QUIT.store(true, Ordering::SeqCst);
|
||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
j.hang_up();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user