rustfmt'd the codebase

This commit is contained in:
2026-03-04 19:52:29 -05:00
parent ecd6eda424
commit 7be79a3803
51 changed files with 4926 additions and 4131 deletions

View File

@@ -18,7 +18,9 @@ pub fn alias(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if argv.is_empty() { if argv.is_empty() {
// Display the environment variables // Display the environment variables
@@ -37,11 +39,19 @@ pub fn alias(node: Node) -> ShResult<()> {
} else { } else {
for (arg, span) in argv { for (arg, span) in argv {
if arg == "command" || arg == "builtin" { if arg == "command" || arg == "builtin" {
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("alias: Cannot assign alias to reserved name '{arg}'"))); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
format!("alias: Cannot assign alias to reserved name '{arg}'"),
));
} }
let Some((name, body)) = arg.split_once('=') else { let Some((name, body)) = arg.split_once('=') else {
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "alias: Expected an assignment in alias args")); return Err(ShErr::at(
ShErrKind::SyntaxErr,
span,
"alias: Expected an assignment in alias args",
));
}; };
write_logic(|l| l.insert_alias(name, body, span.clone())); write_logic(|l| l.insert_alias(name, body, span.clone()));
} }
@@ -60,7 +70,9 @@ pub fn unalias(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if argv.is_empty() { if argv.is_empty() {
// Display the environment variables // Display the environment variables
@@ -79,7 +91,11 @@ pub fn unalias(node: Node) -> ShResult<()> {
} else { } else {
for (arg, span) in argv { for (arg, span) in argv {
if read_logic(|l| l.get_alias(&arg)).is_none() { if read_logic(|l| l.get_alias(&arg)).is_none() {
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("unalias: alias '{}' not found",arg.fg(next_color())))); return Err(ShErr::at(
ShErrKind::SyntaxErr,
span,
format!("unalias: alias '{}' not found", arg.fg(next_color())),
));
}; };
write_logic(|l| l.remove_alias(&arg)) write_logic(|l| l.remove_alias(&arg))
} }

View File

@@ -1,23 +1,28 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::{ use crate::{
getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, procio::borrow_fd, state::{self, VarFlags, VarKind, write_vars} getopt::{Opt, OptSpec, get_opts_from_tokens},
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
procio::borrow_fd,
state::{self, VarFlags, VarKind, write_vars},
}; };
fn arr_op_optspec() -> Vec<OptSpec> { fn arr_op_optspec() -> Vec<OptSpec> {
vec![ vec![
OptSpec { OptSpec {
opt: Opt::Short('c'), opt: Opt::Short('c'),
takes_arg: true takes_arg: true,
}, },
OptSpec { OptSpec {
opt: Opt::Short('r'), opt: Opt::Short('r'),
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('v'), opt: Opt::Short('v'),
takes_arg: true takes_arg: true,
} },
] ]
} }
@@ -38,7 +43,10 @@ impl Default for ArrOpOpts {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum End { Front, Back } enum End {
Front,
Back,
}
fn arr_pop_inner(node: Node, end: End) -> ShResult<()> { fn arr_pop_inner(node: Node, end: End) -> ShResult<()> {
let NdRule::Command { let NdRule::Command {
@@ -52,11 +60,13 @@ fn arr_pop_inner(node: Node, end: End) -> ShResult<()> {
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?; let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
let arr_op_opts = get_arr_op_opts(opts)?; let arr_op_opts = get_arr_op_opts(opts)?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let stdout = borrow_fd(STDOUT_FILENO); let stdout = borrow_fd(STDOUT_FILENO);
let mut status = 0; let mut status = 0;
for (arg,_) in argv { for (arg, _) in argv {
for _ in 0..arr_op_opts.count { for _ in 0..arr_op_opts.count {
let pop = |arr: &mut std::collections::VecDeque<String>| match end { let pop = |arr: &mut std::collections::VecDeque<String>| match end {
End::Front => arr.pop_front(), End::Front => arr.pop_front(),
@@ -94,11 +104,17 @@ fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?; let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
let _arr_op_opts = get_arr_op_opts(opts)?; let _arr_op_opts = get_arr_op_opts(opts)?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut argv = argv.into_iter(); let mut argv = argv.into_iter();
let Some((name, _)) = argv.next() else { let Some((name, _)) = argv.next() else {
return Err(ShErr::at(ShErrKind::ExecFail, blame, "push: missing array name".to_string())); return Err(ShErr::at(
ShErrKind::ExecFail,
blame,
"push: missing array name".to_string(),
));
}; };
for (val, span) in argv { for (val, span) in argv {
@@ -111,9 +127,14 @@ fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
} }
Ok(()) Ok(())
} else { } else {
v.set_var(&name, VarKind::Arr(VecDeque::from([push_val])), VarFlags::NONE) v.set_var(
&name,
VarKind::Arr(VecDeque::from([push_val])),
VarFlags::NONE,
)
} }
}).blame(span)?; })
.blame(span)?;
} }
state::set_status(0); state::set_status(0);
@@ -148,7 +169,9 @@ pub fn arr_rotate(node: Node) -> ShResult<()> {
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?; let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
let arr_op_opts = get_arr_op_opts(opts)?; let arr_op_opts = get_arr_op_opts(opts)?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
for (arg, _) in argv { for (arg, _) in argv {
write_vars(|v| -> ShResult<()> { write_vars(|v| -> ShResult<()> {
@@ -171,12 +194,15 @@ pub fn get_arr_op_opts(opts: Vec<Opt>) -> ShResult<ArrOpOpts> {
for opt in opts { for opt in opts {
match opt { match opt {
Opt::ShortWithArg('c', count) => { Opt::ShortWithArg('c', count) => {
arr_op_opts.count = count.parse::<usize>().map_err(|_| { arr_op_opts.count = count
ShErr::simple(ShErrKind::ParseErr, format!("invalid count: {}", count)) .parse::<usize>()
})?; .map_err(|_| ShErr::simple(ShErrKind::ParseErr, format!("invalid count: {}", count)))?;
} }
Opt::Short('c') => { Opt::Short('c') => {
return Err(ShErr::simple(ShErrKind::ParseErr, "missing count for -c".to_string())); return Err(ShErr::simple(
ShErrKind::ParseErr,
"missing count for -c".to_string(),
));
} }
Opt::Short('r') => { Opt::Short('r') => {
arr_op_opts.reverse = true; arr_op_opts.reverse = true;
@@ -185,10 +211,16 @@ pub fn get_arr_op_opts(opts: Vec<Opt>) -> ShResult<ArrOpOpts> {
arr_op_opts.var = Some(var); arr_op_opts.var = Some(var);
} }
Opt::Short('v') => { Opt::Short('v') => {
return Err(ShErr::simple(ShErrKind::ParseErr, "missing variable name for -v".to_string())); return Err(ShErr::simple(
ShErrKind::ParseErr,
"missing variable name for -v".to_string(),
));
} }
_ => { _ => {
return Err(ShErr::simple(ShErrKind::ParseErr, format!("invalid option: {}", opt))); return Err(ShErr::simple(
ShErrKind::ParseErr,
format!("invalid option: {}", opt),
));
} }
} }
} }

View File

@@ -1,43 +1,51 @@
use regex::Regex; use regex::Regex;
use crate::{ use crate::{
getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, state::{self, AutoCmd, AutoCmdKind, write_logic} getopt::{Opt, OptSpec, get_opts_from_tokens},
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node, execute::prepare_argv},
state::{self, AutoCmd, AutoCmdKind, write_logic},
}; };
pub struct AutoCmdOpts { pub struct AutoCmdOpts {
pattern: Option<Regex>, pattern: Option<Regex>,
clear: bool clear: bool,
} }
fn autocmd_optspec() -> [OptSpec;2] { fn autocmd_optspec() -> [OptSpec; 2] {
[ [
OptSpec { OptSpec {
opt: Opt::Short('p'), opt: Opt::Short('p'),
takes_arg: true takes_arg: true,
}, },
OptSpec { OptSpec {
opt: Opt::Short('c'), opt: Opt::Short('c'),
takes_arg: false takes_arg: false,
} },
] ]
} }
pub fn get_autocmd_opts(opts: &[Opt]) -> ShResult<AutoCmdOpts> { pub fn get_autocmd_opts(opts: &[Opt]) -> ShResult<AutoCmdOpts> {
let mut autocmd_opts = AutoCmdOpts { let mut autocmd_opts = AutoCmdOpts {
pattern: None, pattern: None,
clear: false clear: false,
}; };
let mut opts = opts.iter(); let mut opts = opts.iter();
while let Some(arg) = opts.next() { while let Some(arg) = opts.next() {
match arg { match arg {
Opt::ShortWithArg('p', arg) => { Opt::ShortWithArg('p', arg) => {
autocmd_opts.pattern = Some(Regex::new(arg).map_err(|e| ShErr::simple(ShErrKind::ExecFail, format!("invalid regex for -p: {}", e)))?); autocmd_opts.pattern = Some(Regex::new(arg).map_err(|e| {
ShErr::simple(ShErrKind::ExecFail, format!("invalid regex for -p: {}", e))
})?);
} }
Opt::Short('c') => { Opt::Short('c') => {
autocmd_opts.clear = true; autocmd_opts.clear = true;
} }
_ => { _ => {
return Err(ShErr::simple(ShErrKind::ExecFail, format!("unexpected option: {}", arg))); return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("unexpected option: {}", arg),
));
} }
} }
} }
@@ -55,18 +63,28 @@ pub fn autocmd(node: Node) -> ShResult<()> {
unreachable!() unreachable!()
}; };
let (argv,opts) = get_opts_from_tokens(argv, &autocmd_optspec()).promote_err(span.clone())?; let (argv, opts) = get_opts_from_tokens(argv, &autocmd_optspec()).promote_err(span.clone())?;
let autocmd_opts = get_autocmd_opts(&opts).promote_err(span.clone())?; let autocmd_opts = get_autocmd_opts(&opts).promote_err(span.clone())?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut args = argv.iter(); let mut args = argv.iter();
let Some(autocmd_kind) = args.next() else { let Some(autocmd_kind) = args.next() else {
return Err(ShErr::at(ShErrKind::ExecFail, span, "expected an autocmd kind".to_string())); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
"expected an autocmd kind".to_string(),
));
}; };
let Ok(autocmd_kind) = autocmd_kind.0.parse::<AutoCmdKind>() else { let Ok(autocmd_kind) = autocmd_kind.0.parse::<AutoCmdKind>() else {
return Err(ShErr::at(ShErrKind::ExecFail, autocmd_kind.1.clone(), format!("invalid autocmd kind: {}", autocmd_kind.0))); return Err(ShErr::at(
ShErrKind::ExecFail,
autocmd_kind.1.clone(),
format!("invalid autocmd kind: {}", autocmd_kind.0),
));
}; };
if autocmd_opts.clear { if autocmd_opts.clear {
@@ -76,7 +94,11 @@ pub fn autocmd(node: Node) -> ShResult<()> {
} }
let Some(autocmd_cmd) = args.next() else { let Some(autocmd_cmd) = args.next() else {
return Err(ShErr::at(ShErrKind::ExecFail, span, "expected an autocmd command".to_string())); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
"expected an autocmd command".to_string(),
));
}; };
let autocmd = AutoCmd { let autocmd = AutoCmd {

View File

@@ -20,37 +20,55 @@ pub fn cd(node: Node) -> ShResult<()> {
let cd_span = argv.first().unwrap().span.clone(); let cd_span = argv.first().unwrap().span.clone();
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let (new_dir,arg_span) = if let Some((arg, span)) = argv.into_iter().next() { let (new_dir, arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
(PathBuf::from(arg),Some(span)) (PathBuf::from(arg), Some(span))
} else { } else {
(PathBuf::from(env::var("HOME").unwrap()),None) (PathBuf::from(env::var("HOME").unwrap()), None)
}; };
if !new_dir.exists() { if !new_dir.exists() {
let mut err = ShErr::new( let mut err = ShErr::new(ShErrKind::ExecFail, span.clone())
ShErrKind::ExecFail, .labeled(cd_span.clone(), "Failed to change directory");
span.clone(),
).labeled(cd_span.clone(), "Failed to change directory");
if let Some(span) = arg_span { if let Some(span) = arg_span {
err = err.labeled(span, format!("No such file or directory '{}'", new_dir.display().fg(next_color()))); err = err.labeled(
span,
format!(
"No such file or directory '{}'",
new_dir.display().fg(next_color())
),
);
} }
return Err(err); return Err(err);
} }
if !new_dir.is_dir() { if !new_dir.is_dir() {
return Err(ShErr::new(ShErrKind::ExecFail, span.clone()) return Err(ShErr::new(ShErrKind::ExecFail, span.clone()).labeled(
.labeled(cd_span.clone(), format!("cd: Not a directory '{}'", new_dir.display().fg(next_color())))); cd_span.clone(),
format!(
"cd: Not a directory '{}'",
new_dir.display().fg(next_color())
),
));
} }
if let Err(e) = state::change_dir(new_dir) { if let Err(e) = state::change_dir(new_dir) {
return Err(ShErr::new(ShErrKind::ExecFail, span.clone()) return Err(ShErr::new(ShErrKind::ExecFail, span.clone()).labeled(
.labeled(cd_span.clone(), format!("cd: Failed to change directory: '{}'", e.fg(Color::Red)))); cd_span.clone(),
format!("cd: Failed to change directory: '{}'", e.fg(Color::Red)),
));
} }
let new_dir = env::current_dir().map_err(|e| { let new_dir = env::current_dir().map_err(|e| {
ShErr::new(ShErrKind::ExecFail, span.clone()) ShErr::new(ShErrKind::ExecFail, span.clone()).labeled(
.labeled(cd_span.clone(), format!("cd: Failed to get current directory: '{}'", e.fg(Color::Red))) cd_span.clone(),
format!(
"cd: Failed to get current directory: '{}'",
e.fg(Color::Red)
),
)
})?; })?;
unsafe { env::set_var("PWD", new_dir) }; unsafe { env::set_var("PWD", new_dir) };

View File

@@ -167,7 +167,9 @@ pub fn complete_builtin(node: Node) -> ShResult<()> {
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?; let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
let comp_opts = get_comp_opts(opts)?; let comp_opts = get_comp_opts(opts)?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if comp_opts.flags.contains(CompFlags::PRINT) { if comp_opts.flags.contains(CompFlags::PRINT) {
if argv.is_empty() { if argv.is_empty() {
@@ -204,7 +206,11 @@ pub fn complete_builtin(node: Node) -> ShResult<()> {
if argv.is_empty() { if argv.is_empty() {
state::set_status(1); state::set_status(1);
return Err(ShErr::at(ShErrKind::ExecFail, blame, "complete: no command specified")); return Err(ShErr::at(
ShErrKind::ExecFail,
blame,
"complete: no command specified",
));
} }
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src); let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
@@ -279,7 +285,11 @@ pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE, "space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
_ => { _ => {
let span: crate::parse::lex::Span = Default::default(); let span: crate::parse::lex::Span = Default::default();
return Err(ShErr::at(ShErrKind::InvalidOpt, span, format!("complete: invalid option: {}", opt_flag))); return Err(ShErr::at(
ShErrKind::InvalidOpt,
span,
format!("complete: invalid option: {}", opt_flag),
));
} }
}, },

View File

@@ -47,18 +47,26 @@ fn print_dirs() -> ShResult<()> {
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> { fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
if !target.is_dir() { if !target.is_dir() {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("not a directory: '{}'", target.display().fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!("not a directory: '{}'", target.display().fg(next_color())),
));
} }
if let Err(e) = state::change_dir(target) { if let Err(e) = state::change_dir(target) {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to change directory: '{}'", e.fg(Color::Red))) ShErrKind::ExecFail,
); blame,
format!("Failed to change directory: '{}'", e.fg(Color::Red)),
));
} }
let new_dir = env::current_dir().map_err(|e| { let new_dir = env::current_dir().map_err(|e| {
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to get current directory: '{}'", e.fg(Color::Red))) ShErr::at(
ShErrKind::ExecFail,
blame,
format!("Failed to get current directory: '{}'", e.fg(Color::Red)),
)
})?; })?;
unsafe { env::set_var("PWD", new_dir) }; unsafe { env::set_var("PWD", new_dir) };
Ok(()) Ok(())
@@ -74,24 +82,32 @@ fn parse_stack_idx(arg: &str, blame: Span, cmd: &str) -> ShResult<StackIdx> {
}; };
if digits.is_empty() { if digits.is_empty() {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!( ShErrKind::ExecFail,
blame,
format!(
"{cmd}: missing index after '{}'", "{cmd}: missing index after '{}'",
if from_top { "+" } else { "-" } if from_top { "+" } else { "-" }
)) ),
); ));
} }
for ch in digits.chars() { for ch in digits.chars() {
if !ch.is_ascii_digit() { if !ch.is_ascii_digit() {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid argument: '{}'",arg.fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!("{cmd}: invalid argument: '{}'", arg.fg(next_color())),
));
} }
} }
let n = digits.parse::<usize>().map_err(|e| { let n = digits.parse::<usize>().map_err(|e| {
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid index: '{}'",e.fg(next_color()))) ShErr::at(
ShErrKind::ExecFail,
blame,
format!("{cmd}: invalid index: '{}'", e.fg(next_color())),
)
})?; })?;
if from_top { if from_top {
@@ -112,7 +128,9 @@ pub fn pushd(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut dir = None; let mut dir = None;
let mut rotate_idx = None; let mut rotate_idx = None;
@@ -126,20 +144,29 @@ pub fn pushd(node: Node) -> ShResult<()> {
} else if arg == "-n" { } else if arg == "-n" {
no_cd = true; no_cd = true;
} else if arg.starts_with('-') { } else if arg.starts_with('-') {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: invalid option: '{}'", arg.fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!("pushd: invalid option: '{}'", arg.fg(next_color())),
));
} else { } else {
if dir.is_some() { if dir.is_some() {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, "pushd: too many arguments") ShErrKind::ExecFail,
); blame,
"pushd: too many arguments",
));
} }
let target = PathBuf::from(&arg); let target = PathBuf::from(&arg);
if !target.is_dir() { if !target.is_dir() {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: not a directory: '{}'", target.display().fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!(
"pushd: not a directory: '{}'",
target.display().fg(next_color())
),
));
} }
dir = Some(target); dir = Some(target);
} }
@@ -193,7 +220,9 @@ pub fn popd(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut remove_idx = None; let mut remove_idx = None;
let mut no_cd = false; let mut no_cd = false;
@@ -206,9 +235,11 @@ pub fn popd(node: Node) -> ShResult<()> {
} else if arg == "-n" { } else if arg == "-n" {
no_cd = true; no_cd = true;
} else if arg.starts_with('-') { } else if arg.starts_with('-') {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("popd: invalid option: '{}'", arg.fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!("popd: invalid option: '{}'", arg.fg(next_color())),
));
} }
} }
@@ -221,9 +252,11 @@ pub fn popd(node: Node) -> ShResult<()> {
if let Some(dir) = dir { if let Some(dir) = dir {
change_directory(&dir, blame.clone())?; change_directory(&dir, blame.clone())?;
} else { } else {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty") ShErrKind::ExecFail,
); blame,
"popd: directory stack empty",
));
} }
} }
} }
@@ -233,9 +266,11 @@ pub fn popd(node: Node) -> ShResult<()> {
let dirs = m.dirs_mut(); let dirs = m.dirs_mut();
let idx = n - 1; let idx = n - 1;
if idx >= dirs.len() { if idx >= dirs.len() {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: +{n}")) ShErrKind::ExecFail,
); blame.clone(),
format!("popd: directory index out of range: +{n}"),
));
} }
dirs.remove(idx); dirs.remove(idx);
Ok(()) Ok(())
@@ -245,7 +280,11 @@ pub fn popd(node: Node) -> ShResult<()> {
write_meta(|m| -> ShResult<()> { write_meta(|m| -> ShResult<()> {
let dirs = m.dirs_mut(); let dirs = m.dirs_mut();
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| { let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: -{n}")) ShErr::at(
ShErrKind::ExecFail,
blame.clone(),
format!("popd: directory index out of range: -{n}"),
)
})?; })?;
dirs.remove(actual); dirs.remove(actual);
Ok(()) Ok(())
@@ -265,9 +304,11 @@ pub fn popd(node: Node) -> ShResult<()> {
change_directory(&dir, blame.clone())?; change_directory(&dir, blame.clone())?;
print_dirs()?; print_dirs()?;
} else { } else {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty") ShErrKind::ExecFail,
); blame,
"popd: directory stack empty",
));
} }
} }
@@ -285,7 +326,9 @@ pub fn dirs(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut abbreviate_home = true; let mut abbreviate_home = true;
let mut one_per_line = false; let mut one_per_line = false;
@@ -306,14 +349,18 @@ pub fn dirs(node: Node) -> ShResult<()> {
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?); target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
} }
_ if arg.starts_with('-') => { _ if arg.starts_with('-') => {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: invalid option: '{}'", arg.fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!("dirs: invalid option: '{}'", arg.fg(next_color())),
));
} }
_ => { _ => {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: unexpected argument: '{}'", arg.fg(next_color()))) ShErrKind::ExecFail,
); blame,
format!("dirs: unexpected argument: '{}'", arg.fg(next_color())),
));
} }
} }
} }
@@ -358,15 +405,17 @@ pub fn dirs(node: Node) -> ShResult<()> {
if let Some(dir) = target { if let Some(dir) = target {
dirs = vec![dir.clone()]; dirs = vec![dir.clone()];
} else { } else {
return Err( return Err(ShErr::at(
ShErr::at(ShErrKind::ExecFail, blame, format!( ShErrKind::ExecFail,
blame,
format!(
"dirs: directory index out of range: {}", "dirs: directory index out of range: {}",
match idx { match idx {
StackIdx::FromTop(n) => format!("+{n}"), StackIdx::FromTop(n) => format!("+{n}"),
StackIdx::FromBottom(n) => format!("-{n}"), StackIdx::FromBottom(n) => format!("-{n}"),
} }
)) ),
); ));
} }
} }

View File

@@ -50,7 +50,9 @@ pub fn echo(node: Node) -> ShResult<()> {
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?; let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
let flags = get_echo_flags(opts).blame(blame)?; let flags = get_echo_flags(opts).blame(blame)?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let output_channel = if flags.contains(EchoFlags::USE_STDERR) { let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
borrow_fd(STDERR_FILENO) borrow_fd(STDERR_FILENO)

View File

@@ -1,6 +1,9 @@
use crate::{ use crate::{
libsh::error::ShResult, libsh::error::ShResult,
parse::{NdRule, Node, execute::{exec_input, prepare_argv}}, parse::{
NdRule, Node,
execute::{exec_input, prepare_argv},
},
state, state,
}; };
@@ -14,7 +17,9 @@ pub fn eval(node: Node) -> ShResult<()> {
}; };
let mut expanded_argv = prepare_argv(argv)?; let mut expanded_argv = prepare_argv(argv)?;
if !expanded_argv.is_empty() { expanded_argv.remove(0); } if !expanded_argv.is_empty() {
expanded_argv.remove(0);
}
if expanded_argv.is_empty() { if expanded_argv.is_empty() {
state::set_status(0); state::set_status(0);

View File

@@ -2,7 +2,10 @@ use nix::{errno::Errno, unistd::execvpe};
use crate::{ use crate::{
libsh::error::{ShErr, ShErrKind, ShResult}, libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node, execute::{ExecArgs, prepare_argv}}, parse::{
NdRule, Node,
execute::{ExecArgs, prepare_argv},
},
state, state,
}; };
@@ -16,7 +19,9 @@ pub fn exec_builtin(node: Node) -> ShResult<()> {
}; };
let mut expanded_argv = prepare_argv(argv)?; let mut expanded_argv = prepare_argv(argv)?;
if !expanded_argv.is_empty() { expanded_argv.remove(0); } if !expanded_argv.is_empty() {
expanded_argv.remove(0);
}
if expanded_argv.is_empty() { if expanded_argv.is_empty() {
state::set_status(0); state::set_status(0);
@@ -35,7 +40,7 @@ pub fn exec_builtin(node: Node) -> ShResult<()> {
match e { match e {
Errno::ENOENT => Err( Errno::ENOENT => Err(
ShErr::new(ShErrKind::NotFound, span.clone()) ShErr::new(ShErrKind::NotFound, span.clone())
.labeled(span, format!("exec: command not found: {}", cmd_str)) .labeled(span, format!("exec: command not found: {}", cmd_str)),
), ),
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))), _ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),
} }

View File

@@ -21,7 +21,11 @@ pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
let (arg, span) = argv.into_iter().next().unwrap(); let (arg, span) = argv.into_iter().next().unwrap();
let Ok(status) = arg.parse::<i32>() else { let Ok(status) = arg.parse::<i32>() else {
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("{cmd}: Expected a number"))); return Err(ShErr::at(
ShErrKind::SyntaxErr,
span,
format!("{cmd}: Expected a number"),
));
}; };
code = status; code = status;

View File

@@ -3,18 +3,21 @@ use std::str::FromStr;
use ariadne::Fmt; use ariadne::Fmt;
use crate::{ use crate::{
getopt::{Opt, OptSpec}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::Span}, state::{self, VarFlags, VarKind, read_meta, read_vars, write_meta, write_vars} getopt::{Opt, OptSpec},
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
parse::{NdRule, Node, execute::prepare_argv, lex::Span},
state::{self, VarFlags, VarKind, read_meta, read_vars, write_meta, write_vars},
}; };
enum OptMatch { enum OptMatch {
NoMatch, NoMatch,
IsMatch, IsMatch,
WantsArg WantsArg,
} }
struct GetOptsSpec { struct GetOptsSpec {
silent_err: bool, silent_err: bool,
opt_specs: Vec<OptSpec> opt_specs: Vec<OptSpec>,
} }
impl GetOptsSpec { impl GetOptsSpec {
@@ -24,12 +27,12 @@ impl GetOptsSpec {
match opt { match opt {
Opt::Short(opt_ch) if ch == *opt_ch => { Opt::Short(opt_ch) if ch == *opt_ch => {
if *takes_arg { if *takes_arg {
return OptMatch::WantsArg return OptMatch::WantsArg;
} else { } else {
return OptMatch::IsMatch return OptMatch::IsMatch;
} }
} }
_ => { continue } _ => continue,
} }
} }
OptMatch::NoMatch OptMatch::NoMatch
@@ -59,29 +62,45 @@ impl FromStr for GetOptsSpec {
} }
opt_specs.push(OptSpec { opt, takes_arg }) opt_specs.push(OptSpec { opt, takes_arg })
} }
_ => return Err(ShErr::simple( _ => {
return Err(ShErr::simple(
ShErrKind::ParseErr, ShErrKind::ParseErr,
format!("unexpected character '{}'", ch.fg(next_color())) format!("unexpected character '{}'", ch.fg(next_color())),
)), ));
}
} }
} }
Ok(GetOptsSpec { silent_err, opt_specs }) Ok(GetOptsSpec {
silent_err,
opt_specs,
})
} }
} }
fn advance_optind(opt_index: usize, amount: usize) -> ShResult<()> { fn advance_optind(opt_index: usize, amount: usize) -> ShResult<()> {
write_vars(|v| v.set_var("OPTIND", VarKind::Str((opt_index + amount).to_string()), VarFlags::NONE)) write_vars(|v| {
v.set_var(
"OPTIND",
VarKind::Str((opt_index + amount).to_string()),
VarFlags::NONE,
)
})
} }
fn getopts_inner(opts_spec: &GetOptsSpec, opt_var: &str, argv: &[String], blame: Span) -> ShResult<()> { fn getopts_inner(
opts_spec: &GetOptsSpec,
opt_var: &str,
argv: &[String],
blame: Span,
) -> ShResult<()> {
let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1)); let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1));
// OPTIND is 1-based // OPTIND is 1-based
let arr_idx = opt_index.saturating_sub(1); let arr_idx = opt_index.saturating_sub(1);
let Some(arg) = argv.get(arr_idx) else { let Some(arg) = argv.get(arr_idx) else {
state::set_status(1); state::set_status(1);
return Ok(()) return Ok(());
}; };
// "--" stops option processing // "--" stops option processing
@@ -89,7 +108,7 @@ fn getopts_inner(opts_spec: &GetOptsSpec, opt_var: &str, argv: &[String], blame:
advance_optind(opt_index, 1)?; advance_optind(opt_index, 1)?;
write_meta(|m| m.reset_getopts_char_offset()); write_meta(|m| m.reset_getopts_char_offset());
state::set_status(1); state::set_status(1);
return Ok(()) return Ok(());
} }
// Not an option — done // Not an option — done
@@ -139,8 +158,9 @@ fn getopts_inner(opts_spec: &GetOptsSpec, opt_var: &str, argv: &[String], blame:
ShErr::at( ShErr::at(
ShErrKind::ExecFail, ShErrKind::ExecFail,
blame.clone(), blame.clone(),
format!("illegal option '-{}'", ch.fg(next_color())) format!("illegal option '-{}'", ch.fg(next_color())),
).print_error(); )
.print_error();
} }
state::set_status(0); state::set_status(0);
} }
@@ -172,8 +192,9 @@ fn getopts_inner(opts_spec: &GetOptsSpec, opt_var: &str, argv: &[String], blame:
ShErr::at( ShErr::at(
ShErrKind::ExecFail, ShErrKind::ExecFail,
blame.clone(), blame.clone(),
format!("option '-{}' requires an argument", ch.fg(next_color())) format!("option '-{}' requires an argument", ch.fg(next_color())),
).print_error(); )
.print_error();
} }
advance_optind(opt_index, 1)?; advance_optind(opt_index, 1)?;
state::set_status(0); state::set_status(0);
@@ -199,26 +220,27 @@ pub fn getopts(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut args = argv.into_iter(); let mut args = argv.into_iter();
let Some(arg_string) = args.next() else { let Some(arg_string) = args.next() else {
return Err(ShErr::at( return Err(ShErr::at(
ShErrKind::ExecFail, ShErrKind::ExecFail,
span, span,
"getopts: missing option spec" "getopts: missing option spec",
)) ));
}; };
let Some(opt_var) = args.next() else { let Some(opt_var) = args.next() else {
return Err(ShErr::at( return Err(ShErr::at(
ShErrKind::ExecFail, ShErrKind::ExecFail,
span, span,
"getopts: missing variable name" "getopts: missing variable name",
)) ));
}; };
let opts_spec = GetOptsSpec::from_str(&arg_string.0) let opts_spec = GetOptsSpec::from_str(&arg_string.0).promote_err(arg_string.1.clone())?;
.promote_err(arg_string.1.clone())?;
let explicit_args: Vec<String> = args.map(|s| s.0).collect(); let explicit_args: Vec<String> = args.map(|s| s.0).collect();

View File

@@ -2,7 +2,12 @@ use std::{env, os::unix::fs::PermissionsExt, path::Path};
use ariadne::{Fmt, Span}; use ariadne::{Fmt, Span};
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind, ShResult, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::KEYWORDS}, state::{self, ShAlias, ShFunc, read_logic}}; use crate::{
builtin::BUILTINS,
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
parse::{NdRule, Node, execute::prepare_argv, lex::KEYWORDS},
state::{self, ShAlias, ShFunc, read_logic},
};
pub fn type_builtin(node: Node) -> ShResult<()> { pub fn type_builtin(node: Node) -> ShResult<()> {
let NdRule::Command { let NdRule::Command {
@@ -14,7 +19,9 @@ pub fn type_builtin(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
/* /*
* we have to check in the same order that the dispatcher checks this * we have to check in the same order that the dispatcher checks this
@@ -23,26 +30,32 @@ pub fn type_builtin(node: Node) -> ShResult<()> {
* 3. command * 3. command
*/ */
'outer: for (arg,span) in argv { 'outer: for (arg, span) in argv {
if let Some(func) = read_logic(|v| v.get_func(&arg)) { if let Some(func) = read_logic(|v| v.get_func(&arg)) {
let ShFunc { body: _, source } = func; let ShFunc { body: _, source } = func;
let (line, col) = source.line_and_col(); let (line, col) = source.line_and_col();
let name = source.source().name(); let name = source.source().name();
println!("{arg} is a function defined at {name}:{}:{}", line + 1, col + 1); println!(
"{arg} is a function defined at {name}:{}:{}",
line + 1,
col + 1
);
} else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) { } else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) {
let ShAlias { body, source } = alias; let ShAlias { body, source } = alias;
let (line, col) = source.line_and_col(); let (line, col) = source.line_and_col();
let name = source.source().name(); let name = source.source().name();
println!("{arg} is an alias for '{body}' defined at {name}:{}:{}", line + 1, col + 1); println!(
"{arg} is an alias for '{body}' defined at {name}:{}:{}",
line + 1,
col + 1
);
} else if BUILTINS.contains(&arg.as_str()) { } else if BUILTINS.contains(&arg.as_str()) {
println!("{arg} is a shell builtin"); println!("{arg} is a shell builtin");
} else if KEYWORDS.contains(&arg.as_str()) { } else if KEYWORDS.contains(&arg.as_str()) {
println!("{arg} is a shell keyword"); println!("{arg} is a shell keyword");
} else { } else {
let path = env::var("PATH").unwrap_or_default(); let path = env::var("PATH").unwrap_or_default();
let paths = path.split(':') let paths = path.split(':').map(Path::new).collect::<Vec<_>>();
.map(Path::new)
.collect::<Vec<_>>();
for path in paths { for path in paths {
if let Ok(entries) = path.read_dir() { if let Ok(entries) = path.read_dir() {
@@ -55,7 +68,8 @@ pub fn type_builtin(node: Node) -> ShResult<()> {
if meta.is_file() if meta.is_file()
&& is_exec && is_exec
&& let Some(name) = entry.file_name().to_str() && let Some(name) = entry.file_name().to_str()
&& name == arg { && name == arg
{
println!("{arg} is {}", entry.path().display()); println!("{arg} is {}", entry.path().display());
continue 'outer; continue 'outer;
} }
@@ -64,7 +78,14 @@ pub fn type_builtin(node: Node) -> ShResult<()> {
} }
state::set_status(1); state::set_status(1);
return Err(ShErr::at(ShErrKind::NotFound, span, format!("'{}' is not a command, function, or alias", arg.fg(next_color())))); return Err(ShErr::at(
ShErrKind::NotFound,
span,
format!(
"'{}' is not a command, function, or alias",
arg.fg(next_color())
),
));
} }
} }

View File

@@ -31,11 +31,17 @@ pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut argv = argv.into_iter(); let mut argv = argv.into_iter();
if read_jobs(|j| j.get_fg().is_some()) { if read_jobs(|j| j.get_fg().is_some()) {
return Err(ShErr::at(ShErrKind::InternalErr, cmd_span, format!("Somehow called '{}' with an existing foreground job", cmd))); return Err(ShErr::at(
ShErrKind::InternalErr,
cmd_span,
format!("Somehow called '{}' with an existing foreground job", cmd),
));
} }
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()) {
@@ -55,7 +61,11 @@ pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
if query_result.is_some() { if query_result.is_some() {
Ok(j.remove_job(id.clone()).unwrap()) Ok(j.remove_job(id.clone()).unwrap())
} else { } else {
Err(ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("Job id `{}' not found", tabid))) Err(ShErr::at(
ShErrKind::ExecFail,
blame.clone(),
format!("Job id `{}' not found", tabid),
))
} }
})?; })?;
@@ -84,7 +94,11 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
if arg.chars().all(|ch| ch.is_ascii_digit()) { if arg.chars().all(|ch| ch.is_ascii_digit()) {
let num = arg.parse::<usize>().unwrap_or_default(); let num = arg.parse::<usize>().unwrap_or_default();
if num == 0 { if num == 0 {
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid job id: {}", arg.fg(next_color())))) Err(ShErr::at(
ShErrKind::SyntaxErr,
blame,
format!("Invalid job id: {}", arg.fg(next_color())),
))
} else { } else {
Ok(num.saturating_sub(1)) Ok(num.saturating_sub(1))
} }
@@ -95,7 +109,11 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
}); });
match result { match result {
Some(id) => Ok(id), Some(id) => Ok(id),
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")), None => Err(ShErr::at(
ShErrKind::InternalErr,
blame,
"Found a job but no table id in parse_job_id()",
)),
} }
} }
} else if arg.chars().all(|ch| ch.is_ascii_digit()) { } else if arg.chars().all(|ch| ch.is_ascii_digit()) {
@@ -115,10 +133,18 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
match result { match result {
Some(id) => Ok(id), Some(id) => Ok(id),
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")), None => Err(ShErr::at(
ShErrKind::InternalErr,
blame,
"Found a job but no table id in parse_job_id()",
)),
} }
} else { } else {
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid arg: {}", arg.fg(next_color())))) Err(ShErr::at(
ShErrKind::SyntaxErr,
blame,
format!("Invalid arg: {}", arg.fg(next_color())),
))
} }
} }
@@ -132,13 +158,19 @@ pub fn jobs(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut flags = JobCmdFlags::empty(); let mut flags = JobCmdFlags::empty();
for (arg, span) in argv { for (arg, span) in argv {
let mut chars = arg.chars().peekable(); let mut chars = arg.chars().peekable();
if chars.peek().is_none_or(|ch| *ch != '-') { if chars.peek().is_none_or(|ch| *ch != '-') {
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call")); return Err(ShErr::at(
ShErrKind::SyntaxErr,
span,
"Invalid flag in jobs call",
));
} }
chars.next(); chars.next();
for ch in chars { for ch in chars {
@@ -149,7 +181,11 @@ pub fn jobs(node: Node) -> ShResult<()> {
'r' => JobCmdFlags::RUNNING, 'r' => JobCmdFlags::RUNNING,
's' => JobCmdFlags::STOPPED, 's' => JobCmdFlags::STOPPED,
_ => { _ => {
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call")); return Err(ShErr::at(
ShErrKind::SyntaxErr,
span,
"Invalid flag in jobs call",
));
} }
}; };
flags |= flag flags |= flag
@@ -172,12 +208,15 @@ pub fn wait(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if read_jobs(|j| j.curr_job().is_none()) { if read_jobs(|j| j.curr_job().is_none()) {
state::set_status(0); state::set_status(0);
return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found")); return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found"));
} }
let argv = argv.into_iter() let argv = argv
.into_iter()
.map(|arg| { .map(|arg| {
if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) { if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) {
Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap()))) Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap())))
@@ -210,13 +249,19 @@ pub fn disown(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut argv = argv.into_iter(); let mut argv = argv.into_iter();
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::at(ShErrKind::ExecFail, blame, "disown: No jobs to disown")); return Err(ShErr::at(
ShErrKind::ExecFail,
blame,
"disown: No jobs to disown",
));
}; };
let mut tabid = curr_job_id; let mut tabid = curr_job_id;

View File

@@ -1,5 +1,11 @@
use crate::{ use crate::{
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, readline::keys::KeyEvent, state::{self, write_logic} expand::expand_keymap,
getopt::{Opt, OptSpec, get_opts_from_tokens},
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node, execute::prepare_argv},
prelude::*,
readline::keys::KeyEvent,
state::{self, write_logic},
}; };
bitflags! { bitflags! {
@@ -33,11 +39,19 @@ impl KeyMapOpts {
Opt::Short('r') => flags |= KeyMapFlags::REPLACE, Opt::Short('r') => flags |= KeyMapFlags::REPLACE,
Opt::LongWithArg(name, arg) if name == "remove" => { Opt::LongWithArg(name, arg) if name == "remove" => {
if remove.is_some() { if remove.is_some() {
return Err(ShErr::simple(ShErrKind::ExecFail, "Duplicate --remove option for keymap".to_string())); return Err(ShErr::simple(
ShErrKind::ExecFail,
"Duplicate --remove option for keymap".to_string(),
));
} }
remove = Some(arg.clone()); remove = Some(arg.clone());
}, }
_ => return Err(ShErr::simple(ShErrKind::ExecFail, format!("Invalid option for keymap: {:?}", opt))), _ => {
return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("Invalid option for keymap: {:?}", opt),
));
}
} }
} }
if flags.is_empty() { if flags.is_empty() {
@@ -45,31 +59,31 @@ impl KeyMapOpts {
} }
Ok(Self { remove, flags }) Ok(Self { remove, flags })
} }
pub fn keymap_opts() -> [OptSpec;6] { pub fn keymap_opts() -> [OptSpec; 6] {
[ [
OptSpec { OptSpec {
opt: Opt::Short('n'), // normal mode opt: Opt::Short('n'), // normal mode
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('i'), // insert mode opt: Opt::Short('i'), // insert mode
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('v'), // visual mode opt: Opt::Short('v'), // visual mode
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('x'), // ex mode opt: Opt::Short('x'), // ex mode
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('o'), // operator-pending mode opt: Opt::Short('o'), // operator-pending mode
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('r'), // replace mode opt: Opt::Short('r'), // replace mode
takes_arg: false takes_arg: false,
}, },
] ]
} }
@@ -79,14 +93,14 @@ impl KeyMapOpts {
pub enum KeyMapMatch { pub enum KeyMapMatch {
NoMatch, NoMatch,
IsPrefix, IsPrefix,
IsExact IsExact,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct KeyMap { pub struct KeyMap {
pub flags: KeyMapFlags, pub flags: KeyMapFlags,
pub keys: String, pub keys: String,
pub action: String pub action: String,
} }
impl KeyMap { impl KeyMap {
@@ -97,7 +111,11 @@ impl KeyMap {
expand_keymap(&self.action) expand_keymap(&self.action)
} }
pub fn compare(&self, other: &[KeyEvent]) -> KeyMapMatch { pub fn compare(&self, other: &[KeyEvent]) -> KeyMapMatch {
log::debug!("Comparing keymap keys {:?} with input {:?}", self.keys_expanded(), other); log::debug!(
"Comparing keymap keys {:?} with input {:?}",
self.keys_expanded(),
other
);
let ours = self.keys_expanded(); let ours = self.keys_expanded();
if other == ours { if other == ours {
KeyMapMatch::IsExact KeyMapMatch::IsExact
@@ -128,14 +146,24 @@ pub fn keymap(node: Node) -> ShResult<()> {
} }
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let Some((keys,_)) = argv.first() else { let Some((keys, _)) = argv.first() else {
return Err(ShErr::at(ShErrKind::ExecFail, span, "missing keys argument".to_string())); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
"missing keys argument".to_string(),
));
}; };
let Some((action,_)) = argv.get(1) else { let Some((action, _)) = argv.get(1) else {
return Err(ShErr::at(ShErrKind::ExecFail, span, "missing action argument".to_string())); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
"missing action argument".to_string(),
));
}; };
let keymap = KeyMap { let keymap = KeyMap {

View File

@@ -5,7 +5,15 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
use serde_json::{Map, Value}; use serde_json::{Map, Value};
use crate::{ use crate::{
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_tk, split_tk_at}}, procio::borrow_fd, state::{self, read_vars, write_vars} expand::expand_cmd_sub,
getopt::{Opt, OptSpec, get_opts_from_tokens},
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{
NdRule, Node,
lex::{split_tk, split_tk_at},
},
procio::borrow_fd,
state::{self, read_vars, write_vars},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -26,24 +34,18 @@ impl From<MapNode> for serde_json::Value {
fn from(val: MapNode) -> Self { fn from(val: MapNode) -> Self {
match val { match val {
MapNode::Branch(map) => { MapNode::Branch(map) => {
let val_map = map.into_iter() let val_map = map
.map(|(k,v)| { .into_iter()
(k,v.into()) .map(|(k, v)| (k, v.into()))
}) .collect::<Map<String, Value>>();
.collect::<Map<String,Value>>();
Value::Object(val_map) Value::Object(val_map)
} }
MapNode::Array(nodes) => { MapNode::Array(nodes) => {
let arr = nodes let arr = nodes.into_iter().map(|node| node.into()).collect();
.into_iter()
.map(|node| node.into())
.collect();
Value::Array(arr) Value::Array(arr)
} }
MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => { MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => Value::String(leaf),
Value::String(leaf)
}
} }
} }
} }
@@ -52,23 +54,19 @@ impl From<Value> for MapNode {
fn from(value: Value) -> Self { fn from(value: Value) -> Self {
match value { match value {
Value::Object(map) => { Value::Object(map) => {
let node_map = map.into_iter() let node_map = map
.map(|(k,v)| { .into_iter()
(k, v.into()) .map(|(k, v)| (k, v.into()))
})
.collect::<HashMap<String, MapNode>>(); .collect::<HashMap<String, MapNode>>();
MapNode::Branch(node_map) MapNode::Branch(node_map)
} }
Value::Array(arr) => { Value::Array(arr) => {
let nodes = arr let nodes = arr.into_iter().map(|v| v.into()).collect();
.into_iter()
.map(|v| v.into())
.collect();
MapNode::Array(nodes) MapNode::Array(nodes)
} }
Value::String(s) => MapNode::StaticLeaf(s), Value::String(s) => MapNode::StaticLeaf(s),
v => MapNode::StaticLeaf(v.to_string()) v => MapNode::StaticLeaf(v.to_string()),
} }
} }
} }
@@ -83,8 +81,8 @@ impl MapNode {
let idx: usize = key.parse().ok()?; let idx: usize = key.parse().ok()?;
map_nodes.get(idx)?.get(rest) map_nodes.get(idx)?.get(rest)
} }
MapNode::Branch(map) => map.get(key)?.get(rest) MapNode::Branch(map) => map.get(key)?.get(rest),
} },
} }
} }
@@ -98,9 +96,7 @@ impl MapNode {
} }
match self { match self {
MapNode::Branch(map) => { MapNode::Branch(map) => {
let child = map let child = map.entry(key.to_string()).or_insert_with(Self::default);
.entry(key.to_string())
.or_insert_with(Self::default);
child.set(rest, value); child.set(rest, value);
} }
MapNode::Array(map_nodes) => { MapNode::Array(map_nodes) => {
@@ -110,7 +106,7 @@ impl MapNode {
} }
map_nodes[idx].set(rest, value); map_nodes[idx].set(rest, value);
} }
_ => unreachable!() _ => unreachable!(),
} }
} }
} }
@@ -128,8 +124,8 @@ impl MapNode {
} }
Some(nodes.remove(idx)) Some(nodes.remove(idx))
} }
_ => None _ => None,
} },
[key, rest @ ..] => match self { [key, rest @ ..] => match self {
MapNode::Branch(map) => map.get_mut(key)?.remove(rest), MapNode::Branch(map) => map.get_mut(key)?.remove(rest),
MapNode::Array(nodes) => { MapNode::Array(nodes) => {
@@ -139,15 +135,18 @@ impl MapNode {
} }
nodes[idx].remove(rest) nodes[idx].remove(rest)
} }
_ => None _ => None,
} },
} }
} }
fn keys(&self) -> Vec<String> { fn keys(&self) -> Vec<String> {
match self { match self {
MapNode::Branch(map) => map.keys().map(|k| k.to_string()).collect(), MapNode::Branch(map) => map.keys().map(|k| k.to_string()).collect(),
MapNode::Array(nodes) => nodes.iter().filter_map(|n| n.display(false, false).ok()).collect(), MapNode::Array(nodes) => nodes
.iter()
.filter_map(|n| n.display(false, false).ok())
.collect(),
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![], MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![],
} }
} }
@@ -160,16 +159,16 @@ impl MapNode {
Ok(s) => Ok(s), Ok(s) => Ok(s),
Err(e) => Err(ShErr::simple( Err(e) => Err(ShErr::simple(
ShErrKind::InternalErr, ShErrKind::InternalErr,
format!("failed to serialize map: {e}") format!("failed to serialize map: {e}"),
)) )),
} }
} else { } else {
match serde_json::to_string(&val) { match serde_json::to_string(&val) {
Ok(s) => Ok(s), Ok(s) => Ok(s),
Err(e) => Err(ShErr::simple( Err(e) => Err(ShErr::simple(
ShErrKind::InternalErr, ShErrKind::InternalErr,
format!("failed to serialize map: {e}") format!("failed to serialize map: {e}"),
)) )),
} }
} }
} else { } else {
@@ -189,7 +188,7 @@ impl MapNode {
} }
Ok(s.trim_end_matches('\n').to_string()) Ok(s.trim_end_matches('\n').to_string())
} }
_ => unreachable!() _ => unreachable!(),
} }
} }
} }
@@ -199,27 +198,27 @@ fn map_opts_spec() -> [OptSpec; 6] {
[ [
OptSpec { OptSpec {
opt: Opt::Short('r'), opt: Opt::Short('r'),
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('j'), opt: Opt::Short('j'),
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('k'), opt: Opt::Short('k'),
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Long("pretty".into()), opt: Opt::Long("pretty".into()),
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('F'), opt: Opt::Short('F'),
takes_arg: false takes_arg: false,
}, },
OptSpec { OptSpec {
opt: Opt::Short('l'), opt: Opt::Short('l'),
takes_arg: false takes_arg: false,
}, },
] ]
} }
@@ -257,7 +256,7 @@ pub fn map(node: Node) -> ShResult<()> {
} }
for arg in argv { for arg in argv {
if let Some((lhs,rhs)) = split_tk_at(&arg, "=") { if let Some((lhs, rhs)) = split_tk_at(&arg, "=") {
let path = split_tk(&lhs, ".") let path = split_tk(&lhs, ".")
.into_iter() .into_iter()
.map(|s| s.expand().map(|exp| exp.get_words().join(" "))) .map(|s| s.expand().map(|exp| exp.get_words().join(" ")))
@@ -265,14 +264,18 @@ pub fn map(node: Node) -> ShResult<()> {
let Some(name) = path.first() else { let Some(name) = path.first() else {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::InternalErr, ShErrKind::InternalErr,
format!("invalid map path: {}", lhs.as_str()) format!("invalid map path: {}", lhs.as_str()),
)); ));
}; };
let is_json = map_opts.flags.contains(MapFlags::JSON); let is_json = map_opts.flags.contains(MapFlags::JSON);
let is_func = map_opts.flags.contains(MapFlags::FUNC); let is_func = map_opts.flags.contains(MapFlags::FUNC);
let make_leaf = |s: String| { let make_leaf = |s: String| {
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) } if is_func {
MapNode::DynamicLeaf(s)
} else {
MapNode::StaticLeaf(s)
}
}; };
let expanded = rhs.expand()?.get_words().join(" "); let expanded = rhs.expand()?.get_words().join(" ");
let found = write_vars(|v| -> ShResult<bool> { let found = write_vars(|v| -> ShResult<bool> {
@@ -294,7 +297,9 @@ pub fn map(node: Node) -> ShResult<()> {
if !found? { if !found? {
let mut new = MapNode::default(); let mut new = MapNode::default();
if is_json /*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */{ if is_json
/*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */
{
let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap(); let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
let node: MapNode = parsed.into(); let node: MapNode = parsed.into();
new.set(&path[1..], node); new.set(&path[1..], node);
@@ -309,7 +314,7 @@ pub fn map(node: Node) -> ShResult<()> {
let Some(name) = path.first() else { let Some(name) = path.first() else {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::InternalErr, ShErrKind::InternalErr,
format!("invalid map path: {}", expanded) format!("invalid map path: {}", expanded),
)); ));
}; };
@@ -321,7 +326,7 @@ pub fn map(node: Node) -> ShResult<()> {
let Some(map) = v.get_map_mut(name) else { let Some(map) = v.get_map_mut(name) else {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::ExecFail, ShErrKind::ExecFail,
format!("map not found: {}", name) format!("map not found: {}", name),
)); ));
}; };
map.remove(&path[1..]); map.remove(&path[1..]);
@@ -339,13 +344,11 @@ pub fn map(node: Node) -> ShResult<()> {
if !has_map { if !has_map {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::ExecFail, ShErrKind::ExecFail,
format!("map not found: {}", name) format!("map not found: {}", name),
)); ));
} }
let Some(node) = read_vars(|v| { let Some(node) = read_vars(|v| v.get_map(name).and_then(|map| map.get(&path[1..]).cloned()))
v.get_map(name) else {
.and_then(|map| map.get(&path[1..]).cloned())
}) else {
state::set_status(1); state::set_status(1);
continue; continue;
}; };
@@ -367,7 +370,7 @@ pub fn map(node: Node) -> ShResult<()> {
pub fn get_map_opts(opts: Vec<Opt>) -> MapOpts { pub fn get_map_opts(opts: Vec<Opt>) -> MapOpts {
let mut map_opts = MapOpts { let mut map_opts = MapOpts {
flags: MapFlags::empty() flags: MapFlags::empty(),
}; };
for opt in opts { for opt in opts {
@@ -378,7 +381,7 @@ pub fn get_map_opts(opts: Vec<Opt>) -> MapOpts {
Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL, Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL,
Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY, Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY,
Opt::Short('F') => map_opts.flags |= MapFlags::FUNC, Opt::Short('F') => map_opts.flags |= MapFlags::FUNC,
_ => unreachable!() _ => unreachable!(),
} }
} }
map_opts map_opts

View File

@@ -1,9 +1,8 @@
use crate::{ use crate::{libsh::error::ShResult, state};
libsh::error::ShResult,
state,
};
pub mod alias; pub mod alias;
pub mod arrops;
pub mod autocmd;
pub mod cd; pub mod cd;
pub mod complete; pub mod complete;
pub mod dirstack; pub mod dirstack;
@@ -11,7 +10,11 @@ pub mod echo;
pub mod eval; pub mod eval;
pub mod exec; pub mod exec;
pub mod flowctl; pub mod flowctl;
pub mod getopts;
pub mod intro;
pub mod jobctl; pub mod jobctl;
pub mod keymap;
pub mod map;
pub mod pwd; pub mod pwd;
pub mod read; pub mod read;
pub mod shift; pub mod shift;
@@ -21,19 +24,13 @@ pub mod test; // [[ ]] thing
pub mod trap; pub mod trap;
pub mod varcmds; pub mod varcmds;
pub mod zoltraak; pub mod zoltraak;
pub mod map;
pub mod arrops;
pub mod intro;
pub mod getopts;
pub mod keymap;
pub mod autocmd;
pub const BUILTINS: [&str; 47] = [ pub const BUILTINS: [&str; 47] = [
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown", "echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly", "command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type", "unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type",
"getopts", "keymap", "read_key", "autocmd" "getopts", "keymap", "read_key", "autocmd",
]; ];
pub fn true_builtin() -> ShResult<()> { pub fn true_builtin() -> ShResult<()> {

View File

@@ -6,7 +6,16 @@ use nix::{
}; };
use crate::{ use crate::{
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, sys::TTY_FILENO}, parse::{NdRule, Node, execute::prepare_argv}, procio::borrow_fd, readline::term::{KeyReader, PollReader, RawModeGuard}, state::{self, VarFlags, VarKind, read_vars, write_vars} expand::expand_keymap,
getopt::{Opt, OptSpec, get_opts_from_tokens},
libsh::{
error::{ShErr, ShErrKind, ShResult, ShResultExt},
sys::TTY_FILENO,
},
parse::{NdRule, Node, execute::prepare_argv},
procio::borrow_fd,
readline::term::{KeyReader, PollReader, RawModeGuard},
state::{self, VarFlags, VarKind, read_vars, write_vars},
}; };
pub const READ_OPTS: [OptSpec; 7] = [ pub const READ_OPTS: [OptSpec; 7] = [
@@ -40,19 +49,19 @@ pub const READ_OPTS: [OptSpec; 7] = [
}, // read until delimiter }, // read until delimiter
]; ];
pub const READ_KEY_OPTS: [OptSpec;3] = [ pub const READ_KEY_OPTS: [OptSpec; 3] = [
OptSpec { OptSpec {
opt: Opt::Short('v'), // var name opt: Opt::Short('v'), // var name
takes_arg: true takes_arg: true,
}, },
OptSpec { OptSpec {
opt: Opt::Short('w'), // char whitelist opt: Opt::Short('w'), // char whitelist
takes_arg: true takes_arg: true,
}, },
OptSpec { OptSpec {
opt: Opt::Short('b'), // char blacklist opt: Opt::Short('b'), // char blacklist
takes_arg: true takes_arg: true,
} },
]; ];
bitflags! { bitflags! {
@@ -84,7 +93,9 @@ pub fn read_builtin(node: Node) -> ShResult<()> {
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?; let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?;
let read_opts = get_read_flags(opts).blame(blame.clone())?; let read_opts = get_read_flags(opts).blame(blame.clone())?;
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if let Some(prompt) = read_opts.prompt { if let Some(prompt) = read_opts.prompt {
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?; write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
@@ -259,12 +270,14 @@ pub fn get_read_flags(opts: Vec<Opt>) -> ShResult<ReadOpts> {
pub struct ReadKeyOpts { pub struct ReadKeyOpts {
var_name: Option<String>, var_name: Option<String>,
char_whitelist: Option<String>, char_whitelist: Option<String>,
char_blacklist: Option<String> char_blacklist: Option<String>,
} }
pub fn read_key(node: Node) -> ShResult<()> { pub fn read_key(node: Node) -> ShResult<()> {
let blame = node.get_span().clone(); let blame = node.get_span().clone();
let NdRule::Command { argv, .. } = node.class else { unreachable!() }; let NdRule::Command { argv, .. } = node.class else {
unreachable!()
};
if !isatty(*TTY_FILENO)? { if !isatty(*TTY_FILENO)? {
state::set_status(1); state::set_status(1);
@@ -290,7 +303,7 @@ pub fn read_key(node: Node) -> ShResult<()> {
return Ok(()); return Ok(());
}; };
key key
}, }
Err(Errno::EINTR) => { Err(Errno::EINTR) => {
state::set_status(130); state::set_status(130);
return Ok(()); return Ok(());
@@ -331,7 +344,7 @@ pub fn get_read_key_opts(opts: Vec<Opt>) -> ShResult<ReadKeyOpts> {
let mut read_key_opts = ReadKeyOpts { let mut read_key_opts = ReadKeyOpts {
var_name: None, var_name: None,
char_whitelist: None, char_whitelist: None,
char_blacklist: None char_blacklist: None,
}; };
for opt in opts { for opt in opts {
@@ -342,7 +355,7 @@ pub fn get_read_key_opts(opts: Vec<Opt>) -> ShResult<ReadKeyOpts> {
_ => { _ => {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::ExecFail, ShErrKind::ExecFail,
format!("read_key: Unexpected flag '{opt}'") format!("read_key: Unexpected flag '{opt}'"),
)); ));
} }
} }

View File

@@ -14,12 +14,18 @@ pub fn shift(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
let mut argv = argv.into_iter(); let mut argv = argv.into_iter();
if let Some((arg, span)) = argv.next() { if let Some((arg, span)) = argv.next() {
let Ok(count) = arg.parse::<usize>() else { let Ok(count) = arg.parse::<usize>() else {
return Err(ShErr::at(ShErrKind::ExecFail, span, "Expected a number in shift args")); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
"Expected a number in shift args",
));
}; };
for _ in 0..count { for _ in 0..count {
write_vars(|v| v.cur_scope_mut().fpop_arg()); write_vars(|v| v.cur_scope_mut().fpop_arg());

View File

@@ -16,7 +16,9 @@ pub fn shopt(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if argv.is_empty() { if argv.is_empty() {
let mut output = write_shopts(|s| s.display_opts())?; let mut output = write_shopts(|s| s.display_opts())?;

View File

@@ -15,15 +15,25 @@ pub fn source(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
for (arg, span) in argv { for (arg, span) in argv {
let path = PathBuf::from(arg); let path = PathBuf::from(arg);
if !path.exists() { if !path.exists() {
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: File '{}' not found", path.display()))); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
format!("source: File '{}' not found", path.display()),
));
} }
if !path.is_file() { if !path.is_file() {
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: Given path '{}' is not a file", path.display()))); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
format!("source: Given path '{}' is not a file", path.display()),
));
} }
source_file(path)?; source_file(path)?;
} }

View File

@@ -61,10 +61,7 @@ impl FromStr for UnaryOp {
"-t" => Ok(Self::Terminal), "-t" => Ok(Self::Terminal),
"-n" => Ok(Self::NonNull), "-n" => Ok(Self::NonNull),
"-z" => Ok(Self::Null), "-z" => Ok(Self::Null),
_ => Err(ShErr::simple( _ => Err(ShErr::simple(ShErrKind::SyntaxErr, "Invalid test operator")),
ShErrKind::SyntaxErr,
"Invalid test operator",
)),
} }
} }
} }
@@ -97,10 +94,7 @@ impl FromStr for TestOp {
"-ge" => Ok(Self::IntGe), "-ge" => Ok(Self::IntGe),
"-le" => Ok(Self::IntLe), "-le" => Ok(Self::IntLe),
_ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)), _ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)),
_ => Err(ShErr::simple( _ => Err(ShErr::simple(ShErrKind::SyntaxErr, "Invalid test operator")),
ShErrKind::SyntaxErr,
"Invalid test operator",
)),
} }
} }
} }
@@ -138,7 +132,11 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
let operand = operand.expand()?.get_words().join(" "); let operand = operand.expand()?.get_words().join(" ");
conjunct_op = conjunct; conjunct_op = conjunct;
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else { let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Invalid unary operator")); return Err(ShErr::at(
ShErrKind::SyntaxErr,
err_span,
"Invalid unary operator",
));
}; };
match op { match op {
UnaryOp::Exists => { UnaryOp::Exists => {
@@ -241,7 +239,11 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
let test_op = operator.as_str().parse::<TestOp>()?; let test_op = operator.as_str().parse::<TestOp>()?;
match test_op { match test_op {
TestOp::Unary(_) => { TestOp::Unary(_) => {
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Expected a binary operator in this test call; found a unary operator")); return Err(ShErr::at(
ShErrKind::SyntaxErr,
err_span,
"Expected a binary operator in this test call; found a unary operator",
));
} }
TestOp::StringEq => { TestOp::StringEq => {
let pattern = crate::expand::glob_to_regex(rhs.trim(), true); let pattern = crate::expand::glob_to_regex(rhs.trim(), true);
@@ -257,7 +259,11 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
| TestOp::IntGe | TestOp::IntGe
| TestOp::IntLe | TestOp::IntLe
| TestOp::IntEq => { | TestOp::IntEq => {
let err = ShErr::at(ShErrKind::SyntaxErr, err_span.clone(), format!("Expected an integer with '{}' operator", operator)); let err = ShErr::at(
ShErrKind::SyntaxErr,
err_span.clone(),
format!("Expected an integer with '{}' operator", operator),
);
let Ok(lhs) = lhs.trim().parse::<i32>() else { let Ok(lhs) = lhs.trim().parse::<i32>() else {
return Err(err); return Err(err);
}; };

View File

@@ -122,7 +122,9 @@ pub fn trap(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if argv.is_empty() { if argv.is_empty() {
let stdout = borrow_fd(STDOUT_FILENO); let stdout = borrow_fd(STDOUT_FILENO);

View File

@@ -16,7 +16,11 @@ pub fn readonly(node: Node) -> ShResult<()> {
}; };
// Remove "readonly" from argv // Remove "readonly" from argv
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] }; let argv = if !argv.is_empty() {
&argv[1..]
} else {
&argv[..]
};
if argv.is_empty() { if argv.is_empty() {
// Display the local variables // Display the local variables
@@ -67,15 +71,25 @@ pub fn unset(node: Node) -> ShResult<()> {
}; };
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
if argv.is_empty() { if argv.is_empty() {
return Err(ShErr::at(ShErrKind::SyntaxErr, blame, "unset: Expected at least one argument")); return Err(ShErr::at(
ShErrKind::SyntaxErr,
blame,
"unset: Expected at least one argument",
));
} }
for (arg, span) in argv { for (arg, span) in argv {
if !read_vars(|v| v.var_exists(&arg)) { if !read_vars(|v| v.var_exists(&arg)) {
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("unset: No such variable '{arg}'"))); return Err(ShErr::at(
ShErrKind::ExecFail,
span,
format!("unset: No such variable '{arg}'"),
));
} }
write_vars(|v| v.unset_var(&arg))?; write_vars(|v| v.unset_var(&arg))?;
} }
@@ -94,7 +108,11 @@ pub fn export(node: Node) -> ShResult<()> {
}; };
// Remove "export" from argv // Remove "export" from argv
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] }; let argv = if !argv.is_empty() {
&argv[1..]
} else {
&argv[..]
};
if argv.is_empty() { if argv.is_empty() {
// Display the environment variables // Display the environment variables
@@ -137,7 +155,11 @@ pub fn local(node: Node) -> ShResult<()> {
}; };
// Remove "local" from argv // Remove "local" from argv
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] }; let argv = if !argv.is_empty() {
&argv[1..]
} else {
&argv[..]
};
if argv.is_empty() { if argv.is_empty() {
// Display the local variables // Display the local variables

View File

@@ -104,7 +104,9 @@ pub fn zoltraak(node: Node) -> ShResult<()> {
} }
let mut argv = prepare_argv(argv)?; let mut argv = prepare_argv(argv)?;
if !argv.is_empty() { argv.remove(0); } if !argv.is_empty() {
argv.remove(0);
}
for (arg, span) in argv { for (arg, span) in argv {
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) { if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
@@ -113,9 +115,7 @@ pub fn zoltraak(node: Node) -> ShResult<()> {
ShErrKind::ExecFail, ShErrKind::ExecFail,
"zoltraak: Attempted to destroy root directory '/'", "zoltraak: Attempted to destroy root directory '/'",
) )
.with_note( .with_note("If you really want to do this, you can use the --no-preserve-root flag"),
"If you really want to do this, you can use the --no-preserve-root flag"
),
); );
} }
annihilate(&arg, flags).blame(span)? annihilate(&arg, flags).blame(span)?
@@ -176,9 +176,7 @@ fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
ShErrKind::ExecFail, ShErrKind::ExecFail,
format!("zoltraak: '{path}' is a directory"), format!("zoltraak: '{path}' is a directory"),
) )
.with_note( .with_note("Use the '-r' flag to recursively shred directories"),
"Use the '-r' flag to recursively shred directories"
),
); );
} }
} }

View File

@@ -10,13 +10,14 @@ use crate::libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color};
use crate::parse::execute::exec_input; use crate::parse::execute::exec_input;
use crate::parse::lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule, is_hard_sep}; use crate::parse::lex::{LexFlags, LexStream, QuoteState, Tk, TkFlags, TkRule, is_hard_sep};
use crate::parse::{Redir, RedirType}; use crate::parse::{Redir, RedirType};
use crate::prelude::*;
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack}; use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
use crate::readline::keys::{KeyCode, KeyEvent, ModKeys}; use crate::readline::keys::{KeyCode, KeyEvent, ModKeys};
use crate::readline::markers; use crate::readline::markers;
use crate::state::{ use crate::state::{
self, ArrIndex, LogTab, VarFlags, VarKind, read_jobs, read_logic, read_shopts, read_vars, write_jobs, write_meta, write_vars self, ArrIndex, LogTab, VarFlags, VarKind, read_jobs, read_logic, read_shopts, read_vars,
write_jobs, write_meta, write_vars,
}; };
use crate::prelude::*;
const PARAMETERS: [char; 7] = ['@', '*', '#', '$', '?', '!', '0']; const PARAMETERS: [char; 7] = ['@', '*', '#', '$', '?', '!', '0'];
@@ -25,9 +26,7 @@ impl Tk {
pub fn expand(self) -> ShResult<Self> { pub fn expand(self) -> ShResult<Self> {
let flags = self.flags; let flags = self.flags;
let span = self.span.clone(); let span = self.span.clone();
let exp = Expander::new(self)? let exp = Expander::new(self)?.expand().promote_err(span.clone())?;
.expand()
.promote_err(span.clone())?;
let class = TkRule::Expanded { exp }; let class = TkRule::Expanded { exp };
Ok(Self { class, span, flags }) Ok(Self { class, span, flags })
} }
@@ -534,11 +533,9 @@ pub fn expand_var(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
let arg_sep = markers::ARG_SEP.to_string(); let arg_sep = markers::ARG_SEP.to_string();
read_vars(|v| v.get_arr_elems(&var_name))?.join(&arg_sep) read_vars(|v| v.get_arr_elems(&var_name))?.join(&arg_sep)
} }
ArrIndex::ArgCount => { ArrIndex::ArgCount => read_vars(|v| v.get_arr_elems(&var_name))
read_vars(|v| v.get_arr_elems(&var_name))
.map(|elems| elems.len().to_string()) .map(|elems| elems.len().to_string())
.unwrap_or_else(|_| "0".to_string()) .unwrap_or_else(|_| "0".to_string()),
}
ArrIndex::AllJoined => { ArrIndex::AllJoined => {
let ifs = read_vars(|v| v.try_get_var("IFS")) let ifs = read_vars(|v| v.try_get_var("IFS"))
.unwrap_or_else(|| " \t\n".to_string()) .unwrap_or_else(|| " \t\n".to_string())
@@ -653,7 +650,7 @@ enum ArithTk {
Op(ArithOp), Op(ArithOp),
LParen, LParen,
RParen, RParen,
Var(String) Var(String),
} }
impl ArithTk { impl ArithTk {
@@ -704,7 +701,7 @@ impl ArithTk {
var_name.push(*ch); var_name.push(*ch);
chars.next(); chars.next();
} }
_ => break _ => break,
} }
} }
@@ -757,13 +754,19 @@ impl ArithTk {
let Some(val) = read_vars(|v| v.try_get_var(&var)) else { let Some(val) = read_vars(|v| v.try_get_var(&var)) else {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::NotFound, ShErrKind::NotFound,
format!("Undefined variable in arithmetic expression: '{}'",var.fg(next_color())), format!(
"Undefined variable in arithmetic expression: '{}'",
var.fg(next_color())
),
)); ));
}; };
let Ok(num) = val.parse::<f64>() else { let Ok(num) = val.parse::<f64>() else {
return Err(ShErr::simple( return Err(ShErr::simple(
ShErrKind::ParseErr, ShErrKind::ParseErr,
format!("Variable '{}' does not contain a number", var.fg(next_color())), format!(
"Variable '{}' does not contain a number",
var.fg(next_color())
),
)); ));
}; };
@@ -894,7 +897,12 @@ pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
let mut io_stack = IoStack::new(); let mut io_stack = IoStack::new();
io_stack.push_frame(io_frame); io_stack.push_frame(io_frame);
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false, Some("process_sub".into())) { if let Err(e) = exec_input(
raw.to_string(),
Some(io_stack),
false,
Some("process_sub".into()),
) {
e.print_error(); e.print_error();
exit(1); exit(1);
} }
@@ -925,7 +933,12 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
match unsafe { fork()? } { match unsafe { fork()? } {
ForkResult::Child => { ForkResult::Child => {
io_stack.push_frame(cmd_sub_io_frame); io_stack.push_frame(cmd_sub_io_frame);
if let Err(e) = exec_input(raw.to_string(), Some(io_stack), false, Some("command_sub".into())) { if let Err(e) = exec_input(
raw.to_string(),
Some(io_stack),
false,
Some("command_sub".into()),
) {
e.print_error(); e.print_error();
unsafe { libc::_exit(1) }; unsafe { libc::_exit(1) };
} }
@@ -958,7 +971,7 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
WtStat::Exited(_, code) => { WtStat::Exited(_, code) => {
state::set_status(code); state::set_status(code);
Ok(io_buf.as_str()?.trim_end().to_string()) Ok(io_buf.as_str()?.trim_end().to_string())
}, }
_ => Err(ShErr::simple(ShErrKind::InternalErr, "Command sub failed")), _ => Err(ShErr::simple(ShErrKind::InternalErr, "Command sub failed")),
} }
} }
@@ -1390,7 +1403,7 @@ impl FromStr for ParamExp {
Err(ShErr::simple( Err(ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
"Invalid parameter expansion", "Invalid parameter expansion",
) ) ))
}; };
log::debug!("Parsing parameter expansion: '{:?}'", s); log::debug!("Parsing parameter expansion: '{:?}'", s);
@@ -1554,10 +1567,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
Some(val) => Ok(val), Some(val) => Ok(val),
None => { None => {
let expanded = expand_raw(&mut err.chars().peekable())?; let expanded = expand_raw(&mut err.chars().peekable())?;
Err(ShErr::simple( Err(ShErr::simple(ShErrKind::ExecFail, expanded))
ShErrKind::ExecFail,
expanded,
))
} }
} }
} }
@@ -1565,10 +1575,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
Some(val) => Ok(val), Some(val) => Ok(val),
None => { None => {
let expanded = expand_raw(&mut err.chars().peekable())?; let expanded = expand_raw(&mut err.chars().peekable())?;
Err(ShErr::simple( Err(ShErr::simple(ShErrKind::ExecFail, expanded))
ShErrKind::ExecFail,
expanded,
))
} }
}, },
ParamExp::Substr(pos) => { ParamExp::Substr(pos) => {
@@ -1630,7 +1637,8 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
ParamExp::RemLongestSuffix(suffix) => { ParamExp::RemLongestSuffix(suffix) => {
let value = vars.get_var(&var_name); let value = vars.get_var(&var_name);
let unescaped = unescape_str(&suffix); let unescaped = unescape_str(&suffix);
let expanded_suffix = expand_raw(&mut unescaped.chars().peekable()).unwrap_or(suffix.clone()); let expanded_suffix =
expand_raw(&mut unescaped.chars().peekable()).unwrap_or(suffix.clone());
let pattern = Pattern::new(&expanded_suffix).unwrap(); let pattern = Pattern::new(&expanded_suffix).unwrap();
for i in 0..=value.len() { for i in 0..=value.len() {
let sliced = &value[i..]; let sliced = &value[i..];
@@ -2373,7 +2381,7 @@ pub fn parse_key_alias(alias: &str) -> Option<KeyEvent> {
"PGUP" | "PAGEUP" => KeyCode::PageUp, "PGUP" | "PAGEUP" => KeyCode::PageUp,
"PGDN" | "PAGEDOWN" => KeyCode::PageDown, "PGDN" | "PAGEDOWN" => KeyCode::PageDown,
k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()), k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()),
_ => return None _ => return None,
}; };
Some(KeyEvent(key, mods)) Some(KeyEvent(key, mods))

View File

@@ -60,16 +60,10 @@ impl fmt::Display for DisplayWaitStatus {
pub fn code_from_status(stat: &WtStat) -> Option<i32> { pub fn code_from_status(stat: &WtStat) -> Option<i32> {
match stat { match stat {
WtStat::Exited(_, exit_code) => { WtStat::Exited(_, exit_code) => Some(*exit_code),
Some(*exit_code) WtStat::Stopped(_, sig) => Some(SIG_EXIT_OFFSET + *sig as i32),
} WtStat::Signaled(_, sig, _) => Some(SIG_EXIT_OFFSET + *sig as i32),
WtStat::Stopped(_, sig) => { _ => None,
Some(SIG_EXIT_OFFSET + *sig as i32)
}
WtStat::Signaled(_, sig, _) => {
Some(SIG_EXIT_OFFSET + *sig as i32)
}
_ => { None }
} }
} }
@@ -186,7 +180,12 @@ impl JobTab {
} }
pub fn curr_job(&self) -> Option<usize> { pub fn curr_job(&self) -> Option<usize> {
// Find the most recent valid job (order can have stale entries) // Find the most recent valid job (order can have stale entries)
self.order.iter().rev().find(|&&id| self.jobs.get(id).is_some_and(|slot| slot.is_some())).copied() self
.order
.iter()
.rev()
.find(|&&id| self.jobs.get(id).is_some_and(|slot| slot.is_some()))
.copied()
} }
pub fn prev_job(&self) -> Option<usize> { pub fn prev_job(&self) -> Option<usize> {
// Find the second most recent valid job // Find the second most recent valid job
@@ -283,18 +282,16 @@ impl JobTab {
} }
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> { pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
let Some(job) = self.query_mut(id.clone()) else { let Some(job) = self.query_mut(id.clone()) else {
return Ok(()) return Ok(());
}; };
match id { match id {
JobID::Pid(pid) => { JobID::Pid(pid) => {
let Some(child) = job.children_mut().iter_mut().find(|c| c.pid() == pid) else { let Some(child) = job.children_mut().iter_mut().find(|c| c.pid() == pid) else {
return Ok(()) return Ok(());
}; };
child.set_stat(stat); child.set_stat(stat);
} }
JobID::Pgid(_) | JobID::Pgid(_) | JobID::TableID(_) | JobID::Command(_) => {
JobID::TableID(_) |
JobID::Command(_) => {
job.set_stats(stat); job.set_stats(stat);
} }
} }
@@ -612,10 +609,9 @@ impl Job {
&mut self.children &mut self.children
} }
pub fn is_done(&self) -> bool { pub fn is_done(&self) -> bool {
self self.children.iter().all(|chld| {
.children chld.exited() || chld.stat() == WtStat::Signaled(chld.pid(), Signal::SIGHUP, true)
.iter() })
.all(|chld| chld.exited() || chld.stat() == WtStat::Signaled(chld.pid(), Signal::SIGHUP, true))
} }
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> { pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
let stat = match sig { let stat = match sig {
@@ -795,7 +791,10 @@ pub fn wait_bg(id: JobID) -> ShResult<()> {
} }
_ => { _ => {
let Some(mut job) = write_jobs(|j| j.remove_job(id.clone())) else { let Some(mut job) = write_jobs(|j| j.remove_job(id.clone())) else {
return Err(ShErr::simple(ShErrKind::ExecFail, format!("wait: No such job with id {:?}", id))); return Err(ShErr::simple(
ShErrKind::ExecFail,
format!("wait: No such job with id {:?}", id),
));
}; };
let statuses = job.wait_pgrp()?; let statuses = job.wait_pgrp()?;
let mut was_stopped = false; let mut was_stopped = false;

View File

@@ -1,9 +1,9 @@
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::fmt::Display;
use ariadne::Color; use ariadne::Color;
use ariadne::{Report, ReportKind}; use ariadne::{Report, ReportKind};
use rand::TryRng; use rand::TryRng;
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::fmt::Display;
use crate::procio::RedirGuard; use crate::procio::RedirGuard;
use crate::{ use crate::{
@@ -173,22 +173,36 @@ pub struct ShErr {
/// the RedirGuard(s) so that redirections stay alive until the error /// the RedirGuard(s) so that redirections stay alive until the error
/// is printed. Multiple guards can accumulate as the error bubbles /// is printed. Multiple guards can accumulate as the error bubbles
/// through nested redirect scopes. /// through nested redirect scopes.
io_guards: Vec<RedirGuard> io_guards: Vec<RedirGuard>,
} }
impl ShErr { impl ShErr {
pub fn new(kind: ShErrKind, span: Span) -> Self { pub fn new(kind: ShErrKind, span: Span) -> Self {
Self { kind, src_span: Some(span), labels: vec![], sources: vec![], notes: vec![], io_guards: vec![] } Self {
kind,
src_span: Some(span),
labels: vec![],
sources: vec![],
notes: vec![],
io_guards: vec![],
}
} }
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self { pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()], io_guards: vec![] } Self {
kind,
src_span: None,
labels: vec![],
sources: vec![],
notes: vec![msg.into()],
io_guards: vec![],
}
} }
pub fn is_flow_control(&self) -> bool { pub fn is_flow_control(&self) -> bool {
self.kind.is_flow_control() self.kind.is_flow_control()
} }
pub fn promote(mut self, span: Span) -> Self { pub fn promote(mut self, span: Span) -> Self {
if self.notes.is_empty() { if self.notes.is_empty() {
return self return self;
} }
let first = self.notes[0].clone(); let first = self.notes[0].clone();
if self.notes.len() > 1 { if self.notes.len() > 1 {
@@ -205,23 +219,60 @@ impl ShErr {
let color = last_color(); // use last_color to ensure the same color is used for the label and the message given let color = last_color(); // use last_color to ensure the same color is used for the label and the message given
let src = span.span_source().clone(); let src = span.span_source().clone();
let msg: String = msg.into(); let msg: String = msg.into();
Self::new(kind, span.clone()) Self::new(kind, span.clone()).with_label(
.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg)) src,
ariadne::Label::new(span)
.with_color(color)
.with_message(msg),
)
} }
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self { pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
let color = last_color(); let color = last_color();
let src = span.span_source().clone(); let src = span.span_source().clone();
let msg: String = msg.into(); let msg: String = msg.into();
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg)) self.with_label(
src,
ariadne::Label::new(span)
.with_color(color)
.with_message(msg),
)
} }
pub fn blame(self, span: Span) -> Self { pub fn blame(self, span: Span) -> Self {
let ShErr { kind, src_span: _, labels, sources, notes, io_guards } = self; let ShErr {
Self { kind, src_span: Some(span), labels, sources, notes, io_guards } kind,
src_span: _,
labels,
sources,
notes,
io_guards,
} = self;
Self {
kind,
src_span: Some(span),
labels,
sources,
notes,
io_guards,
}
} }
pub fn try_blame(self, span: Span) -> Self { pub fn try_blame(self, span: Span) -> Self {
match self { match self {
ShErr { kind, src_span: None, labels, sources, notes, io_guards } => Self { kind, src_span: Some(span), labels, sources, notes, io_guards }, ShErr {
_ => self kind,
src_span: None,
labels,
sources,
notes,
io_guards,
} => Self {
kind,
src_span: Some(span),
labels,
sources,
notes,
io_guards,
},
_ => self,
} }
} }
pub fn kind(&self) -> &ShErrKind { pub fn kind(&self) -> &ShErrKind {
@@ -234,23 +285,65 @@ impl ShErr {
self self
} }
pub fn with_label(self, source: SpanSource, label: ariadne::Label<Span>) -> Self { pub fn with_label(self, source: SpanSource, label: ariadne::Label<Span>) -> Self {
let ShErr { kind, src_span, mut labels, mut sources, notes, io_guards } = self; let ShErr {
kind,
src_span,
mut labels,
mut sources,
notes,
io_guards,
} = self;
sources.push(source); sources.push(source);
labels.push(label); labels.push(label);
Self { kind, src_span, labels, sources, notes, io_guards } Self {
kind,
src_span,
labels,
sources,
notes,
io_guards,
}
} }
pub fn with_context(self, ctx: VecDeque<(SpanSource, ariadne::Label<Span>)>) -> Self { pub fn with_context(self, ctx: VecDeque<(SpanSource, ariadne::Label<Span>)>) -> Self {
let ShErr { kind, src_span, mut labels, mut sources, notes, io_guards } = self; let ShErr {
kind,
src_span,
mut labels,
mut sources,
notes,
io_guards,
} = self;
for (src, label) in ctx { for (src, label) in ctx {
sources.push(src); sources.push(src);
labels.push(label); labels.push(label);
} }
Self { kind, src_span, labels, sources, notes, io_guards } Self {
kind,
src_span,
labels,
sources,
notes,
io_guards,
}
} }
pub fn with_note(self, note: impl Into<String>) -> Self { pub fn with_note(self, note: impl Into<String>) -> Self {
let ShErr { kind, src_span, labels, sources, mut notes, io_guards } = self; let ShErr {
kind,
src_span,
labels,
sources,
mut notes,
io_guards,
} = self;
notes.push(note.into()); notes.push(note.into());
Self { kind, src_span, labels, sources, notes, io_guards } Self {
kind,
src_span,
labels,
sources,
notes,
io_guards,
}
} }
pub fn build_report(&self) -> Option<Report<'_, Span>> { pub fn build_report(&self) -> Option<Report<'_, Span>> {
let span = self.src_span.as_ref()?; let span = self.src_span.as_ref()?;
@@ -276,11 +369,13 @@ impl ShErr {
let mut source_map = HashMap::new(); let mut source_map = HashMap::new();
if let Some(span) = &self.src_span { if let Some(span) = &self.src_span {
let src = span.span_source().clone(); let src = span.span_source().clone();
source_map.entry(src.clone()) source_map
.entry(src.clone())
.or_insert_with(|| src.content().to_string()); .or_insert_with(|| src.content().to_string());
} }
for src in &self.sources { for src in &self.sources {
source_map.entry(src.clone()) source_map
.entry(src.clone())
.or_insert_with(|| src.content().to_string()); .or_insert_with(|| src.content().to_string());
} }
source_map source_map
@@ -298,7 +393,8 @@ impl ShErr {
let sources = self.collect_sources(); let sources = self.collect_sources();
let cache = ariadne::FnCache::new(move |src: &SpanSource| { let cache = ariadne::FnCache::new(move |src: &SpanSource| {
sources.get(src) sources
.get(src)
.cloned() .cloned()
.ok_or_else(|| format!("Failed to fetch source '{}'", src.name())) .ok_or_else(|| format!("Failed to fetch source '{}'", src.name()))
}); });
@@ -365,12 +461,13 @@ pub enum ShErrKind {
impl ShErrKind { impl ShErrKind {
pub fn is_flow_control(&self) -> bool { pub fn is_flow_control(&self) -> bool {
matches!(self, matches!(
Self::CleanExit(_) | self,
Self::FuncReturn(_) | Self::CleanExit(_)
Self::LoopContinue(_) | | Self::FuncReturn(_)
Self::LoopBreak(_) | | Self::LoopContinue(_)
Self::ClearReadline | Self::LoopBreak(_)
| Self::ClearReadline
) )
} }
} }

View File

@@ -144,7 +144,8 @@ impl RawModeGuard {
F: FnOnce() -> R, F: FnOnce() -> R,
{ {
let current = tcgetattr(borrow_fd(*TTY_FILENO)).expect("Failed to get terminal attributes"); let current = tcgetattr(borrow_fd(*TTY_FILENO)).expect("Failed to get terminal attributes");
let orig = ORIG_TERMIOS.with(|cell| cell.borrow().clone()) let orig = ORIG_TERMIOS
.with(|cell| cell.borrow().clone())
.expect("with_cooked_mode called before raw_mode()"); .expect("with_cooked_mode called before raw_mode()");
tcsetattr(borrow_fd(*TTY_FILENO), termios::SetArg::TCSANOW, &orig) tcsetattr(borrow_fd(*TTY_FILENO), termios::SetArg::TCSANOW, &orig)
.expect("Failed to restore cooked mode"); .expect("Failed to restore cooked mode");

View File

@@ -44,7 +44,10 @@ impl AutoCmdVecUtils for Vec<AutoCmd> {
fn exec(&self) { fn exec(&self) {
let saved_status = crate::state::get_status(); let saved_status = crate::state::get_status();
for cmd in self { for cmd in self {
let AutoCmd { pattern: _, command } = cmd; let AutoCmd {
pattern: _,
command,
} = cmd;
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) { if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) {
e.print_error(); e.print_error();
} }
@@ -56,8 +59,13 @@ impl AutoCmdVecUtils for Vec<AutoCmd> {
for cmd in self { for cmd in self {
let AutoCmd { pattern, command } = cmd; let AutoCmd { pattern, command } = cmd;
if let Some(pat) = pattern if let Some(pat) = pattern
&& !pat.is_match(other_pattern) { && !pat.is_match(other_pattern)
log::trace!("autocmd pattern '{}' did not match '{}', skipping", pat, other_pattern); {
log::trace!(
"autocmd pattern '{}' did not match '{}', skipping",
pat,
other_pattern
);
continue; continue;
} }
@@ -118,9 +126,12 @@ impl CharDequeUtils for VecDeque<char> {
impl TkVecUtils<Tk> for Vec<Tk> { impl TkVecUtils<Tk> for Vec<Tk> {
fn get_span(&self) -> Option<Span> { fn get_span(&self) -> Option<Span> {
if let Some(first_tk) = self.first() { if let Some(first_tk) = self.first() {
self self.last().map(|last_tk| {
.last() Span::new(
.map(|last_tk| Span::new(first_tk.span.range().start..last_tk.span.range().end, first_tk.source())) first_tk.span.range().start..last_tk.span.range().end,
first_tk.source(),
)
})
} else { } else {
None None
} }
@@ -170,11 +181,15 @@ impl RedirVecUtils<Redir> for Vec<Redir> {
impl NodeVecUtils<Node> for Vec<Node> { impl NodeVecUtils<Node> for Vec<Node> {
fn get_span(&self) -> Option<Span> { fn get_span(&self) -> Option<Span> {
if let Some(first_nd) = self.first() if let Some(first_nd) = self.first()
&& let Some(last_nd) = self.last() { && let Some(last_nd) = self.last()
{
let first_start = first_nd.get_span().range().start; let first_start = first_nd.get_span().range().start;
let last_end = last_nd.get_span().range().end; let last_end = last_nd.get_span().range().end;
if first_start <= last_end { if first_start <= last_end {
return Some(Span::new(first_start..last_end, first_nd.get_span().source().content())); return Some(Span::new(
first_start..last_end,
first_nd.get_span().source().content(),
));
} }
} }
None None

View File

@@ -34,7 +34,6 @@ use crate::libsh::sys::TTY_FILENO;
use crate::libsh::utils::AutoCmdVecUtils; use crate::libsh::utils::AutoCmdVecUtils;
use crate::parse::execute::exec_input; use crate::parse::execute::exec_input;
use crate::prelude::*; use crate::prelude::*;
use crate::procio::IoMode;
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode}; use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
use crate::readline::{Prompt, ReadlineEvent, ShedVi}; use crate::readline::{Prompt, ReadlineEvent, ShedVi};
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending}; use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
@@ -99,7 +98,9 @@ fn setup_panic_handler() {
log_file.write_all(panic_info_raw.as_bytes()).unwrap(); log_file.write_all(panic_info_raw.as_bytes()).unwrap();
let backtrace = std::backtrace::Backtrace::force_capture(); let backtrace = std::backtrace::Backtrace::force_capture();
log_file.write_all(format!("\nBacktrace:\n{:?}", backtrace).as_bytes()).unwrap(); log_file
.write_all(format!("\nBacktrace:\n{:?}", backtrace).as_bytes())
.unwrap();
default_panic_hook(info); default_panic_hook(info);
})); }));
@@ -138,7 +139,8 @@ fn main() -> ExitCode {
}; };
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, Some("trap".into())) { && let Err(e) = exec_input(trap, None, false, Some("trap".into()))
{
e.print_error(); e.print_error();
} }
@@ -267,15 +269,26 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
// Timeout — resolve pending keymap ambiguity // Timeout — resolve pending keymap ambiguity
if !readline.pending_keymap.is_empty() if !readline.pending_keymap.is_empty()
&& fds[0].revents().is_none_or(|r| !r.contains(PollFlags::POLLIN)) && fds[0]
.revents()
.is_none_or(|r| !r.contains(PollFlags::POLLIN))
{ {
log::debug!("[keymap timeout] resolving pending={:?}", readline.pending_keymap); log::debug!(
"[keymap timeout] resolving pending={:?}",
readline.pending_keymap
);
let keymap_flags = readline.curr_keymap_flags(); let keymap_flags = readline.curr_keymap_flags();
let matches = read_logic(|l| l.keymaps_filtered(keymap_flags, &readline.pending_keymap)); let matches = read_logic(|l| l.keymaps_filtered(keymap_flags, &readline.pending_keymap));
// If there's an exact match, fire it; otherwise flush as normal keys // If there's an exact match, fire it; otherwise flush as normal keys
let exact = matches.iter().find(|km| km.compare(&readline.pending_keymap) == KeyMapMatch::IsExact); let exact = matches
.iter()
.find(|km| km.compare(&readline.pending_keymap) == KeyMapMatch::IsExact);
if let Some(km) = exact { if let Some(km) = exact {
log::debug!("[keymap timeout] firing exact match: {:?} -> {:?}", km.keys, km.action); log::debug!(
"[keymap timeout] firing exact match: {:?} -> {:?}",
km.keys,
km.action
);
let action = km.action_expanded(); let action = km.action_expanded();
readline.pending_keymap.clear(); readline.pending_keymap.clear();
for key in action { for key in action {
@@ -284,7 +297,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
ReadlineEvent::Line(input) => { 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) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true, Some("<stdin>".into()))) { if let Err(e) = RawModeGuard::with_cooked_mode(|| {
exec_input(input, None, true, Some("<stdin>".into()))
}) {
match e.kind() { match e.kind() {
ShErrKind::CleanExit(code) => { ShErrKind::CleanExit(code) => {
QUIT_CODE.store(*code, Ordering::SeqCst); QUIT_CODE.store(*code, Ordering::SeqCst);
@@ -310,7 +325,10 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
} }
} }
} else { } else {
log::debug!("[keymap timeout] no exact match, flushing {} keys as normal input", readline.pending_keymap.len()); log::debug!(
"[keymap timeout] no exact match, flushing {} keys as normal input",
readline.pending_keymap.len()
);
let buffered = std::mem::take(&mut readline.pending_keymap); let buffered = std::mem::take(&mut readline.pending_keymap);
for key in buffered { for key in buffered {
if let Some(event) = readline.handle_key(key)? { if let Some(event) = readline.handle_key(key)? {
@@ -318,7 +336,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
ReadlineEvent::Line(input) => { 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) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true, Some("<stdin>".into()))) { if let Err(e) = RawModeGuard::with_cooked_mode(|| {
exec_input(input, None, true, Some("<stdin>".into()))
}) {
match e.kind() { match e.kind() {
ShErrKind::CleanExit(code) => { ShErrKind::CleanExit(code) => {
QUIT_CODE.store(*code, Ordering::SeqCst); QUIT_CODE.store(*code, Ordering::SeqCst);
@@ -383,7 +403,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
let start = Instant::now(); let start = Instant::now();
write_meta(|m| m.start_timer()); write_meta(|m| m.start_timer());
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input.clone(), None, true, Some("<stdin>".into()))) { if let Err(e) = RawModeGuard::with_cooked_mode(|| {
exec_input(input.clone(), None, true, Some("<stdin>".into()))
}) {
match e.kind() { match e.kind() {
ShErrKind::CleanExit(code) => { ShErrKind::CleanExit(code) => {
QUIT_CODE.store(*code, Ordering::SeqCst); QUIT_CODE.store(*code, Ordering::SeqCst);

View File

@@ -1,15 +1,37 @@
use std::{ use std::{
cell::Cell, collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt cell::Cell,
collections::{HashSet, VecDeque},
os::unix::fs::PermissionsExt,
}; };
use ariadne::Fmt; use ariadne::Fmt;
use crate::{ use crate::{
builtin::{ builtin::{
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, autocmd::autocmd, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak alias::{alias, unalias},
arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate},
autocmd::autocmd,
cd::cd,
complete::{compgen_builtin, complete_builtin},
dirstack::{dirs, popd, pushd},
echo::echo,
eval, exec,
flowctl::flowctl,
getopts::getopts,
intro,
jobctl::{self, JobBehavior, continue_job, disown, jobs},
keymap, map,
pwd::pwd,
read::{self, read_builtin},
shift::shift,
shopt::shopt,
source::source,
test::double_bracket_test,
trap::{TrapTarget, trap},
varcmds::{export, local, readonly, unset},
zoltraak::zoltraak,
}, },
expand::{Expander, expand_aliases, expand_case_pattern, expand_raw, glob_to_regex}, expand::{expand_aliases, expand_case_pattern, glob_to_regex},
jobs::{ChildProc, JobStack, attach_tty, dispatch_job}, jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
libsh::{ libsh::{
error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color}, error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
@@ -19,7 +41,7 @@ use crate::{
prelude::*, prelude::*,
procio::{IoMode, IoStack}, procio::{IoMode, IoStack},
state::{ state::{
self, ShFunc, VarFlags, VarKind, read_logic, read_shopts, write_jobs, write_logic, write_vars self, ShFunc, VarFlags, VarKind, read_logic, read_shopts, write_jobs, write_logic, write_vars,
}, },
}; };
@@ -110,7 +132,12 @@ impl ExecArgs {
} }
} }
pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, source_name: Option<String>) -> ShResult<()> { pub fn exec_input(
input: String,
io_stack: Option<IoStack>,
interactive: bool,
source_name: Option<String>,
) -> ShResult<()> {
let log_tab = read_logic(|l| l.clone()); let log_tab = read_logic(|l| l.clone());
let input = expand_aliases(input, HashSet::new(), &log_tab); let input = expand_aliases(input, HashSet::new(), &log_tab);
let lex_flags = if interactive { let lex_flags = if interactive {
@@ -119,7 +146,9 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, s
super::lex::LexFlags::empty() super::lex::LexFlags::empty()
}; };
let source_name = source_name.unwrap_or("<stdin>".into()); let source_name = source_name.unwrap_or("<stdin>".into());
let mut parser = ParsedSrc::new(Arc::new(input)).with_lex_flags(lex_flags).with_name(source_name.clone()); let mut parser = ParsedSrc::new(Arc::new(input))
.with_lex_flags(lex_flags)
.with_name(source_name.clone());
if let Err(errors) = parser.parse_src() { if let Err(errors) = parser.parse_src() {
for error in errors { for error in errors {
error.print_error(); error.print_error();
@@ -208,7 +237,12 @@ impl Dispatcher {
let stack = IoStack { let stack = IoStack {
stack: self.io_stack.clone(), stack: self.io_stack.clone(),
}; };
exec_input(format!("cd {dir}"), Some(stack), self.interactive, Some(self.source_name.clone())) exec_input(
format!("cd {dir}"),
Some(stack),
self.interactive,
Some(self.source_name.clone()),
)
} else { } else {
self.exec_cmd(node) self.exec_cmd(node)
} }
@@ -274,7 +308,7 @@ impl Dispatcher {
return Ok(()); return Ok(());
} }
let func = ShFunc::new(func_parser,blame); let func = ShFunc::new(func_parser, blame);
write_logic(|l| l.insert_func(name, func)); // Store the AST write_logic(|l| l.insert_func(name, func)); // Store the AST
Ok(()) Ok(())
} }
@@ -310,7 +344,10 @@ impl Dispatcher {
fn exec_func(&mut self, func: Node) -> ShResult<()> { fn exec_func(&mut self, func: Node) -> ShResult<()> {
let mut blame = func.get_span().clone(); let mut blame = func.get_span().clone();
let func_name = func.get_command().unwrap().to_string(); let func_name = func.get_command().unwrap().to_string();
let func_ctx = func.get_context(format!("in call to function '{}'",func_name.fg(next_color()))); let func_ctx = func.get_context(format!(
"in call to function '{}'",
func_name.fg(next_color())
));
let NdRule::Command { let NdRule::Command {
assignments, assignments,
mut argv, mut argv,
@@ -354,7 +391,7 @@ impl Dispatcher {
state::set_status(*code); state::set_status(*code);
Ok(()) Ok(())
} }
_ => Err(e) _ => Err(e),
} }
} else { } else {
Ok(()) Ok(())
@@ -423,7 +460,12 @@ impl Dispatcher {
'outer: for block in case_blocks { 'outer: for block in case_blocks {
let CaseNode { pattern, body } = block; let CaseNode { pattern, body } = block;
let block_pattern_raw = pattern.span.as_str().strip_suffix(')').unwrap_or(pattern.span.as_str()).trim(); let block_pattern_raw = pattern
.span
.as_str()
.strip_suffix(')')
.unwrap_or(pattern.span.as_str())
.trim();
// Split at '|' to allow for multiple patterns like `foo|bar)` // Split at '|' to allow for multiple patterns like `foo|bar)`
let block_patterns = block_pattern_raw.split('|'); let block_patterns = block_pattern_raw.split('|');
@@ -450,7 +492,9 @@ impl Dispatcher {
} }
}) })
} else { } else {
case_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard)) case_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
} }
} }
fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> { fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
@@ -513,7 +557,9 @@ impl Dispatcher {
} }
}) })
} else { } else {
loop_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard)) loop_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
} }
} }
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> { fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
@@ -591,7 +637,9 @@ impl Dispatcher {
} }
}) })
} else { } else {
for_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard)) for_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
} }
} }
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> { fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
@@ -648,7 +696,9 @@ impl Dispatcher {
} }
}) })
} else { } else {
if_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard)) if_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
} }
} }
fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> { fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
@@ -692,8 +742,11 @@ impl Dispatcher {
// SIGTTOU when they try to modify terminal attributes. // SIGTTOU when they try to modify terminal attributes.
// Only for interactive (top-level) pipelines — command substitution // Only for interactive (top-level) pipelines — command substitution
// and other non-interactive contexts must not steal the terminal. // and other non-interactive contexts must not steal the terminal.
if !tty_attached && !is_bg && self.interactive if !tty_attached
&& let Some(pgid) = self.job_stack.curr_job_mut().unwrap().pgid() { && !is_bg
&& self.interactive
&& let Some(pgid) = self.job_stack.curr_job_mut().unwrap().pgid()
{
attach_tty(pgid).ok(); attach_tty(pgid).ok();
tty_attached = true; tty_attached = true;
} }

View File

@@ -25,12 +25,12 @@ pub const KEYWORDS: [&str; 16] = [
pub const OPENERS: [&str; 6] = ["if", "while", "until", "for", "select", "case"]; pub const OPENERS: [&str; 6] = ["if", "while", "until", "for", "select", "case"];
/// Used to track whether the lexer is currently inside a quote, and if so, which type /// Used to track whether the lexer is currently inside a quote, and if so, which type
#[derive(Default,Debug)] #[derive(Default, Debug)]
pub enum QuoteState { pub enum QuoteState {
#[default] #[default]
Outside, Outside,
Single, Single,
Double Double,
} }
impl QuoteState { impl QuoteState {
@@ -67,7 +67,7 @@ impl QuoteState {
#[derive(Clone, PartialEq, Default, Debug, Eq, Hash)] #[derive(Clone, PartialEq, Default, Debug, Eq, Hash)]
pub struct SpanSource { pub struct SpanSource {
name: String, name: String,
content: Arc<String> content: Arc<String>,
} }
impl SpanSource { impl SpanSource {
@@ -92,13 +92,16 @@ impl Display for SpanSource {
#[derive(Clone, PartialEq, Default, Debug)] #[derive(Clone, PartialEq, Default, Debug)]
pub struct Span { pub struct Span {
range: Range<usize>, range: Range<usize>,
source: SpanSource source: SpanSource,
} }
impl Span { impl Span {
/// New `Span`. Wraps a range and a string slice that it refers to. /// New `Span`. Wraps a range and a string slice that it refers to.
pub fn new(range: Range<usize>, source: Arc<String>) -> Self { pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
let source = SpanSource { name: "<stdin>".into(), content: source }; let source = SpanSource {
name: "<stdin>".into(),
content: source,
};
Span { range, source } Span { range, source }
} }
pub fn from_span_source(range: Range<usize>, source: SpanSource) -> Self { pub fn from_span_source(range: Range<usize>, source: SpanSource) -> Self {
@@ -111,7 +114,7 @@ impl Span {
self.source.name = name; self.source.name = name;
self self
} }
pub fn line_and_col(&self) -> (usize,usize) { pub fn line_and_col(&self) -> (usize, usize) {
let content = self.source.content(); let content = self.source.content();
let source = ariadne::Source::from(content.as_str()); let source = ariadne::Source::from(content.as_str());
let (_, line, col) = source.get_byte_line(self.range.start).unwrap(); let (_, line, col) = source.get_byte_line(self.range.start).unwrap();
@@ -988,13 +991,16 @@ pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
/// Splits a string at the first occurrence of a pattern, but only if the pattern is not escaped by a backslash /// Splits a string at the first occurrence of a pattern, but only if the pattern is not escaped by a backslash
/// and not in quotes. Returns None if the pattern is not found or only found escaped. /// and not in quotes. Returns None if the pattern is not found or only found escaped.
pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String,String)> { pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String, String)> {
let mut chars = slice.char_indices().peekable(); let mut chars = slice.char_indices().peekable();
let mut qt_state = QuoteState::default(); let mut qt_state = QuoteState::default();
while let Some((i, ch)) = chars.next() { while let Some((i, ch)) = chars.next() {
match ch { match ch {
'\\' => { chars.next(); continue; } '\\' => {
chars.next();
continue;
}
'\'' => qt_state.toggle_single(), '\'' => qt_state.toggle_single(),
'"' => qt_state.toggle_double(), '"' => qt_state.toggle_double(),
_ if qt_state.in_quote() => continue, _ if qt_state.in_quote() => continue,
@@ -1008,7 +1014,6 @@ pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String,String)> {
} }
} }
None None
} }
@@ -1017,12 +1022,18 @@ pub fn split_tk(tk: &Tk, pat: &str) -> Vec<Tk> {
let mut cursor = 0; let mut cursor = 0;
let mut splits = vec![]; let mut splits = vec![];
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) { while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
let before_span = Span::new(tk.span.range().start + cursor..tk.span.range().start + cursor + split.0.len(), tk.source().clone()); let before_span = Span::new(
tk.span.range().start + cursor..tk.span.range().start + cursor + split.0.len(),
tk.source().clone(),
);
splits.push(Tk::new(tk.class.clone(), before_span)); splits.push(Tk::new(tk.class.clone(), before_span));
cursor += split.0.len() + pat.len(); cursor += split.0.len() + pat.len();
} }
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) { if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
let remaining_span = Span::new(tk.span.range().start + cursor..tk.span.range().end, tk.source().clone()); let remaining_span = Span::new(
tk.span.range().start + cursor..tk.span.range().end,
tk.source().clone(),
);
splits.push(Tk::new(tk.class.clone(), remaining_span)); splits.push(Tk::new(tk.class.clone(), remaining_span));
} }
splits splits
@@ -1035,7 +1046,10 @@ pub fn split_tk_at(tk: &Tk, pat: &str) -> Option<(Tk, Tk)> {
while let Some((i, ch)) = chars.next() { while let Some((i, ch)) = chars.next() {
match ch { match ch {
'\\' => { chars.next(); continue; } '\\' => {
chars.next();
continue;
}
'\'' => qt_state.toggle_single(), '\'' => qt_state.toggle_single(),
'"' => qt_state.toggle_double(), '"' => qt_state.toggle_double(),
_ if qt_state.in_quote() => continue, _ if qt_state.in_quote() => continue,
@@ -1043,8 +1057,14 @@ pub fn split_tk_at(tk: &Tk, pat: &str) -> Option<(Tk, Tk)> {
} }
if slice[i..].starts_with(pat) { if slice[i..].starts_with(pat) {
let before_span = Span::new(tk.span.range().start..tk.span.range().start + i, tk.source().clone()); let before_span = Span::new(
let after_span = Span::new(tk.span.range().start + i + pat.len()..tk.span.range().end, tk.source().clone()); tk.span.range().start..tk.span.range().start + i,
tk.source().clone(),
);
let after_span = Span::new(
tk.span.range().start + i + pat.len()..tk.span.range().end,
tk.source().clone(),
);
let before_tk = Tk::new(tk.class.clone(), before_span); let before_tk = Tk::new(tk.class.clone(), before_span);
let after_tk = Tk::new(tk.class.clone(), after_span); let after_tk = Tk::new(tk.class.clone(), after_span);
return Some((before_tk, after_tk)); return Some((before_tk, after_tk));

View File

@@ -9,7 +9,10 @@ use crate::{
libsh::{ libsh::{
error::{ShErr, ShErrKind, ShResult, last_color, next_color}, error::{ShErr, ShErrKind, ShResult, last_color, next_color},
utils::{NodeVecUtils, TkVecUtils}, utils::{NodeVecUtils, TkVecUtils},
}, parse::lex::clean_input, prelude::*, procio::IoMode },
parse::lex::clean_input,
prelude::*,
procio::IoMode,
}; };
pub mod execute; pub mod execute;
@@ -77,7 +80,8 @@ impl ParsedSrc {
} }
pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> { pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> {
let mut tokens = vec![]; let mut tokens = vec![];
for lex_result in LexStream::new(self.src.clone(), self.lex_flags).with_name(self.name.clone()) { for lex_result in LexStream::new(self.src.clone(), self.lex_flags).with_name(self.name.clone())
{
match lex_result { match lex_result {
Ok(token) => tokens.push(token), Ok(token) => tokens.push(token),
Err(error) => return Err(vec![error]), Err(error) => return Err(vec![error]),
@@ -148,7 +152,7 @@ impl Node {
let span = self.get_span().clone(); let span = self.get_span().clone();
( (
span.clone().source().clone(), span.clone().source().clone(),
Label::new(span).with_color(color).with_message(msg) Label::new(span).with_color(color).with_message(msg),
) )
} }
fn walk_tree<F: Fn(&mut Node)>(&mut self, f: &F) { fn walk_tree<F: Fn(&mut Node)>(&mut self, f: &F) {
@@ -662,7 +666,7 @@ pub enum NdRule {
pub struct ParseStream { pub struct ParseStream {
pub tokens: Vec<Tk>, pub tokens: Vec<Tk>,
pub context: LabelCtx pub context: LabelCtx,
} }
impl Debug for ParseStream { impl Debug for ParseStream {
@@ -675,7 +679,10 @@ impl Debug for ParseStream {
impl ParseStream { impl ParseStream {
pub fn new(tokens: Vec<Tk>) -> Self { pub fn new(tokens: Vec<Tk>) -> Self {
Self { tokens, context: VecDeque::new() } Self {
tokens,
context: VecDeque::new(),
}
} }
pub fn with_context(tokens: Vec<Tk>, context: LabelCtx) -> Self { pub fn with_context(tokens: Vec<Tk>, context: LabelCtx) -> Self {
Self { tokens, context } Self { tokens, context }
@@ -839,7 +846,10 @@ impl ParseStream {
self.context.push_back(( self.context.push_back((
src.clone(), src.clone(),
Label::new(name_tk.span.clone().with_name(name_raw.clone())) Label::new(name_tk.span.clone().with_name(name_raw.clone()))
.with_message(format!("in function '{}' defined here", name_raw.clone().fg(color))) .with_message(format!(
"in function '{}' defined here",
name_raw.clone().fg(color)
))
.with_color(color), .with_color(color),
)); ));
@@ -848,7 +858,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected a brace group after function name", "Expected a brace group after function name",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
}; };
body = Box::new(brc_grp); body = Box::new(brc_grp);
@@ -860,7 +870,7 @@ impl ParseStream {
flags: NdFlags::empty(), flags: NdFlags::empty(),
redirs: vec![], redirs: vec![],
tokens: node_tks, tokens: node_tks,
context: self.context.clone() context: self.context.clone(),
}; };
self.context.pop_back(); self.context.pop_back();
@@ -893,7 +903,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Malformed test call", "Malformed test call",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} else { } else {
break; break;
@@ -920,7 +930,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Invalid placement for logical operator in test", "Invalid placement for logical operator in test",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
let op = match tk.class { let op = match tk.class {
@@ -936,7 +946,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Invalid placement for logical operator in test", "Invalid placement for logical operator in test",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
} }
@@ -982,7 +992,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected a closing brace for this brace group", "Expected a closing brace for this brace group",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
} }
@@ -1049,11 +1059,9 @@ impl ParseStream {
let pat_err = parse_err_full( let pat_err = parse_err_full(
"Expected a pattern after 'case' keyword", "Expected a pattern after 'case' keyword",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
) )
.with_note( .with_note("Patterns can be raw text, or anything that gets substituted with raw text");
"Patterns can be raw text, or anything that gets substituted with raw text"
);
let Some(pat_tk) = self.next_tk() else { let Some(pat_tk) = self.next_tk() else {
self.panic_mode(&mut node_tks); self.panic_mode(&mut node_tks);
@@ -1073,7 +1081,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected 'in' after case variable name", "Expected 'in' after case variable name",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1086,7 +1094,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected a case pattern here", "Expected a case pattern here",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
let case_pat_tk = self.next_tk().unwrap(); let case_pat_tk = self.next_tk().unwrap();
@@ -1123,7 +1131,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected 'esac' after case block", "Expected 'esac' after case block",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
} }
@@ -1166,12 +1174,17 @@ impl ParseStream {
self.panic_mode(&mut node_tks); self.panic_mode(&mut node_tks);
let span = node_tks.get_span().unwrap(); let span = node_tks.get_span().unwrap();
let color = next_color(); let color = next_color();
return Err(self.make_err(span.clone(), return Err(
self.make_err(
span.clone(),
Label::new(span) Label::new(span)
.with_message(format!("Expected an expression after '{}'", prefix_keywrd.fg(color))) .with_message(format!(
.with_color(color) "Expected an expression after '{}'",
)); prefix_keywrd.fg(color)
))
.with_color(color),
),
);
}; };
node_tks.extend(cond.tokens.clone()); node_tks.extend(cond.tokens.clone());
@@ -1180,7 +1193,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
&format!("Expected 'then' after '{prefix_keywrd}' condition"), &format!("Expected 'then' after '{prefix_keywrd}' condition"),
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1196,7 +1209,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected an expression after 'then'", "Expected an expression after 'then'",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
}; };
let cond_node = CondNode { let cond_node = CondNode {
@@ -1226,7 +1239,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected an expression after 'else'", "Expected an expression after 'else'",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
} }
@@ -1237,7 +1250,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected 'fi' after if statement", "Expected 'fi' after if statement",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1293,7 +1306,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"This for loop is missing a variable", "This for loop is missing a variable",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
if arr.is_empty() { if arr.is_empty() {
@@ -1301,7 +1314,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"This for loop is missing an array", "This for loop is missing an array",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
if !self.check_keyword("do") || !self.next_tk_is_some() { if !self.check_keyword("do") || !self.next_tk_is_some() {
@@ -1309,7 +1322,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Missing a 'do' for this for loop", "Missing a 'do' for this for loop",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1325,7 +1338,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Missing a 'done' after this for loop", "Missing a 'done' after this for loop",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1366,7 +1379,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display &format!("Expected an expression after '{loop_kind}'"), // It also implements Display
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
}; };
node_tks.extend(cond.tokens.clone()); node_tks.extend(cond.tokens.clone());
@@ -1376,7 +1389,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected 'do' after loop condition", "Expected 'do' after loop condition",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1392,7 +1405,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected an expression after 'do'", "Expected an expression after 'do'",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
}; };
@@ -1402,7 +1415,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Expected 'done' after loop body", "Expected 'done' after loop body",
&node_tks.get_span().unwrap(), &node_tks.get_span().unwrap(),
self.context.clone() self.context.clone(),
)); ));
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
@@ -1479,7 +1492,7 @@ impl ParseStream {
return Err(parse_err_full( return Err(parse_err_full(
"Found case pattern in command", "Found case pattern in command",
&prefix_tk.span, &prefix_tk.span,
self.context.clone() self.context.clone(),
)); ));
} }
let is_cmd = prefix_tk.flags.contains(TkFlags::IS_CMD); let is_cmd = prefix_tk.flags.contains(TkFlags::IS_CMD);
@@ -1521,7 +1534,7 @@ impl ParseStream {
assignments_span.source().clone(), assignments_span.source().clone(),
Label::new(assignments_span) Label::new(assignments_span)
.with_message("in variable assignment defined here".to_string()) .with_message("in variable assignment defined here".to_string())
.with_color(next_color()) .with_color(next_color()),
)); ));
return Ok(Some(Node { return Ok(Some(Node {
class: NdRule::Command { assignments, argv }, class: NdRule::Command { assignments, argv },
@@ -1745,10 +1758,7 @@ pub fn get_redir_file<P: AsRef<Path>>(class: RedirType, path: P) -> ShResult<Fil
.create(true) .create(true)
.truncate(true) .truncate(true)
.open(path), .open(path),
RedirType::Append => OpenOptions::new() RedirType::Append => OpenOptions::new().create(true).append(true).open(path),
.create(true)
.append(true)
.open(path),
_ => unimplemented!(), _ => unimplemented!(),
}; };
Ok(result?) Ok(result?)
@@ -1759,7 +1769,9 @@ fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
ShErr::new(ShErrKind::ParseErr, blame.clone()) ShErr::new(ShErrKind::ParseErr, blame.clone())
.with_label( .with_label(
blame.span_source().clone(), blame.span_source().clone(),
Label::new(blame.clone()).with_message(reason).with_color(color) Label::new(blame.clone())
.with_message(reason)
.with_color(color),
) )
.with_context(context) .with_context(context)
} }

View File

@@ -1,20 +1,26 @@
use std::{ use std::{
collections::HashSet, fmt::{Write,Debug}, path::PathBuf, sync::Arc, collections::HashSet,
fmt::{Debug, Write},
path::PathBuf,
sync::Arc,
}; };
use nix::sys::signal::Signal; use nix::sys::signal::Signal;
use crate::{ use crate::{
builtin::complete::{CompFlags, CompOptFlags, CompOpts}, builtin::complete::{CompFlags, CompOptFlags, CompOpts},
libsh::{ libsh::{error::ShResult, guards::var_ctx_guard, sys::TTY_FILENO, utils::TkVecUtils},
error::ShResult, guards::var_ctx_guard, sys::TTY_FILENO, utils::TkVecUtils
},
parse::{ parse::{
execute::exec_input, execute::exec_input,
lex::{self, LexFlags, Tk, TkRule, ends_with_unescaped}, lex::{self, LexFlags, Tk, TkRule, ends_with_unescaped},
}, },
readline::{ readline::{
Marker, annotate_input_recursive, keys::{KeyCode as C, KeyEvent as K, ModKeys as M}, linebuf::{ClampedUsize, LineBuf}, markers::{self, is_marker}, term::{LineWriter, TermWriter, calc_str_width, get_win_size}, vimode::{ViInsert, ViMode} Marker, annotate_input_recursive,
keys::{KeyCode as C, KeyEvent as K, ModKeys as M},
linebuf::{ClampedUsize, LineBuf},
markers::{self, is_marker},
term::{LineWriter, TermWriter, calc_str_width, get_win_size},
vimode::{ViInsert, ViMode},
}, },
state::{VarFlags, VarKind, read_jobs, read_logic, read_meta, read_vars, write_vars}, state::{VarFlags, VarKind, read_jobs, read_logic, read_meta, read_vars, write_vars},
}; };
@@ -224,9 +230,14 @@ fn complete_filename(start: &str) -> Vec<String> {
pub enum CompSpecResult { pub enum CompSpecResult {
NoSpec, // No compspec registered NoSpec, // No compspec registered
NoMatch { flags: CompOptFlags }, /* Compspec found but no candidates matched, returns NoMatch {
flags: CompOptFlags,
}, /* Compspec found but no candidates matched, returns
* behavior flags */ * behavior flags */
Match { result: CompResult, flags: CompOptFlags }, // Compspec found and candidates returned Match {
result: CompResult,
flags: CompOptFlags,
}, // Compspec found and candidates returned
} }
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
@@ -431,7 +442,8 @@ impl CompSpec for BashCompSpec {
if self.function.is_some() { if self.function.is_some() {
candidates.extend(self.exec_comp_func(ctx)?); candidates.extend(self.exec_comp_func(ctx)?);
} }
candidates = candidates.into_iter() candidates = candidates
.into_iter()
.map(|c| { .map(|c| {
let stripped = c.strip_prefix(&expanded).unwrap_or_default(); let stripped = c.strip_prefix(&expanded).unwrap_or_default();
format!("{prefix}{stripped}") format!("{prefix}{stripped}")
@@ -513,11 +525,16 @@ pub enum CompResponse {
Passthrough, // key falls through Passthrough, // key falls through
Accept(String), // user accepted completion Accept(String), // user accepted completion
Dismiss, // user canceled completion Dismiss, // user canceled completion
Consumed // key was handled, but completion remains active Consumed, // key was handled, but completion remains active
} }
pub trait Completer { pub trait Completer {
fn complete(&mut self, line: String, cursor_pos: usize, direction: i32) -> ShResult<Option<String>>; fn complete(
&mut self,
line: String,
cursor_pos: usize,
direction: i32,
) -> ShResult<Option<String>>;
fn reset(&mut self); fn reset(&mut self);
fn reset_stay_active(&mut self); fn reset_stay_active(&mut self);
fn is_active(&self) -> bool; fn is_active(&self) -> bool;
@@ -525,7 +542,9 @@ pub trait Completer {
fn token_span(&self) -> (usize, usize); fn token_span(&self) -> (usize, usize);
fn original_input(&self) -> &str; fn original_input(&self) -> &str;
fn draw(&mut self, writer: &mut TermWriter) -> ShResult<()>; fn draw(&mut self, writer: &mut TermWriter) -> ShResult<()>;
fn clear(&mut self, _writer: &mut TermWriter) -> ShResult<()> { Ok(()) } fn clear(&mut self, _writer: &mut TermWriter) -> ShResult<()> {
Ok(())
}
fn set_prompt_line_context(&mut self, _line_width: u16, _cursor_col: u16) {} fn set_prompt_line_context(&mut self, _line_width: u16, _cursor_col: u16) {}
fn handle_key(&mut self, key: K) -> ShResult<CompResponse>; fn handle_key(&mut self, key: K) -> ShResult<CompResponse>;
fn get_completed_line(&self, candidate: &str) -> String; fn get_completed_line(&self, candidate: &str) -> String;
@@ -545,7 +564,10 @@ impl ScoredCandidate {
const PENALTY_GAP_EXTEND: i32 = 1; const PENALTY_GAP_EXTEND: i32 = 1;
pub fn new(content: String) -> Self { pub fn new(content: String) -> Self {
Self { content, score: None } Self {
content,
score: None,
}
} }
fn is_word_bound(prev: char, curr: char) -> bool { fn is_word_bound(prev: char, curr: char) -> bool {
match prev { match prev {
@@ -583,7 +605,6 @@ impl ScoredCandidate {
score += Self::BONUS_FIRST_CHAR; score += Self::BONUS_FIRST_CHAR;
} }
if idx == 0 || Self::is_word_bound(content_chars[idx - 1], content_chars[idx]) { if idx == 0 || Self::is_word_bound(content_chars[idx - 1], content_chars[idx]) {
score += Self::BONUS_BOUNDARY; score += Self::BONUS_BOUNDARY;
} }
@@ -605,7 +626,10 @@ impl ScoredCandidate {
impl From<String> for ScoredCandidate { impl From<String> for ScoredCandidate {
fn from(content: String) -> Self { fn from(content: String) -> Self {
Self { content, score: None } Self {
content,
score: None,
}
} }
} }
@@ -626,7 +650,7 @@ pub struct QueryEditor {
mode: ViInsert, mode: ViInsert,
scroll_offset: usize, scroll_offset: usize,
available_width: usize, available_width: usize,
linebuf: LineBuf linebuf: LineBuf,
} }
impl QueryEditor { impl QueryEditor {
@@ -645,9 +669,16 @@ impl QueryEditor {
self.scroll_offset = self.linebuf.cursor.ret_sub(1); self.scroll_offset = self.linebuf.cursor.ret_sub(1);
} }
if cursor_pos >= self.scroll_offset + self.available_width.saturating_sub(1) { if cursor_pos >= self.scroll_offset + self.available_width.saturating_sub(1) {
self.scroll_offset = self.linebuf.cursor.ret_sub(self.available_width.saturating_sub(1)); self.scroll_offset = self
.linebuf
.cursor
.ret_sub(self.available_width.saturating_sub(1));
} }
let max_offset = self.linebuf.grapheme_indices().len().saturating_sub(self.available_width); let max_offset = self
.linebuf
.grapheme_indices()
.len()
.saturating_sub(self.available_width);
self.scroll_offset = self.scroll_offset.min(max_offset); self.scroll_offset = self.scroll_offset.min(max_offset);
} }
pub fn get_window(&mut self) -> String { pub fn get_window(&mut self) -> String {
@@ -656,19 +687,20 @@ impl QueryEditor {
if buf_len <= self.available_width { if buf_len <= self.available_width {
return self.linebuf.as_str().to_string(); return self.linebuf.as_str().to_string();
} }
let start = self.scroll_offset.min(buf_len.saturating_sub(self.available_width)); let start = self
.scroll_offset
.min(buf_len.saturating_sub(self.available_width));
let end = (start + self.available_width).min(buf_len); let end = (start + self.available_width).min(buf_len);
self.linebuf.slice(start..end).unwrap_or("").to_string() self.linebuf.slice(start..end).unwrap_or("").to_string()
} }
pub fn handle_key(&mut self, key: K) -> ShResult<()> { pub fn handle_key(&mut self, key: K) -> ShResult<()> {
let Some(cmd) = self.mode.handle_key(key) else { let Some(cmd) = self.mode.handle_key(key) else {
return Ok(()) return Ok(());
}; };
self.linebuf.exec_cmd(cmd) self.linebuf.exec_cmd(cmd)
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FuzzyCompleter { pub struct FuzzyCompleter {
completer: SimpleCompleter, completer: SimpleCompleter,
@@ -702,7 +734,6 @@ impl FuzzyCompleter {
//const TREE_TOP: &str = "\x1b[90m┬\x1b[0m"; //const TREE_TOP: &str = "\x1b[90m┬\x1b[0m";
//const CROSS: &str = "\x1b[90m┼\x1b[0m"; //const CROSS: &str = "\x1b[90m┼\x1b[0m";
fn get_window(&mut self) -> &[ScoredCandidate] { fn get_window(&mut self) -> &[ScoredCandidate] {
let height = self.filtered.len().min(self.max_height); let height = self.filtered.len().min(self.max_height);
@@ -718,21 +749,21 @@ impl FuzzyCompleter {
if self.cursor.get() >= self.scroll_offset + height.saturating_sub(1) { if self.cursor.get() >= self.scroll_offset + height.saturating_sub(1) {
self.scroll_offset = self.cursor.ret_sub(height.saturating_sub(2)); self.scroll_offset = self.cursor.ret_sub(height.saturating_sub(2));
} }
self.scroll_offset = self.scroll_offset.min(self.filtered.len().saturating_sub(height)); self.scroll_offset = self
.scroll_offset
.min(self.filtered.len().saturating_sub(height));
} }
pub fn score_candidates(&mut self) { pub fn score_candidates(&mut self) {
let mut scored: Vec<_> = self.candidates let mut scored: Vec<_> = self
.candidates
.clone() .clone()
.into_iter() .into_iter()
.filter_map(|c| { .filter_map(|c| {
let mut sc = ScoredCandidate::new(c); let mut sc = ScoredCandidate::new(c);
let score = sc.fuzzy_score(self.query.linebuf.as_str()); let score = sc.fuzzy_score(self.query.linebuf.as_str());
if score > i32::MIN { if score > i32::MIN { Some(sc) } else { None }
Some(sc) })
} else { .collect();
None
}
}).collect();
scored.sort_by_key(|sc| sc.score.unwrap_or(i32::MIN)); scored.sort_by_key(|sc| sc.score.unwrap_or(i32::MIN));
scored.reverse(); scored.reverse();
self.cursor.set_max(scored.len()); self.cursor.set_max(scored.len());
@@ -785,7 +816,12 @@ impl Completer for FuzzyCompleter {
log::debug!("Completed line: {}", ret); log::debug!("Completed line: {}", ret);
ret ret
} }
fn complete(&mut self, line: String, cursor_pos: usize, direction: i32) -> ShResult<Option<String>> { fn complete(
&mut self,
line: String,
cursor_pos: usize,
direction: i32,
) -> ShResult<Option<String>> {
self.completer.complete(line, cursor_pos, direction)?; self.completer.complete(line, cursor_pos, direction)?;
let candidates: Vec<_> = self.completer.candidates.clone(); let candidates: Vec<_> = self.completer.candidates.clone();
if candidates.is_empty() { if candidates.is_empty() {
@@ -806,28 +842,29 @@ impl Completer for FuzzyCompleter {
fn handle_key(&mut self, key: K) -> ShResult<CompResponse> { fn handle_key(&mut self, key: K) -> ShResult<CompResponse> {
match key { match key {
K(C::Char('D'), M::CTRL) | K(C::Char('D'), M::CTRL) | K(C::Esc, M::NONE) => {
K(C::Esc, M::NONE) => {
self.active = false; self.active = false;
self.filtered.clear(); self.filtered.clear();
Ok(CompResponse::Dismiss) Ok(CompResponse::Dismiss)
} }
K(C::Enter, M::NONE) => { K(C::Enter, M::NONE) => {
self.active = false; self.active = false;
if let Some(selected) = self.filtered.get(self.cursor.get()).map(|c| c.content.clone()) { if let Some(selected) = self
.filtered
.get(self.cursor.get())
.map(|c| c.content.clone())
{
Ok(CompResponse::Accept(selected)) Ok(CompResponse::Accept(selected))
} else { } else {
Ok(CompResponse::Dismiss) Ok(CompResponse::Dismiss)
} }
} }
K(C::Tab, M::SHIFT) | K(C::Tab, M::SHIFT) | K(C::Up, M::NONE) => {
K(C::Up, M::NONE) => {
self.cursor.wrap_sub(1); self.cursor.wrap_sub(1);
self.update_scroll_offset(); self.update_scroll_offset();
Ok(CompResponse::Consumed) Ok(CompResponse::Consumed)
} }
K(C::Tab, M::NONE) | K(C::Tab, M::NONE) | K(C::Down, M::NONE) => {
K(C::Down, M::NONE) => {
self.cursor.wrap_add(1); self.cursor.wrap_add(1);
self.update_scroll_offset(); self.update_scroll_offset();
Ok(CompResponse::Consumed) Ok(CompResponse::Consumed)
@@ -890,19 +927,22 @@ impl Completer for FuzzyCompleter {
if !self.active { if !self.active {
return Ok(()); return Ok(());
} }
let (cols,_) = get_win_size(*TTY_FILENO); let (cols, _) = get_win_size(*TTY_FILENO);
let mut buf = String::new(); let mut buf = String::new();
let cursor_pos = self.cursor.get(); let cursor_pos = self.cursor.get();
let offset = self.scroll_offset; let offset = self.scroll_offset;
self.query.set_available_width(cols.saturating_sub(6) as usize); self
.query
.set_available_width(cols.saturating_sub(6) as usize);
self.query.update_scroll_offset(); self.query.update_scroll_offset();
let query = self.query.get_window(); let query = self.query.get_window();
let num_filtered = format!("\x1b[33m{}\x1b[0m",self.filtered.len()); let num_filtered = format!("\x1b[33m{}\x1b[0m", self.filtered.len());
let num_candidates = format!("\x1b[33m{}\x1b[0m",self.candidates.len()); let num_candidates = format!("\x1b[33m{}\x1b[0m", self.candidates.len());
let visible = self.get_window(); let visible = self.get_window();
let mut rows: u16 = 0; let mut rows: u16 = 0;
let top_bar = format!("\n{}{} \x1b[1mComplete\x1b[0m {}{}", let top_bar = format!(
"\n{}{} \x1b[1mComplete\x1b[0m {}{}",
Self::TOP_LEFT, Self::TOP_LEFT,
Self::HOR_LINE, Self::HOR_LINE,
Self::HOR_LINE.repeat(cols.saturating_sub(13) as usize), Self::HOR_LINE.repeat(cols.saturating_sub(13) as usize),
@@ -910,8 +950,7 @@ impl Completer for FuzzyCompleter {
); );
buf.push_str(&top_bar); buf.push_str(&top_bar);
rows += 1; rows += 1;
for _ in 0..rows { for _ in 0..rows {}
}
let prompt = format!("{} {} {}", Self::VERT_LINE, Self::PROMPT_ARROW, &query); let prompt = format!("{} {} {}", Self::VERT_LINE, Self::PROMPT_ARROW, &query);
let cols_used = calc_str_width(&prompt); let cols_used = calc_str_width(&prompt);
@@ -920,7 +959,8 @@ impl Completer for FuzzyCompleter {
buf.push_str(&prompt_line_final); buf.push_str(&prompt_line_final);
rows += 1; rows += 1;
let sep_line_left = format!("{}{}{}/{}", let sep_line_left = format!(
"{}{}{}/{}",
Self::TREE_LEFT, Self::TREE_LEFT,
Self::HOR_LINE.repeat(2), Self::HOR_LINE.repeat(2),
&num_filtered, &num_filtered,
@@ -932,7 +972,6 @@ impl Completer for FuzzyCompleter {
buf.push_str(&sep_line_final); buf.push_str(&sep_line_final);
rows += 1; rows += 1;
for (i, candidate) in visible.iter().enumerate() { for (i, candidate) in visible.iter().enumerate() {
let selector = if i + offset == cursor_pos { let selector = if i + offset == cursor_pos {
Self::SELECTOR_HL Self::SELECTOR_HL
@@ -945,11 +984,7 @@ impl Completer for FuzzyCompleter {
content.truncate(col_lim.saturating_sub(6) as usize); // ui bars + elipses length content.truncate(col_lim.saturating_sub(6) as usize); // ui bars + elipses length
content.push_str("..."); content.push_str("...");
} }
let left = format!("{} {}{}\x1b[0m", let left = format!("{} {}{}\x1b[0m", Self::VERT_LINE, &selector, &content);
Self::VERT_LINE,
&selector,
&content
);
let cols_used = calc_str_width(&left); let cols_used = calc_str_width(&left);
let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize); let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize);
let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE); let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE);
@@ -957,9 +992,12 @@ impl Completer for FuzzyCompleter {
rows += 1; rows += 1;
} }
let bot_bar = format!("{}{}{}", let bot_bar = format!(
"{}{}{}",
Self::BOT_LEFT, Self::BOT_LEFT,
Self::HOR_LINE.to_string().repeat(cols.saturating_sub(2) as usize), Self::HOR_LINE
.to_string()
.repeat(cols.saturating_sub(2) as usize),
Self::BOT_RIGHT Self::BOT_RIGHT
); );
buf.push_str(&bot_bar); buf.push_str(&bot_bar);
@@ -967,7 +1005,12 @@ impl Completer for FuzzyCompleter {
// Move cursor back up to the prompt line (skip: separator + candidates + bottom border) // Move cursor back up to the prompt line (skip: separator + candidates + bottom border)
let lines_below_prompt = rows.saturating_sub(2); // total rows minus top_bar and prompt let lines_below_prompt = rows.saturating_sub(2); // total rows minus top_bar and prompt
let cursor_in_window = self.query.linebuf.cursor.get().saturating_sub(self.query.scroll_offset); let cursor_in_window = self
.query
.linebuf
.cursor
.get()
.saturating_sub(self.query.scroll_offset);
let cursor_col = (cursor_in_window + 4) as u16; // "| > ".len() == 4 let cursor_col = (cursor_in_window + 4) as u16; // "| > ".len() == 4
write!(buf, "\x1b[{}A\r\x1b[{}C", lines_below_prompt, cursor_col).unwrap(); write!(buf, "\x1b[{}A\r\x1b[{}C", lines_below_prompt, cursor_col).unwrap();
@@ -993,7 +1036,10 @@ impl Completer for FuzzyCompleter {
self.active self.active
} }
fn selected_candidate(&self) -> Option<String> { fn selected_candidate(&self) -> Option<String> {
self.filtered.get(self.cursor.get()).map(|c| c.content.clone()) self
.filtered
.get(self.cursor.get())
.map(|c| c.content.clone())
} }
fn original_input(&self) -> &str { fn original_input(&self) -> &str {
&self.completer.original_input &self.completer.original_input
@@ -1020,7 +1066,12 @@ impl Completer for SimpleCompleter {
fn get_completed_line(&self, _candidate: &str) -> String { fn get_completed_line(&self, _candidate: &str) -> String {
self.get_completed_line() self.get_completed_line()
} }
fn complete(&mut self, line: String, cursor_pos: usize, direction: i32) -> ShResult<Option<String>> { fn complete(
&mut self,
line: String,
cursor_pos: usize,
direction: i32,
) -> ShResult<Option<String>> {
if self.active { if self.active {
Ok(Some(self.cycle_completion(direction))) Ok(Some(self.cycle_completion(direction)))
} else { } else {
@@ -1295,7 +1346,8 @@ impl SimpleCompleter {
let (mut marker_ctx, token_start) = self.get_subtoken_completion(&line, cursor_pos); let (mut marker_ctx, token_start) = self.get_subtoken_completion(&line, cursor_pos);
if marker_ctx.last() == Some(&markers::VAR_SUB) if marker_ctx.last() == Some(&markers::VAR_SUB)
&& let Some(cur) = ctx.words.get(ctx.cword) { && let Some(cur) = ctx.words.get(ctx.cword)
{
self.token_span.0 = token_start; self.token_span.0 = token_start;
let mut span = cur.span.clone(); let mut span = cur.span.clone();
span.set_range(token_start..self.token_span.1); span.set_range(token_start..self.token_span.1);

View File

@@ -26,7 +26,7 @@ pub struct Highlighter {
style_stack: Vec<StyleSet>, style_stack: Vec<StyleSet>,
last_was_reset: bool, last_was_reset: bool,
in_selection: bool, in_selection: bool,
only_hl_visual: bool only_hl_visual: bool,
} }
impl Highlighter { impl Highlighter {
@@ -39,7 +39,7 @@ impl Highlighter {
style_stack: Vec::new(), style_stack: Vec::new(),
last_was_reset: true, // start as true so we don't emit a leading reset last_was_reset: true, // start as true so we don't emit a leading reset
in_selection: false, in_selection: false,
only_hl_visual: false only_hl_visual: false,
} }
} }
pub fn only_visual(&mut self, only_visual: bool) { pub fn only_visual(&mut self, only_visual: bool) {

View File

@@ -108,10 +108,12 @@ impl KeyEvent {
} }
match event { match event {
KeyCode::UnknownEscSeq => return Err(ShErr::simple( KeyCode::UnknownEscSeq => {
return Err(ShErr::simple(
ShErrKind::ParseErr, ShErrKind::ParseErr,
"Cannot convert unknown escape sequence to Vim key sequence".to_string(), "Cannot convert unknown escape sequence to Vim key sequence".to_string(),
)), ));
}
KeyCode::Backspace => { KeyCode::Backspace => {
seq.push_str("BS"); seq.push_str("BS");
needs_angle_bracket = true; needs_angle_bracket = true;

View File

@@ -1,5 +1,7 @@
use std::{ use std::{
collections::HashSet, fmt::Display, ops::{Range, RangeInclusive} collections::HashSet,
fmt::Display,
ops::{Range, RangeInclusive},
}; };
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@@ -11,7 +13,10 @@ use super::vicmd::{
}; };
use crate::{ use crate::{
libsh::{error::ShResult, guards::var_ctx_guard}, libsh::{error::ShResult, guards::var_ctx_guard},
parse::{execute::exec_input, lex::{LexFlags, LexStream, Tk, TkFlags, TkRule}}, parse::{
execute::exec_input,
lex::{LexFlags, LexStream, Tk, TkFlags, TkRule},
},
prelude::*, prelude::*,
readline::{ readline::{
markers, markers,
@@ -828,7 +833,7 @@ impl LineBuf {
(start, end) (start, end)
} }
pub fn this_line_content(&mut self) -> Option<&str> { pub fn this_line_content(&mut self) -> Option<&str> {
let (start,end) = self.this_line_exclusive(); let (start, end) = self.this_line_exclusive();
self.slice(start..end) self.slice(start..end)
} }
pub fn this_line(&mut self) -> (usize, usize) { pub fn this_line(&mut self) -> (usize, usize) {
@@ -2367,7 +2372,7 @@ impl LineBuf {
} else { } else {
MotionKind::On(self.grapheme_indices().len()) MotionKind::On(self.grapheme_indices().len())
} }
}, }
MotionCmd(_count, Motion::ToColumn) => todo!(), MotionCmd(_count, Motion::ToColumn) => todo!(),
MotionCmd(count, Motion::Range(start, end)) => { MotionCmd(count, Motion::Range(start, end)) => {
let mut final_end = end; let mut final_end = end;
@@ -2794,7 +2799,11 @@ impl LineBuf {
match content { match content {
RegisterContent::Span(ref text) => { RegisterContent::Span(ref text) => {
let insert_idx = match anchor { let insert_idx = match anchor {
Anchor::After => self.cursor.get().saturating_add(1).min(self.grapheme_indices().len()), Anchor::After => self
.cursor
.get()
.saturating_add(1)
.min(self.grapheme_indices().len()),
Anchor::Before => self.cursor.get(), Anchor::Before => self.cursor.get(),
}; };
self.insert_str_at(insert_idx, text); self.insert_str_at(insert_idx, text);
@@ -2861,7 +2870,8 @@ impl LineBuf {
self.cursor.add(1); self.cursor.add(1);
let before = self.auto_indent_level; let before = self.auto_indent_level;
if read_shopts(|o| o.prompt.auto_indent) if read_shopts(|o| o.prompt.auto_indent)
&& let Some(line_content) = self.this_line_content() { && let Some(line_content) = self.this_line_content()
{
match line_content.trim() { match line_content.trim() {
"esac" | "done" | "fi" | "}" => { "esac" | "done" | "fi" | "}" => {
self.calc_indent_level(); self.calc_indent_level();
@@ -3039,9 +3049,12 @@ impl LineBuf {
} }
} }
} }
Verb::IncrementNumber(n) | Verb::IncrementNumber(n) | Verb::DecrementNumber(n) => {
Verb::DecrementNumber(n) => { let inc = if matches!(verb, Verb::IncrementNumber(_)) {
let inc = if matches!(verb, Verb::IncrementNumber(_)) { n as i64 } else { -(n as i64) }; n as i64
} else {
-(n as i64)
};
let (s, e) = self.select_range().unwrap_or(self.this_word(Word::Normal)); let (s, e) = self.select_range().unwrap_or(self.this_word(Word::Normal));
let end = if self.select_range().is_some() { let end = if self.select_range().is_some() {
if e < self.grapheme_indices().len() - 1 { if e < self.grapheme_indices().len() - 1 {
@@ -3066,7 +3079,9 @@ impl LineBuf {
let width = body.len(); let width = body.len();
if let Ok(num) = i64::from_str_radix(body, 16) { if let Ok(num) = i64::from_str_radix(body, 16) {
let new_num = num + inc; let new_num = num + inc;
self.buffer.replace_range(byte_start..byte_end, &format!("0x{new_num:0>width$x}")); self
.buffer
.replace_range(byte_start..byte_end, &format!("0x{new_num:0>width$x}"));
self.update_graphemes(); self.update_graphemes();
self.cursor.set(s); self.cursor.set(s);
} }
@@ -3075,7 +3090,9 @@ impl LineBuf {
let width = body.len(); let width = body.len();
if let Ok(num) = i64::from_str_radix(body, 2) { if let Ok(num) = i64::from_str_radix(body, 2) {
let new_num = num + inc; let new_num = num + inc;
self.buffer.replace_range(byte_start..byte_end, &format!("0b{new_num:0>width$b}")); self
.buffer
.replace_range(byte_start..byte_end, &format!("0b{new_num:0>width$b}"));
self.update_graphemes(); self.update_graphemes();
self.cursor.set(s); self.cursor.set(s);
} }
@@ -3084,14 +3101,18 @@ impl LineBuf {
let width = body.len(); let width = body.len();
if let Ok(num) = i64::from_str_radix(body, 8) { if let Ok(num) = i64::from_str_radix(body, 8) {
let new_num = num + inc; let new_num = num + inc;
self.buffer.replace_range(byte_start..byte_end, &format!("0o{new_num:0>width$o}")); self
.buffer
.replace_range(byte_start..byte_end, &format!("0o{new_num:0>width$o}"));
self.update_graphemes(); self.update_graphemes();
self.cursor.set(s); self.cursor.set(s);
} }
} else if let Ok(num) = word.parse::<i64>() { } else if let Ok(num) = word.parse::<i64>() {
let width = word.len(); let width = word.len();
let new_num = num + inc; let new_num = num + inc;
self.buffer.replace_range(byte_start..byte_end, &format!("{new_num:0>width$}")); self
.buffer
.replace_range(byte_start..byte_end, &format!("{new_num:0>width$}"));
self.update_graphemes(); self.update_graphemes();
self.cursor.set(s); self.cursor.set(s);
} }
@@ -3120,15 +3141,28 @@ impl LineBuf {
let mut buf = self.as_str().to_string(); let mut buf = self.as_str().to_string();
let mut cursor = self.cursor.get(); let mut cursor = self.cursor.get();
let mut anchor = self.select_range().map(|r| if r.0 != cursor { r.0 } else { r.1 }).unwrap_or(cursor); let mut anchor = self
.select_range()
.map(|r| if r.0 != cursor { r.0 } else { r.1 })
.unwrap_or(cursor);
write_vars(|v| { write_vars(|v| {
v.set_var("_BUFFER", VarKind::Str(buf.clone()), VarFlags::EXPORT)?; v.set_var("_BUFFER", VarKind::Str(buf.clone()), VarFlags::EXPORT)?;
v.set_var("_CURSOR", VarKind::Str(cursor.to_string()), VarFlags::EXPORT)?; v.set_var(
v.set_var("_ANCHOR", VarKind::Str(anchor.to_string()), VarFlags::EXPORT) "_CURSOR",
VarKind::Str(cursor.to_string()),
VarFlags::EXPORT,
)?;
v.set_var(
"_ANCHOR",
VarKind::Str(anchor.to_string()),
VarFlags::EXPORT,
)
})?; })?;
RawModeGuard::with_cooked_mode(|| exec_input(cmd, None, true, Some("<ex-mode-cmd>".into())))?; RawModeGuard::with_cooked_mode(|| {
exec_input(cmd, None, true, Some("<ex-mode-cmd>".into()))
})?;
let keys = write_vars(|v| { let keys = write_vars(|v| {
buf = v.take_var("_BUFFER"); buf = v.take_var("_BUFFER");
@@ -3141,7 +3175,12 @@ impl LineBuf {
self.update_graphemes(); self.update_graphemes();
self.cursor.set_max(self.buffer.graphemes(true).count()); self.cursor.set_max(self.buffer.graphemes(true).count());
self.cursor.set(cursor); self.cursor.set(cursor);
log::debug!("[ShellCmd] post-widget: cursor={}, anchor={}, select_range={:?}", cursor, anchor, self.select_range); log::debug!(
"[ShellCmd] post-widget: cursor={}, anchor={}, select_range={:?}",
cursor,
anchor,
self.select_range
);
if anchor != cursor && self.select_range.is_some() { if anchor != cursor && self.select_range.is_some() {
self.select_range = Some(ordered(cursor, anchor)); self.select_range = Some(ordered(cursor, anchor));
} }
@@ -3149,7 +3188,6 @@ impl LineBuf {
log::debug!("Pending widget keys from shell command: {keys}"); log::debug!("Pending widget keys from shell command: {keys}");
write_meta(|m| m.set_pending_widget_keys(&keys)) write_meta(|m| m.set_pending_widget_keys(&keys))
} }
} }
Verb::Normal(_) Verb::Normal(_)
| Verb::Read(_) | Verb::Read(_)

View File

@@ -1,7 +1,7 @@
use std::fmt::Write;
use history::History; use history::History;
use keys::{KeyCode, KeyEvent, ModKeys}; use keys::{KeyCode, KeyEvent, ModKeys};
use linebuf::{LineBuf, SelectAnchor, SelectMode}; use linebuf::{LineBuf, SelectAnchor, SelectMode};
use std::fmt::Write;
use term::{KeyReader, Layout, LineWriter, PollReader, TermWriter, get_win_size}; use term::{KeyReader, Layout, LineWriter, PollReader, TermWriter, get_win_size};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use vicmd::{CmdFlags, Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd}; use vicmd::{CmdFlags, Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd};
@@ -12,16 +12,21 @@ use crate::expand::expand_prompt;
use crate::libsh::sys::TTY_FILENO; use crate::libsh::sys::TTY_FILENO;
use crate::libsh::utils::AutoCmdVecUtils; use crate::libsh::utils::AutoCmdVecUtils;
use crate::parse::lex::{LexStream, QuoteState}; use crate::parse::lex::{LexStream, QuoteState};
use crate::{prelude::*, state};
use crate::readline::complete::FuzzyCompleter; use crate::readline::complete::FuzzyCompleter;
use crate::readline::term::{Pos, TermReader, calc_str_width}; use crate::readline::term::{Pos, TermReader, calc_str_width};
use crate::readline::vimode::{ViEx, ViVerbatim}; use crate::readline::vimode::{ViEx, ViVerbatim};
use crate::state::{AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars}; use crate::state::{
AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, write_meta, write_vars,
};
use crate::{ use crate::{
libsh::error::ShResult, libsh::error::ShResult,
parse::lex::{self, LexFlags, Tk, TkFlags, TkRule}, parse::lex::{self, LexFlags, Tk, TkFlags, TkRule},
readline::{complete::{CompResponse, Completer}, highlight::Highlighter}, readline::{
complete::{CompResponse, Completer},
highlight::Highlighter,
},
}; };
use crate::{prelude::*, state};
pub mod complete; pub mod complete;
pub mod highlight; pub mod highlight;
@@ -208,11 +213,11 @@ impl Prompt {
if let Ok(expanded) = expand_prompt(&self.ps1_raw) { if let Ok(expanded) = expand_prompt(&self.ps1_raw) {
self.ps1_expanded = expanded; self.ps1_expanded = expanded;
} }
if let Some(psr_raw) = &self.psr_raw { if let Some(psr_raw) = &self.psr_raw
if let Ok(expanded) = expand_prompt(psr_raw) { && let Ok(expanded) = expand_prompt(psr_raw)
{
self.psr_expanded = Some(expanded); self.psr_expanded = Some(expanded);
} }
}
state::set_status(saved_status); state::set_status(saved_status);
self.dirty = false; self.dirty = false;
} }
@@ -276,7 +281,13 @@ impl ShedVi {
history: History::new()?, history: History::new()?,
needs_redraw: true, needs_redraw: true,
}; };
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(new.mode.report_mode().to_string()), VarFlags::NONE))?; write_vars(|v| {
v.set_var(
"SHED_VI_MODE",
VarKind::Str(new.mode.report_mode().to_string()),
VarFlags::NONE,
)
})?;
new.prompt.refresh(); new.prompt.refresh();
new.writer.flush_write("\n")?; // ensure we start on a new line, in case the previous command didn't end with a newline new.writer.flush_write("\n")?; // ensure we start on a new line, in case the previous command didn't end with a newline
new.print_line(false)?; new.print_line(false)?;
@@ -303,7 +314,9 @@ impl ShedVi {
} }
pub fn fix_column(&mut self) -> ShResult<()> { pub fn fix_column(&mut self) -> ShResult<()> {
self.writer.fix_cursor_column(&mut TermReader::new(*TTY_FILENO)) self
.writer
.fix_cursor_column(&mut TermReader::new(*TTY_FILENO))
} }
pub fn reset_active_widget(&mut self, full_redraw: bool) -> ShResult<()> { pub fn reset_active_widget(&mut self, full_redraw: bool) -> ShResult<()> {
@@ -449,7 +462,9 @@ impl ShedVi {
} }
self.needs_redraw = true; self.needs_redraw = true;
continue; continue;
} else if matches.len() == 1 && matches[0].compare(&self.pending_keymap) == KeyMapMatch::IsExact { } else if matches.len() == 1
&& matches[0].compare(&self.pending_keymap) == KeyMapMatch::IsExact
{
// We have a single exact match. Execute it. // We have a single exact match. Execute it.
let keymap = matches[0].clone(); let keymap = matches[0].clone();
self.pending_keymap.clear(); self.pending_keymap.clear();
@@ -467,7 +482,6 @@ impl ShedVi {
} }
} }
if let Some(event) = self.handle_key(key)? { if let Some(event) = self.handle_key(key)? {
return Ok(event); return Ok(event);
} }
@@ -543,7 +557,8 @@ impl ShedVi {
} }
if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key
&& !self.next_is_escaped { && !self.next_is_escaped
{
self.next_is_escaped = true; self.next_is_escaped = true;
} else { } else {
self.next_is_escaped = false; self.next_is_escaped = false;
@@ -569,7 +584,8 @@ impl ShedVi {
if cmd.is_submit_action() if cmd.is_submit_action()
&& !self.next_is_escaped && !self.next_is_escaped
&& !self.editor.buffer.ends_with('\\') && !self.editor.buffer.ends_with('\\')
&& (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete)) { && (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete))
{
self.editor.set_hint(None); self.editor.set_hint(None);
self.editor.cursor.set(self.editor.cursor_max()); self.editor.cursor.set(self.editor.cursor_max());
self.print_line(true)?; self.print_line(true)?;
@@ -694,7 +710,9 @@ impl ShedVi {
let hint = self.editor.get_hint_text(); let hint = self.editor.get_hint_text();
let do_hl = state::read_shopts(|s| s.prompt.highlight); let do_hl = state::read_shopts(|s| s.prompt.highlight);
self.highlighter.only_visual(!do_hl); self.highlighter.only_visual(!do_hl);
self.highlighter.load_input(&line, self.editor.cursor_byte_pos()); self
.highlighter
.load_input(&line, self.editor.cursor_byte_pos());
self.highlighter.expand_control_chars(); self.highlighter.expand_control_chars();
self.highlighter.highlight(); self.highlighter.highlight();
let highlighted = self.highlighter.take(); let highlighted = self.highlighter.take();
@@ -753,7 +771,8 @@ impl ShedVi {
&& !seq.is_empty() && !seq.is_empty()
&& !(prompt_string_right.is_some() && one_line) && !(prompt_string_right.is_some() && one_line)
&& seq_fits && seq_fits
&& self.mode.report_mode() != ModeReport::Ex { && self.mode.report_mode() != ModeReport::Ex
{
let to_col = self.writer.t_cols - calc_str_width(&seq); let to_col = self.writer.t_cols - calc_str_width(&seq);
let up = new_layout.cursor.row; // rows to move up from cursor to top line of prompt let up = new_layout.cursor.row; // rows to move up from cursor to top line of prompt
@@ -768,7 +787,8 @@ impl ShedVi {
write!(buf, "\x1b7{move_up}\x1b[{to_col}G{seq}\x1b8").unwrap(); write!(buf, "\x1b7{move_up}\x1b[{to_col}G{seq}\x1b8").unwrap();
} else if !final_draw } else if !final_draw
&& let Some(psr) = prompt_string_right && let Some(psr) = prompt_string_right
&& psr_fits { && psr_fits
{
let to_col = self.writer.t_cols - calc_str_width(&psr); let to_col = self.writer.t_cols - calc_str_width(&psr);
let down = new_layout.end.row - new_layout.cursor.row; let down = new_layout.end.row - new_layout.cursor.row;
let move_down = if down > 0 { let move_down = if down > 0 {
@@ -781,8 +801,17 @@ impl ShedVi {
// Record where the PSR ends so clear_rows can account for wrapping // Record where the PSR ends so clear_rows can account for wrapping
// if the terminal shrinks. // if the terminal shrinks.
let psr_start = Pos { row: new_layout.end.row, col: to_col }; let psr_start = Pos {
new_layout.psr_end = Some(Layout::calc_pos(self.writer.t_cols, &psr, psr_start, 0, false)); row: new_layout.end.row,
col: to_col,
};
new_layout.psr_end = Some(Layout::calc_pos(
self.writer.t_cols,
&psr,
psr_start,
0,
false,
));
} }
if let ModeReport::Ex = self.mode.report_mode() { if let ModeReport::Ex = self.mode.report_mode() {
@@ -803,7 +832,9 @@ impl ShedVi {
// Without PSR, use the content width on the cursor's row // Without PSR, use the content width on the cursor's row
(new_layout.end.col + 1).max(new_layout.cursor.col + 1) (new_layout.end.col + 1).max(new_layout.cursor.col + 1)
}; };
self.completer.set_prompt_line_context(preceding_width, new_layout.cursor.col); self
.completer
.set_prompt_line_context(preceding_width, new_layout.cursor.col);
self.completer.draw(&mut self.writer)?; self.completer.draw(&mut self.writer)?;
self.old_layout = Some(new_layout); self.old_layout = Some(new_layout);
@@ -821,7 +852,14 @@ impl ShedVi {
std::mem::swap(&mut self.mode, mode); std::mem::swap(&mut self.mode, mode);
self.editor.set_cursor_clamp(self.mode.clamp_cursor()); self.editor.set_cursor_clamp(self.mode.clamp_cursor());
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok(); write_vars(|v| {
v.set_var(
"SHED_VI_MODE",
VarKind::Str(self.mode.report_mode().to_string()),
VarFlags::NONE,
)
})
.ok();
self.prompt.refresh(); self.prompt.refresh();
let post_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PostModeChange)); let post_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PostModeChange));
@@ -834,7 +872,11 @@ impl ShedVi {
if cmd.is_mode_transition() { if cmd.is_mode_transition() {
let count = cmd.verb_count(); let count = cmd.verb_count();
let mut mode: Box<dyn ViMode> = if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) && cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) { let mut mode: Box<dyn ViMode> = if matches!(
self.mode.report_mode(),
ModeReport::Ex | ModeReport::Verbatim
) && cmd.flags.contains(CmdFlags::EXIT_CUR_MODE)
{
if let Some(saved) = self.saved_mode.take() { if let Some(saved) = self.saved_mode.take() {
saved saved
} else { } else {
@@ -847,13 +889,9 @@ impl ShedVi {
Box::new(ViInsert::new().with_count(count as u16)) Box::new(ViInsert::new().with_count(count as u16))
} }
Verb::ExMode => { Verb::ExMode => Box::new(ViEx::new()),
Box::new(ViEx::new())
}
Verb::VerbatimMode => { Verb::VerbatimMode => Box::new(ViVerbatim::new().with_count(count as u16)),
Box::new(ViVerbatim::new().with_count(count as u16))
}
Verb::NormalMode => Box::new(ViNormal::new()), Verb::NormalMode => Box::new(ViNormal::new()),
@@ -885,9 +923,18 @@ impl ShedVi {
self.swap_mode(&mut mode); self.swap_mode(&mut mode);
if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) { if matches!(
self.mode.report_mode(),
ModeReport::Ex | ModeReport::Verbatim
) {
self.saved_mode = Some(mode); self.saved_mode = Some(mode);
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?; write_vars(|v| {
v.set_var(
"SHED_VI_MODE",
VarKind::Str(self.mode.report_mode().to_string()),
VarFlags::NONE,
)
})?;
self.prompt.refresh(); self.prompt.refresh();
return Ok(()); return Ok(());
} }
@@ -912,10 +959,15 @@ impl ShedVi {
self.editor.clear_insert_mode_start_pos(); self.editor.clear_insert_mode_start_pos();
} }
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?; write_vars(|v| {
v.set_var(
"SHED_VI_MODE",
VarKind::Str(self.mode.report_mode().to_string()),
VarFlags::NONE,
)
})?;
self.prompt.refresh(); self.prompt.refresh();
return Ok(()); return Ok(());
} else if cmd.is_cmd_repeat() { } else if cmd.is_cmd_repeat() {
let Some(replay) = self.repeat_action.clone() else { let Some(replay) = self.repeat_action.clone() else {
@@ -989,8 +1041,7 @@ impl ShedVi {
} }
} }
if self.mode.report_mode() == ModeReport::Visual if self.mode.report_mode() == ModeReport::Visual && self.editor.select_range().is_none() {
&& self.editor.select_range().is_none() {
self.editor.stop_selecting(); self.editor.stop_selecting();
let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new()); let mut mode: Box<dyn ViMode> = Box::new(ViNormal::new());
self.swap_mode(&mut mode); self.swap_mode(&mut mode);
@@ -1026,7 +1077,10 @@ impl ShedVi {
} }
if cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) { if cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) {
let mut mode: Box<dyn ViMode> = if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) { let mut mode: Box<dyn ViMode> = if matches!(
self.mode.report_mode(),
ModeReport::Ex | ModeReport::Verbatim
) {
if let Some(saved) = self.saved_mode.take() { if let Some(saved) = self.saved_mode.take() {
saved saved
} else { } else {
@@ -1069,7 +1123,6 @@ pub fn annotate_input(input: &str) -> String {
.filter(|tk| !matches!(tk.class, TkRule::SOI | TkRule::EOI | TkRule::Null)) .filter(|tk| !matches!(tk.class, TkRule::SOI | TkRule::EOI | TkRule::Null))
.collect(); .collect();
for tk in tokens.into_iter().rev() { for tk in tokens.into_iter().rev() {
let insertions = annotate_token(tk); let insertions = annotate_token(tk);
for (pos, marker) in insertions { for (pos, marker) in insertions {
@@ -1112,9 +1165,7 @@ pub fn annotate_input_recursive(input: &str) -> String {
markers::PROC_SUB => match chars.peek().map(|(_, c)| *c) { markers::PROC_SUB => match chars.peek().map(|(_, c)| *c) {
Some('>') => ">(", Some('>') => ">(",
Some('<') => "<(", Some('<') => "<(",
_ => { _ => "<(",
"<("
}
}, },
markers::CMD_SUB => "$(", markers::CMD_SUB => "$(",
markers::SUBSH => "(", markers::SUBSH => "(",
@@ -1256,7 +1307,8 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
let mut insertions: Vec<(usize, Marker)> = vec![]; let mut insertions: Vec<(usize, Marker)> = vec![];
if token.class != TkRule::Str if token.class != TkRule::Str
&& let Some(marker) = marker_for(&token.class) { && let Some(marker) = marker_for(&token.class)
{
insertions.push((token.span.range().end, markers::RESET)); insertions.push((token.span.range().end, markers::RESET));
insertions.push((token.span.range().start, marker)); insertions.push((token.span.range().start, marker));
return insertions; return insertions;
@@ -1350,7 +1402,8 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|| *br_ch == '=' || *br_ch == '='
|| *br_ch == '/' // parameter expansion symbols || *br_ch == '/' // parameter expansion symbols
|| *br_ch == '?' || *br_ch == '?'
|| *br_ch == '$' // we're in some expansion like $foo$bar or ${foo$bar} || *br_ch == '$'
// we're in some expansion like $foo$bar or ${foo$bar}
{ {
token_chars.next(); token_chars.next();
} else if *br_ch == '}' { } else if *br_ch == '}' {
@@ -1371,7 +1424,8 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
while let Some((cur_i, var_ch)) = token_chars.peek() { while let Some((cur_i, var_ch)) = token_chars.peek() {
if var_ch.is_ascii_alphanumeric() if var_ch.is_ascii_alphanumeric()
|| ShellParam::from_char(var_ch).is_some() || ShellParam::from_char(var_ch).is_some()
|| *var_ch == '_' { || *var_ch == '_'
{
end_pos = *cur_i + 1; end_pos = *cur_i + 1;
token_chars.next(); token_chars.next();
} else { } else {
@@ -1399,7 +1453,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
} }
'\\' if qt_state.in_single() => { '\\' if qt_state.in_single() => {
token_chars.next(); token_chars.next();
if let Some(&(_,'\'')) = token_chars.peek() { if let Some(&(_, '\'')) = token_chars.peek() {
token_chars.next(); // consume the escaped single quote token_chars.next(); // consume the escaped single quote
} }
} }

View File

@@ -3,7 +3,8 @@ use std::{
env, env,
fmt::{Debug, Write}, fmt::{Debug, Write},
io::{BufRead, BufReader, Read}, io::{BufRead, BufReader, Read},
os::fd::{AsFd, BorrowedFd, RawFd}, sync::Arc, os::fd::{AsFd, BorrowedFd, RawFd},
sync::Arc,
}; };
use nix::{ use nix::{
@@ -17,14 +18,12 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use vte::{Parser, Perform}; use vte::{Parser, Perform};
pub use crate::libsh::guards::{RawModeGuard, raw_mode}; pub use crate::libsh::guards::{RawModeGuard, raw_mode};
use crate::state::{read_meta, write_meta};
use crate::{ use crate::{
libsh::error::{ShErr, ShErrKind, ShResult}, libsh::error::{ShErr, ShErrKind, ShResult},
readline::keys::{KeyCode, ModKeys}, readline::keys::{KeyCode, ModKeys},
state::read_shopts, state::read_shopts,
}; };
use crate::{
state::{read_meta, write_meta},
};
use super::keys::KeyEvent; use super::keys::KeyEvent;
@@ -457,7 +456,7 @@ impl Perform for KeyCollector {
}; };
KeyEvent(key, mods) KeyEvent(key, mods)
} }
([],'u') => { ([], 'u') => {
let codepoint = params.first().copied().unwrap_or(0); let codepoint = params.first().copied().unwrap_or(0);
let mods = params let mods = params
.get(1) .get(1)
@@ -472,7 +471,7 @@ impl Perform for KeyCollector {
if let Some(ch) = char::from_u32(codepoint as u32) { if let Some(ch) = char::from_u32(codepoint as u32) {
KeyCode::Char(ch) KeyCode::Char(ch)
} else { } else {
return return;
} }
} }
}; };
@@ -518,7 +517,10 @@ impl PollReader {
pub fn feed_bytes(&mut self, bytes: &[u8], verbatim: bool) { pub fn feed_bytes(&mut self, bytes: &[u8], verbatim: bool) {
if verbatim { if verbatim {
let seq = String::from_utf8_lossy(bytes).to_string(); let seq = String::from_utf8_lossy(bytes).to_string();
self.collector.push(KeyEvent(KeyCode::Verbatim(Arc::from(seq.as_str())), ModKeys::empty())); self.collector.push(KeyEvent(
KeyCode::Verbatim(Arc::from(seq.as_str())),
ModKeys::empty(),
));
} else if bytes == [b'\x1b'] { } else if bytes == [b'\x1b'] {
// Single escape byte - user pressed ESC key // Single escape byte - user pressed ESC key
self self
@@ -749,11 +751,7 @@ impl Layout {
} }
fn is_ctl_char(gr: &str) -> bool { fn is_ctl_char(gr: &str) -> bool {
gr.len() > 0 && !gr.is_empty() && gr.as_bytes()[0] <= 0x1F && gr != "\n" && gr != "\t" && gr != "\r"
gr.as_bytes()[0] <= 0x1F &&
gr != "\n" &&
gr != "\t" &&
gr != "\r"
} }
pub fn calc_pos(term_width: u16, s: &str, orig: Pos, left_margin: u16, raw_calc: bool) -> Pos { pub fn calc_pos(term_width: u16, s: &str, orig: Pos, left_margin: u16, raw_calc: bool) -> Pos {
@@ -871,7 +869,7 @@ impl TermWriter {
/// ///
/// Aping zsh with this but it's a nice feature. /// Aping zsh with this but it's a nice feature.
pub fn fix_cursor_column(&mut self, rdr: &mut TermReader) -> ShResult<()> { pub fn fix_cursor_column(&mut self, rdr: &mut TermReader) -> ShResult<()> {
let Some((_,c)) = self.get_cursor_pos(rdr)? else { let Some((_, c)) = self.get_cursor_pos(rdr)? else {
return Ok(()); return Ok(());
}; };
@@ -900,7 +898,9 @@ impl TermWriter {
let row = read_digits_until(rdr, ';')?; let row = read_digits_until(rdr, ';')?;
let col = read_digits_until(rdr, 'R')?; let col = read_digits_until(rdr, 'R')?;
let pos = if let Some(row) = row && let Some(col) = col { let pos = if let Some(row) = row
&& let Some(col) = col
{
Some((row as usize, col as usize)) Some((row as usize, col as usize))
} else { } else {
None None

View File

@@ -9,8 +9,8 @@ use crate::libsh::error::{ShErr, ShErrKind, ShResult};
use crate::readline::keys::KeyEvent; use crate::readline::keys::KeyEvent;
use crate::readline::linebuf::LineBuf; use crate::readline::linebuf::LineBuf;
use crate::readline::vicmd::{ use crate::readline::vicmd::{
Anchor, CmdFlags, Motion, MotionCmd, ReadSrc, RegisterName, To, Val, Verb, VerbCmd, Anchor, CmdFlags, Motion, MotionCmd, ReadSrc, RegisterName, To, Val, Verb, VerbCmd, ViCmd,
ViCmd, WriteDest, WriteDest,
}; };
use crate::readline::vimode::{ModeReport, ViInsert, ViMode}; use crate::readline::vimode::{ModeReport, ViInsert, ViMode};
use crate::state::write_meta; use crate::state::write_meta;
@@ -29,11 +29,10 @@ bitflags! {
} }
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
struct ExEditor { struct ExEditor {
buf: LineBuf, buf: LineBuf,
mode: ViInsert mode: ViInsert,
} }
impl ExEditor { impl ExEditor {
@@ -42,13 +41,12 @@ impl ExEditor {
} }
pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<()> { pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<()> {
let Some(cmd) = self.mode.handle_key(key) else { let Some(cmd) = self.mode.handle_key(key) else {
return Ok(()) return Ok(());
}; };
self.buf.exec_cmd(cmd) self.buf.exec_cmd(cmd)
} }
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct ViEx { pub struct ViEx {
pending_cmd: ExEditor, pending_cmd: ExEditor,
@@ -63,11 +61,10 @@ impl ViEx {
impl ViMode for ViEx { impl ViMode for ViEx {
// Ex mode can return errors, so we use this fallible method instead of the normal one // Ex mode can return errors, so we use this fallible method instead of the normal one
fn handle_key_fallible(&mut self, key: KeyEvent) -> ShResult<Option<ViCmd>> { fn handle_key_fallible(&mut self, key: KeyEvent) -> ShResult<Option<ViCmd>> {
use crate::readline::keys::{KeyEvent as E, KeyCode as C, ModKeys as M}; use crate::readline::keys::{KeyCode as C, KeyEvent as E, ModKeys as M};
log::debug!("[ViEx] handle_key_fallible: key={:?}", key); log::debug!("[ViEx] handle_key_fallible: key={:?}", key);
match key { match key {
E(C::Char('\r'), M::NONE) | E(C::Char('\r'), M::NONE) | E(C::Enter, M::NONE) => {
E(C::Enter, M::NONE) => {
let input = self.pending_cmd.buf.as_str(); let input = self.pending_cmd.buf.as_str();
log::debug!("[ViEx] Enter pressed, pending_cmd={:?}", input); log::debug!("[ViEx] Enter pressed, pending_cmd={:?}", input);
match parse_ex_cmd(input) { match parse_ex_cmd(input) {
@@ -146,10 +143,10 @@ impl ViMode for ViEx {
} }
} }
fn parse_ex_cmd(raw: &str) -> Result<Option<ViCmd>,Option<String>> { fn parse_ex_cmd(raw: &str) -> Result<Option<ViCmd>, Option<String>> {
let raw = raw.trim(); let raw = raw.trim();
if raw.is_empty() { if raw.is_empty() {
return Ok(None) return Ok(None);
} }
let mut chars = raw.chars().peekable(); let mut chars = raw.chars().peekable();
let (verb, motion) = { let (verb, motion) = {
@@ -160,14 +157,16 @@ fn parse_ex_cmd(raw: &str) -> Result<Option<ViCmd>,Option<String>> {
cmd_name.push(*ch); cmd_name.push(*ch);
chars.next(); chars.next();
} else { } else {
break break;
} }
} }
if !"global".starts_with(&cmd_name) { if !"global".starts_with(&cmd_name) {
return Err(None) return Err(None);
} }
let Some(result) = parse_global(&mut chars)? else { return Ok(None) }; let Some(result) = parse_global(&mut chars)? else {
(Some(VerbCmd(1,result.1)), Some(MotionCmd(1,result.0))) return Ok(None);
};
(Some(VerbCmd(1, result.1)), Some(MotionCmd(1, result.0)))
} else { } else {
(parse_ex_command(&mut chars)?.map(|v| VerbCmd(1, v)), None) (parse_ex_command(&mut chars)?.map(|v| VerbCmd(1, v)), None)
} }
@@ -204,16 +203,16 @@ fn unescape_shell_cmd(cmd: &str) -> String {
result result
} }
fn parse_ex_command(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<String>> { fn parse_ex_command(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
let mut cmd_name = String::new(); let mut cmd_name = String::new();
while let Some(ch) = chars.peek() { while let Some(ch) = chars.peek() {
if ch == &'!' { if ch == &'!' {
cmd_name.push(*ch); cmd_name.push(*ch);
chars.next(); chars.next();
break break;
} else if !ch.is_alphanumeric() { } else if !ch.is_alphanumeric() {
break break;
} }
cmd_name.push(*ch); cmd_name.push(*ch);
chars.next(); chars.next();
@@ -232,25 +231,36 @@ fn parse_ex_command(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Opti
_ if "read".starts_with(&cmd_name) => parse_read(chars), _ if "read".starts_with(&cmd_name) => parse_read(chars),
_ if "write".starts_with(&cmd_name) => parse_write(chars), _ if "write".starts_with(&cmd_name) => parse_write(chars),
_ if "substitute".starts_with(&cmd_name) => parse_substitute(chars), _ if "substitute".starts_with(&cmd_name) => parse_substitute(chars),
_ => Err(None) _ => Err(None),
} }
} }
fn parse_normal(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<String>> { fn parse_normal(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
chars.peeking_take_while(|c| c.is_whitespace()).for_each(drop); chars
.peeking_take_while(|c| c.is_whitespace())
.for_each(drop);
let seq: String = chars.collect(); let seq: String = chars.collect();
Ok(Some(Verb::Normal(seq))) Ok(Some(Verb::Normal(seq)))
} }
fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<String>> { fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
chars.peeking_take_while(|c| c.is_whitespace()).for_each(drop); chars
.peeking_take_while(|c| c.is_whitespace())
.for_each(drop);
let is_shell_read = if chars.peek() == Some(&'!') { chars.next(); true } else { false }; let is_shell_read = if chars.peek() == Some(&'!') {
chars.next();
true
} else {
false
};
let arg: String = chars.collect(); let arg: String = chars.collect();
if arg.trim().is_empty() { if arg.trim().is_empty() {
return Err(Some("Expected file path or shell command after ':r'".into())) return Err(Some(
"Expected file path or shell command after ':r'".into(),
));
} }
if is_shell_read { if is_shell_read {
@@ -263,18 +273,22 @@ fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<Str
fn get_path(path: &str) -> PathBuf { fn get_path(path: &str) -> PathBuf {
if let Some(stripped) = path.strip_prefix("~/") if let Some(stripped) = path.strip_prefix("~/")
&& let Some(home) = std::env::var_os("HOME") { && let Some(home) = std::env::var_os("HOME")
return PathBuf::from(home).join(stripped) {
return PathBuf::from(home).join(stripped);
} }
if path == "~" if path == "~"
&& let Some(home) = std::env::var_os("HOME") { && let Some(home) = std::env::var_os("HOME")
return PathBuf::from(home) {
return PathBuf::from(home);
} }
PathBuf::from(path) PathBuf::from(path)
} }
fn parse_write(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<String>> { fn parse_write(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
chars.peeking_take_while(|c| c.is_whitespace()).for_each(drop); chars
.peeking_take_while(|c| c.is_whitespace())
.for_each(drop);
let is_shell_write = chars.peek() == Some(&'!'); let is_shell_write = chars.peek() == Some(&'!');
if is_shell_write { if is_shell_write {
@@ -302,20 +316,27 @@ fn parse_write(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<St
Ok(Some(Verb::Write(dest))) Ok(Some(Verb::Write(dest)))
} }
fn parse_global(chars: &mut Peekable<Chars<'_>>) -> Result<Option<(Motion,Verb)>,Option<String>> { fn parse_global(chars: &mut Peekable<Chars<'_>>) -> Result<Option<(Motion, Verb)>, Option<String>> {
let is_negated = if chars.peek() == Some(&'!') { chars.next(); true } else { false }; let is_negated = if chars.peek() == Some(&'!') {
chars.next();
true
} else {
false
};
chars.peeking_take_while(|c| c.is_whitespace()).for_each(drop); // Ignore whitespace chars
.peeking_take_while(|c| c.is_whitespace())
.for_each(drop); // Ignore whitespace
let Some(delimiter) = chars.next() else { let Some(delimiter) = chars.next() else {
return Ok(Some((Motion::Null,Verb::RepeatGlobal))) return Ok(Some((Motion::Null, Verb::RepeatGlobal)));
}; };
if delimiter.is_alphanumeric() { if delimiter.is_alphanumeric() {
return Err(None) return Err(None);
} }
let global_pat = parse_pattern(chars, delimiter)?; let global_pat = parse_pattern(chars, delimiter)?;
let Some(command) = parse_ex_command(chars)? else { let Some(command) = parse_ex_command(chars)? else {
return Err(Some("Expected a command after global pattern".into())) return Err(Some("Expected a command after global pattern".into()));
}; };
if is_negated { if is_negated {
Ok(Some((Motion::NotGlobal(Val::new_str(global_pat)), command))) Ok(Some((Motion::NotGlobal(Val::new_str(global_pat)), command)))
@@ -324,14 +345,16 @@ fn parse_global(chars: &mut Peekable<Chars<'_>>) -> Result<Option<(Motion,Verb)>
} }
} }
fn parse_substitute(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Option<String>> { fn parse_substitute(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
while chars.peek().is_some_and(|c| c.is_whitespace()) { chars.next(); } // Ignore whitespace while chars.peek().is_some_and(|c| c.is_whitespace()) {
chars.next();
} // Ignore whitespace
let Some(delimiter) = chars.next() else { let Some(delimiter) = chars.next() else {
return Ok(Some(Verb::RepeatSubstitute)) return Ok(Some(Verb::RepeatSubstitute));
}; };
if delimiter.is_alphanumeric() { if delimiter.is_alphanumeric() {
return Err(None) return Err(None);
} }
let old_pat = parse_pattern(chars, delimiter)?; let old_pat = parse_pattern(chars, delimiter)?;
let new_pat = parse_pattern(chars, delimiter)?; let new_pat = parse_pattern(chars, delimiter)?;
@@ -342,13 +365,16 @@ fn parse_substitute(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>,Opti
'i' => flags |= SubFlags::IGNORE_CASE, 'i' => flags |= SubFlags::IGNORE_CASE,
'I' => flags |= SubFlags::NO_IGNORE_CASE, 'I' => flags |= SubFlags::NO_IGNORE_CASE,
'n' => flags |= SubFlags::SHOW_COUNT, 'n' => flags |= SubFlags::SHOW_COUNT,
_ => return Err(None) _ => return Err(None),
} }
} }
Ok(Some(Verb::Substitute(old_pat, new_pat, flags))) Ok(Some(Verb::Substitute(old_pat, new_pat, flags)))
} }
fn parse_pattern(chars: &mut Peekable<Chars<'_>>, delimiter: char) -> Result<String,Option<String>> { fn parse_pattern(
chars: &mut Peekable<Chars<'_>>,
delimiter: char,
) -> Result<String, Option<String>> {
let mut pat = String::new(); let mut pat = String::new();
let mut closed = false; let mut closed = false;
while let Some(ch) = chars.next() { while let Some(ch) = chars.next() {
@@ -357,7 +383,7 @@ fn parse_pattern(chars: &mut Peekable<Chars<'_>>, delimiter: char) -> Result<Str
if chars.peek().is_some_and(|c| *c == delimiter) { if chars.peek().is_some_and(|c| *c == delimiter) {
// We escaped the delimiter, so we consume the escape char and continue // We escaped the delimiter, so we consume the escape char and continue
pat.push(chars.next().unwrap()); pat.push(chars.next().unwrap());
continue continue;
} else { } else {
// The escape char is probably for the regex in the pattern // The escape char is probably for the regex in the pattern
pat.push(ch); pat.push(ch);
@@ -368,9 +394,9 @@ fn parse_pattern(chars: &mut Peekable<Chars<'_>>, delimiter: char) -> Result<Str
} }
_ if ch == delimiter => { _ if ch == delimiter => {
closed = true; closed = true;
break break;
} }
_ => pat.push(ch) _ => pat.push(ch),
} }
} }
if !closed { if !closed {

View File

@@ -1,8 +1,6 @@
use super::{common_cmds, CmdReplay, ModeReport, ViMode}; use super::{CmdReplay, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::vicmd::{ use crate::readline::vicmd::{Direction, Motion, MotionCmd, To, Verb, VerbCmd, ViCmd, Word};
Direction, Motion, MotionCmd, To, Verb, VerbCmd, ViCmd, Word,
};
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct ViInsert { pub struct ViInsert {

View File

@@ -4,23 +4,21 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::libsh::error::ShResult; use crate::libsh::error::ShResult;
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::vicmd::{ use crate::readline::vicmd::{Motion, MotionCmd, To, Verb, VerbCmd, ViCmd};
Motion, MotionCmd, To, Verb, VerbCmd, ViCmd,
};
pub mod ex;
pub mod insert; pub mod insert;
pub mod normal; pub mod normal;
pub mod replace; pub mod replace;
pub mod visual;
pub mod ex;
pub mod verbatim; pub mod verbatim;
pub mod visual;
pub use ex::ViEx; pub use ex::ViEx;
pub use insert::ViInsert; pub use insert::ViInsert;
pub use normal::ViNormal; pub use normal::ViNormal;
pub use replace::ViReplace; pub use replace::ViReplace;
pub use visual::ViVisual;
pub use verbatim::ViVerbatim; pub use verbatim::ViVerbatim;
pub use visual::ViVisual;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModeReport { pub enum ModeReport {
@@ -73,13 +71,17 @@ pub enum CmdState {
} }
pub trait ViMode { pub trait ViMode {
fn handle_key_fallible(&mut self, key: E) -> ShResult<Option<ViCmd>> { Ok(self.handle_key(key)) } fn handle_key_fallible(&mut self, key: E) -> ShResult<Option<ViCmd>> {
Ok(self.handle_key(key))
}
fn handle_key(&mut self, key: E) -> Option<ViCmd>; fn handle_key(&mut self, key: E) -> Option<ViCmd>;
fn is_repeatable(&self) -> bool; fn is_repeatable(&self) -> bool;
fn as_replay(&self) -> Option<CmdReplay>; fn as_replay(&self) -> Option<CmdReplay>;
fn cursor_style(&self) -> String; fn cursor_style(&self) -> String;
fn pending_seq(&self) -> Option<String>; fn pending_seq(&self) -> Option<String>;
fn pending_cursor(&self) -> Option<usize> { None } fn pending_cursor(&self) -> Option<usize> {
None
}
fn move_cursor_on_undo(&self) -> bool; fn move_cursor_on_undo(&self) -> bool;
fn clamp_cursor(&self) -> bool; fn clamp_cursor(&self) -> bool;
fn hist_scroll_start_pos(&self) -> Option<To>; fn hist_scroll_start_pos(&self) -> Option<To>;

View File

@@ -1,7 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use super::{common_cmds, CmdReplay, CmdState, ModeReport, ViMode}; use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::vicmd::{ use crate::readline::vicmd::{
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
@@ -316,7 +316,7 @@ impl ViNormal {
motion: None, motion: None,
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: self.flags(), flags: self.flags(),
}) });
} }
'i' => { 'i' => {
return Some(ViCmd { return Some(ViCmd {
@@ -757,7 +757,9 @@ impl ViMode for ViNormal {
flags: self.flags(), flags: self.flags(),
}), }),
E(K::Char('A'), M::CTRL) => { E(K::Char('A'), M::CTRL) => {
let count = self.parse_count(&mut self.pending_seq.chars().peekable()).unwrap_or(1) as u16; let count = self
.parse_count(&mut self.pending_seq.chars().peekable())
.unwrap_or(1) as u16;
self.pending_seq.clear(); self.pending_seq.clear();
Some(ViCmd { Some(ViCmd {
register: Default::default(), register: Default::default(),
@@ -766,9 +768,11 @@ impl ViMode for ViNormal {
raw_seq: "".into(), raw_seq: "".into(),
flags: self.flags(), flags: self.flags(),
}) })
}, }
E(K::Char('X'), M::CTRL) => { E(K::Char('X'), M::CTRL) => {
let count = self.parse_count(&mut self.pending_seq.chars().peekable()).unwrap_or(1) as u16; let count = self
.parse_count(&mut self.pending_seq.chars().peekable())
.unwrap_or(1) as u16;
self.pending_seq.clear(); self.pending_seq.clear();
Some(ViCmd { Some(ViCmd {
register: Default::default(), register: Default::default(),
@@ -777,7 +781,7 @@ impl ViMode for ViNormal {
raw_seq: "".into(), raw_seq: "".into(),
flags: self.flags(), flags: self.flags(),
}) })
}, }
E(K::Char(ch), M::NONE) => self.try_parse(ch), E(K::Char(ch), M::NONE) => self.try_parse(ch),
E(K::Backspace, M::NONE) => Some(ViCmd { E(K::Backspace, M::NONE) => Some(ViCmd {

View File

@@ -1,8 +1,6 @@
use super::{common_cmds, CmdReplay, ModeReport, ViMode}; use super::{CmdReplay, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::vicmd::{ use crate::readline::vicmd::{Direction, Motion, MotionCmd, To, Verb, VerbCmd, ViCmd, Word};
Direction, Motion, MotionCmd, To, Verb, VerbCmd, ViCmd, Word,
};
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct ViReplace { pub struct ViReplace {

View File

@@ -1,14 +1,11 @@
use super::{common_cmds, CmdReplay, ModeReport, ViMode}; use super::{CmdReplay, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E};
use crate::readline::register::Register; use crate::readline::vicmd::{CmdFlags, RegisterName, To, Verb, VerbCmd, ViCmd};
use crate::readline::vicmd::{
CmdFlags, Direction, Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd, Word
};
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct ViVerbatim { pub struct ViVerbatim {
sent_cmd: Vec<ViCmd>, sent_cmd: Vec<ViCmd>,
repeat_count: u16 repeat_count: u16,
} }
impl ViVerbatim { impl ViVerbatim {
@@ -16,20 +13,24 @@ impl ViVerbatim {
Self::default() Self::default()
} }
pub fn with_count(self, repeat_count: u16) -> Self { pub fn with_count(self, repeat_count: u16) -> Self {
Self { repeat_count, ..self } Self {
repeat_count,
..self
}
} }
} }
impl ViMode for ViVerbatim { impl ViMode for ViVerbatim {
fn handle_key(&mut self, key: E) -> Option<ViCmd> { fn handle_key(&mut self, key: E) -> Option<ViCmd> {
match key { match key {
E(K::Verbatim(seq),_mods) => { E(K::Verbatim(seq), _mods) => {
log::debug!("Received verbatim key sequence: {:?}", seq); log::debug!("Received verbatim key sequence: {:?}", seq);
let cmd = ViCmd { register: RegisterName::default(), let cmd = ViCmd {
verb: Some(VerbCmd(1,Verb::Insert(seq.to_string()))), register: RegisterName::default(),
verb: Some(VerbCmd(1, Verb::Insert(seq.to_string()))),
motion: None, motion: None,
raw_seq: seq.to_string(), raw_seq: seq.to_string(),
flags: CmdFlags::EXIT_CUR_MODE flags: CmdFlags::EXIT_CUR_MODE,
}; };
self.sent_cmd.push(cmd.clone()); self.sent_cmd.push(cmd.clone());
Some(cmd) Some(cmd)

View File

@@ -1,7 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use super::{common_cmds, CmdReplay, CmdState, ModeReport, ViMode}; use super::{CmdReplay, CmdState, ModeReport, ViMode, common_cmds};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M}; use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::vicmd::{ use crate::readline::vicmd::{
Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb, Anchor, Bound, CmdFlags, Dest, Direction, Motion, MotionCmd, RegisterName, TextObj, To, Verb,
@@ -136,7 +136,7 @@ impl ViVisual {
motion: None, motion: None,
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}) });
} }
'x' => { 'x' => {
chars = chars_clone; chars = chars_clone;
@@ -615,7 +615,9 @@ impl ViMode for ViVisual {
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}), }),
E(K::Char('A'), M::CTRL) => { E(K::Char('A'), M::CTRL) => {
let count = self.parse_count(&mut self.pending_seq.chars().peekable()).unwrap_or(1) as u16; let count = self
.parse_count(&mut self.pending_seq.chars().peekable())
.unwrap_or(1) as u16;
self.pending_seq.clear(); self.pending_seq.clear();
Some(ViCmd { Some(ViCmd {
register: Default::default(), register: Default::default(),
@@ -624,9 +626,11 @@ impl ViMode for ViVisual {
raw_seq: "".into(), raw_seq: "".into(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}) })
}, }
E(K::Char('X'), M::CTRL) => { E(K::Char('X'), M::CTRL) => {
let count = self.parse_count(&mut self.pending_seq.chars().peekable()).unwrap_or(1) as u16; let count = self
.parse_count(&mut self.pending_seq.chars().peekable())
.unwrap_or(1) as u16;
self.pending_seq.clear(); self.pending_seq.clear();
Some(ViCmd { Some(ViCmd {
register: Default::default(), register: Default::default(),

View File

@@ -104,12 +104,10 @@ impl ShOpts {
"core" => self.core.set(&remainder, val)?, "core" => self.core.set(&remainder, val)?,
"prompt" => self.prompt.set(&remainder, val)?, "prompt" => self.prompt.set(&remainder, val)?,
_ => { _ => {
return Err( return Err(ShErr::simple(
ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
"shopt: expected 'core' or 'prompt' in shopt key", "shopt: expected 'core' or 'prompt' in shopt key",
) ));
);
} }
} }
Ok(()) Ok(())
@@ -129,12 +127,10 @@ impl ShOpts {
match key { match key {
"core" => self.core.get(&remainder), "core" => self.core.get(&remainder),
"prompt" => self.prompt.get(&remainder), "prompt" => self.prompt.get(&remainder),
_ => Err( _ => Err(ShErr::simple(
ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
"shopt: Expected 'core' or 'prompt' in shopt key", "shopt: Expected 'core' or 'prompt' in shopt key",
) )),
),
} }
} }
} }
@@ -227,12 +223,10 @@ impl ShOptCore {
self.max_recurse_depth = val; self.max_recurse_depth = val;
} }
_ => { _ => {
return Err( return Err(ShErr::simple(
ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
format!("shopt: Unexpected 'core' option '{opt}'"), format!("shopt: Unexpected 'core' option '{opt}'"),
) ));
);
} }
} }
Ok(()) Ok(())
@@ -289,12 +283,10 @@ impl ShOptCore {
output.push_str(&format!("{}", self.max_recurse_depth)); output.push_str(&format!("{}", self.max_recurse_depth));
Ok(Some(output)) Ok(Some(output))
} }
_ => Err( _ => Err(ShErr::simple(
ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
format!("shopt: Unexpected 'core' option '{query}'"), format!("shopt: Unexpected 'core' option '{query}'"),
) )),
),
} }
} }
} }
@@ -344,6 +336,7 @@ pub struct ShOptPrompt {
pub auto_indent: bool, pub auto_indent: bool,
pub linebreak_on_incomplete: bool, pub linebreak_on_incomplete: bool,
pub leader: String, pub leader: String,
pub line_numbers: bool,
} }
impl ShOptPrompt { impl ShOptPrompt {
@@ -406,16 +399,23 @@ impl ShOptPrompt {
"leader" => { "leader" => {
self.leader = val.to_string(); self.leader = val.to_string();
} }
"line_numbers" => {
let Ok(val) = val.parse::<bool>() else {
return Err(ShErr::simple(
ShErrKind::SyntaxErr,
"shopt: expected 'true' or 'false' for line_numbers value",
));
};
self.line_numbers = val;
}
"custom" => { "custom" => {
todo!() todo!()
} }
_ => { _ => {
return Err( return Err(ShErr::simple(
ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
format!("shopt: Unexpected 'prompt' option '{opt}'"), format!("shopt: Unexpected 'prompt' option '{opt}'"),
) ));
);
} }
} }
Ok(()) Ok(())
@@ -464,17 +464,19 @@ impl ShOptPrompt {
Ok(Some(output)) Ok(Some(output))
} }
"leader" => { "leader" => {
let mut output = let mut output = String::from("The leader key sequence used in keymap bindings\n");
String::from("The leader key sequence used in keymap bindings\n");
output.push_str(&self.leader); output.push_str(&self.leader);
Ok(Some(output)) Ok(Some(output))
} }
_ => Err( "line_numbers" => {
ShErr::simple( let mut output = String::from("Whether to display line numbers in multiline input\n");
output.push_str(&format!("{}", self.line_numbers));
Ok(Some(output))
}
_ => Err(ShErr::simple(
ShErrKind::SyntaxErr, ShErrKind::SyntaxErr,
format!("shopt: Unexpected 'prompt' option '{query}'"), format!("shopt: Unexpected 'prompt' option '{query}'"),
) )),
),
} }
} }
} }
@@ -493,6 +495,7 @@ impl Display for ShOptPrompt {
self.linebreak_on_incomplete self.linebreak_on_incomplete
)); ));
output.push(format!("leader = {}", self.leader)); output.push(format!("leader = {}", self.leader));
output.push(format!("line_numbers = {}", self.line_numbers));
let final_output = output.join("\n"); let final_output = output.join("\n");
@@ -510,6 +513,7 @@ impl Default for ShOptPrompt {
auto_indent: true, auto_indent: true,
linebreak_on_incomplete: true, linebreak_on_incomplete: true,
leader: "\\".to_string(), leader: "\\".to_string(),
line_numbers: true,
} }
} }
} }

View File

@@ -148,7 +148,6 @@ pub fn sig_setup(is_login: bool) {
sigaction(Signal::SIGSYS, &action).unwrap(); sigaction(Signal::SIGSYS, &action).unwrap();
} }
if is_login { if is_login {
let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0)); let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0));
take_term().ok(); take_term().ok();
@@ -318,7 +317,8 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
for cmd in post_job_hooks { for cmd in post_job_hooks {
let AutoCmd { pattern, command } = cmd; let AutoCmd { pattern, command } = cmd;
if let Some(pat) = pattern if let Some(pat) = pattern
&& job.get_cmds().iter().all(|p| !pat.is_match(p)) { && job.get_cmds().iter().all(|p| !pat.is_match(p))
{
continue; continue;
} }

View File

@@ -12,14 +12,30 @@ use nix::unistd::{User, gethostname, getppid};
use regex::Regex; use regex::Regex;
use crate::{ use crate::{
builtin::{BUILTINS, keymap::{KeyMap, KeyMapFlags, KeyMapMatch}, map::MapNode, trap::TrapTarget}, exec_input, expand::expand_keymap, jobs::JobTab, libsh::{ builtin::{
error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt BUILTINS,
}, parse::{ keymap::{KeyMap, KeyMapFlags, KeyMapMatch},
map::MapNode,
trap::TrapTarget,
},
exec_input,
expand::expand_keymap,
jobs::JobTab,
libsh::{
error::{ShErr, ShErrKind, ShResult},
utils::VecDequeExt,
},
parse::{
ConjunctNode, NdRule, Node, ParsedSrc, ConjunctNode, NdRule, Node, ParsedSrc,
lex::{LexFlags, LexStream, Span, Tk}, lex::{LexFlags, LexStream, Span, Tk},
}, prelude::*, readline::{ },
complete::{BashCompSpec, CompSpec}, keys::KeyEvent, markers prelude::*,
}, shopt::ShOpts readline::{
complete::{BashCompSpec, CompSpec},
keys::KeyEvent,
markers,
},
shopt::ShOpts,
}; };
pub struct Shed { pub struct Shed {
@@ -299,10 +315,12 @@ impl ScopeStack {
{ {
match var.kind_mut() { match var.kind_mut() {
VarKind::Arr(items) => return Ok(items), VarKind::Arr(items) => return Ok(items),
_ => return Err(ShErr::simple( _ => {
return Err(ShErr::simple(
ShErrKind::ExecFail, ShErrKind::ExecFail,
format!("Variable '{}' is not an array", var_name), format!("Variable '{}' is not an array", var_name),
)), ));
}
} }
} }
} }
@@ -369,7 +387,7 @@ impl ScopeStack {
pub fn get_map(&self, map_name: &str) -> Option<&MapNode> { pub fn get_map(&self, map_name: &str) -> Option<&MapNode> {
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
if let Some(map) = scope.get_map(map_name) { if let Some(map) = scope.get_map(map_name) {
return Some(map) return Some(map);
} }
} }
None None
@@ -377,14 +395,13 @@ impl ScopeStack {
pub fn get_map_mut(&mut self, map_name: &str) -> Option<&mut MapNode> { pub fn get_map_mut(&mut self, map_name: &str) -> Option<&mut MapNode> {
for scope in self.scopes.iter_mut().rev() { for scope in self.scopes.iter_mut().rev() {
if let Some(map) = scope.get_map_mut(map_name) { if let Some(map) = scope.get_map_mut(map_name) {
return Some(map) return Some(map);
} }
} }
None None
} }
pub fn set_map(&mut self, map_name: &str, map: MapNode, local: bool) { pub fn set_map(&mut self, map_name: &str, map: MapNode, local: bool) {
if local if local && let Some(scope) = self.scopes.last_mut() {
&& let Some(scope) = self.scopes.last_mut() {
scope.set_map(map_name, map); scope.set_map(map_name, map);
} else if let Some(scope) = self.scopes.first_mut() { } else if let Some(scope) = self.scopes.first_mut() {
scope.set_map(map_name, map); scope.set_map(map_name, map);
@@ -481,7 +498,7 @@ thread_local! {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ShAlias { pub struct ShAlias {
pub body: String, pub body: String,
pub source: Span pub source: Span,
} }
impl Display for ShAlias { impl Display for ShAlias {
@@ -496,13 +513,13 @@ impl Display for ShAlias {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ShFunc { pub struct ShFunc {
pub body: Node, pub body: Node,
pub source: Span pub source: Span,
} }
impl ShFunc { impl ShFunc {
pub fn new(mut src: ParsedSrc, source: Span) -> Self { pub fn new(mut src: ParsedSrc, source: Span) -> Self {
let body = Self::extract_brc_grp_hack(src.extract_nodes()); let body = Self::extract_brc_grp_hack(src.extract_nodes());
Self{ body, source } Self { body, source }
} }
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node { fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
// FIXME: find a better way to do this // FIXME: find a better way to do this
@@ -533,7 +550,7 @@ pub enum AutoCmdKind {
PostPrompt, PostPrompt,
PreModeChange, PreModeChange,
PostModeChange, PostModeChange,
OnExit OnExit,
} }
impl Display for AutoCmdKind { impl Display for AutoCmdKind {
@@ -590,7 +607,7 @@ pub struct LogTab {
aliases: HashMap<String, ShAlias>, aliases: HashMap<String, ShAlias>,
traps: HashMap<TrapTarget, String>, traps: HashMap<TrapTarget, String>,
keymaps: Vec<KeyMap>, keymaps: Vec<KeyMap>,
autocmds: HashMap<AutoCmdKind, Vec<AutoCmd>> autocmds: HashMap<AutoCmdKind, Vec<AutoCmd>>,
} }
impl LogTab { impl LogTab {
@@ -635,7 +652,8 @@ impl LogTab {
self.keymaps.retain(|km| km.keys != keys); self.keymaps.retain(|km| km.keys != keys);
} }
pub fn keymaps_filtered(&self, flags: KeyMapFlags, pending: &[KeyEvent]) -> Vec<KeyMap> { pub fn keymaps_filtered(&self, flags: KeyMapFlags, pending: &[KeyEvent]) -> Vec<KeyMap> {
self.keymaps self
.keymaps
.iter() .iter()
.filter(|km| km.flags.intersects(flags) && km.compare(pending) != KeyMapMatch::NoMatch) .filter(|km| km.flags.intersects(flags) && km.compare(pending) != KeyMapMatch::NoMatch)
.cloned() .cloned()
@@ -666,7 +684,13 @@ impl LogTab {
&self.aliases &self.aliases
} }
pub fn insert_alias(&mut self, name: &str, body: &str, source: Span) { pub fn insert_alias(&mut self, name: &str, body: &str, source: Span) {
self.aliases.insert(name.into(), ShAlias { body: body.into(), source }); self.aliases.insert(
name.into(),
ShAlias {
body: body.into(),
source,
},
);
} }
pub fn get_alias(&self, name: &str) -> Option<ShAlias> { pub fn get_alias(&self, name: &str) -> Option<ShAlias> {
self.aliases.get(name).cloned() self.aliases.get(name).cloned()
@@ -896,7 +920,7 @@ pub struct VarTab {
params: HashMap<ShellParam, String>, params: HashMap<ShellParam, String>,
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift` straightforward */ sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift` straightforward */
maps: HashMap<String, MapNode> maps: HashMap<String, MapNode>,
} }
impl VarTab { impl VarTab {
@@ -1217,7 +1241,7 @@ pub struct MetaTab {
comp_specs: HashMap<String, Box<dyn CompSpec>>, comp_specs: HashMap<String, Box<dyn CompSpec>>,
// pending keys from widget function // pending keys from widget function
pending_widget_keys: Vec<KeyEvent> pending_widget_keys: Vec<KeyEvent>,
} }
impl MetaTab { impl MetaTab {
@@ -1589,12 +1613,12 @@ pub fn get_shopt(path: &str) -> String {
read_shopts(|s| s.get(path)).unwrap().unwrap() read_shopts(|s| s.get(path)).unwrap().unwrap()
} }
pub fn with_vars<F,H,V,T>(vars: H, f: F) -> T pub fn with_vars<F, H, V, T>(vars: H, f: F) -> T
where where
F: FnOnce() -> T, F: FnOnce() -> T,
H: Into<HashMap<String,V>>, H: Into<HashMap<String, V>>,
V: Into<Var> { V: Into<Var>,
{
let snapshot = read_vars(|v| v.clone()); let snapshot = read_vars(|v| v.clone());
let vars = vars.into(); let vars = vars.into();
for (name, val) in vars { for (name, val) in vars {
@@ -1614,36 +1638,59 @@ pub fn change_dir<P: AsRef<Path>>(dir: P) -> ShResult<()> {
let post_cd = read_logic(|l| l.get_autocmds(AutoCmdKind::PostChangeDir)); let post_cd = read_logic(|l| l.get_autocmds(AutoCmdKind::PostChangeDir));
let current_dir = env::current_dir()?.display().to_string(); let current_dir = env::current_dir()?.display().to_string();
with_vars([("_NEW_DIR".into(), dir_raw.as_str()), ("_OLD_DIR".into(), current_dir.as_str())], || { with_vars(
[
("_NEW_DIR".into(), dir_raw.as_str()),
("_OLD_DIR".into(), current_dir.as_str()),
],
|| {
for cmd in pre_cd { for cmd in pre_cd {
let AutoCmd { command, pattern } = cmd; let AutoCmd { command, pattern } = cmd;
if let Some(pat) = pattern if let Some(pat) = pattern
&& !pat.is_match(dir_raw) { && !pat.is_match(dir_raw)
{
continue; continue;
} }
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd (pre-changedir)".to_string())) { if let Err(e) = exec_input(
command.clone(),
None,
false,
Some("autocmd (pre-changedir)".to_string()),
) {
e.print_error(); e.print_error();
}; };
} }
}); },
);
env::set_current_dir(dir)?; env::set_current_dir(dir)?;
with_vars([("_NEW_DIR".into(), dir_raw.as_str()), ("_OLD_DIR".into(), current_dir.as_str())], || { with_vars(
[
("_NEW_DIR".into(), dir_raw.as_str()),
("_OLD_DIR".into(), current_dir.as_str()),
],
|| {
for cmd in post_cd { for cmd in post_cd {
let AutoCmd { command, pattern } = cmd; let AutoCmd { command, pattern } = cmd;
if let Some(pat) = pattern if let Some(pat) = pattern
&& !pat.is_match(dir_raw) { && !pat.is_match(dir_raw)
{
continue; continue;
} }
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd (post-changedir)".to_string())) { if let Err(e) = exec_input(
command.clone(),
None,
false,
Some("autocmd (post-changedir)".to_string()),
) {
e.print_error(); e.print_error();
}; };
} }
}); },
);
Ok(()) Ok(())
} }