Compare commits
2 Commits
62d651eb8d
...
cab7a0fea7
| Author | SHA1 | Date | |
|---|---|---|---|
| cab7a0fea7 | |||
| 367218d3e8 |
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state::{self, read_logic, write_logic},
|
state::{self, read_logic, write_logic},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ use std::sync::LazyLock;
|
|||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
builtin::setup_builtin,
|
||||||
expand::expand_prompt,
|
expand::expand_prompt,
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
getopt::{get_opts_from_tokens, Opt, OptSpec},
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
builtin::setup_builtin,
|
builtin::setup_builtin,
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{NdRule, Node, execute::exec_input},
|
parse::{execute::exec_input, NdRule, Node},
|
||||||
procio::IoStack,
|
procio::IoStack,
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
@@ -25,10 +25,11 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let joined_argv = expanded_argv.into_iter()
|
let joined_argv = expanded_argv
|
||||||
.map(|(s, _)| s)
|
.into_iter()
|
||||||
.collect::<Vec<_>>()
|
.map(|(s, _)| s)
|
||||||
.join(" ");
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
exec_input(joined_argv, None, false)
|
exec_input(joined_argv, None, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
builtin::setup_builtin,
|
builtin::setup_builtin,
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node, execute::ExecArgs},
|
parse::{execute::ExecArgs, NdRule, Node},
|
||||||
procio::IoStack,
|
procio::IoStack,
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
@@ -40,11 +40,7 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
// execvpe only returns on error
|
// execvpe only returns on error
|
||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => {
|
Errno::ENOENT => Err(ShErr::full(ShErrKind::CmdNotFound(cmd_str), "", span)),
|
||||||
Err(ShErr::full(ShErrKind::CmdNotFound(cmd_str), "", span))
|
_ => Err(ShErr::full(ShErrKind::Errno(e), format!("{e}"), span)),
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
Err(ShErr::full(ShErrKind::Errno(e), format!("{e}"), span))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state::{self, VarFlags, read_vars, write_vars},
|
state::{self, read_vars, write_vars, VarFlags},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
@@ -35,10 +35,10 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
for (arg, _) in argv {
|
for (arg, _) in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var, val)) = arg.split_once('=') {
|
||||||
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT)); // Export an assignment like
|
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT)); // Export an assignment like
|
||||||
// 'foo=bar'
|
// 'foo=bar'
|
||||||
} else {
|
} else {
|
||||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
||||||
// any
|
// any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,16 +59,17 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the local variables
|
// Display the local variables
|
||||||
let vars_output = read_vars(|v| {
|
let vars_output = read_vars(|v| {
|
||||||
let mut vars = v.flatten_vars()
|
let mut vars = v
|
||||||
.into_iter()
|
.flatten_vars()
|
||||||
.map(|(k, v)| format!("{}={}", k, v))
|
.into_iter()
|
||||||
.collect::<Vec<String>>();
|
.map(|(k, v)| format!("{}={}", k, v))
|
||||||
vars.sort();
|
.collect::<Vec<String>>();
|
||||||
let mut vars_joined = vars.join("\n");
|
vars.sort();
|
||||||
vars_joined.push('\n');
|
let mut vars_joined = vars.join("\n");
|
||||||
vars_joined
|
vars_joined.push('\n');
|
||||||
});
|
vars_joined
|
||||||
|
});
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, vars_output.as_bytes())?; // Write it
|
write(stdout, vars_output.as_bytes())?; // Write it
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node, execute::prepare_argv},
|
parse::{execute::prepare_argv, NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::{JobBldr, JobCmdFlags, JobID},
|
jobs::{JobBldr, JobCmdFlags, JobID},
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node, lex::Span},
|
parse::{lex::Span, NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state::{self, read_jobs, write_jobs},
|
state::{self, read_jobs, write_jobs},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -196,29 +196,33 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(ShErrKind::ExecFail, "disown: No jobs to disown", blame));
|
return Err(ShErr::full(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
"disown: No jobs to disown",
|
||||||
|
blame,
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tabid = curr_job_id;
|
let mut tabid = curr_job_id;
|
||||||
let mut nohup = false;
|
let mut nohup = false;
|
||||||
let mut disown_all = false;
|
let mut disown_all = false;
|
||||||
|
|
||||||
while let Some((arg, span)) = argv.next() {
|
while let Some((arg, span)) = argv.next() {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-h" => nohup = true,
|
"-h" => nohup = true,
|
||||||
"-a" => disown_all = true,
|
"-a" => disown_all = true,
|
||||||
_ => {
|
_ => {
|
||||||
tabid = parse_job_id(&arg, span.clone())?;
|
tabid = parse_job_id(&arg, span.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if disown_all {
|
if disown_all {
|
||||||
write_jobs(|j| j.disown_all(nohup))?;
|
write_jobs(|j| j.disown_all(nohup))?;
|
||||||
} else {
|
} else {
|
||||||
write_jobs(|j| j.disown(JobID::TableID(tabid), nohup))?;
|
write_jobs(|j| j.disown(JobID::TableID(tabid), nohup))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ use nix::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin,
|
builtin::setup_builtin,
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
getopt::{get_opts_from_tokens, Opt, OptSpec},
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
prompt::readline::term::RawModeGuard,
|
prompt::readline::term::RawModeGuard,
|
||||||
state::{self, VarFlags, read_vars, write_vars},
|
state::{self, read_vars, write_vars, VarFlags},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const READ_OPTS: [OptSpec; 7] = [
|
pub const READ_OPTS: [OptSpec; 7] = [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
libsh::error::{ShResult, ShResultExt},
|
libsh::error::{ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state::write_shopts,
|
state::write_shopts,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use regex::Regex;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{ConjunctOp, NdRule, Node, TEST_UNARY_OPS, TestCase},
|
parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
state::{self, read_logic, write_logic},
|
state::{self, read_logic, write_logic},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{Opt, OptSet, OptSpec, get_opts_from_tokens},
|
getopt::{get_opts_from_tokens, Opt, OptSet, OptSpec},
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{borrow_fd, IoStack},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|||||||
92
src/jobs.rs
92
src/jobs.rs
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
term::{Style, Styled},
|
term::{Style, Styled},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoMode, borrow_fd},
|
procio::{borrow_fd, IoMode},
|
||||||
signal::{disable_reaping, enable_reaping},
|
signal::{disable_reaping, enable_reaping},
|
||||||
state::{self, read_jobs, set_status, write_jobs},
|
state::{self, read_jobs, set_status, write_jobs},
|
||||||
};
|
};
|
||||||
@@ -384,40 +384,40 @@ impl JobTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hang_up(&mut self) {
|
pub fn hang_up(&mut self) {
|
||||||
for job in self.jobs_mut().iter_mut().flatten() {
|
for job in self.jobs_mut().iter_mut().flatten() {
|
||||||
if job.send_hup {
|
if job.send_hup {
|
||||||
job.killpg(Signal::SIGHUP).ok();
|
job.killpg(Signal::SIGHUP).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disown(&mut self, id: JobID, nohup: bool) -> ShResult<()> {
|
pub fn disown(&mut self, id: JobID, nohup: bool) -> ShResult<()> {
|
||||||
if let Some(job) = self.query_mut(id.clone()) {
|
if let Some(job) = self.query_mut(id.clone()) {
|
||||||
if nohup {
|
if nohup {
|
||||||
job.no_hup();
|
job.no_hup();
|
||||||
} else {
|
} else {
|
||||||
self.remove_job(id);
|
self.remove_job(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disown_all(&mut self, nohup: bool) -> ShResult<()> {
|
pub fn disown_all(&mut self, nohup: bool) -> ShResult<()> {
|
||||||
let mut ids_to_remove = vec![];
|
let mut ids_to_remove = vec![];
|
||||||
for job in self.jobs_mut().iter_mut().flatten() {
|
for job in self.jobs_mut().iter_mut().flatten() {
|
||||||
if nohup {
|
if nohup {
|
||||||
job.no_hup();
|
job.no_hup();
|
||||||
} else {
|
} else {
|
||||||
ids_to_remove.push(JobID::TableID(job.tabid().unwrap()));
|
ids_to_remove.push(JobID::TableID(job.tabid().unwrap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for id in ids_to_remove {
|
for id in ids_to_remove {
|
||||||
self.remove_job(id);
|
self.remove_job(id);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -425,7 +425,7 @@ pub struct JobBldr {
|
|||||||
table_id: Option<usize>,
|
table_id: Option<usize>,
|
||||||
pgid: Option<Pid>,
|
pgid: Option<Pid>,
|
||||||
children: Vec<ChildProc>,
|
children: Vec<ChildProc>,
|
||||||
send_hup: bool,
|
send_hup: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JobBldr {
|
impl Default for JobBldr {
|
||||||
@@ -440,7 +440,7 @@ impl JobBldr {
|
|||||||
table_id: None,
|
table_id: None,
|
||||||
pgid: None,
|
pgid: None,
|
||||||
children: vec![],
|
children: vec![],
|
||||||
send_hup: true,
|
send_hup: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_id(self, id: usize) -> Self {
|
pub fn with_id(self, id: usize) -> Self {
|
||||||
@@ -448,7 +448,7 @@ impl JobBldr {
|
|||||||
table_id: Some(id),
|
table_id: Some(id),
|
||||||
pgid: self.pgid,
|
pgid: self.pgid,
|
||||||
children: self.children,
|
children: self.children,
|
||||||
send_hup: self.send_hup,
|
send_hup: self.send_hup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_pgid(self, pgid: Pid) -> Self {
|
pub fn with_pgid(self, pgid: Pid) -> Self {
|
||||||
@@ -456,7 +456,7 @@ impl JobBldr {
|
|||||||
table_id: self.table_id,
|
table_id: self.table_id,
|
||||||
pgid: Some(pgid),
|
pgid: Some(pgid),
|
||||||
children: self.children,
|
children: self.children,
|
||||||
send_hup: self.send_hup,
|
send_hup: self.send_hup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_pgid(&mut self, pgid: Pid) {
|
pub fn set_pgid(&mut self, pgid: Pid) {
|
||||||
@@ -465,16 +465,16 @@ impl JobBldr {
|
|||||||
pub fn pgid(&self) -> Option<Pid> {
|
pub fn pgid(&self) -> Option<Pid> {
|
||||||
self.pgid
|
self.pgid
|
||||||
}
|
}
|
||||||
pub fn no_hup(mut self) -> Self {
|
pub fn no_hup(mut self) -> Self {
|
||||||
self.send_hup = false;
|
self.send_hup = false;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
table_id: self.table_id,
|
table_id: self.table_id,
|
||||||
pgid: self.pgid,
|
pgid: self.pgid,
|
||||||
children,
|
children,
|
||||||
send_hup: self.send_hup,
|
send_hup: self.send_hup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn push_child(&mut self, child: ChildProc) {
|
pub fn push_child(&mut self, child: ChildProc) {
|
||||||
@@ -485,7 +485,7 @@ impl JobBldr {
|
|||||||
table_id: self.table_id,
|
table_id: self.table_id,
|
||||||
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
||||||
children: self.children,
|
children: self.children,
|
||||||
send_hup: self.send_hup,
|
send_hup: self.send_hup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,16 +514,16 @@ pub struct Job {
|
|||||||
table_id: Option<usize>,
|
table_id: Option<usize>,
|
||||||
pgid: Pid,
|
pgid: Pid,
|
||||||
children: Vec<ChildProc>,
|
children: Vec<ChildProc>,
|
||||||
send_hup: bool,
|
send_hup: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Job {
|
impl Job {
|
||||||
pub fn set_tabid(&mut self, id: usize) {
|
pub fn set_tabid(&mut self, id: usize) {
|
||||||
self.table_id = Some(id)
|
self.table_id = Some(id)
|
||||||
}
|
}
|
||||||
pub fn no_hup(&mut self) {
|
pub fn no_hup(&mut self) {
|
||||||
self.send_hup = false;
|
self.send_hup = false;
|
||||||
}
|
}
|
||||||
pub fn running(&self) -> bool {
|
pub fn running(&self) -> bool {
|
||||||
!self.children.iter().all(|chld| chld.exited())
|
!self.children.iter().all(|chld| chld.exited())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ use crate::prelude::*;
|
|||||||
pub(crate) static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
pub(crate) static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||||
|
|
||||||
pub static TTY_FILENO: LazyLock<RawFd> = LazyLock::new(|| {
|
pub static TTY_FILENO: LazyLock<RawFd> = LazyLock::new(|| {
|
||||||
open("/dev/tty", OFlag::O_RDWR, Mode::empty())
|
open("/dev/tty", OFlag::O_RDWR, Mode::empty()).expect("Failed to open /dev/tty")
|
||||||
.expect("Failed to open /dev/tty")
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ impl<T: Display> Styled for T {}
|
|||||||
pub enum Style {
|
pub enum Style {
|
||||||
// Undoes all styles
|
// Undoes all styles
|
||||||
Reset,
|
Reset,
|
||||||
ResetFg,
|
ResetFg,
|
||||||
ResetBg,
|
ResetBg,
|
||||||
// Foreground Colors
|
// Foreground Colors
|
||||||
Black,
|
Black,
|
||||||
Red,
|
Red,
|
||||||
@@ -68,8 +68,8 @@ impl Display for Style {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Style::Reset => write!(f, "\x1b[0m"),
|
Style::Reset => write!(f, "\x1b[0m"),
|
||||||
Style::ResetFg => write!(f, "\x1b[39m"),
|
Style::ResetFg => write!(f, "\x1b[39m"),
|
||||||
Style::ResetBg => write!(f, "\x1b[49m"),
|
Style::ResetBg => write!(f, "\x1b[49m"),
|
||||||
|
|
||||||
// Foreground colors
|
// Foreground colors
|
||||||
Style::Black => write!(f, "\x1b[30m"),
|
Style::Black => write!(f, "\x1b[30m"),
|
||||||
@@ -131,13 +131,13 @@ impl StyleSet {
|
|||||||
Self { styles: vec![] }
|
Self { styles: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn styles(&self) -> &[Style] {
|
pub fn styles(&self) -> &[Style] {
|
||||||
&self.styles
|
&self.styles
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn styles_mut(&mut self) -> &mut Vec<Style> {
|
pub fn styles_mut(&mut self) -> &mut Vec<Style> {
|
||||||
&mut self.styles
|
&mut self.styles
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_style(mut self, style: Style) -> Self {
|
pub fn add_style(mut self, style: Style) -> Self {
|
||||||
if !self.styles.contains(&style) {
|
if !self.styles.contains(&style) {
|
||||||
|
|||||||
347
src/main.rs
347
src/main.rs
@@ -1,7 +1,7 @@
|
|||||||
#![allow(
|
#![allow(
|
||||||
clippy::derivable_impls,
|
clippy::derivable_impls,
|
||||||
clippy::tabs_in_doc_comments,
|
clippy::tabs_in_doc_comments,
|
||||||
clippy::while_let_on_iterator
|
clippy::while_let_on_iterator
|
||||||
)]
|
)]
|
||||||
pub mod builtin;
|
pub mod builtin;
|
||||||
pub mod expand;
|
pub mod expand;
|
||||||
@@ -23,7 +23,6 @@ use std::process::ExitCode;
|
|||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use nix::errno::Errno;
|
use nix::errno::Errno;
|
||||||
use nix::libc::STDIN_FILENO;
|
|
||||||
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
|
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
|
||||||
use nix::unistd::read;
|
use nix::unistd::read;
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ use crate::libsh::sys::TTY_FILENO;
|
|||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::prompt::get_prompt;
|
use crate::prompt::get_prompt;
|
||||||
use crate::prompt::readline::term::raw_mode;
|
use crate::prompt::readline::term::{RawModeGuard, raw_mode};
|
||||||
use crate::prompt::readline::{FernVi, ReadlineEvent};
|
use crate::prompt::readline::{FernVi, ReadlineEvent};
|
||||||
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
|
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||||
use crate::state::{read_logic, source_rc, write_jobs, write_meta};
|
use crate::state::{read_logic, source_rc, write_jobs, write_meta};
|
||||||
@@ -42,16 +41,16 @@ use state::{read_vars, write_vars};
|
|||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct FernArgs {
|
struct FernArgs {
|
||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
|
|
||||||
#[arg(short)]
|
#[arg(short)]
|
||||||
command: Option<String>,
|
command: Option<String>,
|
||||||
|
|
||||||
#[arg(trailing_var_arg = true)]
|
#[arg(trailing_var_arg = true)]
|
||||||
script_args: Vec<String>,
|
script_args: Vec<String>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
version: bool,
|
version: bool,
|
||||||
|
|
||||||
#[arg(short)]
|
#[arg(short)]
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
@@ -71,7 +70,7 @@ struct FernArgs {
|
|||||||
/// closure, which forces access to the variable table and causes its `LazyLock`
|
/// closure, which forces access to the variable table and causes its `LazyLock`
|
||||||
/// constructor to run.
|
/// constructor to run.
|
||||||
fn kickstart_lazy_evals() {
|
fn kickstart_lazy_evals() {
|
||||||
read_vars(|_| {});
|
read_vars(|_| {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We need to make sure that even if we panic, our child processes get sighup
|
/// We need to make sure that even if we panic, our child processes get sighup
|
||||||
@@ -89,196 +88,192 @@ fn setup_panic_handler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
kickstart_lazy_evals();
|
kickstart_lazy_evals();
|
||||||
setup_panic_handler();
|
setup_panic_handler();
|
||||||
|
|
||||||
let mut args = FernArgs::parse();
|
let mut args = FernArgs::parse();
|
||||||
if env::args().next().is_some_and(|a| a.starts_with('-')) {
|
if env::args().next().is_some_and(|a| a.starts_with('-')) {
|
||||||
// first arg is '-fern'
|
// first arg is '-fern'
|
||||||
// meaning we are in a login shell
|
// meaning we are in a login shell
|
||||||
args.login_shell = true;
|
args.login_shell = true;
|
||||||
}
|
}
|
||||||
if args.version {
|
if args.version {
|
||||||
println!("fern {} ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::ARCH, std::env::consts::OS);
|
println!("fern {} ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::ARCH, std::env::consts::OS);
|
||||||
return ExitCode::SUCCESS;
|
return ExitCode::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = if let Some(path) = args.script {
|
if let Err(e) = if let Some(path) = args.script {
|
||||||
run_script(path, args.script_args)
|
run_script(path, args.script_args)
|
||||||
} else if let Some(cmd) = args.command {
|
} else if let Some(cmd) = args.command {
|
||||||
exec_input(cmd, None, false)
|
exec_input(cmd, None, false)
|
||||||
} else {
|
} else {
|
||||||
fern_interactive()
|
fern_interactive()
|
||||||
} {
|
} {
|
||||||
eprintln!("fern: {e}");
|
eprintln!("fern: {e}");
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit))
|
if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit))
|
||||||
&& let Err(e) = exec_input(trap, None, false)
|
&& let Err(e) = exec_input(trap, None, false) {
|
||||||
{
|
eprintln!("fern: error running EXIT trap: {e}");
|
||||||
eprintln!("fern: error running EXIT trap: {e}");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
write_jobs(|j| j.hang_up());
|
write_jobs(|j| j.hang_up());
|
||||||
ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8)
|
ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
eprintln!("fern: Failed to open input file: {}", path.display());
|
eprintln!("fern: Failed to open input file: {}", path.display());
|
||||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::CleanExit(1),
|
ShErrKind::CleanExit(1),
|
||||||
"input file not found",
|
"input file not found",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let Ok(input) = fs::read_to_string(path) else {
|
let Ok(input) = fs::read_to_string(path) else {
|
||||||
eprintln!("fern: Failed to read input file: {}", path.display());
|
eprintln!("fern: Failed to read input file: {}", path.display());
|
||||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::CleanExit(1),
|
ShErrKind::CleanExit(1),
|
||||||
"failed to read input file",
|
"failed to read input file",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
v.cur_scope_mut()
|
v.cur_scope_mut()
|
||||||
.bpush_arg(path.to_string_lossy().to_string())
|
.bpush_arg(path.to_string_lossy().to_string())
|
||||||
});
|
});
|
||||||
for arg in args {
|
for arg in args {
|
||||||
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
exec_input(input, None, false)
|
exec_input(input, None, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fern_interactive() -> ShResult<()> {
|
fn fern_interactive() -> ShResult<()> {
|
||||||
let _raw_mode = raw_mode(); // sets raw mode, restores termios on drop
|
let _raw_mode = raw_mode(); // sets raw mode, restores termios on drop
|
||||||
sig_setup();
|
sig_setup();
|
||||||
|
|
||||||
if let Err(e) = source_rc() {
|
if let Err(e) = source_rc() {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create readline instance with initial prompt
|
// Create readline instance with initial prompt
|
||||||
let mut readline = match FernVi::new(get_prompt().ok(), *TTY_FILENO) {
|
let mut readline = match FernVi::new(get_prompt().ok(), *TTY_FILENO) {
|
||||||
Ok(rl) => rl,
|
Ok(rl) => rl,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to initialize readline: {e}");
|
eprintln!("Failed to initialize readline: {e}");
|
||||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::CleanExit(1),
|
ShErrKind::CleanExit(1),
|
||||||
"readline initialization failed",
|
"readline initialization failed",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main poll loop
|
// Main poll loop
|
||||||
loop {
|
loop {
|
||||||
// Handle any pending signals
|
// Handle any pending signals
|
||||||
while signals_pending() {
|
while signals_pending() {
|
||||||
if let Err(e) = check_signals() {
|
if let Err(e) = check_signals() {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::ClearReadline => {
|
ShErrKind::ClearReadline => {
|
||||||
// Ctrl+C - clear current input and show new prompt
|
// Ctrl+C - clear current input and show new prompt
|
||||||
readline.reset(get_prompt().ok());
|
readline.reset(get_prompt().ok());
|
||||||
}
|
}
|
||||||
ShErrKind::CleanExit(code) => {
|
ShErrKind::CleanExit(code) => {
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => eprintln!("{e}"),
|
_ => eprintln!("{e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readline.print_line()?;
|
readline.print_line()?;
|
||||||
|
|
||||||
// Poll for stdin input
|
// Poll for stdin input
|
||||||
let mut fds = [PollFd::new(
|
let mut fds = [PollFd::new(
|
||||||
unsafe { BorrowedFd::borrow_raw(*TTY_FILENO) },
|
unsafe { BorrowedFd::borrow_raw(*TTY_FILENO) },
|
||||||
PollFlags::POLLIN,
|
PollFlags::POLLIN,
|
||||||
)];
|
)];
|
||||||
|
|
||||||
match poll(&mut fds, PollTimeout::MAX) {
|
match poll(&mut fds, PollTimeout::MAX) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(Errno::EINTR) => {
|
Err(Errno::EINTR) => {
|
||||||
// Interrupted by signal, loop back to handle it
|
// Interrupted by signal, loop back to handle it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("poll error: {e}");
|
eprintln!("poll error: {e}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if stdin has data
|
// Check if stdin has data
|
||||||
if fds[0]
|
if fds[0].revents().is_some_and(|r| r.contains(PollFlags::POLLIN)) {
|
||||||
.revents()
|
let mut buffer = [0u8; 1024];
|
||||||
.is_some_and(|r| r.contains(PollFlags::POLLIN))
|
match read(*TTY_FILENO, &mut buffer) {
|
||||||
{
|
Ok(0) => {
|
||||||
let mut buffer = [0u8; 1024];
|
// EOF
|
||||||
match read(*TTY_FILENO, &mut buffer) {
|
break;
|
||||||
Ok(0) => {
|
}
|
||||||
// EOF
|
Ok(n) => {
|
||||||
break;
|
readline.feed_bytes(&buffer[..n]);
|
||||||
}
|
}
|
||||||
Ok(n) => {
|
Err(Errno::EINTR) => {
|
||||||
readline.feed_bytes(&buffer[..n]);
|
// Interrupted, continue to handle signals
|
||||||
}
|
continue;
|
||||||
Err(Errno::EINTR) => {
|
}
|
||||||
// Interrupted, continue to handle signals
|
Err(e) => {
|
||||||
continue;
|
eprintln!("read error: {e}");
|
||||||
}
|
break;
|
||||||
Err(e) => {
|
}
|
||||||
eprintln!("read error: {e}");
|
}
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process any available input
|
// Process any available input
|
||||||
match readline.process_input() {
|
match readline.process_input() {
|
||||||
Ok(ReadlineEvent::Line(input)) => {
|
Ok(ReadlineEvent::Line(input)) => {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
write_meta(|m| m.start_timer());
|
write_meta(|m| m.start_timer());
|
||||||
if let Err(e) = exec_input(input, None, true) {
|
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true)) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::CleanExit(code) => {
|
ShErrKind::CleanExit(code) => {
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => eprintln!("{e}"),
|
_ => eprintln!("{e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let command_run_time = start.elapsed();
|
let command_run_time = start.elapsed();
|
||||||
log::info!("Command executed in {:.2?}", command_run_time);
|
log::info!("Command executed in {:.2?}", command_run_time);
|
||||||
write_meta(|m| m.stop_timer());
|
write_meta(|m| m.stop_timer());
|
||||||
|
|
||||||
// Reset for next command with fresh prompt
|
// Reset for next command with fresh prompt
|
||||||
readline.reset(get_prompt().ok());
|
readline.reset(get_prompt().ok());
|
||||||
let real_end = start.elapsed();
|
let real_end = start.elapsed();
|
||||||
log::info!("Total round trip time: {:.2?}", real_end);
|
log::info!("Total round trip time: {:.2?}", real_end);
|
||||||
}
|
}
|
||||||
Ok(ReadlineEvent::Eof) => {
|
Ok(ReadlineEvent::Eof) => {
|
||||||
// Ctrl+D on empty line
|
// Ctrl+D on empty line
|
||||||
QUIT_CODE.store(0, Ordering::SeqCst);
|
QUIT_CODE.store(0, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Ok(ReadlineEvent::Pending) => {
|
Ok(ReadlineEvent::Pending) => {
|
||||||
// No complete input yet, keep polling
|
// No complete input yet, keep polling
|
||||||
}
|
}
|
||||||
Err(e) => match e.kind() {
|
Err(e) => match e.kind() {
|
||||||
ShErrKind::CleanExit(code) => {
|
ShErrKind::CleanExit(code) => {
|
||||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => eprintln!("{e}"),
|
_ => eprintln!("{e}"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dispatcher = Dispatcher::new(parser.extract_nodes(), interactive);
|
let nodes = parser.extract_nodes();
|
||||||
|
|
||||||
|
let mut dispatcher = Dispatcher::new(nodes, interactive);
|
||||||
if let Some(mut stack) = io_stack {
|
if let Some(mut stack) = io_stack {
|
||||||
dispatcher.io_stack.extend(stack.drain(..));
|
dispatcher.io_stack.extend(stack.drain(..));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -659,6 +659,9 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
_ if is_cmd_sub(text) => {
|
_ if is_cmd_sub(text) => {
|
||||||
new_tk.mark(TkFlags::IS_CMDSUB);
|
new_tk.mark(TkFlags::IS_CMDSUB);
|
||||||
|
if self.next_is_cmd() {
|
||||||
|
new_tk.mark(TkFlags::IS_CMD);
|
||||||
|
}
|
||||||
self.set_next_is_cmd(false);
|
self.set_next_is_cmd(false);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -846,11 +849,26 @@ pub fn is_field_sep(ch: char) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_keyword(slice: &str) -> bool {
|
pub fn is_keyword(slice: &str) -> bool {
|
||||||
KEYWORDS.contains(&slice) || (slice.ends_with("()") && !slice.ends_with("\\()"))
|
KEYWORDS.contains(&slice) || ends_with_unescaped(slice, "()")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_cmd_sub(slice: &str) -> bool {
|
pub fn is_cmd_sub(slice: &str) -> bool {
|
||||||
(slice.starts_with("$(") && slice.ends_with(')')) && !slice.ends_with("\\)")
|
slice.starts_with("$(") && ends_with_unescaped(slice,")")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
|
||||||
|
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
|
||||||
|
let bytes = slice.as_bytes();
|
||||||
|
let mut escaped = false;
|
||||||
|
let mut i = pos;
|
||||||
|
while i > 0 && bytes[i - 1] == b'\\' {
|
||||||
|
escaped = !escaped;
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
escaped
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
|
pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
|
||||||
|
|||||||
@@ -19,17 +19,17 @@ pub use std::os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd,
|
|||||||
pub use bitflags::bitflags;
|
pub use bitflags::bitflags;
|
||||||
pub use nix::{
|
pub use nix::{
|
||||||
errno::Errno,
|
errno::Errno,
|
||||||
fcntl::{OFlag, open},
|
fcntl::{open, OFlag},
|
||||||
libc::{self, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO},
|
libc::{self, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO},
|
||||||
sys::{
|
sys::{
|
||||||
signal::{self, SigHandler, SigSet, SigmaskHow, Signal, kill, killpg, pthread_sigmask, signal},
|
signal::{self, kill, killpg, pthread_sigmask, signal, SigHandler, SigSet, SigmaskHow, Signal},
|
||||||
stat::Mode,
|
stat::Mode,
|
||||||
termios::{self},
|
termios::{self},
|
||||||
wait::{WaitPidFlag as WtFlag, WaitStatus as WtStat, waitpid},
|
wait::{waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat},
|
||||||
},
|
},
|
||||||
unistd::{
|
unistd::{
|
||||||
ForkResult, Pid, close, dup, dup2, execvpe, fork, getpgid, getpgrp, isatty, pipe, read,
|
close, dup, dup2, execvpe, fork, getpgid, getpgrp, isatty, pipe, read, setpgid, tcgetpgrp,
|
||||||
setpgid, tcgetpgrp, tcsetpgrp, write,
|
tcsetpgrp, write, ForkResult, Pid,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
error::{ShErr, ShErrKind, ShResult},
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
utils::RedirVecUtils,
|
utils::RedirVecUtils,
|
||||||
},
|
},
|
||||||
parse::{Redir, RedirType, get_redir_file},
|
parse::{get_redir_file, Redir, RedirType},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,9 +39,9 @@ pub enum IoMode {
|
|||||||
buf: String,
|
buf: String,
|
||||||
pipe: Arc<OwnedFd>,
|
pipe: Arc<OwnedFd>,
|
||||||
},
|
},
|
||||||
Close {
|
Close {
|
||||||
tgt_fd: RawFd,
|
tgt_fd: RawFd,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoMode {
|
impl IoMode {
|
||||||
@@ -79,7 +79,7 @@ impl IoMode {
|
|||||||
let path_raw = path.as_os_str().to_str().unwrap_or_default().to_string();
|
let path_raw = path.as_os_str().to_str().unwrap_or_default().to_string();
|
||||||
|
|
||||||
let expanded_path = Expander::from_raw(&path_raw)?.expand()?.join(" "); // should just be one string, will have to find some way to handle a return of
|
let expanded_path = Expander::from_raw(&path_raw)?.expand()?.join(" "); // should just be one string, will have to find some way to handle a return of
|
||||||
// multiple
|
// multiple
|
||||||
|
|
||||||
let expanded_pathbuf = PathBuf::from(expanded_path);
|
let expanded_pathbuf = PathBuf::from(expanded_path);
|
||||||
|
|
||||||
@@ -157,17 +157,17 @@ impl<R: Read> IoBuf<R> {
|
|||||||
pub struct RedirGuard(IoFrame);
|
pub struct RedirGuard(IoFrame);
|
||||||
|
|
||||||
impl RedirGuard {
|
impl RedirGuard {
|
||||||
pub fn persist(mut self) {
|
pub fn persist(mut self) {
|
||||||
if let Some(saved) = self.0.saved_io.take() {
|
if let Some(saved) = self.0.saved_io.take() {
|
||||||
close(saved.0).ok();
|
close(saved.0).ok();
|
||||||
close(saved.1).ok();
|
close(saved.1).ok();
|
||||||
close(saved.2).ok();
|
close(saved.2).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// the guard is dropped here
|
// the guard is dropped here
|
||||||
// but since we took the saved fds
|
// but since we took the saved fds
|
||||||
// the drop does not restore them
|
// the drop does not restore them
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RedirGuard {
|
impl Drop for RedirGuard {
|
||||||
|
|||||||
@@ -184,14 +184,16 @@ impl Highlighter {
|
|||||||
input_chars.next();
|
input_chars.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inner_clean = Self::strip_markers(&inner);
|
||||||
|
|
||||||
// Determine prefix from content (handles both <( and >( for proc subs)
|
// Determine prefix from content (handles both <( and >( for proc subs)
|
||||||
let prefix = match ch {
|
let prefix = match ch {
|
||||||
markers::CMD_SUB => "$(",
|
markers::CMD_SUB => "$(",
|
||||||
markers::SUBSH => "(",
|
markers::SUBSH => "(",
|
||||||
markers::PROC_SUB => {
|
markers::PROC_SUB => {
|
||||||
if inner.starts_with("<(") {
|
if inner_clean.starts_with("<(") {
|
||||||
"<("
|
"<("
|
||||||
} else if inner.starts_with(">(") {
|
} else if inner_clean.starts_with(">(") {
|
||||||
">("
|
">("
|
||||||
} else {
|
} else {
|
||||||
"<("
|
"<("
|
||||||
@@ -199,14 +201,13 @@ impl Highlighter {
|
|||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let inner_content = if incomplete {
|
let inner_content = if incomplete {
|
||||||
inner.strip_prefix(prefix).unwrap_or(&inner)
|
inner_clean.strip_prefix(prefix).unwrap_or(&inner_clean)
|
||||||
} else {
|
} else {
|
||||||
inner
|
inner_clean
|
||||||
.strip_prefix(prefix)
|
.strip_prefix(prefix)
|
||||||
.and_then(|s| s.strip_suffix(")"))
|
.and_then(|s| s.strip_suffix(")"))
|
||||||
.unwrap_or(&inner)
|
.unwrap_or(&inner_clean)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut recursive_highlighter = Self::new();
|
let mut recursive_highlighter = Self::new();
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ use std::{
|
|||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, prompt::readline::linebuf::LineBuf};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::{
|
||||||
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
|
prompt::readline::linebuf::LineBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use super::vicmd::Direction; // surprisingly useful
|
use super::vicmd::Direction; // surprisingly useful
|
||||||
|
|
||||||
@@ -271,23 +274,23 @@ impl History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_pending_cmd(&mut self, buf: (&str, usize)) {
|
pub fn update_pending_cmd(&mut self, buf: (&str, usize)) {
|
||||||
let cursor_pos = if let Some(pending) = &self.pending {
|
let cursor_pos = if let Some(pending) = &self.pending {
|
||||||
pending.cursor.get()
|
pending.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
buf.1
|
buf.1
|
||||||
};
|
};
|
||||||
let cmd = buf.0.to_string();
|
let cmd = buf.0.to_string();
|
||||||
let constraint = SearchConstraint {
|
let constraint = SearchConstraint {
|
||||||
kind: SearchKind::Prefix,
|
kind: SearchKind::Prefix,
|
||||||
term: cmd.clone(),
|
term: cmd.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(pending) = &mut self.pending {
|
if let Some(pending) = &mut self.pending {
|
||||||
pending.set_buffer(cmd);
|
pending.set_buffer(cmd);
|
||||||
pending.cursor.set(cursor_pos);
|
pending.cursor.set(cursor_pos);
|
||||||
} else {
|
} else {
|
||||||
self.pending = Some(LineBuf::new().with_initial(&cmd, cursor_pos));
|
self.pending = Some(LineBuf::new().with_initial(&cmd, cursor_pos));
|
||||||
}
|
}
|
||||||
self.constrain_entries(constraint);
|
self.constrain_entries(constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
|||||||
use vte::{Parser, Perform};
|
use vte::{Parser, Perform};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{error::{ShErr, ShErrKind, ShResult}, sys::TTY_FILENO}, prompt::readline::keys::{KeyCode, ModKeys}, shopt::FernBellStyle, state::read_shopts
|
libsh::{
|
||||||
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
|
sys::TTY_FILENO,
|
||||||
|
},
|
||||||
|
prompt::readline::keys::{KeyCode, ModKeys},
|
||||||
|
shopt::FernBellStyle,
|
||||||
|
state::read_shopts,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -107,7 +113,8 @@ fn write_all(fd: RawFd, buf: &str) -> nix::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a string ends with a newline, ignoring any trailing ANSI escape sequences.
|
/// Check if a string ends with a newline, ignoring any trailing ANSI escape
|
||||||
|
/// sequences.
|
||||||
fn ends_with_newline(s: &str) -> bool {
|
fn ends_with_newline(s: &str) -> bool {
|
||||||
let bytes = s.as_bytes();
|
let bytes = s.as_bytes();
|
||||||
let mut i = bytes.len();
|
let mut i = bytes.len();
|
||||||
@@ -208,7 +215,7 @@ pub trait LineWriter {
|
|||||||
fn clear_rows(&mut self, layout: &Layout) -> ShResult<()>;
|
fn clear_rows(&mut self, layout: &Layout) -> ShResult<()>;
|
||||||
fn redraw(&mut self, prompt: &str, line: &str, new_layout: &Layout) -> ShResult<()>;
|
fn redraw(&mut self, prompt: &str, line: &str, new_layout: &Layout) -> ShResult<()>;
|
||||||
fn flush_write(&mut self, buf: &str) -> ShResult<()>;
|
fn flush_write(&mut self, buf: &str) -> ShResult<()>;
|
||||||
fn send_bell(&mut self) -> ShResult<()>;
|
fn send_bell(&mut self) -> ShResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -757,12 +764,7 @@ impl Layout {
|
|||||||
end: Pos::default(),
|
end: Pos::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from_parts(
|
pub fn from_parts(term_width: u16, prompt: &str, to_cursor: &str, to_end: &str) -> Self {
|
||||||
term_width: u16,
|
|
||||||
prompt: &str,
|
|
||||||
to_cursor: &str,
|
|
||||||
to_end: &str,
|
|
||||||
) -> Self {
|
|
||||||
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 });
|
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 });
|
||||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end);
|
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end);
|
||||||
let end = Self::calc_pos(term_width, to_end, prompt_end);
|
let end = Self::calc_pos(term_width, to_end, prompt_end);
|
||||||
@@ -998,11 +1000,10 @@ impl LineWriter for TermWriter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_bell(&mut self) -> ShResult<()> {
|
fn send_bell(&mut self) -> ShResult<()> {
|
||||||
if read_shopts(|o| o.core.bell_enabled) {
|
if read_shopts(|o| o.core.bell_enabled) {
|
||||||
self.flush_write("\x07")?;
|
self.flush_write("\x07")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/shopt.rs
47
src/shopt.rs
@@ -212,12 +212,10 @@ impl ShOptCore {
|
|||||||
}
|
}
|
||||||
"bell_enabled" => {
|
"bell_enabled" => {
|
||||||
let Ok(val) = val.parse::<bool>() else {
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
return Err(
|
return Err(ShErr::simple(
|
||||||
ShErr::simple(
|
ShErrKind::SyntaxErr,
|
||||||
ShErrKind::SyntaxErr,
|
"shopt: expected 'true' or 'false' for bell_enabled value",
|
||||||
"shopt: expected 'true' or 'false' for bell_enabled value",
|
));
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
self.bell_enabled = val;
|
self.bell_enabled = val;
|
||||||
}
|
}
|
||||||
@@ -371,7 +369,7 @@ pub struct ShOptPrompt {
|
|||||||
pub edit_mode: FernEditMode,
|
pub edit_mode: FernEditMode,
|
||||||
pub comp_limit: usize,
|
pub comp_limit: usize,
|
||||||
pub highlight: bool,
|
pub highlight: bool,
|
||||||
pub auto_indent: bool
|
pub auto_indent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShOptPrompt {
|
impl ShOptPrompt {
|
||||||
@@ -413,15 +411,15 @@ impl ShOptPrompt {
|
|||||||
};
|
};
|
||||||
self.highlight = val;
|
self.highlight = val;
|
||||||
}
|
}
|
||||||
"auto_indent" => {
|
"auto_indent" => {
|
||||||
let Ok(val) = val.parse::<bool>() else {
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"shopt: expected 'true' or 'false' for auto_indent value",
|
"shopt: expected 'true' or 'false' for auto_indent value",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
self.auto_indent = val;
|
self.auto_indent = val;
|
||||||
}
|
}
|
||||||
"custom" => {
|
"custom" => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
@@ -480,11 +478,12 @@ impl ShOptPrompt {
|
|||||||
output.push_str(&format!("{}", self.highlight));
|
output.push_str(&format!("{}", self.highlight));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"auto_indent" => {
|
"auto_indent" => {
|
||||||
let mut output = String::from("Whether to automatically indent new lines in multiline commands\n");
|
let mut output =
|
||||||
output.push_str(&format!("{}", self.auto_indent));
|
String::from("Whether to automatically indent new lines in multiline commands\n");
|
||||||
Ok(Some(output))
|
output.push_str(&format!("{}", self.auto_indent));
|
||||||
}
|
Ok(Some(output))
|
||||||
|
}
|
||||||
_ => Err(
|
_ => Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
@@ -499,7 +498,7 @@ impl ShOptPrompt {
|
|||||||
"edit_mode",
|
"edit_mode",
|
||||||
"comp_limit",
|
"comp_limit",
|
||||||
"highlight",
|
"highlight",
|
||||||
"auto_indent",
|
"auto_indent",
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -515,7 +514,7 @@ impl Display for ShOptPrompt {
|
|||||||
output.push(format!("edit_mode = {}", self.edit_mode));
|
output.push(format!("edit_mode = {}", self.edit_mode));
|
||||||
output.push(format!("comp_limit = {}", self.comp_limit));
|
output.push(format!("comp_limit = {}", self.comp_limit));
|
||||||
output.push(format!("highlight = {}", self.highlight));
|
output.push(format!("highlight = {}", self.highlight));
|
||||||
output.push(format!("auto_indent = {}", self.auto_indent));
|
output.push(format!("auto_indent = {}", self.auto_indent));
|
||||||
|
|
||||||
let final_output = output.join("\n");
|
let final_output = output.join("\n");
|
||||||
|
|
||||||
@@ -530,7 +529,7 @@ impl Default for ShOptPrompt {
|
|||||||
edit_mode: FernEditMode::Vi,
|
edit_mode: FernEditMode::Vi,
|
||||||
comp_limit: 100,
|
comp_limit: 100,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
auto_indent: true
|
auto_indent: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use tempfile::TempDir;
|
|||||||
|
|
||||||
use crate::prompt::readline::complete::Completer;
|
use crate::prompt::readline::complete::Completer;
|
||||||
use crate::prompt::readline::markers;
|
use crate::prompt::readline::markers;
|
||||||
use crate::state::{VarFlags, write_logic, write_vars};
|
use crate::state::{write_logic, write_vars, VarFlags};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -192,12 +192,10 @@ fn complete_filename_with_slash() {
|
|||||||
|
|
||||||
// Should complete files in subdir/
|
// Should complete files in subdir/
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
assert!(
|
assert!(completer
|
||||||
completer
|
.candidates
|
||||||
.candidates
|
.iter()
|
||||||
.iter()
|
.any(|c| c.contains("nested.txt")));
|
||||||
.any(|c| c.contains("nested.txt"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,12 +702,10 @@ fn complete_special_characters_in_filename() {
|
|||||||
|
|
||||||
if result.is_some() {
|
if result.is_some() {
|
||||||
// Should handle special chars in filenames
|
// Should handle special chars in filenames
|
||||||
assert!(
|
assert!(completer
|
||||||
completer
|
.candidates
|
||||||
.candidates
|
.iter()
|
||||||
.iter()
|
.any(|c| c.contains("file-with-dash") || c.contains("file_with_underscore")));
|
||||||
.any(|c| c.contains("file-with-dash") || c.contains("file_with_underscore"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::expand::{DUB_QUOTE, VAR_SUB, perform_param_expansion};
|
use crate::expand::{perform_param_expansion, DUB_QUOTE, VAR_SUB};
|
||||||
use crate::state::VarFlags;
|
use crate::state::VarFlags;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ use super::*;
|
|||||||
use crate::expand::{expand_aliases, unescape_str};
|
use crate::expand::{expand_aliases, unescape_str};
|
||||||
use crate::libsh::error::{Note, ShErr, ShErrKind};
|
use crate::libsh::error::{Note, ShErr, ShErrKind};
|
||||||
use crate::parse::{
|
use crate::parse::{
|
||||||
NdRule, Node, ParseStream,
|
|
||||||
lex::{LexFlags, LexStream, Tk, TkRule},
|
lex::{LexFlags, LexStream, Tk, TkRule},
|
||||||
node_operation,
|
node_operation, NdRule, Node, ParseStream,
|
||||||
};
|
};
|
||||||
use crate::state::{write_logic, write_vars};
|
use crate::state::{write_logic, write_vars};
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ use crate::{
|
|||||||
term::{Style, Styled},
|
term::{Style, Styled},
|
||||||
},
|
},
|
||||||
prompt::readline::{
|
prompt::readline::{
|
||||||
FernVi,
|
|
||||||
history::History,
|
history::History,
|
||||||
keys::{KeyCode, KeyEvent, ModKeys},
|
keys::{KeyCode, KeyEvent, ModKeys},
|
||||||
linebuf::LineBuf,
|
linebuf::LineBuf,
|
||||||
term::{KeyReader, LineWriter, raw_mode},
|
term::{raw_mode, KeyReader, LineWriter},
|
||||||
vimode::{ViInsert, ViMode, ViNormal},
|
vimode::{ViInsert, ViMode, ViNormal},
|
||||||
|
FernVi,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,9 +175,9 @@ impl LineWriter for TestWriter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_bell(&mut self) -> ShResult<()> {
|
fn send_bell(&mut self) -> ShResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: FernVi structure has changed significantly and readline() method no
|
// NOTE: FernVi structure has changed significantly and readline() method no
|
||||||
@@ -605,31 +605,27 @@ fn editor_delete_line_up() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn editor_insert_at_line_start() {
|
fn editor_insert_at_line_start() {
|
||||||
// I should move cursor to position 0 when line starts with non-whitespace
|
// I should move cursor to position 0 when line starts with non-whitespace
|
||||||
assert_eq!(
|
assert_eq!(normal_cmd("I", "hello world", 5), ("hello world".into(), 0));
|
||||||
normal_cmd("I", "hello world", 5),
|
|
||||||
("hello world".into(), 0)
|
|
||||||
);
|
|
||||||
// I should skip leading whitespace
|
// I should skip leading whitespace
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
normal_cmd("I", " hello world", 8),
|
normal_cmd("I", " hello world", 8),
|
||||||
(" hello world".into(), 2)
|
(" hello world".into(), 2)
|
||||||
);
|
);
|
||||||
// I should move to the first non-whitespace on the current line in a multiline buffer
|
// I should move to the first non-whitespace on the current line in a multiline
|
||||||
|
// buffer
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
normal_cmd("I", "first line\nsecond line", 14),
|
normal_cmd("I", "first line\nsecond line", 14),
|
||||||
("first line\nsecond line".into(), 11)
|
("first line\nsecond line".into(), 11)
|
||||||
);
|
);
|
||||||
// I should land on position 0 when cursor is already at 0
|
// I should land on position 0 when cursor is already at 0
|
||||||
assert_eq!(
|
assert_eq!(normal_cmd("I", "hello", 0), ("hello".into(), 0));
|
||||||
normal_cmd("I", "hello", 0),
|
|
||||||
("hello".into(), 0)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn editor_f_char_from_position_zero() {
|
fn editor_f_char_from_position_zero() {
|
||||||
// f<char> at position 0 should skip the cursor and find the next occurrence
|
// f<char> at position 0 should skip the cursor and find the next occurrence
|
||||||
// Regression: previously at pos 0, f would match the char under the cursor itself
|
// Regression: previously at pos 0, f would match the char under the cursor
|
||||||
|
// itself
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
normal_cmd("fa", "abcaef", 0),
|
normal_cmd("fa", "abcaef", 0),
|
||||||
("abcaef".into(), 3) // should find second 'a', not the 'a' at position 0
|
("abcaef".into(), 3) // should find second 'a', not the 'a' at position 0
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::parse::{
|
use crate::parse::{
|
||||||
NdRule, Node, ParseStream, Redir, RedirType,
|
|
||||||
lex::{LexFlags, LexStream},
|
lex::{LexFlags, LexStream},
|
||||||
|
NdRule, Node, ParseStream, Redir, RedirType,
|
||||||
};
|
};
|
||||||
use crate::procio::{IoFrame, IoMode, IoStack};
|
use crate::procio::{IoFrame, IoMode, IoStack};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use crate::state::{LogTab, MetaTab, ScopeStack, ShellParam, VarFlags, VarTab};
|
use crate::state::{LogTab, MetaTab, ScopeStack, ShellParam, VarFlags, VarTab};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ScopeStack Tests - Variable Scoping
|
// ScopeStack Tests - Variable Scoping
|
||||||
@@ -11,8 +11,8 @@ fn scopestack_new() {
|
|||||||
|
|
||||||
// Should start with one global scope
|
// Should start with one global scope
|
||||||
assert!(stack.var_exists("PATH") || !stack.var_exists("PATH")); // Just check
|
assert!(stack.var_exists("PATH") || !stack.var_exists("PATH")); // Just check
|
||||||
// it doesn't
|
// it doesn't
|
||||||
// panic
|
// panic
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -296,13 +296,21 @@ fn scopestack_local_var_mutation() {
|
|||||||
|
|
||||||
// `foo="bar"` — reassign without LOCAL flag (plain assignment)
|
// `foo="bar"` — reassign without LOCAL flag (plain assignment)
|
||||||
stack.set_var("foo", "bar", VarFlags::NONE);
|
stack.set_var("foo", "bar", VarFlags::NONE);
|
||||||
assert_eq!(stack.get_var("foo"), "bar", "Local var should be mutated in place");
|
assert_eq!(
|
||||||
|
stack.get_var("foo"),
|
||||||
|
"bar",
|
||||||
|
"Local var should be mutated in place"
|
||||||
|
);
|
||||||
|
|
||||||
// Ascend back to global
|
// Ascend back to global
|
||||||
stack.ascend();
|
stack.ascend();
|
||||||
|
|
||||||
// foo should not exist in global scope
|
// foo should not exist in global scope
|
||||||
assert_eq!(stack.get_var("foo"), "", "Local var should not leak to global scope");
|
assert_eq!(
|
||||||
|
stack.get_var("foo"),
|
||||||
|
"",
|
||||||
|
"Local var should not leak to global scope"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -318,13 +326,21 @@ fn scopestack_local_var_uninitialized() {
|
|||||||
|
|
||||||
// `foo="bar"` — assign a value later
|
// `foo="bar"` — assign a value later
|
||||||
stack.set_var("foo", "bar", VarFlags::NONE);
|
stack.set_var("foo", "bar", VarFlags::NONE);
|
||||||
assert_eq!(stack.get_var("foo"), "bar", "Uninitialized local should be assignable");
|
assert_eq!(
|
||||||
|
stack.get_var("foo"),
|
||||||
|
"bar",
|
||||||
|
"Uninitialized local should be assignable"
|
||||||
|
);
|
||||||
|
|
||||||
// Ascend back to global
|
// Ascend back to global
|
||||||
stack.ascend();
|
stack.ascend();
|
||||||
|
|
||||||
// foo should not exist in global scope
|
// foo should not exist in global scope
|
||||||
assert_eq!(stack.get_var("foo"), "", "Local var should not leak to global scope");
|
assert_eq!(
|
||||||
|
stack.get_var("foo"),
|
||||||
|
"",
|
||||||
|
"Local var should not leak to global scope"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -740,11 +756,14 @@ fn dirstack_pushd_rotation_with_cwd() {
|
|||||||
|
|
||||||
assert_eq!(new_cwd, Some(PathBuf::from("/var")));
|
assert_eq!(new_cwd, Some(PathBuf::from("/var")));
|
||||||
let remaining: Vec<_> = meta.dirs().iter().collect();
|
let remaining: Vec<_> = meta.dirs().iter().collect();
|
||||||
assert_eq!(remaining, vec![
|
assert_eq!(
|
||||||
&PathBuf::from("/etc"),
|
remaining,
|
||||||
&PathBuf::from("/home"),
|
vec![
|
||||||
&PathBuf::from("/tmp"),
|
&PathBuf::from("/etc"),
|
||||||
]);
|
&PathBuf::from("/home"),
|
||||||
|
&PathBuf::from("/tmp"),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -821,10 +840,10 @@ fn dirstack_popd_plus_n_offset() {
|
|||||||
assert_eq!(removed, Some(PathBuf::from("/var")));
|
assert_eq!(removed, Some(PathBuf::from("/var")));
|
||||||
|
|
||||||
let remaining: Vec<_> = meta.dirs().iter().collect();
|
let remaining: Vec<_> = meta.dirs().iter().collect();
|
||||||
assert_eq!(remaining, vec![
|
assert_eq!(
|
||||||
&PathBuf::from("/tmp"),
|
remaining,
|
||||||
&PathBuf::from("/etc"),
|
vec![&PathBuf::from("/tmp"), &PathBuf::from("/etc"),]
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user