rustfmt'd the codebase
This commit is contained in:
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,52 @@
|
|||||||
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,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ArrOpOpts {
|
pub struct ArrOpOpts {
|
||||||
count: usize,
|
count: usize,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
var: Option<String>,
|
var: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ArrOpOpts {
|
impl Default for ArrOpOpts {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
count: 1,
|
count: 1,
|
||||||
reverse: false,
|
reverse: false,
|
||||||
var: None,
|
var: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
||||||
@@ -49,40 +57,42 @@ fn arr_pop_inner(node: Node, end: End) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
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(),
|
||||||
End::Back => arr.pop_back(),
|
End::Back => arr.pop_back(),
|
||||||
};
|
};
|
||||||
let Some(popped) = write_vars(|v| v.get_arr_mut(&arg).ok().and_then(pop)) else {
|
let Some(popped) = write_vars(|v| v.get_arr_mut(&arg).ok().and_then(pop)) else {
|
||||||
status = 1;
|
status = 1;
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
status = 0;
|
status = 0;
|
||||||
|
|
||||||
if let Some(ref var) = arr_op_opts.var {
|
if let Some(ref var) = arr_op_opts.var {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(popped), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(var, VarKind::Str(popped), VarFlags::NONE))?;
|
||||||
} else {
|
} else {
|
||||||
write(stdout, popped.as_bytes())?;
|
write(stdout, popped.as_bytes())?;
|
||||||
write(stdout, b"\n")?;
|
write(stdout, b"\n")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(status);
|
state::set_status(status);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
|
fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -91,49 +101,60 @@ fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
||||||
let push_val = val.clone();
|
let push_val = val.clone();
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
if let Ok(arr) = v.get_arr_mut(&name) {
|
if let Ok(arr) = v.get_arr_mut(&name) {
|
||||||
match end {
|
match end {
|
||||||
End::Front => arr.push_front(push_val),
|
End::Front => arr.push_front(push_val),
|
||||||
End::Back => arr.push_back(push_val),
|
End::Back => arr.push_back(push_val),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
v.set_var(&name, VarKind::Arr(VecDeque::from([push_val])), VarFlags::NONE)
|
v.set_var(
|
||||||
}
|
&name,
|
||||||
}).blame(span)?;
|
VarKind::Arr(VecDeque::from([push_val])),
|
||||||
}
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.blame(span)?;
|
||||||
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_pop(node: Node) -> ShResult<()> {
|
pub fn arr_pop(node: Node) -> ShResult<()> {
|
||||||
arr_pop_inner(node, End::Back)
|
arr_pop_inner(node, End::Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_fpop(node: Node) -> ShResult<()> {
|
pub fn arr_fpop(node: Node) -> ShResult<()> {
|
||||||
arr_pop_inner(node, End::Front)
|
arr_pop_inner(node, End::Front)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_push(node: Node) -> ShResult<()> {
|
pub fn arr_push(node: Node) -> ShResult<()> {
|
||||||
arr_push_inner(node, End::Back)
|
arr_push_inner(node, End::Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_fpush(node: Node) -> ShResult<()> {
|
pub fn arr_fpush(node: Node) -> ShResult<()> {
|
||||||
arr_push_inner(node, End::Front)
|
arr_push_inner(node, End::Front)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arr_rotate(node: Node) -> ShResult<()> {
|
pub fn arr_rotate(node: Node) -> ShResult<()> {
|
||||||
@@ -145,52 +166,63 @@ pub fn arr_rotate(node: Node) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
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<()> {
|
||||||
let arr = v.get_arr_mut(&arg)?;
|
let arr = v.get_arr_mut(&arg)?;
|
||||||
if arr_op_opts.reverse {
|
if arr_op_opts.reverse {
|
||||||
arr.rotate_right(arr_op_opts.count.min(arr.len()));
|
arr.rotate_right(arr_op_opts.count.min(arr.len()));
|
||||||
} else {
|
} else {
|
||||||
arr.rotate_left(arr_op_opts.count.min(arr.len()));
|
arr.rotate_left(arr_op_opts.count.min(arr.len()));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_arr_op_opts(opts: Vec<Opt>) -> ShResult<ArrOpOpts> {
|
pub fn get_arr_op_opts(opts: Vec<Opt>) -> ShResult<ArrOpOpts> {
|
||||||
let mut arr_op_opts = ArrOpOpts::default();
|
let mut arr_op_opts = ArrOpOpts::default();
|
||||||
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,
|
||||||
Opt::Short('r') => {
|
"missing count for -c".to_string(),
|
||||||
arr_op_opts.reverse = true;
|
));
|
||||||
}
|
}
|
||||||
Opt::ShortWithArg('v', var) => {
|
Opt::Short('r') => {
|
||||||
arr_op_opts.var = Some(var);
|
arr_op_opts.reverse = true;
|
||||||
}
|
}
|
||||||
Opt::Short('v') => {
|
Opt::ShortWithArg('v', var) => {
|
||||||
return Err(ShErr::simple(ShErrKind::ParseErr, "missing variable name for -v".to_string()));
|
arr_op_opts.var = Some(var);
|
||||||
}
|
}
|
||||||
_ => {
|
Opt::Short('v') => {
|
||||||
return Err(ShErr::simple(ShErrKind::ParseErr, format!("invalid option: {}", opt)));
|
return Err(ShErr::simple(
|
||||||
}
|
ShErrKind::ParseErr,
|
||||||
}
|
"missing variable name for -v".to_string(),
|
||||||
}
|
));
|
||||||
Ok(arr_op_opts)
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
format!("invalid option: {}", opt),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(arr_op_opts)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,56 @@
|
|||||||
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') => {
|
})?);
|
||||||
autocmd_opts.clear = true;
|
}
|
||||||
}
|
Opt::Short('c') => {
|
||||||
_ => {
|
autocmd_opts.clear = true;
|
||||||
return Err(ShErr::simple(ShErrKind::ExecFail, format!("unexpected option: {}", arg)));
|
}
|
||||||
}
|
_ => {
|
||||||
}
|
return Err(ShErr::simple(
|
||||||
}
|
ShErrKind::ExecFail,
|
||||||
|
format!("unexpected option: {}", arg),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(autocmd_opts)
|
Ok(autocmd_opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn autocmd(node: Node) -> ShResult<()> {
|
pub fn autocmd(node: Node) -> ShResult<()> {
|
||||||
@@ -55,36 +63,50 @@ 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() {
|
||||||
let mut args = argv.iter();
|
argv.remove(0);
|
||||||
|
}
|
||||||
|
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 {
|
||||||
write_logic(|l| l.clear_autocmds(autocmd_kind));
|
write_logic(|l| l.clear_autocmds(autocmd_kind));
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
pattern: autocmd_opts.pattern,
|
pattern: autocmd_opts.pattern,
|
||||||
command: autocmd_cmd.0.clone(),
|
command: autocmd_cmd.0.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
write_logic(|l| l.insert_autocmd(autocmd_kind, autocmd));
|
write_logic(|l| l.insert_autocmd(autocmd_kind, autocmd));
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -17,40 +17,58 @@ pub fn cd(node: Node) -> ShResult<()> {
|
|||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
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(),
|
if let Some(span) = arg_span {
|
||||||
).labeled(cd_span.clone(), "Failed to change directory");
|
err = err.labeled(
|
||||||
if let Some(span) = arg_span {
|
span,
|
||||||
err = err.labeled(span, format!("No such file or directory '{}'", new_dir.display().fg(next_color())));
|
format!(
|
||||||
}
|
"No such file or directory '{}'",
|
||||||
return Err(err);
|
new_dir.display().fg(next_color())
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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) };
|
||||||
|
|
||||||
|
|||||||
@@ -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,12 +285,16 @@ 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),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Opt::Short('a') => comp_opts.flags |= CompFlags::ALIAS,
|
Opt::Short('a') => comp_opts.flags |= CompFlags::ALIAS,
|
||||||
Opt::Short('S') => comp_opts.flags |= CompFlags::SIGNALS,
|
Opt::Short('S') => comp_opts.flags |= CompFlags::SIGNALS,
|
||||||
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
|
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
|
||||||
Opt::Short('j') => comp_opts.flags |= CompFlags::JOBS,
|
Opt::Short('j') => comp_opts.flags |= CompFlags::JOBS,
|
||||||
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
|
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
|
||||||
|
|||||||
@@ -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}"),
|
||||||
}
|
}
|
||||||
))
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -34,9 +39,9 @@ pub fn exec_builtin(node: Node) -> ShResult<()> {
|
|||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
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}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -3,229 +3,251 @@ 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 {
|
||||||
pub fn matches(&self, ch: char) -> OptMatch {
|
pub fn matches(&self, ch: char) -> OptMatch {
|
||||||
for spec in &self.opt_specs {
|
for spec in &self.opt_specs {
|
||||||
let OptSpec { opt, takes_arg } = spec;
|
let OptSpec { opt, takes_arg } = spec;
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for GetOptsSpec {
|
impl FromStr for GetOptsSpec {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut s = s;
|
let mut s = s;
|
||||||
let mut opt_specs = vec![];
|
let mut opt_specs = vec![];
|
||||||
let mut silent_err = false;
|
let mut silent_err = false;
|
||||||
if s.starts_with(':') {
|
if s.starts_with(':') {
|
||||||
silent_err = true;
|
silent_err = true;
|
||||||
s = &s[1..];
|
s = &s[1..];
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chars = s.chars().peekable();
|
let mut chars = s.chars().peekable();
|
||||||
while let Some(ch) = chars.peek() {
|
while let Some(ch) = chars.peek() {
|
||||||
match ch {
|
match ch {
|
||||||
ch if ch.is_alphanumeric() => {
|
ch if ch.is_alphanumeric() => {
|
||||||
let opt = Opt::Short(*ch);
|
let opt = Opt::Short(*ch);
|
||||||
chars.next();
|
chars.next();
|
||||||
let takes_arg = chars.peek() == Some(&':');
|
let takes_arg = chars.peek() == Some(&':');
|
||||||
if takes_arg {
|
if takes_arg {
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
opt_specs.push(OptSpec { opt, takes_arg })
|
opt_specs.push(OptSpec { opt, takes_arg })
|
||||||
}
|
}
|
||||||
_ => return Err(ShErr::simple(
|
_ => {
|
||||||
ShErrKind::ParseErr,
|
return Err(ShErr::simple(
|
||||||
format!("unexpected character '{}'", ch.fg(next_color()))
|
ShErrKind::ParseErr,
|
||||||
)),
|
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(
|
||||||
let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1));
|
opts_spec: &GetOptsSpec,
|
||||||
// OPTIND is 1-based
|
opt_var: &str,
|
||||||
let arr_idx = opt_index.saturating_sub(1);
|
argv: &[String],
|
||||||
|
blame: Span,
|
||||||
|
) -> ShResult<()> {
|
||||||
|
let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1));
|
||||||
|
// OPTIND is 1-based
|
||||||
|
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
|
||||||
if arg.as_str() == "--" {
|
if arg.as_str() == "--" {
|
||||||
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
|
||||||
let Some(opt_str) = arg.strip_prefix('-') else {
|
let Some(opt_str) = arg.strip_prefix('-') else {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bare "-" is not an option
|
// Bare "-" is not an option
|
||||||
if opt_str.is_empty() {
|
if opt_str.is_empty() {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let char_idx = read_meta(|m| m.getopts_char_offset());
|
let char_idx = read_meta(|m| m.getopts_char_offset());
|
||||||
let Some(ch) = opt_str.chars().nth(char_idx) else {
|
let Some(ch) = opt_str.chars().nth(char_idx) else {
|
||||||
// Ran out of chars in this arg (shouldn't normally happen),
|
// Ran out of chars in this arg (shouldn't normally happen),
|
||||||
// advance to next arg and signal done for this call
|
// advance to next arg and signal done for this call
|
||||||
write_meta(|m| m.reset_getopts_char_offset());
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
advance_optind(opt_index, 1)?;
|
advance_optind(opt_index, 1)?;
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let last_char_in_arg = char_idx >= opt_str.len() - 1;
|
let last_char_in_arg = char_idx >= opt_str.len() - 1;
|
||||||
|
|
||||||
// Advance past this character: either move to next char in this
|
// Advance past this character: either move to next char in this
|
||||||
// arg, or reset offset and bump OPTIND to the next arg.
|
// arg, or reset offset and bump OPTIND to the next arg.
|
||||||
let advance_one_char = |last: bool| -> ShResult<()> {
|
let advance_one_char = |last: bool| -> ShResult<()> {
|
||||||
if last {
|
if last {
|
||||||
write_meta(|m| m.reset_getopts_char_offset());
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
advance_optind(opt_index, 1)?;
|
advance_optind(opt_index, 1)?;
|
||||||
} else {
|
} else {
|
||||||
write_meta(|m| m.inc_getopts_char_offset());
|
write_meta(|m| m.inc_getopts_char_offset());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
match opts_spec.matches(ch) {
|
match opts_spec.matches(ch) {
|
||||||
OptMatch::NoMatch => {
|
OptMatch::NoMatch => {
|
||||||
advance_one_char(last_char_in_arg)?;
|
advance_one_char(last_char_in_arg)?;
|
||||||
if opts_spec.silent_err {
|
if opts_spec.silent_err {
|
||||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
} else {
|
} else {
|
||||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||||
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);
|
||||||
OptMatch::IsMatch => {
|
}
|
||||||
advance_one_char(last_char_in_arg)?;
|
OptMatch::IsMatch => {
|
||||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
advance_one_char(last_char_in_arg)?;
|
||||||
state::set_status(0);
|
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
}
|
state::set_status(0);
|
||||||
OptMatch::WantsArg => {
|
}
|
||||||
write_meta(|m| m.reset_getopts_char_offset());
|
OptMatch::WantsArg => {
|
||||||
|
write_meta(|m| m.reset_getopts_char_offset());
|
||||||
|
|
||||||
if !last_char_in_arg {
|
if !last_char_in_arg {
|
||||||
// Remaining chars in this arg are the argument: -bVALUE
|
// Remaining chars in this arg are the argument: -bVALUE
|
||||||
let optarg: String = opt_str.chars().skip(char_idx + 1).collect();
|
let optarg: String = opt_str.chars().skip(char_idx + 1).collect();
|
||||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(optarg), VarFlags::NONE))?;
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(optarg), VarFlags::NONE))?;
|
||||||
advance_optind(opt_index, 1)?;
|
advance_optind(opt_index, 1)?;
|
||||||
} else if let Some(next_arg) = argv.get(arr_idx + 1) {
|
} else if let Some(next_arg) = argv.get(arr_idx + 1) {
|
||||||
// Next arg is the argument
|
// Next arg is the argument
|
||||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(next_arg.clone()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(next_arg.clone()), VarFlags::NONE))?;
|
||||||
// Skip both the option arg and its value
|
// Skip both the option arg and its value
|
||||||
advance_optind(opt_index, 2)?;
|
advance_optind(opt_index, 2)?;
|
||||||
} else {
|
} else {
|
||||||
// Missing required argument
|
// Missing required argument
|
||||||
if opts_spec.silent_err {
|
if opts_spec.silent_err {
|
||||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(":".into()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(opt_var, VarKind::Str(":".into()), VarFlags::NONE))?;
|
||||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
} else {
|
} else {
|
||||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||||
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)?;
|
}
|
||||||
state::set_status(0);
|
advance_optind(opt_index, 1)?;
|
||||||
return Ok(());
|
state::set_status(0);
|
||||||
}
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getopts(node: Node) -> ShResult<()> {
|
pub fn getopts(node: Node) -> ShResult<()> {
|
||||||
let span = node.get_span().clone();
|
let span = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
} = node.class
|
} = node.class
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut argv = prepare_argv(argv)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
if !argv.is_empty() { argv.remove(0); }
|
if !argv.is_empty() {
|
||||||
let mut args = argv.into_iter();
|
argv.remove(0);
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
if !explicit_args.is_empty() {
|
if !explicit_args.is_empty() {
|
||||||
getopts_inner(&opts_spec, &opt_var.0, &explicit_args, span)
|
getopts_inner(&opts_spec, &opt_var.0, &explicit_args, span)
|
||||||
} else {
|
} else {
|
||||||
let pos_params: Vec<String> = read_vars(|v| v.sh_argv().iter().skip(1).cloned().collect());
|
let pos_params: Vec<String> = read_vars(|v| v.sh_argv().iter().skip(1).cloned().collect());
|
||||||
getopts_inner(&opts_spec, &opt_var.0, &pos_params, span)
|
getopts_inner(&opts_spec, &opt_var.0, &pos_params, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,59 +19,75 @@ 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
|
||||||
* 1. function
|
* 1. function
|
||||||
* 2. builtin
|
* 2. builtin
|
||||||
* 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!(
|
||||||
} else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) {
|
"{arg} is a function defined at {name}:{}:{}",
|
||||||
let ShAlias { body, source } = alias;
|
line + 1,
|
||||||
let (line, col) = source.line_and_col();
|
col + 1
|
||||||
let name = source.source().name();
|
);
|
||||||
println!("{arg} is an alias for '{body}' defined at {name}:{}:{}", line + 1, col + 1);
|
} else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) {
|
||||||
} else if BUILTINS.contains(&arg.as_str()) {
|
let ShAlias { body, source } = alias;
|
||||||
println!("{arg} is a shell builtin");
|
let (line, col) = source.line_and_col();
|
||||||
} else if KEYWORDS.contains(&arg.as_str()) {
|
let name = source.source().name();
|
||||||
println!("{arg} is a shell keyword");
|
println!(
|
||||||
} else {
|
"{arg} is an alias for '{body}' defined at {name}:{}:{}",
|
||||||
let path = env::var("PATH").unwrap_or_default();
|
line + 1,
|
||||||
let paths = path.split(':')
|
col + 1
|
||||||
.map(Path::new)
|
);
|
||||||
.collect::<Vec<_>>();
|
} else if BUILTINS.contains(&arg.as_str()) {
|
||||||
|
println!("{arg} is a shell builtin");
|
||||||
|
} else if KEYWORDS.contains(&arg.as_str()) {
|
||||||
|
println!("{arg} is a shell keyword");
|
||||||
|
} else {
|
||||||
|
let path = env::var("PATH").unwrap_or_default();
|
||||||
|
let paths = path.split(':').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() {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
||||||
|
|
||||||
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());
|
{
|
||||||
continue 'outer;
|
println!("{arg} is {}", entry.path().display());
|
||||||
}
|
continue 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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())
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ pub enum JobBehavior {
|
|||||||
|
|
||||||
pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
|
pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let cmd_tk = node.get_command();
|
let cmd_tk = node.get_command();
|
||||||
let cmd_span = cmd_tk.unwrap().span.clone();
|
let cmd_span = cmd_tk.unwrap().span.clone();
|
||||||
let cmd = match behavior {
|
let cmd = match behavior {
|
||||||
JobBehavior::Foregound => "fg",
|
JobBehavior::Foregound => "fg",
|
||||||
JobBehavior::Background => "bg",
|
JobBehavior::Background => "bg",
|
||||||
@@ -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),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -82,12 +92,16 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
if arg.starts_with('%') {
|
if arg.starts_with('%') {
|
||||||
let arg = arg.strip_prefix('%').unwrap();
|
let arg = arg.strip_prefix('%').unwrap();
|
||||||
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(
|
||||||
} else {
|
ShErrKind::SyntaxErr,
|
||||||
Ok(num.saturating_sub(1))
|
blame,
|
||||||
}
|
format!("Invalid job id: {}", arg.fg(next_color())),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(num.saturating_sub(1))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let result = write_jobs(|j| {
|
let result = write_jobs(|j| {
|
||||||
let query_result = j.query(JobID::Command(arg.into()));
|
let query_result = j.query(JobID::Command(arg.into()));
|
||||||
@@ -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
|
||||||
@@ -162,41 +198,44 @@ pub fn jobs(node: Node) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(node: Node) -> ShResult<()> {
|
pub fn wait(node: Node) -> ShResult<()> {
|
||||||
let blame = node.get_span().clone();
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
} = node.class
|
} = node.class
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut argv = prepare_argv(argv)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
if !argv.is_empty() { argv.remove(0); }
|
if !argv.is_empty() {
|
||||||
if read_jobs(|j| j.curr_job().is_none()) {
|
argv.remove(0);
|
||||||
state::set_status(0);
|
}
|
||||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found"));
|
if read_jobs(|j| j.curr_job().is_none()) {
|
||||||
}
|
state::set_status(0);
|
||||||
let argv = argv.into_iter()
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found"));
|
||||||
.map(|arg| {
|
}
|
||||||
if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) {
|
let argv = argv
|
||||||
Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap())))
|
.into_iter()
|
||||||
} else {
|
.map(|arg| {
|
||||||
Ok(JobID::TableID(parse_job_id(&arg.0, arg.1)?))
|
if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
}
|
Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap())))
|
||||||
})
|
} else {
|
||||||
.collect::<ShResult<Vec<JobID>>>()?;
|
Ok(JobID::TableID(parse_job_id(&arg.0, arg.1)?))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<ShResult<Vec<JobID>>>()?;
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
write_jobs(|j| j.wait_all_bg())?;
|
write_jobs(|j| j.wait_all_bg())?;
|
||||||
} else {
|
} else {
|
||||||
for arg in argv {
|
for arg in argv {
|
||||||
wait_bg(arg)?;
|
wait_bg(arg)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't set status here, the status of the waited-on job should be the status of the wait builtin
|
// don't set status here, the status of the waited-on job should be the status of the wait builtin
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disown(node: Node) -> ShResult<()> {
|
pub fn disown(node: Node) -> ShResult<()> {
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,112 +1,130 @@
|
|||||||
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! {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct KeyMapFlags: u32 {
|
pub struct KeyMapFlags: u32 {
|
||||||
const NORMAL = 0b0000001;
|
const NORMAL = 0b0000001;
|
||||||
const INSERT = 0b0000010;
|
const INSERT = 0b0000010;
|
||||||
const VISUAL = 0b0000100;
|
const VISUAL = 0b0000100;
|
||||||
const EX = 0b0001000;
|
const EX = 0b0001000;
|
||||||
const OP_PENDING = 0b0010000;
|
const OP_PENDING = 0b0010000;
|
||||||
const REPLACE = 0b0100000;
|
const REPLACE = 0b0100000;
|
||||||
const VERBATIM = 0b1000000;
|
const VERBATIM = 0b1000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeyMapOpts {
|
pub struct KeyMapOpts {
|
||||||
remove: Option<String>,
|
remove: Option<String>,
|
||||||
flags: KeyMapFlags,
|
flags: KeyMapFlags,
|
||||||
}
|
}
|
||||||
impl KeyMapOpts {
|
impl KeyMapOpts {
|
||||||
pub fn from_opts(opts: &[Opt]) -> ShResult<Self> {
|
pub fn from_opts(opts: &[Opt]) -> ShResult<Self> {
|
||||||
let mut flags = KeyMapFlags::empty();
|
let mut flags = KeyMapFlags::empty();
|
||||||
let mut remove = None;
|
let mut remove = None;
|
||||||
for opt in opts {
|
for opt in opts {
|
||||||
match opt {
|
match opt {
|
||||||
Opt::Short('n') => flags |= KeyMapFlags::NORMAL,
|
Opt::Short('n') => flags |= KeyMapFlags::NORMAL,
|
||||||
Opt::Short('i') => flags |= KeyMapFlags::INSERT,
|
Opt::Short('i') => flags |= KeyMapFlags::INSERT,
|
||||||
Opt::Short('v') => flags |= KeyMapFlags::VISUAL,
|
Opt::Short('v') => flags |= KeyMapFlags::VISUAL,
|
||||||
Opt::Short('x') => flags |= KeyMapFlags::EX,
|
Opt::Short('x') => flags |= KeyMapFlags::EX,
|
||||||
Opt::Short('o') => flags |= KeyMapFlags::OP_PENDING,
|
Opt::Short('o') => flags |= KeyMapFlags::OP_PENDING,
|
||||||
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,
|
||||||
remove = Some(arg.clone());
|
"Duplicate --remove option for keymap".to_string(),
|
||||||
},
|
));
|
||||||
_ => return Err(ShErr::simple(ShErrKind::ExecFail, format!("Invalid option for keymap: {:?}", opt))),
|
}
|
||||||
}
|
remove = Some(arg.clone());
|
||||||
}
|
}
|
||||||
if flags.is_empty() {
|
_ => {
|
||||||
return Err(ShErr::simple(ShErrKind::ExecFail, "At least one mode option must be specified for keymap".to_string()).with_note("Use -n for normal mode, -i for insert mode, -v for visual mode, -x for ex mode, and -o for operator-pending mode".to_string()));
|
return Err(ShErr::simple(
|
||||||
}
|
ShErrKind::ExecFail,
|
||||||
Ok(Self { remove, flags })
|
format!("Invalid option for keymap: {:?}", opt),
|
||||||
}
|
));
|
||||||
pub fn keymap_opts() -> [OptSpec;6] {
|
}
|
||||||
[
|
}
|
||||||
OptSpec {
|
}
|
||||||
opt: Opt::Short('n'), // normal mode
|
if flags.is_empty() {
|
||||||
takes_arg: false
|
return Err(ShErr::simple(ShErrKind::ExecFail, "At least one mode option must be specified for keymap".to_string()).with_note("Use -n for normal mode, -i for insert mode, -v for visual mode, -x for ex mode, and -o for operator-pending mode".to_string()));
|
||||||
},
|
}
|
||||||
OptSpec {
|
Ok(Self { remove, flags })
|
||||||
opt: Opt::Short('i'), // insert mode
|
}
|
||||||
takes_arg: false
|
pub fn keymap_opts() -> [OptSpec; 6] {
|
||||||
},
|
[
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('v'), // visual mode
|
opt: Opt::Short('n'), // normal mode
|
||||||
takes_arg: false
|
takes_arg: false,
|
||||||
},
|
},
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('x'), // ex mode
|
opt: Opt::Short('i'), // insert mode
|
||||||
takes_arg: false
|
takes_arg: false,
|
||||||
},
|
},
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('o'), // operator-pending mode
|
opt: Opt::Short('v'), // visual mode
|
||||||
takes_arg: false
|
takes_arg: false,
|
||||||
},
|
},
|
||||||
OptSpec {
|
OptSpec {
|
||||||
opt: Opt::Short('r'), // replace mode
|
opt: Opt::Short('x'), // ex mode
|
||||||
takes_arg: false
|
takes_arg: false,
|
||||||
},
|
},
|
||||||
]
|
OptSpec {
|
||||||
}
|
opt: Opt::Short('o'), // operator-pending mode
|
||||||
|
takes_arg: false,
|
||||||
|
},
|
||||||
|
OptSpec {
|
||||||
|
opt: Opt::Short('r'), // replace mode
|
||||||
|
takes_arg: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
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 {
|
||||||
pub fn keys_expanded(&self) -> Vec<KeyEvent> {
|
pub fn keys_expanded(&self) -> Vec<KeyEvent> {
|
||||||
expand_keymap(&self.keys)
|
expand_keymap(&self.keys)
|
||||||
}
|
}
|
||||||
pub fn action_expanded(&self) -> Vec<KeyEvent> {
|
pub fn action_expanded(&self) -> Vec<KeyEvent> {
|
||||||
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!(
|
||||||
let ours = self.keys_expanded();
|
"Comparing keymap keys {:?} with input {:?}",
|
||||||
if other == ours {
|
self.keys_expanded(),
|
||||||
KeyMapMatch::IsExact
|
other
|
||||||
} else if ours.starts_with(other) {
|
);
|
||||||
KeyMapMatch::IsPrefix
|
let ours = self.keys_expanded();
|
||||||
} else {
|
if other == ours {
|
||||||
KeyMapMatch::NoMatch
|
KeyMapMatch::IsExact
|
||||||
}
|
} else if ours.starts_with(other) {
|
||||||
}
|
KeyMapMatch::IsPrefix
|
||||||
|
} else {
|
||||||
|
KeyMapMatch::NoMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keymap(node: Node) -> ShResult<()> {
|
pub fn keymap(node: Node) -> ShResult<()> {
|
||||||
@@ -119,32 +137,42 @@ pub fn keymap(node: Node) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv, &KeyMapOpts::keymap_opts())?;
|
let (argv, opts) = get_opts_from_tokens(argv, &KeyMapOpts::keymap_opts())?;
|
||||||
let opts = KeyMapOpts::from_opts(&opts).promote_err(span.clone())?;
|
let opts = KeyMapOpts::from_opts(&opts).promote_err(span.clone())?;
|
||||||
if let Some(to_rm) = opts.remove {
|
if let Some(to_rm) = opts.remove {
|
||||||
write_logic(|l| l.remove_keymap(&to_rm));
|
write_logic(|l| l.remove_keymap(&to_rm));
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
flags: opts.flags,
|
flags: opts.flags,
|
||||||
keys: keys.clone(),
|
keys: keys.clone(),
|
||||||
action: action.clone(),
|
action: action.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
write_logic(|l| l.insert_keymap(keymap));
|
write_logic(|l| l.insert_keymap(keymap));
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -5,240 +5,239 @@ 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)]
|
||||||
pub enum MapNode {
|
pub enum MapNode {
|
||||||
DynamicLeaf(String), // eval'd on access
|
DynamicLeaf(String), // eval'd on access
|
||||||
StaticLeaf(String), // static value
|
StaticLeaf(String), // static value
|
||||||
Array(Vec<MapNode>),
|
Array(Vec<MapNode>),
|
||||||
Branch(HashMap<String, MapNode>),
|
Branch(HashMap<String, MapNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MapNode {
|
impl Default for MapNode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Branch(HashMap::new())
|
Self::Branch(HashMap::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MapNode> for serde_json::Value {
|
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()
|
Value::Array(arr)
|
||||||
.map(|node| node.into())
|
}
|
||||||
.collect();
|
MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => Value::String(leaf),
|
||||||
Value::Array(arr)
|
}
|
||||||
}
|
}
|
||||||
MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => {
|
|
||||||
Value::String(leaf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Value> for MapNode {
|
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()
|
MapNode::Array(nodes)
|
||||||
.map(|v| v.into())
|
}
|
||||||
.collect();
|
Value::String(s) => MapNode::StaticLeaf(s),
|
||||||
MapNode::Array(nodes)
|
v => MapNode::StaticLeaf(v.to_string()),
|
||||||
}
|
}
|
||||||
Value::String(s) => MapNode::StaticLeaf(s),
|
}
|
||||||
v => MapNode::StaticLeaf(v.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapNode {
|
impl MapNode {
|
||||||
fn get(&self, path: &[String]) -> Option<&MapNode> {
|
fn get(&self, path: &[String]) -> Option<&MapNode> {
|
||||||
match path {
|
match path {
|
||||||
[] => Some(self),
|
[] => Some(self),
|
||||||
[key, rest @ ..] => match self {
|
[key, rest @ ..] => match self {
|
||||||
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => None,
|
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => None,
|
||||||
MapNode::Array(map_nodes) => {
|
MapNode::Array(map_nodes) => {
|
||||||
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),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, path: &[String], value: MapNode) {
|
fn set(&mut self, path: &[String], value: MapNode) {
|
||||||
match path {
|
match path {
|
||||||
[] => *self = value,
|
[] => *self = value,
|
||||||
[key, rest @ ..] => {
|
[key, rest @ ..] => {
|
||||||
if matches!(self, MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_)) {
|
if matches!(self, MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_)) {
|
||||||
// promote leaf to branch if we still have path left to traverse
|
// promote leaf to branch if we still have path left to traverse
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
}
|
}
|
||||||
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())
|
child.set(rest, value);
|
||||||
.or_insert_with(Self::default);
|
}
|
||||||
child.set(rest, value);
|
MapNode::Array(map_nodes) => {
|
||||||
}
|
let idx: usize = key.parse().expect("expected array index");
|
||||||
MapNode::Array(map_nodes) => {
|
if idx >= map_nodes.len() {
|
||||||
let idx: usize = key.parse().expect("expected array index");
|
map_nodes.resize(idx + 1, Self::default());
|
||||||
if idx >= map_nodes.len() {
|
}
|
||||||
map_nodes.resize(idx + 1, Self::default());
|
map_nodes[idx].set(rest, value);
|
||||||
}
|
}
|
||||||
map_nodes[idx].set(rest, value);
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, path: &[String]) -> Option<MapNode> {
|
fn remove(&mut self, path: &[String]) -> Option<MapNode> {
|
||||||
match path {
|
match path {
|
||||||
[] => None,
|
[] => None,
|
||||||
[key] => match self {
|
[key] => match self {
|
||||||
MapNode::Branch(map) => map.remove(key),
|
MapNode::Branch(map) => map.remove(key),
|
||||||
MapNode::Array(nodes) => {
|
MapNode::Array(nodes) => {
|
||||||
let idx: usize = key.parse().ok()?;
|
let idx: usize = key.parse().ok()?;
|
||||||
if idx >= nodes.len() {
|
if idx >= nodes.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
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) => {
|
||||||
let idx: usize = key.parse().ok()?;
|
let idx: usize = key.parse().ok()?;
|
||||||
if idx >= nodes.len() {
|
if idx >= nodes.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
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
|
||||||
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![],
|
.iter()
|
||||||
}
|
.filter_map(|n| n.display(false, false).ok())
|
||||||
}
|
.collect(),
|
||||||
|
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn display(&self, json: bool, pretty: bool) -> ShResult<String> {
|
fn display(&self, json: bool, pretty: bool) -> ShResult<String> {
|
||||||
if json || matches!(self, MapNode::Branch(_)) {
|
if json || matches!(self, MapNode::Branch(_)) {
|
||||||
let val: Value = self.clone().into();
|
let val: Value = self.clone().into();
|
||||||
if pretty {
|
if pretty {
|
||||||
match serde_json::to_string_pretty(&val) {
|
match serde_json::to_string_pretty(&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 {
|
||||||
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 {
|
||||||
match self {
|
match self {
|
||||||
MapNode::StaticLeaf(leaf) => Ok(leaf.clone()),
|
MapNode::StaticLeaf(leaf) => Ok(leaf.clone()),
|
||||||
MapNode::DynamicLeaf(cmd) => expand_cmd_sub(cmd),
|
MapNode::DynamicLeaf(cmd) => expand_cmd_sub(cmd),
|
||||||
MapNode::Array(nodes) => {
|
MapNode::Array(nodes) => {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
let display = node.display(json, pretty)?;
|
let display = node.display(json, pretty)?;
|
||||||
if matches!(node, MapNode::Branch(_)) {
|
if matches!(node, MapNode::Branch(_)) {
|
||||||
s.push_str(&format!("'{}'", display));
|
s.push_str(&format!("'{}'", display));
|
||||||
} else {
|
} else {
|
||||||
s.push_str(&node.display(json, pretty)?);
|
s.push_str(&node.display(json, pretty)?);
|
||||||
}
|
}
|
||||||
s.push('\n');
|
s.push('\n');
|
||||||
}
|
}
|
||||||
Ok(s.trim_end_matches('\n').to_string())
|
Ok(s.trim_end_matches('\n').to_string())
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_opts_spec() -> [OptSpec; 6] {
|
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,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct MapOpts {
|
pub struct MapOpts {
|
||||||
flags: MapFlags,
|
flags: MapFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct MapFlags: u32 {
|
pub struct MapFlags: u32 {
|
||||||
const REMOVE = 0b000001;
|
const REMOVE = 0b000001;
|
||||||
const KEYS = 0b000010;
|
const KEYS = 0b000010;
|
||||||
const JSON = 0b000100;
|
const JSON = 0b000100;
|
||||||
const LOCAL = 0b001000;
|
const LOCAL = 0b001000;
|
||||||
const PRETTY = 0b010000;
|
const PRETTY = 0b010000;
|
||||||
const FUNC = 0b100000;
|
const FUNC = 0b100000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map(node: Node) -> ShResult<()> {
|
pub fn map(node: Node) -> ShResult<()> {
|
||||||
@@ -250,136 +249,140 @@ pub fn map(node: Node) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
||||||
let map_opts = get_map_opts(opts);
|
let map_opts = get_map_opts(opts);
|
||||||
if !argv.is_empty() {
|
if !argv.is_empty() {
|
||||||
argv.remove(0); // remove "map" command from argv
|
argv.remove(0); // remove "map" command from argv
|
||||||
}
|
}
|
||||||
|
|
||||||
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(" ")))
|
||||||
.collect::<ShResult<Vec<String>>>()?;
|
.collect::<ShResult<Vec<String>>>()?;
|
||||||
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)
|
||||||
let expanded = rhs.expand()?.get_words().join(" ");
|
} else {
|
||||||
let found = write_vars(|v| -> ShResult<bool> {
|
MapNode::StaticLeaf(s)
|
||||||
if let Some(map) = v.get_map_mut(name) {
|
}
|
||||||
if is_json {
|
};
|
||||||
if let Ok(parsed) = serde_json::from_str::<Value>(expanded.as_str()) {
|
let expanded = rhs.expand()?.get_words().join(" ");
|
||||||
map.set(&path[1..], parsed.into());
|
let found = write_vars(|v| -> ShResult<bool> {
|
||||||
} else {
|
if let Some(map) = v.get_map_mut(name) {
|
||||||
map.set(&path[1..], make_leaf(expanded.clone()));
|
if is_json {
|
||||||
}
|
if let Ok(parsed) = serde_json::from_str::<Value>(expanded.as_str()) {
|
||||||
} else {
|
map.set(&path[1..], parsed.into());
|
||||||
map.set(&path[1..], make_leaf(expanded.clone()));
|
} else {
|
||||||
}
|
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||||
Ok(true)
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||||
}
|
}
|
||||||
});
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
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 parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
|
/*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */
|
||||||
let node: MapNode = parsed.into();
|
{
|
||||||
new.set(&path[1..], node);
|
let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
|
||||||
} else {
|
let node: MapNode = parsed.into();
|
||||||
new.set(&path[1..], make_leaf(expanded));
|
new.set(&path[1..], node);
|
||||||
}
|
} else {
|
||||||
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
new.set(&path[1..], make_leaf(expanded));
|
||||||
}
|
}
|
||||||
} else {
|
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||||
let expanded = arg.expand()?.get_words().join(" ");
|
}
|
||||||
let path: Vec<String> = expanded.split('.').map(|s| s.to_string()).collect();
|
} else {
|
||||||
let Some(name) = path.first() else {
|
let expanded = arg.expand()?.get_words().join(" ");
|
||||||
return Err(ShErr::simple(
|
let path: Vec<String> = expanded.split('.').map(|s| s.to_string()).collect();
|
||||||
ShErrKind::InternalErr,
|
let Some(name) = path.first() else {
|
||||||
format!("invalid map path: {}", expanded)
|
return Err(ShErr::simple(
|
||||||
));
|
ShErrKind::InternalErr,
|
||||||
};
|
format!("invalid map path: {}", expanded),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
if map_opts.flags.contains(MapFlags::REMOVE) {
|
if map_opts.flags.contains(MapFlags::REMOVE) {
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
if path.len() == 1 {
|
if path.len() == 1 {
|
||||||
v.remove_map(name);
|
v.remove_map(name);
|
||||||
} else {
|
} else {
|
||||||
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..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let json = map_opts.flags.contains(MapFlags::JSON);
|
let json = map_opts.flags.contains(MapFlags::JSON);
|
||||||
let pretty = map_opts.flags.contains(MapFlags::PRETTY);
|
let pretty = map_opts.flags.contains(MapFlags::PRETTY);
|
||||||
let keys = map_opts.flags.contains(MapFlags::KEYS);
|
let keys = map_opts.flags.contains(MapFlags::KEYS);
|
||||||
let has_map = read_vars(|v| v.get_map(name).is_some());
|
let has_map = read_vars(|v| v.get_map(name).is_some());
|
||||||
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())
|
state::set_status(1);
|
||||||
}) else {
|
continue;
|
||||||
state::set_status(1);
|
};
|
||||||
continue;
|
let output = if keys {
|
||||||
};
|
node.keys().join(" ")
|
||||||
let output = if keys {
|
} else {
|
||||||
node.keys().join(" ")
|
node.display(json, pretty)?
|
||||||
} else {
|
};
|
||||||
node.display(json, pretty)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, output.as_bytes())?;
|
write(stdout, output.as_bytes())?;
|
||||||
write(stdout, b"\n")?;
|
write(stdout, b"\n")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
match opt {
|
match opt {
|
||||||
Opt::Short('r') => map_opts.flags |= MapFlags::REMOVE,
|
Opt::Short('r') => map_opts.flags |= MapFlags::REMOVE,
|
||||||
Opt::Short('j') => map_opts.flags |= MapFlags::JSON,
|
Opt::Short('j') => map_opts.flags |= MapFlags::JSON,
|
||||||
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<()> {
|
||||||
|
|||||||
@@ -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())?;
|
||||||
@@ -257,96 +268,98 @@ 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);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, opts) = get_opts_from_tokens(argv, &READ_KEY_OPTS).blame(blame.clone())?;
|
let (_, opts) = get_opts_from_tokens(argv, &READ_KEY_OPTS).blame(blame.clone())?;
|
||||||
let read_key_opts = get_read_key_opts(opts).blame(blame.clone())?;
|
let read_key_opts = get_read_key_opts(opts).blame(blame.clone())?;
|
||||||
|
|
||||||
let key = {
|
let key = {
|
||||||
let _raw = crate::readline::term::raw_mode();
|
let _raw = crate::readline::term::raw_mode();
|
||||||
let mut buf = [0u8; 16];
|
let mut buf = [0u8; 16];
|
||||||
match read(*TTY_FILENO, &mut buf) {
|
match read(*TTY_FILENO, &mut buf) {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let mut reader = PollReader::new();
|
let mut reader = PollReader::new();
|
||||||
reader.feed_bytes(&buf[..n], false);
|
reader.feed_bytes(&buf[..n], false);
|
||||||
let Some(key) = reader.read_key()? else {
|
let Some(key) = reader.read_key()? else {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
key
|
key
|
||||||
},
|
}
|
||||||
Err(Errno::EINTR) => {
|
Err(Errno::EINTR) => {
|
||||||
state::set_status(130);
|
state::set_status(130);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) => return Err(ShErr::simple(ShErrKind::ExecFail, format!("read_key: {e}"))),
|
Err(e) => return Err(ShErr::simple(ShErrKind::ExecFail, format!("read_key: {e}"))),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let vim_seq = key.as_vim_seq()?;
|
let vim_seq = key.as_vim_seq()?;
|
||||||
|
|
||||||
if let Some(wl) = read_key_opts.char_whitelist {
|
if let Some(wl) = read_key_opts.char_whitelist {
|
||||||
let allowed = expand_keymap(&wl);
|
let allowed = expand_keymap(&wl);
|
||||||
if !allowed.contains(&key) {
|
if !allowed.contains(&key) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bl) = read_key_opts.char_blacklist {
|
if let Some(bl) = read_key_opts.char_blacklist {
|
||||||
let disallowed = expand_keymap(&bl);
|
let disallowed = expand_keymap(&bl);
|
||||||
if disallowed.contains(&key) {
|
if disallowed.contains(&key) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(var) = read_key_opts.var_name {
|
if let Some(var) = read_key_opts.var_name {
|
||||||
write_vars(|v| v.set_var(&var, VarKind::Str(vim_seq), VarFlags::NONE))?;
|
write_vars(|v| v.set_var(&var, VarKind::Str(vim_seq), VarFlags::NONE))?;
|
||||||
} else {
|
} else {
|
||||||
write(borrow_fd(STDOUT_FILENO), vim_seq.as_bytes())?;
|
write(borrow_fd(STDOUT_FILENO), vim_seq.as_bytes())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_read_key_opts(opts: Vec<Opt>) -> ShResult<ReadKeyOpts> {
|
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 {
|
||||||
match opt {
|
match opt {
|
||||||
Opt::ShortWithArg('v', var_name) => read_key_opts.var_name = Some(var_name),
|
Opt::ShortWithArg('v', var_name) => read_key_opts.var_name = Some(var_name),
|
||||||
Opt::ShortWithArg('w', char_whitelist) => read_key_opts.char_whitelist = Some(char_whitelist),
|
Opt::ShortWithArg('w', char_whitelist) => read_key_opts.char_whitelist = Some(char_whitelist),
|
||||||
Opt::ShortWithArg('b', char_blacklist) => read_key_opts.char_blacklist = Some(char_blacklist),
|
Opt::ShortWithArg('b', char_blacklist) => read_key_opts.char_blacklist = Some(char_blacklist),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
format!("read_key: Unexpected flag '{opt}'")
|
format!("read_key: Unexpected flag '{opt}'"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(read_key_opts)
|
Ok(read_key_opts)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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())?;
|
||||||
|
|||||||
@@ -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)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
530
src/expand.rs
530
src/expand.rs
@@ -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 })
|
||||||
}
|
}
|
||||||
@@ -251,14 +250,14 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
prefix.push(next);
|
prefix.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
qt_state.toggle_single();
|
qt_state.toggle_single();
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
qt_state.toggle_double();
|
qt_state.toggle_double();
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
}
|
}
|
||||||
'{' if qt_state.outside() => {
|
'{' if qt_state.outside() => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -279,14 +278,14 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
inner.push(next);
|
inner.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
qt_state.toggle_single();
|
qt_state.toggle_single();
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
qt_state.toggle_double();
|
qt_state.toggle_double();
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'{' if qt_state.outside() => {
|
'{' if qt_state.outside() => {
|
||||||
depth += 1;
|
depth += 1;
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
@@ -330,14 +329,14 @@ fn split_brace_inner(inner: &str) -> Vec<String> {
|
|||||||
current.push(next);
|
current.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
qt_state.toggle_single();
|
qt_state.toggle_single();
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
qt_state.toggle_double();
|
qt_state.toggle_double();
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'{' if qt_state.outside() => {
|
'{' if qt_state.outside() => {
|
||||||
depth += 1;
|
depth += 1;
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
@@ -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 {
|
||||||
@@ -695,21 +692,21 @@ impl ArithTk {
|
|||||||
tokens.push(Self::RParen);
|
tokens.push(Self::RParen);
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
_ if ch.is_alphabetic() || ch == '_' => {
|
_ if ch.is_alphabetic() || ch == '_' => {
|
||||||
chars.next();
|
chars.next();
|
||||||
let mut var_name = ch.to_string();
|
let mut var_name = ch.to_string();
|
||||||
while let Some(ch) = chars.peek() {
|
while let Some(ch) = chars.peek() {
|
||||||
match ch {
|
match ch {
|
||||||
_ if ch.is_alphabetic() || *ch == '_' => {
|
_ if ch.is_alphabetic() || *ch == '_' => {
|
||||||
var_name.push(*ch);
|
var_name.push(*ch);
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
_ => break
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens.push(Self::Var(var_name));
|
tokens.push(Self::Var(var_name));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@@ -753,22 +750,28 @@ impl ArithTk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArithTk::Var(var) => {
|
ArithTk::Var(var) => {
|
||||||
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 {
|
),
|
||||||
return Err(ShErr::simple(
|
));
|
||||||
ShErrKind::ParseErr,
|
};
|
||||||
format!("Variable '{}' does not contain a number", var.fg(next_color())),
|
let Ok(num) = val.parse::<f64>() else {
|
||||||
));
|
return Err(ShErr::simple(
|
||||||
};
|
ShErrKind::ParseErr,
|
||||||
|
format!(
|
||||||
|
"Variable '{}' does not contain a number",
|
||||||
|
var.fg(next_color())
|
||||||
|
),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
output.push(ArithTk::Num(num));
|
output.push(ArithTk::Num(num));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,14 +788,14 @@ impl ArithTk {
|
|||||||
match token {
|
match token {
|
||||||
ArithTk::Num(n) => stack.push(n),
|
ArithTk::Num(n) => stack.push(n),
|
||||||
ArithTk::Op(op) => {
|
ArithTk::Op(op) => {
|
||||||
let rhs = stack.pop().ok_or(ShErr::simple(
|
let rhs = stack.pop().ok_or(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Missing right-hand operand",
|
"Missing right-hand operand",
|
||||||
))?;
|
))?;
|
||||||
let lhs = stack.pop().ok_or(ShErr::simple(
|
let lhs = stack.pop().ok_or(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Missing left-hand operand",
|
"Missing left-hand operand",
|
||||||
))?;
|
))?;
|
||||||
let result = match op {
|
let result = match op {
|
||||||
ArithOp::Add => lhs + rhs,
|
ArithOp::Add => lhs + rhs,
|
||||||
ArithOp::Sub => lhs - rhs,
|
ArithOp::Sub => lhs - rhs,
|
||||||
@@ -803,19 +806,19 @@ impl ArithTk {
|
|||||||
stack.push(result);
|
stack.push(result);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unexpected token during evaluation",
|
"Unexpected token during evaluation",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if stack.len() != 1 {
|
if stack.len() != 1 {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Invalid arithmetic expression",
|
"Invalid arithmetic expression",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(stack[0])
|
Ok(stack[0])
|
||||||
@@ -840,10 +843,10 @@ impl FromStr for ArithOp {
|
|||||||
'*' => Ok(Self::Mul),
|
'*' => Ok(Self::Mul),
|
||||||
'/' => Ok(Self::Div),
|
'/' => Ok(Self::Div),
|
||||||
'%' => Ok(Self::Mod),
|
'%' => Ok(Self::Mod),
|
||||||
_ => Err(ShErr::simple(
|
_ => Err(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Invalid arithmetic operator",
|
"Invalid arithmetic operator",
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -853,8 +856,8 @@ pub fn expand_arithmetic(raw: &str) -> ShResult<Option<String>> {
|
|||||||
let unescaped = unescape_math(body);
|
let unescaped = unescape_math(body);
|
||||||
let expanded = expand_raw(&mut unescaped.chars().peekable())?;
|
let expanded = expand_raw(&mut unescaped.chars().peekable())?;
|
||||||
let Some(tokens) = ArithTk::tokenize(&expanded)? else {
|
let Some(tokens) = ArithTk::tokenize(&expanded)? else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let rpn = ArithTk::to_rpn(tokens)?;
|
let rpn = ArithTk::to_rpn(tokens)?;
|
||||||
let result = ArithTk::eval_rpn(rpn)?;
|
let result = ArithTk::eval_rpn(rpn)?;
|
||||||
Ok(Some(result.to_string()))
|
Ok(Some(result.to_string()))
|
||||||
@@ -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,11 +933,16 @@ 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) };
|
||||||
}
|
}
|
||||||
let status = state::get_status();
|
let status = state::get_status();
|
||||||
unsafe { libc::_exit(status) };
|
unsafe { libc::_exit(status) };
|
||||||
}
|
}
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => {
|
||||||
@@ -956,9 +969,9 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
|
|
||||||
match status {
|
match status {
|
||||||
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")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1062,76 +1075,76 @@ pub fn unescape_str(raw: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'$' => {
|
'$' => {
|
||||||
log::debug!("Found ANSI-C quoting");
|
log::debug!("Found ANSI-C quoting");
|
||||||
chars.next();
|
chars.next();
|
||||||
while let Some(q_ch) = chars.next() {
|
while let Some(q_ch) = chars.next() {
|
||||||
match q_ch {
|
match q_ch {
|
||||||
'\'' => {
|
'\'' => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
'\\' => {
|
'\\' => {
|
||||||
if let Some(esc) = chars.next() {
|
if let Some(esc) = chars.next() {
|
||||||
match esc {
|
match esc {
|
||||||
'n' => result.push('\n'),
|
'n' => result.push('\n'),
|
||||||
't' => result.push('\t'),
|
't' => result.push('\t'),
|
||||||
'r' => result.push('\r'),
|
'r' => result.push('\r'),
|
||||||
'\'' => result.push('\''),
|
'\'' => result.push('\''),
|
||||||
'\\' => result.push('\\'),
|
'\\' => result.push('\\'),
|
||||||
'a' => result.push('\x07'),
|
'a' => result.push('\x07'),
|
||||||
'b' => result.push('\x08'),
|
'b' => result.push('\x08'),
|
||||||
'e' | 'E' => result.push('\x1b'),
|
'e' | 'E' => result.push('\x1b'),
|
||||||
'v' => result.push('\x0b'),
|
'v' => result.push('\x0b'),
|
||||||
'x' => {
|
'x' => {
|
||||||
let mut hex = String::new();
|
let mut hex = String::new();
|
||||||
if let Some(h1) = chars.next() {
|
if let Some(h1) = chars.next() {
|
||||||
hex.push(h1);
|
hex.push(h1);
|
||||||
} else {
|
} else {
|
||||||
result.push_str("\\x");
|
result.push_str("\\x");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(h2) = chars.next() {
|
if let Some(h2) = chars.next() {
|
||||||
hex.push(h2);
|
hex.push(h2);
|
||||||
} else {
|
} else {
|
||||||
result.push_str(&format!("\\x{hex}"));
|
result.push_str(&format!("\\x{hex}"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
|
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
|
||||||
result.push(byte as char);
|
result.push(byte as char);
|
||||||
} else {
|
} else {
|
||||||
result.push_str(&format!("\\x{hex}"));
|
result.push_str(&format!("\\x{hex}"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'o' => {
|
'o' => {
|
||||||
let mut oct = String::new();
|
let mut oct = String::new();
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
if let Some(o) = chars.peek() {
|
if let Some(o) = chars.peek() {
|
||||||
if o.is_digit(8) {
|
if o.is_digit(8) {
|
||||||
oct.push(*o);
|
oct.push(*o);
|
||||||
chars.next();
|
chars.next();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Ok(byte) = u8::from_str_radix(&oct, 8) {
|
if let Ok(byte) = u8::from_str_radix(&oct, 8) {
|
||||||
result.push(byte as char);
|
result.push(byte as char);
|
||||||
} else {
|
} else {
|
||||||
result.push_str(&format!("\\o{oct}"));
|
result.push_str(&format!("\\o{oct}"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => result.push(esc),
|
_ => result.push(esc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => result.push(q_ch),
|
_ => result.push(q_ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
result.push(markers::DUB_QUOTE);
|
result.push(markers::DUB_QUOTE);
|
||||||
break;
|
break;
|
||||||
@@ -1144,14 +1157,14 @@ pub fn unescape_str(raw: &str) -> String {
|
|||||||
result.push(markers::SNG_QUOTE);
|
result.push(markers::SNG_QUOTE);
|
||||||
while let Some(q_ch) = chars.next() {
|
while let Some(q_ch) = chars.next() {
|
||||||
match q_ch {
|
match q_ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
if chars.peek() == Some(&'\'') {
|
if chars.peek() == Some(&'\'') {
|
||||||
result.push('\'');
|
result.push('\'');
|
||||||
chars.next();
|
chars.next();
|
||||||
} else {
|
} else {
|
||||||
result.push('\\');
|
result.push('\\');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
result.push(markers::SNG_QUOTE);
|
result.push(markers::SNG_QUOTE);
|
||||||
break;
|
break;
|
||||||
@@ -1219,7 +1232,7 @@ pub fn unescape_str(raw: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
'$' if chars.peek() == Some(&'\'') => {
|
'$' if chars.peek() == Some(&'\'') => {
|
||||||
log::debug!("Found ANSI-C quoting");
|
log::debug!("Found ANSI-C quoting");
|
||||||
chars.next();
|
chars.next();
|
||||||
result.push(markers::SNG_QUOTE);
|
result.push(markers::SNG_QUOTE);
|
||||||
while let Some(q_ch) = chars.next() {
|
while let Some(q_ch) = chars.next() {
|
||||||
@@ -1387,13 +1400,13 @@ impl FromStr for ParamExp {
|
|||||||
use ParamExp::*;
|
use ParamExp::*;
|
||||||
|
|
||||||
let parse_err = || {
|
let parse_err = || {
|
||||||
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);
|
||||||
|
|
||||||
// Handle indirect var expansion: ${!var}
|
// Handle indirect var expansion: ${!var}
|
||||||
if let Some(var) = s.strip_prefix('!') {
|
if let Some(var) = s.strip_prefix('!') {
|
||||||
@@ -1410,7 +1423,7 @@ impl FromStr for ParamExp {
|
|||||||
return Ok(RemShortestPrefix(rest.to_string()));
|
return Ok(RemShortestPrefix(rest.to_string()));
|
||||||
}
|
}
|
||||||
if let Some(rest) = s.strip_prefix("%%") {
|
if let Some(rest) = s.strip_prefix("%%") {
|
||||||
log::debug!("Matched longest suffix pattern: '{}'", rest);
|
log::debug!("Matched longest suffix pattern: '{}'", rest);
|
||||||
return Ok(RemLongestSuffix(rest.to_string()));
|
return Ok(RemLongestSuffix(rest.to_string()));
|
||||||
} else if let Some(rest) = s.strip_prefix('%') {
|
} else if let Some(rest) = s.strip_prefix('%') {
|
||||||
return Ok(RemShortestSuffix(rest.to_string()));
|
return Ok(RemShortestSuffix(rest.to_string()));
|
||||||
@@ -1476,11 +1489,11 @@ impl FromStr for ParamExp {
|
|||||||
pub fn parse_pos_len(s: &str) -> Option<(usize, Option<usize>)> {
|
pub fn parse_pos_len(s: &str) -> Option<(usize, Option<usize>)> {
|
||||||
let raw = s.strip_prefix(':')?;
|
let raw = s.strip_prefix(':')?;
|
||||||
if let Some((start, len)) = raw.split_once(':') {
|
if let Some((start, len)) = raw.split_once(':') {
|
||||||
let start = expand_raw(&mut start.chars().peekable()).unwrap_or_else(|_| start.to_string());
|
let start = expand_raw(&mut start.chars().peekable()).unwrap_or_else(|_| start.to_string());
|
||||||
let len = expand_raw(&mut len.chars().peekable()).unwrap_or_else(|_| len.to_string());
|
let len = expand_raw(&mut len.chars().peekable()).unwrap_or_else(|_| len.to_string());
|
||||||
Some((start.parse::<usize>().ok()?, len.parse::<usize>().ok()))
|
Some((start.parse::<usize>().ok()?, len.parse::<usize>().ok()))
|
||||||
} else {
|
} else {
|
||||||
let raw = expand_raw(&mut raw.chars().peekable()).unwrap_or_else(|_| raw.to_string());
|
let raw = expand_raw(&mut raw.chars().peekable()).unwrap_or_else(|_| raw.to_string());
|
||||||
Some((raw.parse::<usize>().ok()?, None))
|
Some((raw.parse::<usize>().ok()?, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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..];
|
||||||
@@ -2299,82 +2307,82 @@ pub fn expand_aliases(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_keymap(s: &str) -> Vec<KeyEvent> {
|
pub fn expand_keymap(s: &str) -> Vec<KeyEvent> {
|
||||||
let mut keys = Vec::new();
|
let mut keys = Vec::new();
|
||||||
let mut chars = s.chars().collect::<VecDeque<char>>();
|
let mut chars = s.chars().collect::<VecDeque<char>>();
|
||||||
while let Some(ch) = chars.pop_front() {
|
while let Some(ch) = chars.pop_front() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
if let Some(next_ch) = chars.pop_front() {
|
if let Some(next_ch) = chars.pop_front() {
|
||||||
keys.push(KeyEvent(KeyCode::Char(next_ch), ModKeys::NONE));
|
keys.push(KeyEvent(KeyCode::Char(next_ch), ModKeys::NONE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'<' => {
|
'<' => {
|
||||||
let mut alias = String::new();
|
let mut alias = String::new();
|
||||||
while let Some(a_ch) = chars.pop_front() {
|
while let Some(a_ch) = chars.pop_front() {
|
||||||
match a_ch {
|
match a_ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
if let Some(esc_ch) = chars.pop_front() {
|
if let Some(esc_ch) = chars.pop_front() {
|
||||||
alias.push(esc_ch);
|
alias.push(esc_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'>' => {
|
'>' => {
|
||||||
if alias.eq_ignore_ascii_case("leader") {
|
if alias.eq_ignore_ascii_case("leader") {
|
||||||
let mut leader = read_shopts(|o| o.prompt.leader.clone());
|
let mut leader = read_shopts(|o| o.prompt.leader.clone());
|
||||||
if leader == "\\" {
|
if leader == "\\" {
|
||||||
leader.push('\\');
|
leader.push('\\');
|
||||||
}
|
}
|
||||||
keys.extend(expand_keymap(&leader));
|
keys.extend(expand_keymap(&leader));
|
||||||
} else if let Some(key) = parse_key_alias(&alias) {
|
} else if let Some(key) = parse_key_alias(&alias) {
|
||||||
keys.push(key);
|
keys.push(key);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => alias.push(a_ch),
|
_ => alias.push(a_ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
keys.push(KeyEvent(KeyCode::Char(ch), ModKeys::NONE));
|
keys.push(KeyEvent(KeyCode::Char(ch), ModKeys::NONE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keys
|
keys
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_key_alias(alias: &str) -> Option<KeyEvent> {
|
pub fn parse_key_alias(alias: &str) -> Option<KeyEvent> {
|
||||||
let parts: Vec<&str> = alias.split('-').collect();
|
let parts: Vec<&str> = alias.split('-').collect();
|
||||||
let (mods_parts, key_name) = parts.split_at(parts.len() - 1);
|
let (mods_parts, key_name) = parts.split_at(parts.len() - 1);
|
||||||
let mut mods = ModKeys::NONE;
|
let mut mods = ModKeys::NONE;
|
||||||
for m in mods_parts {
|
for m in mods_parts {
|
||||||
match m.to_uppercase().as_str() {
|
match m.to_uppercase().as_str() {
|
||||||
"C" => mods |= ModKeys::CTRL,
|
"C" => mods |= ModKeys::CTRL,
|
||||||
"A" | "M" => mods |= ModKeys::ALT,
|
"A" | "M" => mods |= ModKeys::ALT,
|
||||||
"S" => mods |= ModKeys::SHIFT,
|
"S" => mods |= ModKeys::SHIFT,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = match *key_name.first()? {
|
let key = match *key_name.first()? {
|
||||||
"CR" => KeyCode::Char('\r'),
|
"CR" => KeyCode::Char('\r'),
|
||||||
"ENTER" | "RETURN" => KeyCode::Enter,
|
"ENTER" | "RETURN" => KeyCode::Enter,
|
||||||
"ESC" | "ESCAPE" => KeyCode::Esc,
|
"ESC" | "ESCAPE" => KeyCode::Esc,
|
||||||
"TAB" => KeyCode::Tab,
|
"TAB" => KeyCode::Tab,
|
||||||
"BS" | "BACKSPACE" => KeyCode::Backspace,
|
"BS" | "BACKSPACE" => KeyCode::Backspace,
|
||||||
"DEL" | "DELETE" => KeyCode::Delete,
|
"DEL" | "DELETE" => KeyCode::Delete,
|
||||||
"INS" | "INSERT" => KeyCode::Insert,
|
"INS" | "INSERT" => KeyCode::Insert,
|
||||||
"SPACE" => KeyCode::Char(' '),
|
"SPACE" => KeyCode::Char(' '),
|
||||||
"UP" => KeyCode::Up,
|
"UP" => KeyCode::Up,
|
||||||
"DOWN" => KeyCode::Down,
|
"DOWN" => KeyCode::Down,
|
||||||
"LEFT" => KeyCode::Left,
|
"LEFT" => KeyCode::Left,
|
||||||
"RIGHT" => KeyCode::Right,
|
"RIGHT" => KeyCode::Right,
|
||||||
"HOME" => KeyCode::Home,
|
"HOME" => KeyCode::Home,
|
||||||
"END" => KeyCode::End,
|
"END" => KeyCode::End,
|
||||||
"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))
|
||||||
}
|
}
|
||||||
|
|||||||
221
src/jobs.rs
221
src/jobs.rs
@@ -59,18 +59,12 @@ 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 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -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
|
||||||
@@ -233,7 +232,7 @@ impl JobTab {
|
|||||||
self.next_open_pos()
|
self.next_open_pos()
|
||||||
};
|
};
|
||||||
job.set_tabid(tab_pos);
|
job.set_tabid(tab_pos);
|
||||||
let last_pid = job.children().last().map(|c| c.pid());
|
let last_pid = job.children().last().map(|c| c.pid());
|
||||||
self.order.push(tab_pos);
|
self.order.push(tab_pos);
|
||||||
if !silent {
|
if !silent {
|
||||||
write(
|
write(
|
||||||
@@ -247,9 +246,9 @@ impl JobTab {
|
|||||||
self.jobs[tab_pos] = Some(job);
|
self.jobs[tab_pos] = Some(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pid) = last_pid {
|
if let Some(pid) = last_pid {
|
||||||
write_vars(|v| v.set_param(ShellParam::LastJob, &pid.to_string()))
|
write_vars(|v| v.set_param(ShellParam::LastJob, &pid.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tab_pos)
|
Ok(tab_pos)
|
||||||
}
|
}
|
||||||
@@ -281,25 +280,23 @@ 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(_) |
|
job.set_stats(stat);
|
||||||
JobID::Command(_) => {
|
}
|
||||||
job.set_stats(stat);
|
}
|
||||||
}
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||||
match identifier {
|
match identifier {
|
||||||
// Match by process group ID
|
// Match by process group ID
|
||||||
@@ -355,17 +352,17 @@ impl JobTab {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn wait_all_bg(&mut self) -> ShResult<()> {
|
pub fn wait_all_bg(&mut self) -> ShResult<()> {
|
||||||
disable_reaping();
|
disable_reaping();
|
||||||
defer! {
|
defer! {
|
||||||
enable_reaping();
|
enable_reaping();
|
||||||
}
|
}
|
||||||
for job in self.jobs.iter_mut() {
|
for job in self.jobs.iter_mut() {
|
||||||
let Some(job) = job else { continue };
|
let Some(job) = job else { continue };
|
||||||
job.wait_pgrp()?;
|
job.wait_pgrp()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||||
if let Some(tabid) = tabid {
|
if let Some(tabid) = tabid {
|
||||||
@@ -611,12 +608,11 @@ impl Job {
|
|||||||
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
||||||
&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 {
|
||||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||||
@@ -711,8 +707,8 @@ impl Job {
|
|||||||
let padding = " ".repeat(padding_count);
|
let padding = " ".repeat(padding_count);
|
||||||
|
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let id_box = format!("[{}]{}", id + 1, symbol);
|
let id_box = format!("[{}]{}", id + 1, symbol);
|
||||||
output.push_str(&format!("{id_box}\t"));
|
output.push_str(&format!("{id_box}\t"));
|
||||||
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
||||||
let pid = if pids || init {
|
let pid = if pids || init {
|
||||||
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
||||||
@@ -735,12 +731,12 @@ impl Job {
|
|||||||
},
|
},
|
||||||
_ => stat_line.styled(Style::Cyan),
|
_ => stat_line.styled(Style::Cyan),
|
||||||
};
|
};
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
let padding = " ".repeat(id_box.len() - 1);
|
let padding = " ".repeat(id_box.len() - 1);
|
||||||
stat_line = format!("{padding}{}", stat_line);
|
stat_line = format!("{padding}{}", stat_line);
|
||||||
}
|
}
|
||||||
if i != self.get_cmds().len() - 1 {
|
if i != self.get_cmds().len() - 1 {
|
||||||
stat_line.push_str(" |");
|
stat_line.push_str(" |");
|
||||||
}
|
}
|
||||||
|
|
||||||
let stat_final = if long {
|
let stat_final = if long {
|
||||||
@@ -767,61 +763,64 @@ pub fn term_ctlr() -> Pid {
|
|||||||
/// Calls attach_tty() on the shell's process group to retake control of the
|
/// Calls attach_tty() on the shell's process group to retake control of the
|
||||||
/// terminal
|
/// terminal
|
||||||
pub fn take_term() -> ShResult<()> {
|
pub fn take_term() -> ShResult<()> {
|
||||||
// take the terminal back
|
// take the terminal back
|
||||||
attach_tty(getpgrp())?;
|
attach_tty(getpgrp())?;
|
||||||
|
|
||||||
// send SIGWINCH to tell readline to update its window size in case it changed while we were in the background
|
// send SIGWINCH to tell readline to update its window size in case it changed while we were in the background
|
||||||
killpg(getpgrp(), Signal::SIGWINCH)?;
|
killpg(getpgrp(), Signal::SIGWINCH)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_bg(id: JobID) -> ShResult<()> {
|
pub fn wait_bg(id: JobID) -> ShResult<()> {
|
||||||
disable_reaping();
|
disable_reaping();
|
||||||
defer! {
|
defer! {
|
||||||
enable_reaping();
|
enable_reaping();
|
||||||
};
|
};
|
||||||
match id {
|
match id {
|
||||||
JobID::Pid(pid) => {
|
JobID::Pid(pid) => {
|
||||||
let stat = loop {
|
let stat = loop {
|
||||||
match waitpid(pid, None) {
|
match waitpid(pid, None) {
|
||||||
Ok(stat) => break stat,
|
Ok(stat) => break stat,
|
||||||
Err(Errno::EINTR) => continue, // Retry on signal interruption
|
Err(Errno::EINTR) => continue, // Retry on signal interruption
|
||||||
Err(Errno::ECHILD) => return Ok(()), // No such child, treat as already reaped
|
Err(Errno::ECHILD) => return Ok(()), // No such child, treat as already reaped
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
write_jobs(|j| j.update_by_id(id, stat))?;
|
write_jobs(|j| j.update_by_id(id, stat))?;
|
||||||
set_status(code_from_status(&stat).unwrap_or(0));
|
set_status(code_from_status(&stat).unwrap_or(0));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
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,
|
||||||
let statuses = job.wait_pgrp()?;
|
format!("wait: No such job with id {:?}", id),
|
||||||
let mut was_stopped = false;
|
));
|
||||||
let mut code = 0;
|
};
|
||||||
for status in statuses {
|
let statuses = job.wait_pgrp()?;
|
||||||
code = code_from_status(&status).unwrap_or(0);
|
let mut was_stopped = false;
|
||||||
match status {
|
let mut code = 0;
|
||||||
WtStat::Stopped(_, _) => {
|
for status in statuses {
|
||||||
was_stopped = true;
|
code = code_from_status(&status).unwrap_or(0);
|
||||||
}
|
match status {
|
||||||
WtStat::Signaled(_, sig, _) => {
|
WtStat::Stopped(_, _) => {
|
||||||
if sig == Signal::SIGTSTP {
|
was_stopped = true;
|
||||||
was_stopped = true;
|
}
|
||||||
}
|
WtStat::Signaled(_, sig, _) => {
|
||||||
}
|
if sig == Signal::SIGTSTP {
|
||||||
_ => { /* Do nothing */ }
|
was_stopped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => { /* Do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if was_stopped {
|
if was_stopped {
|
||||||
write_jobs(|j| j.insert_job(job, false))?;
|
write_jobs(|j| j.insert_job(job, false))?;
|
||||||
}
|
}
|
||||||
set_status(code);
|
set_status(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Waits on the current foreground job and updates the shell's last status code
|
/// Waits on the current foreground job and updates the shell's last status code
|
||||||
@@ -835,12 +834,12 @@ pub fn wait_fg(job: Job, interactive: bool) -> ShResult<()> {
|
|||||||
attach_tty(job.pgid())?;
|
attach_tty(job.pgid())?;
|
||||||
}
|
}
|
||||||
disable_reaping();
|
disable_reaping();
|
||||||
defer! {
|
defer! {
|
||||||
enable_reaping();
|
enable_reaping();
|
||||||
}
|
}
|
||||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
code = code_from_status(&status).unwrap_or(0);
|
code = code_from_status(&status).unwrap_or(0);
|
||||||
match status {
|
match status {
|
||||||
WtStat::Stopped(_, _) => {
|
WtStat::Stopped(_, _) => {
|
||||||
was_stopped = true;
|
was_stopped = true;
|
||||||
|
|||||||
@@ -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::{
|
||||||
@@ -15,96 +15,96 @@ use crate::{
|
|||||||
pub type ShResult<T> = Result<T, ShErr>;
|
pub type ShResult<T> = Result<T, ShErr>;
|
||||||
|
|
||||||
pub struct ColorRng {
|
pub struct ColorRng {
|
||||||
last_color: Option<Color>,
|
last_color: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorRng {
|
impl ColorRng {
|
||||||
fn get_colors() -> &'static [Color] {
|
fn get_colors() -> &'static [Color] {
|
||||||
&[
|
&[
|
||||||
Color::Red,
|
Color::Red,
|
||||||
Color::Cyan,
|
Color::Cyan,
|
||||||
Color::Blue,
|
Color::Blue,
|
||||||
Color::Green,
|
Color::Green,
|
||||||
Color::Yellow,
|
Color::Yellow,
|
||||||
Color::Magenta,
|
Color::Magenta,
|
||||||
Color::Fixed(208), // orange
|
Color::Fixed(208), // orange
|
||||||
Color::Fixed(39), // deep sky blue
|
Color::Fixed(39), // deep sky blue
|
||||||
Color::Fixed(170), // orchid / magenta-pink
|
Color::Fixed(170), // orchid / magenta-pink
|
||||||
Color::Fixed(76), // chartreuse
|
Color::Fixed(76), // chartreuse
|
||||||
Color::Fixed(51), // aqua
|
Color::Fixed(51), // aqua
|
||||||
Color::Fixed(226), // bright yellow
|
Color::Fixed(226), // bright yellow
|
||||||
Color::Fixed(99), // slate blue
|
Color::Fixed(99), // slate blue
|
||||||
Color::Fixed(214), // light orange
|
Color::Fixed(214), // light orange
|
||||||
Color::Fixed(48), // spring green
|
Color::Fixed(48), // spring green
|
||||||
Color::Fixed(201), // hot pink
|
Color::Fixed(201), // hot pink
|
||||||
Color::Fixed(81), // steel blue
|
Color::Fixed(81), // steel blue
|
||||||
Color::Fixed(220), // gold
|
Color::Fixed(220), // gold
|
||||||
Color::Fixed(105), // medium purple
|
Color::Fixed(105), // medium purple
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_color(&mut self) -> Color {
|
pub fn last_color(&mut self) -> Color {
|
||||||
if let Some(color) = self.last_color.take() {
|
if let Some(color) = self.last_color.take() {
|
||||||
color
|
color
|
||||||
} else {
|
} else {
|
||||||
let color = self.next().unwrap_or(Color::White);
|
let color = self.next().unwrap_or(Color::White);
|
||||||
self.last_color = Some(color);
|
self.last_color = Some(color);
|
||||||
color
|
color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for ColorRng {
|
impl Iterator for ColorRng {
|
||||||
type Item = Color;
|
type Item = Color;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let colors = Self::get_colors();
|
let colors = Self::get_colors();
|
||||||
let idx = rand::rngs::SysRng.try_next_u32().ok()? as usize % colors.len();
|
let idx = rand::rngs::SysRng.try_next_u32().ok()? as usize % colors.len();
|
||||||
Some(colors[idx])
|
Some(colors[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng { last_color: None }) };
|
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng { last_color: None }) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_color() -> Color {
|
pub fn next_color() -> Color {
|
||||||
COLOR_RNG.with(|rng| {
|
COLOR_RNG.with(|rng| {
|
||||||
let color = rng.borrow_mut().next().unwrap();
|
let color = rng.borrow_mut().next().unwrap();
|
||||||
rng.borrow_mut().last_color = Some(color);
|
rng.borrow_mut().last_color = Some(color);
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_color() -> Color {
|
pub fn last_color() -> Color {
|
||||||
COLOR_RNG.with(|rng| rng.borrow_mut().last_color())
|
COLOR_RNG.with(|rng| rng.borrow_mut().last_color())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_color() {
|
pub fn clear_color() {
|
||||||
COLOR_RNG.with(|rng| rng.borrow_mut().last_color = None);
|
COLOR_RNG.with(|rng| rng.borrow_mut().last_color = None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ShResultExt {
|
pub trait ShResultExt {
|
||||||
fn blame(self, span: Span) -> Self;
|
fn blame(self, span: Span) -> Self;
|
||||||
fn try_blame(self, span: Span) -> Self;
|
fn try_blame(self, span: Span) -> Self;
|
||||||
fn promote_err(self, span: Span) -> Self;
|
fn promote_err(self, span: Span) -> Self;
|
||||||
fn is_flow_control(&self) -> bool;
|
fn is_flow_control(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ShResultExt for Result<T, ShErr> {
|
impl<T> ShResultExt for Result<T, ShErr> {
|
||||||
/// Blame a span for an error
|
/// Blame a span for an error
|
||||||
fn blame(self, new_span: Span) -> Self {
|
fn blame(self, new_span: Span) -> Self {
|
||||||
self.map_err(|e| e.blame(new_span))
|
self.map_err(|e| e.blame(new_span))
|
||||||
}
|
}
|
||||||
/// Blame a span if no blame has been assigned yet
|
/// Blame a span if no blame has been assigned yet
|
||||||
fn try_blame(self, new_span: Span) -> Self {
|
fn try_blame(self, new_span: Span) -> Self {
|
||||||
self.map_err(|e| e.try_blame(new_span))
|
self.map_err(|e| e.try_blame(new_span))
|
||||||
|
}
|
||||||
|
fn promote_err(self, span: Span) -> Self {
|
||||||
|
self.map_err(|e| e.promote(span))
|
||||||
|
}
|
||||||
|
fn is_flow_control(&self) -> bool {
|
||||||
|
self.as_ref().is_err_and(|e| e.is_flow_control())
|
||||||
}
|
}
|
||||||
fn promote_err(self, span: Span) -> Self {
|
|
||||||
self.map_err(|e| e.promote(span))
|
|
||||||
}
|
|
||||||
fn is_flow_control(&self) -> bool {
|
|
||||||
self.as_ref().is_err_and(|e| e.is_flow_control())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -163,160 +163,256 @@ impl Display for Note {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ShErr {
|
pub struct ShErr {
|
||||||
kind: ShErrKind,
|
kind: ShErrKind,
|
||||||
src_span: Option<Span>,
|
src_span: Option<Span>,
|
||||||
labels: Vec<ariadne::Label<Span>>,
|
labels: Vec<ariadne::Label<Span>>,
|
||||||
sources: Vec<SpanSource>,
|
sources: Vec<SpanSource>,
|
||||||
notes: Vec<String>,
|
notes: Vec<String>,
|
||||||
|
|
||||||
/// If we propagate through a redirect boundary, we take ownership of
|
/// If we propagate through a redirect boundary, we take ownership of
|
||||||
/// 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,
|
||||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
src_span: Some(span),
|
||||||
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()], io_guards: vec![] }
|
labels: vec![],
|
||||||
}
|
sources: vec![],
|
||||||
pub fn is_flow_control(&self) -> bool {
|
notes: vec![],
|
||||||
self.kind.is_flow_control()
|
io_guards: vec![],
|
||||||
}
|
}
|
||||||
pub fn promote(mut self, span: Span) -> Self {
|
}
|
||||||
if self.notes.is_empty() {
|
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||||
return self
|
Self {
|
||||||
}
|
kind,
|
||||||
let first = self.notes[0].clone();
|
src_span: None,
|
||||||
if self.notes.len() > 1 {
|
labels: vec![],
|
||||||
self.notes = self.notes[1..].to_vec();
|
sources: vec![],
|
||||||
}
|
notes: vec![msg.into()],
|
||||||
|
io_guards: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_flow_control(&self) -> bool {
|
||||||
|
self.kind.is_flow_control()
|
||||||
|
}
|
||||||
|
pub fn promote(mut self, span: Span) -> Self {
|
||||||
|
if self.notes.is_empty() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
let first = self.notes[0].clone();
|
||||||
|
if self.notes.len() > 1 {
|
||||||
|
self.notes = self.notes[1..].to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
self.labeled(span, first)
|
self.labeled(span, first)
|
||||||
}
|
}
|
||||||
pub fn with_redirs(mut self, guard: RedirGuard) -> Self {
|
pub fn with_redirs(mut self, guard: RedirGuard) -> Self {
|
||||||
self.io_guards.push(guard);
|
self.io_guards.push(guard);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
||||||
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)
|
||||||
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
|
.with_color(color)
|
||||||
let color = last_color();
|
.with_message(msg),
|
||||||
let src = span.span_source().clone();
|
)
|
||||||
let msg: String = msg.into();
|
}
|
||||||
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
|
||||||
}
|
let color = last_color();
|
||||||
pub fn blame(self, span: Span) -> Self {
|
let src = span.span_source().clone();
|
||||||
let ShErr { kind, src_span: _, labels, sources, notes, io_guards } = self;
|
let msg: String = msg.into();
|
||||||
Self { kind, src_span: Some(span), labels, sources, notes, io_guards }
|
self.with_label(
|
||||||
}
|
src,
|
||||||
pub fn try_blame(self, span: Span) -> Self {
|
ariadne::Label::new(span)
|
||||||
match self {
|
.with_color(color)
|
||||||
ShErr { kind, src_span: None, labels, sources, notes, io_guards } => Self { kind, src_span: Some(span), labels, sources, notes, io_guards },
|
.with_message(msg),
|
||||||
_ => self
|
)
|
||||||
}
|
}
|
||||||
}
|
pub fn blame(self, span: Span) -> Self {
|
||||||
pub fn kind(&self) -> &ShErrKind {
|
let ShErr {
|
||||||
&self.kind
|
kind,
|
||||||
}
|
src_span: _,
|
||||||
pub fn rename(mut self, name: impl Into<String>) -> Self {
|
labels,
|
||||||
if let Some(span) = self.src_span.as_mut() {
|
sources,
|
||||||
span.rename(name.into());
|
notes,
|
||||||
}
|
io_guards,
|
||||||
self
|
} = self;
|
||||||
}
|
Self {
|
||||||
pub fn with_label(self, source: SpanSource, label: ariadne::Label<Span>) -> Self {
|
kind,
|
||||||
let ShErr { kind, src_span, mut labels, mut sources, notes, io_guards } = self;
|
src_span: Some(span),
|
||||||
sources.push(source);
|
labels,
|
||||||
labels.push(label);
|
sources,
|
||||||
Self { kind, src_span, labels, sources, notes, io_guards }
|
notes,
|
||||||
}
|
io_guards,
|
||||||
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;
|
}
|
||||||
for (src, label) in ctx {
|
pub fn try_blame(self, span: Span) -> Self {
|
||||||
sources.push(src);
|
match self {
|
||||||
labels.push(label);
|
ShErr {
|
||||||
}
|
kind,
|
||||||
Self { kind, src_span, labels, sources, notes, io_guards }
|
src_span: None,
|
||||||
}
|
labels,
|
||||||
pub fn with_note(self, note: impl Into<String>) -> Self {
|
sources,
|
||||||
let ShErr { kind, src_span, labels, sources, mut notes, io_guards } = self;
|
notes,
|
||||||
notes.push(note.into());
|
io_guards,
|
||||||
Self { kind, src_span, labels, sources, notes, io_guards }
|
} => Self {
|
||||||
}
|
kind,
|
||||||
pub fn build_report(&self) -> Option<Report<'_, Span>> {
|
src_span: Some(span),
|
||||||
let span = self.src_span.as_ref()?;
|
labels,
|
||||||
let mut report = Report::build(ReportKind::Error, span.clone())
|
sources,
|
||||||
.with_config(ariadne::Config::default().with_color(true));
|
notes,
|
||||||
let msg = if self.notes.is_empty() {
|
io_guards,
|
||||||
self.kind.to_string()
|
},
|
||||||
} else {
|
_ => self,
|
||||||
format!("{} - {}", self.kind, self.notes.first().unwrap())
|
}
|
||||||
};
|
}
|
||||||
report = report.with_message(msg);
|
pub fn kind(&self) -> &ShErrKind {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
pub fn rename(mut self, name: impl Into<String>) -> Self {
|
||||||
|
if let Some(span) = self.src_span.as_mut() {
|
||||||
|
span.rename(name.into());
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
sources.push(source);
|
||||||
|
labels.push(label);
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
src_span,
|
||||||
|
labels,
|
||||||
|
sources,
|
||||||
|
notes,
|
||||||
|
io_guards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
for (src, label) in ctx {
|
||||||
|
sources.push(src);
|
||||||
|
labels.push(label);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
src_span,
|
||||||
|
labels,
|
||||||
|
sources,
|
||||||
|
notes,
|
||||||
|
io_guards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn with_note(self, note: impl Into<String>) -> Self {
|
||||||
|
let ShErr {
|
||||||
|
kind,
|
||||||
|
src_span,
|
||||||
|
labels,
|
||||||
|
sources,
|
||||||
|
mut notes,
|
||||||
|
io_guards,
|
||||||
|
} = self;
|
||||||
|
notes.push(note.into());
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
src_span,
|
||||||
|
labels,
|
||||||
|
sources,
|
||||||
|
notes,
|
||||||
|
io_guards,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn build_report(&self) -> Option<Report<'_, Span>> {
|
||||||
|
let span = self.src_span.as_ref()?;
|
||||||
|
let mut report = Report::build(ReportKind::Error, span.clone())
|
||||||
|
.with_config(ariadne::Config::default().with_color(true));
|
||||||
|
let msg = if self.notes.is_empty() {
|
||||||
|
self.kind.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{} - {}", self.kind, self.notes.first().unwrap())
|
||||||
|
};
|
||||||
|
report = report.with_message(msg);
|
||||||
|
|
||||||
for label in self.labels.clone() {
|
for label in self.labels.clone() {
|
||||||
report = report.with_label(label);
|
report = report.with_label(label);
|
||||||
}
|
}
|
||||||
for note in &self.notes {
|
for note in &self.notes {
|
||||||
report = report.with_note(note);
|
report = report.with_note(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(report.finish())
|
Some(report.finish())
|
||||||
}
|
}
|
||||||
fn collect_sources(&self) -> HashMap<SpanSource, String> {
|
fn collect_sources(&self) -> HashMap<SpanSource, String> {
|
||||||
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
|
||||||
.or_insert_with(|| src.content().to_string());
|
.entry(src.clone())
|
||||||
}
|
.or_insert_with(|| src.content().to_string());
|
||||||
for src in &self.sources {
|
}
|
||||||
source_map.entry(src.clone())
|
for src in &self.sources {
|
||||||
.or_insert_with(|| src.content().to_string());
|
source_map
|
||||||
}
|
.entry(src.clone())
|
||||||
source_map
|
.or_insert_with(|| src.content().to_string());
|
||||||
}
|
}
|
||||||
pub fn print_error(&self) {
|
source_map
|
||||||
let default = || {
|
}
|
||||||
eprintln!("\n{}", self.kind);
|
pub fn print_error(&self) {
|
||||||
for note in &self.notes {
|
let default = || {
|
||||||
eprintln!("note: {note}");
|
eprintln!("\n{}", self.kind);
|
||||||
}
|
for note in &self.notes {
|
||||||
};
|
eprintln!("note: {note}");
|
||||||
let Some(report) = self.build_report() else {
|
}
|
||||||
return default();
|
};
|
||||||
};
|
let Some(report) = self.build_report() else {
|
||||||
|
return default();
|
||||||
|
};
|
||||||
|
|
||||||
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
|
||||||
.cloned()
|
.get(src)
|
||||||
.ok_or_else(|| format!("Failed to fetch source '{}'", src.name()))
|
.cloned()
|
||||||
});
|
.ok_or_else(|| format!("Failed to fetch source '{}'", src.name()))
|
||||||
eprintln!();
|
});
|
||||||
if report.eprint(cache).is_err() {
|
eprintln!();
|
||||||
default();
|
if report.eprint(cache).is_err() {
|
||||||
}
|
default();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErr {
|
impl Display for ShErr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
if self.notes.is_empty() {
|
if self.notes.is_empty() {
|
||||||
write!(f, "{}", self.kind)
|
write!(f, "{}", self.kind)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{} - {}", self.kind, self.notes.first().unwrap())
|
write!(f, "{} - {}", self.kind, self.notes.first().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ShErr {
|
impl From<std::io::Error> for ShErr {
|
||||||
@@ -350,7 +446,7 @@ pub enum ShErrKind {
|
|||||||
ResourceLimitExceeded,
|
ResourceLimitExceeded,
|
||||||
BadPermission,
|
BadPermission,
|
||||||
Errno(Errno),
|
Errno(Errno),
|
||||||
NotFound,
|
NotFound,
|
||||||
ReadlineErr,
|
ReadlineErr,
|
||||||
ExCommand,
|
ExCommand,
|
||||||
|
|
||||||
@@ -364,15 +460,16 @@ 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
|
||||||
}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErrKind {
|
impl Display for ShErrKind {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ pub trait TkVecUtils<Tk> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait AutoCmdVecUtils {
|
pub trait AutoCmdVecUtils {
|
||||||
fn exec(&self);
|
fn exec(&self);
|
||||||
fn exec_with(&self, pattern: &str);
|
fn exec_with(&self, pattern: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RedirVecUtils<Redir> {
|
pub trait RedirVecUtils<Redir> {
|
||||||
@@ -37,36 +37,44 @@ pub trait RedirVecUtils<Redir> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait NodeVecUtils<Node> {
|
pub trait NodeVecUtils<Node> {
|
||||||
fn get_span(&self) -> Option<Span>;
|
fn get_span(&self) -> Option<Span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AutoCmdVecUtils for Vec<AutoCmd> {
|
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 {
|
||||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) {
|
pattern: _,
|
||||||
e.print_error();
|
command,
|
||||||
}
|
} = cmd;
|
||||||
}
|
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) {
|
||||||
crate::state::set_status(saved_status);
|
e.print_error();
|
||||||
}
|
}
|
||||||
fn exec_with(&self, other_pattern: &str) {
|
}
|
||||||
let saved_status = crate::state::get_status();
|
crate::state::set_status(saved_status);
|
||||||
for cmd in self {
|
}
|
||||||
let AutoCmd { pattern, command } = cmd;
|
fn exec_with(&self, other_pattern: &str) {
|
||||||
if let Some(pat) = pattern
|
let saved_status = crate::state::get_status();
|
||||||
&& !pat.is_match(other_pattern) {
|
for cmd in self {
|
||||||
log::trace!("autocmd pattern '{}' did not match '{}', skipping", pat, other_pattern);
|
let AutoCmd { pattern, command } = cmd;
|
||||||
continue;
|
if let Some(pat) = pattern
|
||||||
}
|
&& !pat.is_match(other_pattern)
|
||||||
|
{
|
||||||
|
log::trace!(
|
||||||
|
"autocmd pattern '{}' did not match '{}', skipping",
|
||||||
|
pat,
|
||||||
|
other_pattern
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
crate::state::set_status(saved_status);
|
crate::state::set_status(saved_status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||||
@@ -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,13 +181,17 @@ 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 last_end = last_nd.get_span().range().end;
|
let first_start = first_nd.get_span().range().start;
|
||||||
if first_start <= last_end {
|
let last_end = last_nd.get_span().range().end;
|
||||||
return Some(Span::new(first_start..last_end, first_nd.get_span().source().content()));
|
if first_start <= last_end {
|
||||||
}
|
return Some(Span::new(
|
||||||
|
first_start..last_end,
|
||||||
|
first_nd.get_span().source().content(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/main.rs
92
src/main.rs
@@ -2,7 +2,7 @@
|
|||||||
clippy::derivable_impls,
|
clippy::derivable_impls,
|
||||||
clippy::tabs_in_doc_comments,
|
clippy::tabs_in_doc_comments,
|
||||||
clippy::while_let_on_iterator,
|
clippy::while_let_on_iterator,
|
||||||
clippy::result_large_err
|
clippy::result_large_err
|
||||||
)]
|
)]
|
||||||
pub mod builtin;
|
pub mod builtin;
|
||||||
pub mod expand;
|
pub mod expand;
|
||||||
@@ -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};
|
||||||
@@ -86,20 +85,22 @@ fn setup_panic_handler() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let data_dir = env::var("XDG_DATA_HOME").unwrap_or_else(|_| {
|
let data_dir = env::var("XDG_DATA_HOME").unwrap_or_else(|_| {
|
||||||
let home = env::var("HOME").unwrap();
|
let home = env::var("HOME").unwrap();
|
||||||
format!("{home}/.local/share")
|
format!("{home}/.local/share")
|
||||||
});
|
});
|
||||||
let log_dir = Path::new(&data_dir).join("shed").join("log");
|
let log_dir = Path::new(&data_dir).join("shed").join("log");
|
||||||
std::fs::create_dir_all(&log_dir).unwrap();
|
std::fs::create_dir_all(&log_dir).unwrap();
|
||||||
let log_file_path = log_dir.join("panic.log");
|
let log_file_path = log_dir.join("panic.log");
|
||||||
let mut log_file = parse::get_redir_file(parse::RedirType::Output, log_file_path).unwrap();
|
let mut log_file = parse::get_redir_file(parse::RedirType::Output, log_file_path).unwrap();
|
||||||
|
|
||||||
let panic_info_raw = info.to_string();
|
let panic_info_raw = info.to_string();
|
||||||
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);
|
||||||
}));
|
}));
|
||||||
@@ -134,16 +135,17 @@ fn main() -> ExitCode {
|
|||||||
} else {
|
} else {
|
||||||
shed_interactive(args)
|
shed_interactive(args)
|
||||||
} {
|
} {
|
||||||
e.print_error();
|
e.print_error();
|
||||||
};
|
};
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
let on_exit_autocmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnExit));
|
let on_exit_autocmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnExit));
|
||||||
on_exit_autocmds.exec();
|
on_exit_autocmds.exec();
|
||||||
|
|
||||||
write_jobs(|j| j.hang_up());
|
write_jobs(|j| j.hang_up());
|
||||||
ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8)
|
ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8)
|
||||||
@@ -151,7 +153,7 @@ fn main() -> ExitCode {
|
|||||||
|
|
||||||
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let path_raw = path.to_string_lossy().to_string();
|
let path_raw = path.to_string_lossy().to_string();
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
eprintln!("shed: Failed to open input file: {}", path.display());
|
eprintln!("shed: Failed to open input file: {}", path.display());
|
||||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||||
@@ -207,7 +209,7 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
m.try_rehash_commands();
|
m.try_rehash_commands();
|
||||||
m.try_rehash_cwd_listing();
|
m.try_rehash_cwd_listing();
|
||||||
});
|
});
|
||||||
error::clear_color();
|
error::clear_color();
|
||||||
|
|
||||||
// Handle any pending signals
|
// Handle any pending signals
|
||||||
while signals_pending() {
|
while signals_pending() {
|
||||||
@@ -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);
|
||||||
@@ -376,14 +396,16 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
// Process any available input
|
// Process any available input
|
||||||
match readline.process_input() {
|
match readline.process_input() {
|
||||||
Ok(ReadlineEvent::Line(input)) => {
|
Ok(ReadlineEvent::Line(input)) => {
|
||||||
let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd));
|
let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd));
|
||||||
let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd));
|
let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd));
|
||||||
|
|
||||||
pre_exec.exec_with(&input);
|
pre_exec.exec_with(&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.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);
|
||||||
@@ -396,10 +418,10 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
log::info!("Command executed in {:.2?}", command_run_time);
|
log::info!("Command executed in {:.2?}", command_run_time);
|
||||||
write_meta(|m| m.stop_timer());
|
write_meta(|m| m.stop_timer());
|
||||||
|
|
||||||
post_exec.exec_with(&input);
|
post_exec.exec_with(&input);
|
||||||
|
|
||||||
readline.fix_column()?;
|
readline.fix_column()?;
|
||||||
readline.writer.flush_write("\n\r")?;
|
readline.writer.flush_write("\n\r")?;
|
||||||
|
|
||||||
// Reset for next command with fresh prompt
|
// Reset for next command with fresh prompt
|
||||||
readline.reset(true)?;
|
readline.reset(true)?;
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -118,8 +145,10 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, s
|
|||||||
} else {
|
} else {
|
||||||
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();
|
||||||
@@ -149,7 +178,7 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, s
|
|||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
nodes: VecDeque<Node>,
|
nodes: VecDeque<Node>,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
source_name: String,
|
source_name: String,
|
||||||
pub io_stack: IoStack,
|
pub io_stack: IoStack,
|
||||||
pub job_stack: JobStack,
|
pub job_stack: JobStack,
|
||||||
fg_job: bool,
|
fg_job: bool,
|
||||||
@@ -161,7 +190,7 @@ impl Dispatcher {
|
|||||||
Self {
|
Self {
|
||||||
nodes,
|
nodes,
|
||||||
interactive,
|
interactive,
|
||||||
source_name,
|
source_name,
|
||||||
io_stack: IoStack::new(),
|
io_stack: IoStack::new(),
|
||||||
job_stack: JobStack::new(),
|
job_stack: JobStack::new(),
|
||||||
fg_job: true,
|
fg_job: true,
|
||||||
@@ -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,16 +308,16 @@ 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(())
|
||||||
}
|
}
|
||||||
fn exec_subsh(&mut self, subsh: Node) -> ShResult<()> {
|
fn exec_subsh(&mut self, subsh: Node) -> ShResult<()> {
|
||||||
let blame = subsh.get_span().clone();
|
let blame = subsh.get_span().clone();
|
||||||
let NdRule::Command { assignments, argv } = subsh.class else {
|
let NdRule::Command { assignments, argv } = subsh.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let name = self.source_name.clone();
|
let name = self.source_name.clone();
|
||||||
|
|
||||||
self.run_fork("anonymous_subshell", |s| {
|
self.run_fork("anonymous_subshell", |s| {
|
||||||
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
|
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
|
||||||
@@ -309,8 +343,11 @@ 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,
|
||||||
@@ -340,12 +377,12 @@ impl Dispatcher {
|
|||||||
|
|
||||||
self.io_stack.append_to_frame(func.redirs);
|
self.io_stack.append_to_frame(func.redirs);
|
||||||
|
|
||||||
blame.rename(func_name.clone());
|
blame.rename(func_name.clone());
|
||||||
|
|
||||||
let argv = prepare_argv(argv).try_blame(blame.clone())?;
|
let argv = prepare_argv(argv).try_blame(blame.clone())?;
|
||||||
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let _guard = scope_guard(Some(argv));
|
let _guard = scope_guard(Some(argv));
|
||||||
func_body.body_mut().propagate_context(func_ctx);
|
func_body.body_mut().propagate_context(func_ctx);
|
||||||
func_body.body_mut().flags = func.flags;
|
func_body.body_mut().flags = func.flags;
|
||||||
|
|
||||||
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
||||||
@@ -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,12 +460,17 @@ 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('|');
|
||||||
|
|
||||||
for pattern in block_patterns {
|
for pattern in block_patterns {
|
||||||
let pattern_exp = expand_case_pattern(pattern)?;
|
let pattern_exp = expand_case_pattern(pattern)?;
|
||||||
let pattern_regex = glob_to_regex(&pattern_exp, false);
|
let pattern_regex = glob_to_regex(&pattern_exp, false);
|
||||||
if pattern_regex.is_match(&pattern_raw) {
|
if pattern_regex.is_match(&pattern_raw) {
|
||||||
for node in &body {
|
for node in &body {
|
||||||
@@ -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,11 +742,14 @@ 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
|
||||||
attach_tty(pgid).ok();
|
&& self.interactive
|
||||||
tty_attached = true;
|
&& let Some(pgid) = self.job_stack.curr_job_mut().unwrap().pgid()
|
||||||
}
|
{
|
||||||
|
attach_tty(pgid).ok();
|
||||||
|
tty_attached = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let job = self.job_stack.finalize_job().unwrap();
|
let job = self.job_stack.finalize_job().unwrap();
|
||||||
dispatch_job(job, is_bg, self.interactive)?;
|
dispatch_job(job, is_bg, self.interactive)?;
|
||||||
@@ -732,7 +785,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||||
let cmd_raw = cmd.get_command().unwrap().to_string();
|
let cmd_raw = cmd.get_command().unwrap().to_string();
|
||||||
let context = cmd.context.clone();
|
let context = cmd.context.clone();
|
||||||
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -815,18 +868,18 @@ impl Dispatcher {
|
|||||||
"unset" => unset(cmd),
|
"unset" => unset(cmd),
|
||||||
"complete" => complete_builtin(cmd),
|
"complete" => complete_builtin(cmd),
|
||||||
"compgen" => compgen_builtin(cmd),
|
"compgen" => compgen_builtin(cmd),
|
||||||
"map" => map::map(cmd),
|
"map" => map::map(cmd),
|
||||||
"pop" => arr_pop(cmd),
|
"pop" => arr_pop(cmd),
|
||||||
"fpop" => arr_fpop(cmd),
|
"fpop" => arr_fpop(cmd),
|
||||||
"push" => arr_push(cmd),
|
"push" => arr_push(cmd),
|
||||||
"fpush" => arr_fpush(cmd),
|
"fpush" => arr_fpush(cmd),
|
||||||
"rotate" => arr_rotate(cmd),
|
"rotate" => arr_rotate(cmd),
|
||||||
"wait" => jobctl::wait(cmd),
|
"wait" => jobctl::wait(cmd),
|
||||||
"type" => intro::type_builtin(cmd),
|
"type" => intro::type_builtin(cmd),
|
||||||
"getopts" => getopts(cmd),
|
"getopts" => getopts(cmd),
|
||||||
"keymap" => keymap::keymap(cmd),
|
"keymap" => keymap::keymap(cmd),
|
||||||
"read_key" => read::read_key(cmd),
|
"read_key" => read::read_key(cmd),
|
||||||
"autocmd" => autocmd(cmd),
|
"autocmd" => autocmd(cmd),
|
||||||
"true" | ":" => {
|
"true" | ":" => {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -838,17 +891,17 @@ impl Dispatcher {
|
|||||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
|
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
if !e.is_flow_control() {
|
if !e.is_flow_control() {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
}
|
}
|
||||||
Err(e.with_context(context).with_redirs(redir_guard))
|
Err(e.with_context(context).with_redirs(redir_guard))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
let blame = cmd.get_span().clone();
|
let blame = cmd.get_span().clone();
|
||||||
let context = cmd.context.clone();
|
let context = cmd.context.clone();
|
||||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -887,7 +940,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
// For foreground jobs, take the terminal BEFORE resetting
|
// For foreground jobs, take the terminal BEFORE resetting
|
||||||
// signals. SIGTTOU is still SIG_IGN (inherited from the shell),
|
// signals. SIGTTOU is still SIG_IGN (inherited from the shell),
|
||||||
// so tcsetpgrp won't stop us. This prevents a race
|
// so tcsetpgrp won't stop us. This prevents a race
|
||||||
// where the child exec's and tries to read stdin before the
|
// where the child exec's and tries to read stdin before the
|
||||||
// parent has called tcsetpgrp — which would deliver SIGTTIN
|
// parent has called tcsetpgrp — which would deliver SIGTTIN
|
||||||
// (now SIG_DFL after reset_signals) and stop the child.
|
// (now SIG_DFL after reset_signals) and stop the child.
|
||||||
@@ -918,14 +971,14 @@ impl Dispatcher {
|
|||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => {
|
Errno::ENOENT => {
|
||||||
ShErr::new(ShErrKind::NotFound, span.clone())
|
ShErr::new(ShErrKind::NotFound, span.clone())
|
||||||
.labeled(span, format!("{cmd_str}: command not found"))
|
.labeled(span, format!("{cmd_str}: command not found"))
|
||||||
.with_context(context)
|
.with_context(context)
|
||||||
.print_error();
|
.print_error();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))
|
ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))
|
||||||
.with_context(context)
|
.with_context(context)
|
||||||
.print_error();
|
.print_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(e as i32)
|
exit(e as i32)
|
||||||
|
|||||||
388
src/parse/lex.rs
388
src/parse/lex.rs
@@ -25,98 +25,101 @@ 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 {
|
||||||
pub fn outside(&self) -> bool {
|
pub fn outside(&self) -> bool {
|
||||||
matches!(self, QuoteState::Outside)
|
matches!(self, QuoteState::Outside)
|
||||||
}
|
}
|
||||||
pub fn in_single(&self) -> bool {
|
pub fn in_single(&self) -> bool {
|
||||||
matches!(self, QuoteState::Single)
|
matches!(self, QuoteState::Single)
|
||||||
}
|
}
|
||||||
pub fn in_double(&self) -> bool {
|
pub fn in_double(&self) -> bool {
|
||||||
matches!(self, QuoteState::Double)
|
matches!(self, QuoteState::Double)
|
||||||
}
|
}
|
||||||
pub fn in_quote(&self) -> bool {
|
pub fn in_quote(&self) -> bool {
|
||||||
!self.outside()
|
!self.outside()
|
||||||
}
|
}
|
||||||
/// Toggles whether we are in a double quote. If self = QuoteState::Single, this does nothing, since double quotes inside single quotes are just literal characters
|
/// Toggles whether we are in a double quote. If self = QuoteState::Single, this does nothing, since double quotes inside single quotes are just literal characters
|
||||||
pub fn toggle_double(&mut self) {
|
pub fn toggle_double(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
QuoteState::Outside => *self = QuoteState::Double,
|
QuoteState::Outside => *self = QuoteState::Double,
|
||||||
QuoteState::Double => *self = QuoteState::Outside,
|
QuoteState::Double => *self = QuoteState::Outside,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Toggles whether we are in a single quote. If self == QuoteState::Double, this does nothing, since single quotes are not interpreted inside double quotes
|
/// Toggles whether we are in a single quote. If self == QuoteState::Double, this does nothing, since single quotes are not interpreted inside double quotes
|
||||||
pub fn toggle_single(&mut self) {
|
pub fn toggle_single(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
QuoteState::Outside => *self = QuoteState::Single,
|
QuoteState::Outside => *self = QuoteState::Single,
|
||||||
QuoteState::Single => *self = QuoteState::Outside,
|
QuoteState::Single => *self = QuoteState::Outside,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
pub fn content(&self) -> Arc<String> {
|
pub fn content(&self) -> Arc<String> {
|
||||||
self.content.clone()
|
self.content.clone()
|
||||||
}
|
}
|
||||||
pub fn rename(&mut self, name: String) {
|
pub fn rename(&mut self, name: String) {
|
||||||
self.name = name;
|
self.name = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SpanSource {
|
impl Display for SpanSource {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.name)
|
write!(f, "{}", self.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Span::new(10..20)
|
/// Span::new(10..20)
|
||||||
#[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 {
|
||||||
Span { range, source }
|
Span { range, source }
|
||||||
}
|
}
|
||||||
pub fn rename(&mut self, name: String) {
|
pub fn rename(&mut self, name: String) {
|
||||||
self.source.name = name;
|
self.source.name = name;
|
||||||
}
|
}
|
||||||
pub fn with_name(mut self, name: String) -> Self {
|
pub fn with_name(mut self, name: String) -> Self {
|
||||||
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();
|
||||||
(line, col)
|
(line, col)
|
||||||
}
|
}
|
||||||
/// Slice the source string at the wrapped range
|
/// Slice the source string at the wrapped range
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.source.content[self.range().start..self.range().end]
|
&self.source.content[self.range().start..self.range().end]
|
||||||
@@ -138,19 +141,19 @@ impl Span {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ariadne::Span for Span {
|
impl ariadne::Span for Span {
|
||||||
type SourceId = SpanSource;
|
type SourceId = SpanSource;
|
||||||
|
|
||||||
fn source(&self) -> &Self::SourceId {
|
fn source(&self) -> &Self::SourceId {
|
||||||
&self.source
|
&self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&self) -> usize {
|
fn start(&self) -> usize {
|
||||||
self.range.start
|
self.range.start
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(&self) -> usize {
|
fn end(&self) -> usize {
|
||||||
self.range.end
|
self.range.end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows simple access to the underlying range wrapped by the span
|
/// Allows simple access to the underlying range wrapped by the span
|
||||||
@@ -243,7 +246,7 @@ bitflags! {
|
|||||||
pub struct LexStream {
|
pub struct LexStream {
|
||||||
source: Arc<String>,
|
source: Arc<String>,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
quote_state: QuoteState,
|
quote_state: QuoteState,
|
||||||
brc_grp_depth: usize,
|
brc_grp_depth: usize,
|
||||||
brc_grp_start: Option<usize>,
|
brc_grp_start: Option<usize>,
|
||||||
@@ -273,23 +276,23 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean_input(input: &str) -> String {
|
pub fn clean_input(input: &str) -> String {
|
||||||
let mut chars = input.chars().peekable();
|
let mut chars = input.chars().peekable();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' if chars.peek() == Some(&'\n') => {
|
'\\' if chars.peek() == Some(&'\n') => {
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
'\r' => {
|
'\r' => {
|
||||||
if chars.peek() == Some(&'\n') {
|
if chars.peek() == Some(&'\n') {
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
_ => output.push(ch),
|
_ => output.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LexStream {
|
impl LexStream {
|
||||||
@@ -298,7 +301,7 @@ impl LexStream {
|
|||||||
Self {
|
Self {
|
||||||
flags,
|
flags,
|
||||||
source,
|
source,
|
||||||
name: "<stdin>".into(),
|
name: "<stdin>".into(),
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
quote_state: QuoteState::default(),
|
quote_state: QuoteState::default(),
|
||||||
brc_grp_depth: 0,
|
brc_grp_depth: 0,
|
||||||
@@ -327,10 +330,10 @@ impl LexStream {
|
|||||||
};
|
};
|
||||||
self.source.get(start..end)
|
self.source.get(start..end)
|
||||||
}
|
}
|
||||||
pub fn with_name(mut self, name: String) -> Self {
|
pub fn with_name(mut self, name: String) -> Self {
|
||||||
self.name = name;
|
self.name = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn slice_from_cursor(&self) -> Option<&str> {
|
pub fn slice_from_cursor(&self) -> Option<&str> {
|
||||||
self.slice(self.cursor..)
|
self.slice(self.cursor..)
|
||||||
}
|
}
|
||||||
@@ -475,11 +478,11 @@ impl LexStream {
|
|||||||
pos += ch.len_utf8();
|
pos += ch.len_utf8();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
pos += 1;
|
pos += 1;
|
||||||
self.quote_state.toggle_single();
|
self.quote_state.toggle_single();
|
||||||
}
|
}
|
||||||
_ if self.quote_state.in_single() => pos += ch.len_utf8(),
|
_ if self.quote_state.in_single() => pos += ch.len_utf8(),
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
'$' if chars.peek() == Some(&'(') => {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
chars.next();
|
chars.next();
|
||||||
@@ -543,11 +546,11 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
pos += 1;
|
pos += 1;
|
||||||
self.quote_state.toggle_double();
|
self.quote_state.toggle_double();
|
||||||
}
|
}
|
||||||
_ if self.quote_state.in_double() => pos += ch.len_utf8(),
|
_ if self.quote_state.in_double() => pos += ch.len_utf8(),
|
||||||
'<' if chars.peek() == Some(&'(') => {
|
'<' if chars.peek() == Some(&'(') => {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
chars.next();
|
chars.next();
|
||||||
@@ -770,7 +773,7 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
||||||
let mut span = Span::new(range, self.source.clone());
|
let mut span = Span::new(range, self.source.clone());
|
||||||
span.rename(self.name.clone());
|
span.rename(self.name.clone());
|
||||||
Tk::new(class, span)
|
Tk::new(class, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -845,15 +848,15 @@ impl Iterator for LexStream {
|
|||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
|
|
||||||
while let Some(ch) = get_char(&self.source, self.cursor) {
|
while let Some(ch) = get_char(&self.source, self.cursor) {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' if get_char(&self.source, self.cursor + 1) == Some('\n') => {
|
'\\' if get_char(&self.source, self.cursor + 1) == Some('\n') => {
|
||||||
self.cursor = (self.cursor + 2).min(self.source.len());
|
self.cursor = (self.cursor + 2).min(self.source.len());
|
||||||
}
|
}
|
||||||
_ if is_hard_sep(ch) => {
|
_ if is_hard_sep(ch) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
}
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.get_token(ch_idx..self.cursor, TkRule::Sep)
|
self.get_token(ch_idx..self.cursor, TkRule::Sep)
|
||||||
}
|
}
|
||||||
@@ -974,84 +977,101 @@ pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
|
|||||||
/// Splits a string by a pattern, but only if the pattern is not escaped by a backslash
|
/// Splits a string by a pattern, but only if the pattern is not escaped by a backslash
|
||||||
/// and not in quotes.
|
/// and not in quotes.
|
||||||
pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
||||||
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) {
|
||||||
cursor += split.0.len() + pat.len();
|
cursor += split.0.len() + pat.len();
|
||||||
splits.push(split.0);
|
splits.push(split.0);
|
||||||
}
|
}
|
||||||
if let Some(remaining) = slice.get(cursor..) {
|
if let Some(remaining) = slice.get(cursor..) {
|
||||||
splits.push(remaining.to_string());
|
splits.push(remaining.to_string());
|
||||||
}
|
}
|
||||||
splits
|
splits
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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; }
|
'\\' => {
|
||||||
'\'' => qt_state.toggle_single(),
|
chars.next();
|
||||||
'"' => qt_state.toggle_double(),
|
continue;
|
||||||
_ if qt_state.in_quote() => continue,
|
}
|
||||||
_ => {}
|
'\'' => qt_state.toggle_single(),
|
||||||
}
|
'"' => qt_state.toggle_double(),
|
||||||
|
_ if qt_state.in_quote() => continue,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if slice[i..].starts_with(pat) {
|
if slice[i..].starts_with(pat) {
|
||||||
let before = slice[..i].to_string();
|
let before = slice[..i].to_string();
|
||||||
let after = slice[i + pat.len()..].to_string();
|
let after = slice[i + pat.len()..].to_string();
|
||||||
return Some((before, after));
|
return Some((before, after));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split_tk(tk: &Tk, pat: &str) -> Vec<Tk> {
|
pub fn split_tk(tk: &Tk, pat: &str) -> Vec<Tk> {
|
||||||
let slice = tk.as_str();
|
let slice = tk.as_str();
|
||||||
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(
|
||||||
splits.push(Tk::new(tk.class.clone(), before_span));
|
tk.span.range().start + cursor..tk.span.range().start + cursor + split.0.len(),
|
||||||
cursor += split.0.len() + pat.len();
|
tk.source().clone(),
|
||||||
}
|
);
|
||||||
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
|
splits.push(Tk::new(tk.class.clone(), before_span));
|
||||||
let remaining_span = Span::new(tk.span.range().start + cursor..tk.span.range().end, tk.source().clone());
|
cursor += split.0.len() + pat.len();
|
||||||
splits.push(Tk::new(tk.class.clone(), remaining_span));
|
}
|
||||||
}
|
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
|
||||||
splits
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split_tk_at(tk: &Tk, pat: &str) -> Option<(Tk, Tk)> {
|
pub fn split_tk_at(tk: &Tk, pat: &str) -> Option<(Tk, Tk)> {
|
||||||
let slice = tk.as_str();
|
let slice = tk.as_str();
|
||||||
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; }
|
'\\' => {
|
||||||
'\'' => qt_state.toggle_single(),
|
chars.next();
|
||||||
'"' => qt_state.toggle_double(),
|
continue;
|
||||||
_ if qt_state.in_quote() => continue,
|
}
|
||||||
_ => {}
|
'\'' => qt_state.toggle_single(),
|
||||||
}
|
'"' => qt_state.toggle_double(),
|
||||||
|
_ if qt_state.in_quote() => continue,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
let before_tk = Tk::new(tk.class.clone(), before_span);
|
tk.source().clone(),
|
||||||
let after_tk = Tk::new(tk.class.clone(), after_span);
|
);
|
||||||
return Some((before_tk, after_tk));
|
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 after_tk = Tk::new(tk.class.clone(), after_span);
|
||||||
|
return Some((before_tk, after_tk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
|
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
|
||||||
@@ -1083,7 +1103,7 @@ pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
|
|||||||
|
|
||||||
pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
let mut qt_state = QuoteState::default();
|
let mut qt_state = QuoteState::default();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
pos += ch.len_utf8();
|
pos += ch.len_utf8();
|
||||||
match ch {
|
match ch {
|
||||||
@@ -1108,12 +1128,12 @@ pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
qt_state.toggle_single();
|
qt_state.toggle_single();
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
qt_state.toggle_double();
|
qt_state.toggle_double();
|
||||||
}
|
}
|
||||||
')' if qt_state.outside() => return Some(pos),
|
')' if qt_state.outside() => return Some(pos),
|
||||||
'(' if qt_state.outside() => return None,
|
'(' if qt_state.outside() => return None,
|
||||||
_ => { /* continue */ }
|
_ => { /* continue */ }
|
||||||
|
|||||||
400
src/parse/mod.rs
400
src/parse/mod.rs
@@ -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;
|
||||||
@@ -42,7 +45,7 @@ macro_rules! try_match {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ParsedSrc {
|
pub struct ParsedSrc {
|
||||||
pub src: Arc<String>,
|
pub src: Arc<String>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub ast: Ast,
|
pub ast: Ast,
|
||||||
pub lex_flags: LexFlags,
|
pub lex_flags: LexFlags,
|
||||||
pub context: LabelCtx,
|
pub context: LabelCtx,
|
||||||
@@ -57,16 +60,16 @@ impl ParsedSrc {
|
|||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
src,
|
src,
|
||||||
name: "<stdin>".into(),
|
name: "<stdin>".into(),
|
||||||
ast: Ast::new(vec![]),
|
ast: Ast::new(vec![]),
|
||||||
lex_flags: LexFlags::empty(),
|
lex_flags: LexFlags::empty(),
|
||||||
context: VecDeque::new(),
|
context: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_name(mut self, name: String) -> Self {
|
pub fn with_name(mut self, name: String) -> Self {
|
||||||
self.name = name;
|
self.name = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
||||||
self.lex_flags = flags;
|
self.lex_flags = flags;
|
||||||
self
|
self
|
||||||
@@ -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]),
|
||||||
@@ -128,7 +132,7 @@ pub struct Node {
|
|||||||
pub flags: NdFlags,
|
pub flags: NdFlags,
|
||||||
pub redirs: Vec<Redir>,
|
pub redirs: Vec<Redir>,
|
||||||
pub tokens: Vec<Tk>,
|
pub tokens: Vec<Tk>,
|
||||||
pub context: LabelCtx,
|
pub context: LabelCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
@@ -143,108 +147,108 @@ impl Node {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
|
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
|
||||||
let color = last_color();
|
let color = last_color();
|
||||||
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) {
|
||||||
f(self);
|
f(self);
|
||||||
|
|
||||||
match self.class {
|
match self.class {
|
||||||
NdRule::IfNode {
|
NdRule::IfNode {
|
||||||
ref mut cond_nodes,
|
ref mut cond_nodes,
|
||||||
ref mut else_block,
|
ref mut else_block,
|
||||||
} => {
|
} => {
|
||||||
for node in cond_nodes {
|
for node in cond_nodes {
|
||||||
let CondNode { cond, body } = node;
|
let CondNode { cond, body } = node;
|
||||||
cond.walk_tree(f);
|
cond.walk_tree(f);
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
body_node.walk_tree(f);
|
body_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for else_node in else_block {
|
for else_node in else_block {
|
||||||
else_node.walk_tree(f);
|
else_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::LoopNode {
|
NdRule::LoopNode {
|
||||||
kind: _,
|
kind: _,
|
||||||
ref mut cond_node,
|
ref mut cond_node,
|
||||||
} => {
|
} => {
|
||||||
let CondNode { cond, body } = cond_node;
|
let CondNode { cond, body } = cond_node;
|
||||||
cond.walk_tree(f);
|
cond.walk_tree(f);
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
body_node.walk_tree(f);
|
body_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::ForNode {
|
NdRule::ForNode {
|
||||||
vars: _,
|
vars: _,
|
||||||
arr: _,
|
arr: _,
|
||||||
ref mut body,
|
ref mut body,
|
||||||
} => {
|
} => {
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
body_node.walk_tree(f);
|
body_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::CaseNode {
|
NdRule::CaseNode {
|
||||||
pattern: _,
|
pattern: _,
|
||||||
ref mut case_blocks,
|
ref mut case_blocks,
|
||||||
} => {
|
} => {
|
||||||
for block in case_blocks {
|
for block in case_blocks {
|
||||||
let CaseNode { pattern: _, body } = block;
|
let CaseNode { pattern: _, body } = block;
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
body_node.walk_tree(f);
|
body_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::Command {
|
NdRule::Command {
|
||||||
ref mut assignments,
|
ref mut assignments,
|
||||||
argv: _,
|
argv: _,
|
||||||
} => {
|
} => {
|
||||||
for assign_node in assignments {
|
for assign_node in assignments {
|
||||||
assign_node.walk_tree(f);
|
assign_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::Pipeline {
|
NdRule::Pipeline {
|
||||||
ref mut cmds,
|
ref mut cmds,
|
||||||
pipe_err: _,
|
pipe_err: _,
|
||||||
} => {
|
} => {
|
||||||
for cmd_node in cmds {
|
for cmd_node in cmds {
|
||||||
cmd_node.walk_tree(f);
|
cmd_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::Conjunction { ref mut elements } => {
|
NdRule::Conjunction { ref mut elements } => {
|
||||||
for node in elements.iter_mut() {
|
for node in elements.iter_mut() {
|
||||||
let ConjunctNode { cmd, operator: _ } = node;
|
let ConjunctNode { cmd, operator: _ } = node;
|
||||||
cmd.walk_tree(f);
|
cmd.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::Assignment {
|
NdRule::Assignment {
|
||||||
kind: _,
|
kind: _,
|
||||||
var: _,
|
var: _,
|
||||||
val: _,
|
val: _,
|
||||||
} => (), // No nodes to check
|
} => (), // No nodes to check
|
||||||
NdRule::BraceGrp { ref mut body } => {
|
NdRule::BraceGrp { ref mut body } => {
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
body_node.walk_tree(f);
|
body_node.walk_tree(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::FuncDef {
|
NdRule::FuncDef {
|
||||||
name: _,
|
name: _,
|
||||||
ref mut body,
|
ref mut body,
|
||||||
} => {
|
} => {
|
||||||
body.walk_tree(f);
|
body.walk_tree(f);
|
||||||
}
|
}
|
||||||
NdRule::Test { cases: _ } => (),
|
NdRule::Test { cases: _ } => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn propagate_context(&mut self, ctx: (SpanSource, Label<Span>)) {
|
pub fn propagate_context(&mut self, ctx: (SpanSource, Label<Span>)) {
|
||||||
self.walk_tree(&|nd| nd.context.push_back(ctx.clone()));
|
self.walk_tree(&|nd| nd.context.push_back(ctx.clone()));
|
||||||
}
|
}
|
||||||
pub fn get_span(&self) -> Span {
|
pub fn get_span(&self) -> Span {
|
||||||
let Some(first_tk) = self.tokens.first() else {
|
let Some(first_tk) = self.tokens.first() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -662,20 +666,23 @@ 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 {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("ParseStream")
|
f.debug_struct("ParseStream")
|
||||||
.field("tokens", &self.tokens)
|
.field("tokens", &self.tokens)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }
|
||||||
@@ -831,39 +838,42 @@ impl ParseStream {
|
|||||||
let name_tk = self.next_tk().unwrap();
|
let name_tk = self.next_tk().unwrap();
|
||||||
node_tks.push(name_tk.clone());
|
node_tks.push(name_tk.clone());
|
||||||
let name = name_tk.clone();
|
let name = name_tk.clone();
|
||||||
let name_raw = name.to_string();
|
let name_raw = name.to_string();
|
||||||
let mut src = name_tk.span.span_source().clone();
|
let mut src = name_tk.span.span_source().clone();
|
||||||
src.rename(name_raw.clone());
|
src.rename(name_raw.clone());
|
||||||
let color = next_color();
|
let color = next_color();
|
||||||
// Push a placeholder context so child nodes inherit it
|
// Push a placeholder context so child nodes inherit it
|
||||||
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!(
|
||||||
.with_color(color),
|
"in function '{}' defined here",
|
||||||
));
|
name_raw.clone().fg(color)
|
||||||
|
))
|
||||||
|
.with_color(color),
|
||||||
|
));
|
||||||
|
|
||||||
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
||||||
self.context.pop_back();
|
self.context.pop_back();
|
||||||
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);
|
||||||
// Replace placeholder with full-span label
|
// Replace placeholder with full-span label
|
||||||
self.context.pop_back();
|
self.context.pop_back();
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
class: NdRule::FuncDef { name, body },
|
class: NdRule::FuncDef { name, body },
|
||||||
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();
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
||||||
@@ -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(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1140,12 +1148,12 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn make_err(&self, span: lex::Span, label: Label<lex::Span>) -> ShErr {
|
fn make_err(&self, span: lex::Span, label: Label<lex::Span>) -> ShErr {
|
||||||
let src = span.span_source().clone();
|
let src = span.span_source().clone();
|
||||||
ShErr::new(ShErrKind::ParseErr, span)
|
ShErr::new(ShErrKind::ParseErr, span)
|
||||||
.with_label(src, label)
|
.with_label(src, label)
|
||||||
.with_context(self.context.clone())
|
.with_context(self.context.clone())
|
||||||
}
|
}
|
||||||
fn parse_if(&mut self) -> ShResult<Option<Node>> {
|
fn parse_if(&mut self) -> ShResult<Option<Node>> {
|
||||||
// Needs at last one 'if-then',
|
// Needs at last one 'if-then',
|
||||||
// Any number of 'elif-then',
|
// Any number of 'elif-then',
|
||||||
@@ -1164,14 +1172,19 @@ impl ParseStream {
|
|||||||
let prefix_keywrd = if cond_nodes.is_empty() { "if" } else { "elif" };
|
let prefix_keywrd = if cond_nodes.is_empty() { "if" } else { "elif" };
|
||||||
let Some(cond) = self.parse_cmd_list()? else {
|
let Some(cond) = self.parse_cmd_list()? else {
|
||||||
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(
|
||||||
Label::new(span)
|
self.make_err(
|
||||||
.with_message(format!("Expected an expression after '{}'", prefix_keywrd.fg(color)))
|
span.clone(),
|
||||||
.with_color(color)
|
Label::new(span)
|
||||||
));
|
.with_message(format!(
|
||||||
|
"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 {
|
||||||
@@ -1205,7 +1218,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
cond_nodes.push(cond_node);
|
cond_nodes.push(cond_node);
|
||||||
|
|
||||||
self.catch_separator(&mut node_tks);
|
self.catch_separator(&mut node_tks);
|
||||||
if !self.check_keyword("elif") || !self.next_tk_is_some() {
|
if !self.check_keyword("elif") || !self.next_tk_is_some() {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -1214,7 +1227,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.catch_separator(&mut node_tks);
|
self.catch_separator(&mut node_tks);
|
||||||
if self.check_keyword("else") {
|
if self.check_keyword("else") {
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
self.catch_separator(&mut node_tks);
|
self.catch_separator(&mut node_tks);
|
||||||
@@ -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);
|
||||||
@@ -1515,14 +1528,14 @@ impl ParseStream {
|
|||||||
// If we have assignments but no command word,
|
// If we have assignments but no command word,
|
||||||
// return the assignment-only command without parsing more tokens
|
// return the assignment-only command without parsing more tokens
|
||||||
self.commit(node_tks.len());
|
self.commit(node_tks.len());
|
||||||
let mut context = self.context.clone();
|
let mut context = self.context.clone();
|
||||||
let assignments_span = assignments.get_span().unwrap();
|
let assignments_span = assignments.get_span().unwrap();
|
||||||
context.push_back((
|
context.push_back((
|
||||||
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 },
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
@@ -1559,7 +1572,7 @@ impl ParseStream {
|
|||||||
let path_tk = tk_iter.next();
|
let path_tk = tk_iter.next();
|
||||||
|
|
||||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
self.panic_mode(&mut node_tks);
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(ShErr::at(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
tk.span.clone(),
|
tk.span.clone(),
|
||||||
@@ -1737,7 +1750,7 @@ fn node_is_punctuated(tokens: &[Tk]) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_redir_file<P: AsRef<Path>>(class: RedirType, path: P) -> ShResult<File> {
|
pub fn get_redir_file<P: AsRef<Path>>(class: RedirType, path: P) -> ShResult<File> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let result = match class {
|
let result = match class {
|
||||||
RedirType::Input => OpenOptions::new().read(true).open(Path::new(&path)),
|
RedirType::Input => OpenOptions::new().read(true).open(Path::new(&path)),
|
||||||
RedirType::Output => OpenOptions::new()
|
RedirType::Output => OpenOptions::new()
|
||||||
@@ -1745,23 +1758,22 @@ 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?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
|
fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
|
||||||
let color = last_color();
|
let color = last_color();
|
||||||
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_context(context)
|
.with_color(color),
|
||||||
|
)
|
||||||
|
.with_context(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_func_name(tk: Option<&Tk>) -> bool {
|
fn is_func_name(tk: Option<&Tk>) -> bool {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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,12 +39,12 @@ 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) {
|
||||||
self.only_hl_visual = only_visual;
|
self.only_hl_visual = only_visual;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads raw input text and annotates it with syntax markers
|
/// Loads raw input text and annotates it with syntax markers
|
||||||
///
|
///
|
||||||
@@ -66,25 +66,25 @@ impl Highlighter {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_control_chars(&mut self) {
|
pub fn expand_control_chars(&mut self) {
|
||||||
let mut expanded = String::new();
|
let mut expanded = String::new();
|
||||||
let mut chars = self.input.chars().peekable();
|
let mut chars = self.input.chars().peekable();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\n' | '\t' | '\r' => expanded.push(ch),
|
'\n' | '\t' | '\r' => expanded.push(ch),
|
||||||
c if c as u32 <= 0x1F => {
|
c if c as u32 <= 0x1F => {
|
||||||
let display = (c as u8 + b'@') as char;
|
let display = (c as u8 + b'@') as char;
|
||||||
expanded.push_str("\x1b[7m^");
|
expanded.push_str("\x1b[7m^");
|
||||||
expanded.push(display);
|
expanded.push(display);
|
||||||
expanded.push_str("\x1b[0m");
|
expanded.push_str("\x1b[0m");
|
||||||
}
|
}
|
||||||
_ => expanded.push(ch),
|
_ => expanded.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.input = expanded;
|
self.input = expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the annotated input and generates ANSI-styled output
|
/// Processes the annotated input and generates ANSI-styled output
|
||||||
///
|
///
|
||||||
@@ -104,9 +104,9 @@ impl Highlighter {
|
|||||||
self.reapply_style();
|
self.reapply_style();
|
||||||
self.in_selection = false;
|
self.in_selection = false;
|
||||||
}
|
}
|
||||||
_ if self.only_hl_visual => {
|
_ if self.only_hl_visual => {
|
||||||
self.output.push(ch);
|
self.output.push(ch);
|
||||||
}
|
}
|
||||||
markers::STRING_DQ_END
|
markers::STRING_DQ_END
|
||||||
| markers::STRING_SQ_END
|
| markers::STRING_SQ_END
|
||||||
| markers::VAR_SUB_END
|
| markers::VAR_SUB_END
|
||||||
@@ -471,7 +471,7 @@ impl Highlighter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Highlighter {
|
impl Default for Highlighter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ impl HistEntry {
|
|||||||
impl FromStr for HistEntry {
|
impl FromStr for HistEntry {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let err = Err(ShErr::simple(
|
let err = Err(ShErr::simple(
|
||||||
ShErrKind::HistoryReadErr,
|
ShErrKind::HistoryReadErr,
|
||||||
format!("Bad formatting on history entry '{s}'"),
|
format!("Bad formatting on history entry '{s}'"),
|
||||||
));
|
));
|
||||||
|
|
||||||
//: 248972349;148;echo foo; echo bar
|
//: 248972349;148;echo foo; echo bar
|
||||||
let Some(cleaned) = s.strip_prefix(": ") else {
|
let Some(cleaned) = s.strip_prefix(": ") else {
|
||||||
@@ -132,10 +132,10 @@ impl FromStr for HistEntries {
|
|||||||
|
|
||||||
while let Some((i, line)) = lines.next() {
|
while let Some((i, line)) = lines.next() {
|
||||||
if !line.starts_with(": ") {
|
if !line.starts_with(": ") {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::HistoryReadErr,
|
ShErrKind::HistoryReadErr,
|
||||||
format!("Bad formatting on line {i}"),
|
format!("Bad formatting on line {i}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let mut chars = line.chars().peekable();
|
let mut chars = line.chars().peekable();
|
||||||
let mut feeding_lines = true;
|
let mut feeding_lines = true;
|
||||||
@@ -161,10 +161,10 @@ impl FromStr for HistEntries {
|
|||||||
}
|
}
|
||||||
if feeding_lines {
|
if feeding_lines {
|
||||||
let Some((_, line)) = lines.next() else {
|
let Some((_, line)) = lines.next() else {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::HistoryReadErr,
|
ShErrKind::HistoryReadErr,
|
||||||
format!("Bad formatting on line {i}"),
|
format!("Bad formatting on line {i}"),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
chars = line.chars().peekable();
|
chars = line.chars().peekable();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,110 +89,112 @@ impl KeyEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn as_vim_seq(&self) -> ShResult<String> {
|
pub fn as_vim_seq(&self) -> ShResult<String> {
|
||||||
let mut seq = String::new();
|
let mut seq = String::new();
|
||||||
let KeyEvent(event, mods) = self;
|
let KeyEvent(event, mods) = self;
|
||||||
let mut needs_angle_bracket = false;
|
let mut needs_angle_bracket = false;
|
||||||
|
|
||||||
if mods.contains(ModKeys::CTRL) {
|
if mods.contains(ModKeys::CTRL) {
|
||||||
seq.push_str("C-");
|
seq.push_str("C-");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
if mods.contains(ModKeys::ALT) {
|
if mods.contains(ModKeys::ALT) {
|
||||||
seq.push_str("A-");
|
seq.push_str("A-");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
if mods.contains(ModKeys::SHIFT) {
|
if mods.contains(ModKeys::SHIFT) {
|
||||||
seq.push_str("S-");
|
seq.push_str("S-");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
KeyCode::UnknownEscSeq => return Err(ShErr::simple(
|
KeyCode::UnknownEscSeq => {
|
||||||
ShErrKind::ParseErr,
|
return Err(ShErr::simple(
|
||||||
"Cannot convert unknown escape sequence to Vim key sequence".to_string(),
|
ShErrKind::ParseErr,
|
||||||
)),
|
"Cannot convert unknown escape sequence to Vim key sequence".to_string(),
|
||||||
KeyCode::Backspace => {
|
));
|
||||||
seq.push_str("BS");
|
}
|
||||||
needs_angle_bracket = true;
|
KeyCode::Backspace => {
|
||||||
}
|
seq.push_str("BS");
|
||||||
KeyCode::BackTab => {
|
needs_angle_bracket = true;
|
||||||
seq.push_str("S-Tab");
|
}
|
||||||
needs_angle_bracket = true;
|
KeyCode::BackTab => {
|
||||||
}
|
seq.push_str("S-Tab");
|
||||||
KeyCode::BracketedPasteStart => todo!(),
|
needs_angle_bracket = true;
|
||||||
KeyCode::BracketedPasteEnd => todo!(),
|
}
|
||||||
KeyCode::Delete => {
|
KeyCode::BracketedPasteStart => todo!(),
|
||||||
seq.push_str("Del");
|
KeyCode::BracketedPasteEnd => todo!(),
|
||||||
needs_angle_bracket = true;
|
KeyCode::Delete => {
|
||||||
}
|
seq.push_str("Del");
|
||||||
KeyCode::Down => {
|
needs_angle_bracket = true;
|
||||||
seq.push_str("Down");
|
}
|
||||||
needs_angle_bracket = true;
|
KeyCode::Down => {
|
||||||
}
|
seq.push_str("Down");
|
||||||
KeyCode::End => {
|
needs_angle_bracket = true;
|
||||||
seq.push_str("End");
|
}
|
||||||
needs_angle_bracket = true;
|
KeyCode::End => {
|
||||||
}
|
seq.push_str("End");
|
||||||
KeyCode::Enter => {
|
needs_angle_bracket = true;
|
||||||
seq.push_str("Enter");
|
}
|
||||||
needs_angle_bracket = true;
|
KeyCode::Enter => {
|
||||||
}
|
seq.push_str("Enter");
|
||||||
KeyCode::Esc => {
|
needs_angle_bracket = true;
|
||||||
seq.push_str("Esc");
|
}
|
||||||
needs_angle_bracket = true;
|
KeyCode::Esc => {
|
||||||
}
|
seq.push_str("Esc");
|
||||||
|
needs_angle_bracket = true;
|
||||||
|
}
|
||||||
|
|
||||||
KeyCode::F(f) => {
|
KeyCode::F(f) => {
|
||||||
seq.push_str(&format!("F{}", f));
|
seq.push_str(&format!("F{}", f));
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Home => {
|
KeyCode::Home => {
|
||||||
seq.push_str("Home");
|
seq.push_str("Home");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Insert => {
|
KeyCode::Insert => {
|
||||||
seq.push_str("Insert");
|
seq.push_str("Insert");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
seq.push_str("Left");
|
seq.push_str("Left");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Null => todo!(),
|
KeyCode::Null => todo!(),
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
seq.push_str("PgDn");
|
seq.push_str("PgDn");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::PageUp => {
|
KeyCode::PageUp => {
|
||||||
seq.push_str("PgUp");
|
seq.push_str("PgUp");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
seq.push_str("Right");
|
seq.push_str("Right");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
seq.push_str("Tab");
|
seq.push_str("Tab");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
seq.push_str("Up");
|
seq.push_str("Up");
|
||||||
needs_angle_bracket = true;
|
needs_angle_bracket = true;
|
||||||
}
|
}
|
||||||
KeyCode::Char(ch) => {
|
KeyCode::Char(ch) => {
|
||||||
seq.push(*ch);
|
seq.push(*ch);
|
||||||
}
|
}
|
||||||
KeyCode::Grapheme(gr) => seq.push_str(gr),
|
KeyCode::Grapheme(gr) => seq.push_str(gr),
|
||||||
KeyCode::Verbatim(s) => seq.push_str(s),
|
KeyCode::Verbatim(s) => seq.push_str(s),
|
||||||
}
|
}
|
||||||
|
|
||||||
if needs_angle_bracket {
|
if needs_angle_bracket {
|
||||||
Ok(format!("<{}>", seq))
|
Ok(format!("<{}>", seq))
|
||||||
} else {
|
} else {
|
||||||
Ok(seq)
|
Ok(seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
@@ -204,7 +206,7 @@ pub enum KeyCode {
|
|||||||
BracketedPasteEnd,
|
BracketedPasteEnd,
|
||||||
Char(char),
|
Char(char),
|
||||||
Grapheme(Arc<str>),
|
Grapheme(Arc<str>),
|
||||||
Verbatim(Arc<str>), // For sequences that should be treated as literal input, not parsed into a KeyCode
|
Verbatim(Arc<str>), // For sequences that should be treated as literal input, not parsed into a KeyCode
|
||||||
Delete,
|
Delete,
|
||||||
Down,
|
Down,
|
||||||
End,
|
End,
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -297,20 +302,20 @@ impl ClampedUsize {
|
|||||||
pub fn sub(&mut self, value: usize) {
|
pub fn sub(&mut self, value: usize) {
|
||||||
self.value = self.value.saturating_sub(value)
|
self.value = self.value.saturating_sub(value)
|
||||||
}
|
}
|
||||||
pub fn wrap_add(&mut self, value: usize) {
|
pub fn wrap_add(&mut self, value: usize) {
|
||||||
self.value = self.ret_wrap_add(value);
|
self.value = self.ret_wrap_add(value);
|
||||||
}
|
}
|
||||||
pub fn wrap_sub(&mut self, value: usize) {
|
pub fn wrap_sub(&mut self, value: usize) {
|
||||||
self.value = self.ret_wrap_sub(value);
|
self.value = self.ret_wrap_sub(value);
|
||||||
}
|
}
|
||||||
pub fn ret_wrap_add(&self, value: usize) -> usize {
|
pub fn ret_wrap_add(&self, value: usize) -> usize {
|
||||||
let max = self.upper_bound();
|
let max = self.upper_bound();
|
||||||
(self.value + value) % (max + 1)
|
(self.value + value) % (max + 1)
|
||||||
}
|
}
|
||||||
pub fn ret_wrap_sub(&self, value: usize) -> usize {
|
pub fn ret_wrap_sub(&self, value: usize) -> usize {
|
||||||
let max = self.upper_bound();
|
let max = self.upper_bound();
|
||||||
(self.value + (max + 1) - (value % (max + 1))) % (max + 1)
|
(self.value + (max + 1) - (value % (max + 1))) % (max + 1)
|
||||||
}
|
}
|
||||||
/// Add a value to the wrapped usize, return the result
|
/// Add a value to the wrapped usize, return the result
|
||||||
///
|
///
|
||||||
/// Returns the result instead of mutating the inner value
|
/// Returns the result instead of mutating the inner value
|
||||||
@@ -352,12 +357,12 @@ pub struct LineBuf {
|
|||||||
|
|
||||||
impl LineBuf {
|
impl LineBuf {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut new = Self {
|
let mut new = Self {
|
||||||
grapheme_indices: Some(vec![]), // We know the buffer is empty, so this keeps us safe from unwrapping None
|
grapheme_indices: Some(vec![]), // We know the buffer is empty, so this keeps us safe from unwrapping None
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
new.update_graphemes();
|
new.update_graphemes();
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
/// Only update self.grapheme_indices if it is None
|
/// Only update self.grapheme_indices if it is None
|
||||||
pub fn update_graphemes_lazy(&mut self) {
|
pub fn update_graphemes_lazy(&mut self) {
|
||||||
@@ -437,17 +442,17 @@ impl LineBuf {
|
|||||||
self.cursor.set_max(indices.len());
|
self.cursor.set_max(indices.len());
|
||||||
self.grapheme_indices = Some(indices)
|
self.grapheme_indices = Some(indices)
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn grapheme_indices(&self) -> &[usize] {
|
pub fn grapheme_indices(&self) -> &[usize] {
|
||||||
if self.grapheme_indices.is_none() {
|
if self.grapheme_indices.is_none() {
|
||||||
let caller = std::panic::Location::caller();
|
let caller = std::panic::Location::caller();
|
||||||
panic!(
|
panic!(
|
||||||
"grapheme_indices is None. This likely means you forgot to call update_graphemes() before calling a method that relies on grapheme_indices, or you called a method that relies on grapheme_indices from another method that also relies on grapheme_indices without updating graphemes in between. Caller: {}:{}:{}",
|
"grapheme_indices is None. This likely means you forgot to call update_graphemes() before calling a method that relies on grapheme_indices, or you called a method that relies on grapheme_indices from another method that also relies on grapheme_indices without updating graphemes in between. Caller: {}:{}:{}",
|
||||||
caller.file(),
|
caller.file(),
|
||||||
caller.line(),
|
caller.line(),
|
||||||
caller.column(),
|
caller.column(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.grapheme_indices.as_ref().unwrap()
|
self.grapheme_indices.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
pub fn grapheme_indices_owned(&self) -> Vec<usize> {
|
pub fn grapheme_indices_owned(&self) -> Vec<usize> {
|
||||||
@@ -806,19 +811,19 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
Some(self.line_bounds(line_no))
|
Some(self.line_bounds(line_no))
|
||||||
}
|
}
|
||||||
pub fn this_word(&mut self, word: Word) -> (usize, usize) {
|
pub fn this_word(&mut self, word: Word) -> (usize, usize) {
|
||||||
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
self.start_of_word_backward(self.cursor.get(), word)
|
self.start_of_word_backward(self.cursor.get(), word)
|
||||||
};
|
};
|
||||||
let end = if self.is_word_bound(self.cursor.get(), word, Direction::Forward) {
|
let end = if self.is_word_bound(self.cursor.get(), word, Direction::Forward) {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
self.end_of_word_forward(self.cursor.get(), word)
|
self.end_of_word_forward(self.cursor.get(), word)
|
||||||
};
|
};
|
||||||
(start, end)
|
(start, end)
|
||||||
}
|
}
|
||||||
pub fn this_line_exclusive(&mut self) -> (usize, usize) {
|
pub fn this_line_exclusive(&mut self) -> (usize, usize) {
|
||||||
let line_no = self.cursor_line_number();
|
let line_no = self.cursor_line_number();
|
||||||
let (start, mut end) = self.line_bounds(line_no);
|
let (start, mut end) = self.line_bounds(line_no);
|
||||||
@@ -827,10 +832,10 @@ 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) {
|
||||||
let line_no = self.cursor_line_number();
|
let line_no = self.cursor_line_number();
|
||||||
self.line_bounds(line_no)
|
self.line_bounds(line_no)
|
||||||
@@ -940,7 +945,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
pub fn is_word_bound(&mut self, pos: usize, word: Word, dir: Direction) -> bool {
|
pub fn is_word_bound(&mut self, pos: usize, word: Word, dir: Direction) -> bool {
|
||||||
let clamped_pos = ClampedUsize::new(pos, self.cursor.max, true);
|
let clamped_pos = ClampedUsize::new(pos, self.cursor.max, true);
|
||||||
log::debug!("clamped_pos: {}", clamped_pos.get());
|
log::debug!("clamped_pos: {}", clamped_pos.get());
|
||||||
let cur_char = self
|
let cur_char = self
|
||||||
.grapheme_at(clamped_pos.get())
|
.grapheme_at(clamped_pos.get())
|
||||||
.map(|c| c.to_string())
|
.map(|c| c.to_string())
|
||||||
@@ -1011,11 +1016,11 @@ impl LineBuf {
|
|||||||
} else {
|
} else {
|
||||||
self.start_of_word_backward(self.cursor.get(), word)
|
self.start_of_word_backward(self.cursor.get(), word)
|
||||||
};
|
};
|
||||||
let end = if self.is_word_bound(self.cursor.get(), word, Direction::Forward) {
|
let end = if self.is_word_bound(self.cursor.get(), word, Direction::Forward) {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
self.end_of_word_forward(self.cursor.get(), word)
|
self.end_of_word_forward(self.cursor.get(), word)
|
||||||
};
|
};
|
||||||
Some((start, end))
|
Some((start, end))
|
||||||
}
|
}
|
||||||
Bound::Around => {
|
Bound::Around => {
|
||||||
@@ -1994,9 +1999,9 @@ impl LineBuf {
|
|||||||
|
|
||||||
let mut level: usize = 0;
|
let mut level: usize = 0;
|
||||||
|
|
||||||
if to_cursor.ends_with("\\\n") {
|
if to_cursor.ends_with("\\\n") {
|
||||||
level += 1; // Line continuation, so we need to add an extra level
|
level += 1; // Line continuation, so we need to add an extra level
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = Arc::new(to_cursor);
|
let input = Arc::new(to_cursor);
|
||||||
let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>()
|
let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>()
|
||||||
@@ -2004,23 +2009,23 @@ impl LineBuf {
|
|||||||
log::error!("Failed to lex buffer for indent calculation");
|
log::error!("Failed to lex buffer for indent calculation");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut last_keyword: Option<String> = None;
|
let mut last_keyword: Option<String> = None;
|
||||||
for tk in tokens {
|
for tk in tokens {
|
||||||
if tk.flags.contains(TkFlags::KEYWORD) {
|
if tk.flags.contains(TkFlags::KEYWORD) {
|
||||||
match tk.as_str() {
|
match tk.as_str() {
|
||||||
"in" => {
|
"in" => {
|
||||||
if last_keyword.as_deref() == Some("case") {
|
if last_keyword.as_deref() == Some("case") {
|
||||||
level += 1;
|
level += 1;
|
||||||
} else {
|
} else {
|
||||||
// 'in' is also used in for loops, but we already increment level on 'do' for those
|
// 'in' is also used in for loops, but we already increment level on 'do' for those
|
||||||
// so we just skip it here
|
// so we just skip it here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"then" | "do" => level += 1,
|
"then" | "do" => level += 1,
|
||||||
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
"done" | "fi" | "esac" => level = level.saturating_sub(1),
|
||||||
_ => { /* Continue */ }
|
_ => { /* Continue */ }
|
||||||
}
|
}
|
||||||
last_keyword = Some(tk.to_string());
|
last_keyword = Some(tk.to_string());
|
||||||
} else if tk.class == TkRule::BraceGrpStart {
|
} else if tk.class == TkRule::BraceGrpStart {
|
||||||
level += 1;
|
level += 1;
|
||||||
} else if tk.class == TkRule::BraceGrpEnd {
|
} else if tk.class == TkRule::BraceGrpEnd {
|
||||||
@@ -2362,12 +2367,12 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
MotionCmd(_count, Motion::BeginningOfBuffer) => MotionKind::On(0),
|
MotionCmd(_count, Motion::BeginningOfBuffer) => MotionKind::On(0),
|
||||||
MotionCmd(_count, Motion::EndOfBuffer) => {
|
MotionCmd(_count, Motion::EndOfBuffer) => {
|
||||||
if self.cursor.exclusive {
|
if self.cursor.exclusive {
|
||||||
MotionKind::On(self.grapheme_indices().len().saturating_sub(1))
|
MotionKind::On(self.grapheme_indices().len().saturating_sub(1))
|
||||||
} 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);
|
||||||
@@ -2859,34 +2868,35 @@ impl LineBuf {
|
|||||||
Verb::InsertChar(ch) => {
|
Verb::InsertChar(ch) => {
|
||||||
self.insert_at_cursor(ch);
|
self.insert_at_cursor(ch);
|
||||||
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() {
|
{
|
||||||
"esac" | "done" | "fi" | "}" => {
|
match line_content.trim() {
|
||||||
self.calc_indent_level();
|
"esac" | "done" | "fi" | "}" => {
|
||||||
if self.auto_indent_level < before {
|
self.calc_indent_level();
|
||||||
let delta = before - self.auto_indent_level;
|
if self.auto_indent_level < before {
|
||||||
let line_start = self.start_of_line();
|
let delta = before - self.auto_indent_level;
|
||||||
for _ in 0..delta {
|
let line_start = self.start_of_line();
|
||||||
if self.grapheme_at(line_start).is_some_and(|gr| gr == "\t") {
|
for _ in 0..delta {
|
||||||
self.remove(line_start);
|
if self.grapheme_at(line_start).is_some_and(|gr| gr == "\t") {
|
||||||
if !self.cursor_at_max() {
|
self.remove(line_start);
|
||||||
self.cursor.sub(1);
|
if !self.cursor_at_max() {
|
||||||
}
|
self.cursor.sub(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => { /* nothing to see here */ }
|
}
|
||||||
}
|
_ => { /* nothing to see here */ }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Verb::Insert(string) => {
|
Verb::Insert(string) => {
|
||||||
self.push_str(&string);
|
self.push_str(&string);
|
||||||
let graphemes = string.graphemes(true).count();
|
let graphemes = string.graphemes(true).count();
|
||||||
log::debug!("Inserted string: {string:?}, graphemes: {graphemes}");
|
log::debug!("Inserted string: {string:?}, graphemes: {graphemes}");
|
||||||
log::debug!("buffer after insert: {:?}", self.buffer);
|
log::debug!("buffer after insert: {:?}", self.buffer);
|
||||||
self.cursor.add(graphemes);
|
self.cursor.add(graphemes);
|
||||||
}
|
}
|
||||||
Verb::Indent => {
|
Verb::Indent => {
|
||||||
@@ -3039,71 +3049,82 @@ 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
|
||||||
let (s, e) = self.select_range().unwrap_or(self.this_word(Word::Normal));
|
} else {
|
||||||
let end = if self.select_range().is_some() {
|
-(n as i64)
|
||||||
if e < self.grapheme_indices().len() - 1 {
|
};
|
||||||
e
|
let (s, e) = self.select_range().unwrap_or(self.this_word(Word::Normal));
|
||||||
} else {
|
let end = if self.select_range().is_some() {
|
||||||
e + 1
|
if e < self.grapheme_indices().len() - 1 {
|
||||||
}
|
e
|
||||||
} else {
|
} else {
|
||||||
(e + 1).min(self.grapheme_indices().len())
|
e + 1
|
||||||
}; // inclusive → exclusive, capped at buffer len
|
}
|
||||||
let word = self.slice(s..end).unwrap_or_default().to_lowercase();
|
} else {
|
||||||
|
(e + 1).min(self.grapheme_indices().len())
|
||||||
|
}; // inclusive → exclusive, capped at buffer len
|
||||||
|
let word = self.slice(s..end).unwrap_or_default().to_lowercase();
|
||||||
|
|
||||||
let byte_start = self.index_byte_pos(s);
|
let byte_start = self.index_byte_pos(s);
|
||||||
let byte_end = if end >= self.grapheme_indices().len() {
|
let byte_end = if end >= self.grapheme_indices().len() {
|
||||||
self.buffer.len()
|
self.buffer.len()
|
||||||
} else {
|
} else {
|
||||||
self.index_byte_pos(end)
|
self.index_byte_pos(end)
|
||||||
};
|
};
|
||||||
|
|
||||||
if word.starts_with("0x") {
|
if word.starts_with("0x") {
|
||||||
let body = word.strip_prefix("0x").unwrap();
|
let body = word.strip_prefix("0x").unwrap();
|
||||||
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
|
||||||
self.update_graphemes();
|
.buffer
|
||||||
self.cursor.set(s);
|
.replace_range(byte_start..byte_end, &format!("0x{new_num:0>width$x}"));
|
||||||
}
|
self.update_graphemes();
|
||||||
} else if word.starts_with("0b") {
|
self.cursor.set(s);
|
||||||
let body = word.strip_prefix("0b").unwrap();
|
}
|
||||||
let width = body.len();
|
} else if word.starts_with("0b") {
|
||||||
if let Ok(num) = i64::from_str_radix(body, 2) {
|
let body = word.strip_prefix("0b").unwrap();
|
||||||
let new_num = num + inc;
|
let width = body.len();
|
||||||
self.buffer.replace_range(byte_start..byte_end, &format!("0b{new_num:0>width$b}"));
|
if let Ok(num) = i64::from_str_radix(body, 2) {
|
||||||
self.update_graphemes();
|
let new_num = num + inc;
|
||||||
self.cursor.set(s);
|
self
|
||||||
}
|
.buffer
|
||||||
} else if word.starts_with("0o") {
|
.replace_range(byte_start..byte_end, &format!("0b{new_num:0>width$b}"));
|
||||||
let body = word.strip_prefix("0o").unwrap();
|
self.update_graphemes();
|
||||||
let width = body.len();
|
self.cursor.set(s);
|
||||||
if let Ok(num) = i64::from_str_radix(body, 8) {
|
}
|
||||||
let new_num = num + inc;
|
} else if word.starts_with("0o") {
|
||||||
self.buffer.replace_range(byte_start..byte_end, &format!("0o{new_num:0>width$o}"));
|
let body = word.strip_prefix("0o").unwrap();
|
||||||
self.update_graphemes();
|
let width = body.len();
|
||||||
self.cursor.set(s);
|
if let Ok(num) = i64::from_str_radix(body, 8) {
|
||||||
}
|
let new_num = num + inc;
|
||||||
} else if let Ok(num) = word.parse::<i64>() {
|
self
|
||||||
let width = word.len();
|
.buffer
|
||||||
let new_num = num + inc;
|
.replace_range(byte_start..byte_end, &format!("0o{new_num:0>width$o}"));
|
||||||
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);
|
}
|
||||||
}
|
} else if let Ok(num) = word.parse::<i64>() {
|
||||||
}
|
let width = word.len();
|
||||||
|
let new_num = num + inc;
|
||||||
|
self
|
||||||
|
.buffer
|
||||||
|
.replace_range(byte_start..byte_end, &format!("{new_num:0>width$}"));
|
||||||
|
self.update_graphemes();
|
||||||
|
self.cursor.set(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Verb::Complete
|
Verb::Complete
|
||||||
| Verb::ExMode
|
| Verb::ExMode
|
||||||
| Verb::EndOfFile
|
| Verb::EndOfFile
|
||||||
| Verb::InsertMode
|
| Verb::InsertMode
|
||||||
| Verb::NormalMode
|
| Verb::NormalMode
|
||||||
| Verb::VisualMode
|
| Verb::VisualMode
|
||||||
| Verb::VerbatimMode
|
| Verb::VerbatimMode
|
||||||
| Verb::ReplaceMode
|
| Verb::ReplaceMode
|
||||||
| Verb::VisualModeLine
|
| Verb::VisualModeLine
|
||||||
| Verb::VisualModeBlock
|
| Verb::VisualModeBlock
|
||||||
@@ -3111,46 +3132,63 @@ impl LineBuf {
|
|||||||
| Verb::VisualModeSelectLast => self.apply_motion(motion), // Already handled logic for these
|
| Verb::VisualModeSelectLast => self.apply_motion(motion), // Already handled logic for these
|
||||||
|
|
||||||
Verb::ShellCmd(cmd) => {
|
Verb::ShellCmd(cmd) => {
|
||||||
log::debug!("Executing ex-mode command from widget: {cmd}");
|
log::debug!("Executing ex-mode command from widget: {cmd}");
|
||||||
let mut vars = HashSet::new();
|
let mut vars = HashSet::new();
|
||||||
vars.insert("_BUFFER".into());
|
vars.insert("_BUFFER".into());
|
||||||
vars.insert("_CURSOR".into());
|
vars.insert("_CURSOR".into());
|
||||||
vars.insert("_ANCHOR".into());
|
vars.insert("_ANCHOR".into());
|
||||||
let _guard = var_ctx_guard(vars);
|
let _guard = var_ctx_guard(vars);
|
||||||
|
|
||||||
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");
|
||||||
cursor = v.take_var("_CURSOR").parse().unwrap_or(cursor);
|
cursor = v.take_var("_CURSOR").parse().unwrap_or(cursor);
|
||||||
anchor = v.take_var("_ANCHOR").parse().unwrap_or(anchor);
|
anchor = v.take_var("_ANCHOR").parse().unwrap_or(anchor);
|
||||||
v.take_var("_KEYS")
|
v.take_var("_KEYS")
|
||||||
});
|
});
|
||||||
|
|
||||||
self.set_buffer(buf);
|
self.set_buffer(buf);
|
||||||
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!(
|
||||||
if anchor != cursor && self.select_range.is_some() {
|
"[ShellCmd] post-widget: cursor={}, anchor={}, select_range={:?}",
|
||||||
self.select_range = Some(ordered(cursor, anchor));
|
cursor,
|
||||||
}
|
anchor,
|
||||||
if !keys.is_empty() {
|
self.select_range
|
||||||
log::debug!("Pending widget keys from shell command: {keys}");
|
);
|
||||||
write_meta(|m| m.set_pending_widget_keys(&keys))
|
if anchor != cursor && self.select_range.is_some() {
|
||||||
}
|
self.select_range = Some(ordered(cursor, anchor));
|
||||||
|
}
|
||||||
}
|
if !keys.is_empty() {
|
||||||
|
log::debug!("Pending widget keys from shell command: {keys}");
|
||||||
|
write_meta(|m| m.set_pending_widget_keys(&keys))
|
||||||
|
}
|
||||||
|
}
|
||||||
Verb::Normal(_)
|
Verb::Normal(_)
|
||||||
| Verb::Read(_)
|
| Verb::Read(_)
|
||||||
| Verb::Write(_)
|
| Verb::Write(_)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -150,8 +155,8 @@ impl Prompt {
|
|||||||
let Ok(ps1_raw) = env::var("PS1") else {
|
let Ok(ps1_raw) = env::var("PS1") else {
|
||||||
return Self::default();
|
return Self::default();
|
||||||
};
|
};
|
||||||
// PS1 expansion may involve running commands (e.g., for \h or \W), which can modify shell state
|
// PS1 expansion may involve running commands (e.g., for \h or \W), which can modify shell state
|
||||||
let saved_status = state::get_status();
|
let saved_status = state::get_status();
|
||||||
|
|
||||||
let Ok(ps1_expanded) = expand_prompt(&ps1_raw) else {
|
let Ok(ps1_expanded) = expand_prompt(&ps1_raw) else {
|
||||||
return Self::default();
|
return Self::default();
|
||||||
@@ -164,8 +169,8 @@ impl Prompt {
|
|||||||
.ok()
|
.ok()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
// Restore shell state after prompt expansion, since it may have been modified by command substitutions in the prompt
|
// Restore shell state after prompt expansion, since it may have been modified by command substitutions in the prompt
|
||||||
state::set_status(saved_status);
|
state::set_status(saved_status);
|
||||||
Self {
|
Self {
|
||||||
ps1_expanded,
|
ps1_expanded,
|
||||||
ps1_raw,
|
ps1_raw,
|
||||||
@@ -208,10 +213,10 @@ 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;
|
||||||
@@ -244,12 +249,12 @@ pub struct ShedVi {
|
|||||||
pub completer: Box<dyn Completer>,
|
pub completer: Box<dyn Completer>,
|
||||||
|
|
||||||
pub mode: Box<dyn ViMode>,
|
pub mode: Box<dyn ViMode>,
|
||||||
pub saved_mode: Option<Box<dyn ViMode>>,
|
pub saved_mode: Option<Box<dyn ViMode>>,
|
||||||
pub pending_keymap: Vec<KeyEvent>,
|
pub pending_keymap: Vec<KeyEvent>,
|
||||||
pub repeat_action: Option<CmdReplay>,
|
pub repeat_action: Option<CmdReplay>,
|
||||||
pub repeat_motion: Option<MotionCmd>,
|
pub repeat_motion: Option<MotionCmd>,
|
||||||
pub editor: LineBuf,
|
pub editor: LineBuf,
|
||||||
pub next_is_escaped: bool,
|
pub next_is_escaped: bool,
|
||||||
|
|
||||||
pub old_layout: Option<Layout>,
|
pub old_layout: Option<Layout>,
|
||||||
pub history: History,
|
pub history: History,
|
||||||
@@ -266,9 +271,9 @@ impl ShedVi {
|
|||||||
completer: Box::new(FuzzyCompleter::default()),
|
completer: Box::new(FuzzyCompleter::default()),
|
||||||
highlighter: Highlighter::new(),
|
highlighter: Highlighter::new(),
|
||||||
mode: Box::new(ViInsert::new()),
|
mode: Box::new(ViInsert::new()),
|
||||||
next_is_escaped: false,
|
next_is_escaped: false,
|
||||||
saved_mode: None,
|
saved_mode: None,
|
||||||
pending_keymap: Vec::new(),
|
pending_keymap: Vec::new(),
|
||||||
old_layout: None,
|
old_layout: None,
|
||||||
repeat_action: None,
|
repeat_action: None,
|
||||||
repeat_motion: None,
|
repeat_motion: None,
|
||||||
@@ -276,8 +281,14 @@ 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| {
|
||||||
new.prompt.refresh();
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(new.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
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)?;
|
||||||
Ok(new)
|
Ok(new)
|
||||||
@@ -293,7 +304,7 @@ impl ShedVi {
|
|||||||
|
|
||||||
/// Feed raw bytes from stdin into the reader's buffer
|
/// Feed raw bytes from stdin into the reader's buffer
|
||||||
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
pub fn feed_bytes(&mut self, bytes: &[u8]) {
|
||||||
let verbatim = self.mode.report_mode() == ModeReport::Verbatim;
|
let verbatim = self.mode.report_mode() == ModeReport::Verbatim;
|
||||||
self.reader.feed_bytes(bytes, verbatim);
|
self.reader.feed_bytes(bytes, verbatim);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,19 +313,21 @@ impl ShedVi {
|
|||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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<()> {
|
||||||
if self.completer.is_active() {
|
if self.completer.is_active() {
|
||||||
self.completer.reset_stay_active();
|
self.completer.reset_stay_active();
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.reset(full_redraw)
|
self.reset(full_redraw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset readline state for a new prompt
|
/// Reset readline state for a new prompt
|
||||||
pub fn reset(&mut self, full_redraw: bool) -> ShResult<()> {
|
pub fn reset(&mut self, full_redraw: bool) -> ShResult<()> {
|
||||||
@@ -322,7 +335,7 @@ impl ShedVi {
|
|||||||
// so print_line can call clear_rows with the full multi-line layout
|
// so print_line can call clear_rows with the full multi-line layout
|
||||||
self.prompt.refresh();
|
self.prompt.refresh();
|
||||||
self.editor = Default::default();
|
self.editor = Default::default();
|
||||||
self.swap_mode(&mut (Box::new(ViInsert::new()) as Box<dyn ViMode>));
|
self.swap_mode(&mut (Box::new(ViInsert::new()) as Box<dyn ViMode>));
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
if full_redraw {
|
if full_redraw {
|
||||||
self.old_layout = None;
|
self.old_layout = None;
|
||||||
@@ -340,24 +353,24 @@ impl ShedVi {
|
|||||||
&mut self.prompt
|
&mut self.prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn curr_keymap_flags(&self) -> KeyMapFlags {
|
pub fn curr_keymap_flags(&self) -> KeyMapFlags {
|
||||||
let mut flags = KeyMapFlags::empty();
|
let mut flags = KeyMapFlags::empty();
|
||||||
match self.mode.report_mode() {
|
match self.mode.report_mode() {
|
||||||
ModeReport::Insert => flags |= KeyMapFlags::INSERT,
|
ModeReport::Insert => flags |= KeyMapFlags::INSERT,
|
||||||
ModeReport::Normal => flags |= KeyMapFlags::NORMAL,
|
ModeReport::Normal => flags |= KeyMapFlags::NORMAL,
|
||||||
ModeReport::Ex => flags |= KeyMapFlags::EX,
|
ModeReport::Ex => flags |= KeyMapFlags::EX,
|
||||||
ModeReport::Visual => flags |= KeyMapFlags::VISUAL,
|
ModeReport::Visual => flags |= KeyMapFlags::VISUAL,
|
||||||
ModeReport::Replace => flags |= KeyMapFlags::REPLACE,
|
ModeReport::Replace => flags |= KeyMapFlags::REPLACE,
|
||||||
ModeReport::Verbatim => flags |= KeyMapFlags::VERBATIM,
|
ModeReport::Verbatim => flags |= KeyMapFlags::VERBATIM,
|
||||||
ModeReport::Unknown => todo!(),
|
ModeReport::Unknown => todo!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode.pending_seq().is_some_and(|seq| !seq.is_empty()) {
|
if self.mode.pending_seq().is_some_and(|seq| !seq.is_empty()) {
|
||||||
flags |= KeyMapFlags::OP_PENDING;
|
flags |= KeyMapFlags::OP_PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_submit(&mut self) -> ShResult<bool> {
|
fn should_submit(&mut self) -> ShResult<bool> {
|
||||||
if self.mode.report_mode() == ModeReport::Normal {
|
if self.mode.report_mode() == ModeReport::Normal {
|
||||||
@@ -398,7 +411,7 @@ impl ShedVi {
|
|||||||
while let Some(key) = self.reader.read_key()? {
|
while let Some(key) = self.reader.read_key()? {
|
||||||
// If completer is active, delegate input to it
|
// If completer is active, delegate input to it
|
||||||
if self.completer.is_active() {
|
if self.completer.is_active() {
|
||||||
self.print_line(false)?;
|
self.print_line(false)?;
|
||||||
match self.completer.handle_key(key.clone())? {
|
match self.completer.handle_key(key.clone())? {
|
||||||
CompResponse::Accept(candidate) => {
|
CompResponse::Accept(candidate) => {
|
||||||
let span_start = self.completer.token_span().0;
|
let span_start = self.completer.token_span().0;
|
||||||
@@ -416,57 +429,58 @@ impl ShedVi {
|
|||||||
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
|
||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
self.completer.clear(&mut self.writer)?;
|
self.completer.clear(&mut self.writer)?;
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
self.completer.reset();
|
self.completer.reset();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CompResponse::Dismiss => {
|
CompResponse::Dismiss => {
|
||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
self.completer.clear(&mut self.writer)?;
|
self.completer.clear(&mut self.writer)?;
|
||||||
self.completer.reset();
|
self.completer.reset();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CompResponse::Consumed => {
|
CompResponse::Consumed => {
|
||||||
/* just redraw */
|
/* just redraw */
|
||||||
self.needs_redraw = true;
|
self.needs_redraw = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CompResponse::Passthrough => { /* fall through to normal handling below */ }
|
CompResponse::Passthrough => { /* fall through to normal handling below */ }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let keymap_flags = self.curr_keymap_flags();
|
let keymap_flags = self.curr_keymap_flags();
|
||||||
self.pending_keymap.push(key.clone());
|
self.pending_keymap.push(key.clone());
|
||||||
|
|
||||||
let matches = read_logic(|l| l.keymaps_filtered(keymap_flags, &self.pending_keymap));
|
|
||||||
if matches.is_empty() {
|
|
||||||
// No matches. Drain the buffered keys and execute them.
|
|
||||||
for key in std::mem::take(&mut self.pending_keymap) {
|
|
||||||
if let Some(event) = self.handle_key(key)? {
|
|
||||||
return Ok(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
} else if matches.len() == 1 && matches[0].compare(&self.pending_keymap) == KeyMapMatch::IsExact {
|
|
||||||
// We have a single exact match. Execute it.
|
|
||||||
let keymap = matches[0].clone();
|
|
||||||
self.pending_keymap.clear();
|
|
||||||
let action = keymap.action_expanded();
|
|
||||||
for key in action {
|
|
||||||
if let Some(event) = self.handle_key(key)? {
|
|
||||||
return Ok(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.needs_redraw = true;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// There is ambiguity. Allow the timeout in the main loop to handle this.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let matches = read_logic(|l| l.keymaps_filtered(keymap_flags, &self.pending_keymap));
|
||||||
|
if matches.is_empty() {
|
||||||
|
// No matches. Drain the buffered keys and execute them.
|
||||||
|
for key in std::mem::take(&mut self.pending_keymap) {
|
||||||
|
if let Some(event) = self.handle_key(key)? {
|
||||||
|
return Ok(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
} else if matches.len() == 1
|
||||||
|
&& matches[0].compare(&self.pending_keymap) == KeyMapMatch::IsExact
|
||||||
|
{
|
||||||
|
// We have a single exact match. Execute it.
|
||||||
|
let keymap = matches[0].clone();
|
||||||
|
self.pending_keymap.clear();
|
||||||
|
let action = keymap.action_expanded();
|
||||||
|
for key in action {
|
||||||
|
if let Some(event) = self.handle_key(key)? {
|
||||||
|
return Ok(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// There is ambiguity. Allow the timeout in the main loop to handle this.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(event) = self.handle_key(key)? {
|
if let Some(event) = self.handle_key(key)? {
|
||||||
return Ok(event);
|
return Ok(event);
|
||||||
@@ -542,12 +556,13 @@ impl ShedVi {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
{
|
||||||
} else {
|
self.next_is_escaped = true;
|
||||||
self.next_is_escaped = false;
|
} else {
|
||||||
}
|
self.next_is_escaped = false;
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(cmd) = self.mode.handle_key_fallible(key) else {
|
let Ok(cmd) = self.mode.handle_key_fallible(key) else {
|
||||||
// it's an ex mode error
|
// it's an ex mode error
|
||||||
@@ -567,9 +582,10 @@ 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)?;
|
||||||
@@ -600,11 +616,11 @@ impl ShedVi {
|
|||||||
|
|
||||||
let before = self.editor.buffer.clone();
|
let before = self.editor.buffer.clone();
|
||||||
self.exec_cmd(cmd)?;
|
self.exec_cmd(cmd)?;
|
||||||
if let Some(keys) = write_meta(|m| m.take_pending_widget_keys()) {
|
if let Some(keys) = write_meta(|m| m.take_pending_widget_keys()) {
|
||||||
for key in keys {
|
for key in keys {
|
||||||
self.handle_key(key)?;
|
self.handle_key(key)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let after = self.editor.as_str();
|
let after = self.editor.as_str();
|
||||||
|
|
||||||
if before != after {
|
if before != after {
|
||||||
@@ -670,7 +686,7 @@ impl ShedVi {
|
|||||||
|| (self.mode.pending_seq().unwrap(/* always Some on normal mode */).is_empty()
|
|| (self.mode.pending_seq().unwrap(/* always Some on normal mode */).is_empty()
|
||||||
&& matches!(event, KeyEvent(KeyCode::Char('l'), ModKeys::NONE)))
|
&& matches!(event, KeyEvent(KeyCode::Char('l'), ModKeys::NONE)))
|
||||||
}
|
}
|
||||||
ModeReport::Ex | ModeReport::Verbatim | ModeReport::Unknown => false,
|
ModeReport::Ex | ModeReport::Verbatim | ModeReport::Unknown => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -692,13 +708,15 @@ impl ShedVi {
|
|||||||
pub fn line_text(&mut self) -> String {
|
pub fn line_text(&mut self) -> String {
|
||||||
let line = self.editor.to_string();
|
let line = self.editor.to_string();
|
||||||
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
|
||||||
self.highlighter.expand_control_chars();
|
.highlighter
|
||||||
self.highlighter.highlight();
|
.load_input(&line, self.editor.cursor_byte_pos());
|
||||||
let highlighted = self.highlighter.take();
|
self.highlighter.expand_control_chars();
|
||||||
format!("{highlighted}{hint}")
|
self.highlighter.highlight();
|
||||||
|
let highlighted = self.highlighter.take();
|
||||||
|
format!("{highlighted}{hint}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_line(&mut self, final_draw: bool) -> ShResult<()> {
|
pub fn print_line(&mut self, final_draw: bool) -> ShResult<()> {
|
||||||
@@ -716,7 +734,7 @@ impl ShedVi {
|
|||||||
prompt_string_right =
|
prompt_string_right =
|
||||||
prompt_string_right.map(|psr| psr.lines().next().unwrap_or_default().to_string());
|
prompt_string_right.map(|psr| psr.lines().next().unwrap_or_default().to_string());
|
||||||
}
|
}
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|
||||||
let row0_used = self
|
let row0_used = self
|
||||||
.prompt
|
.prompt
|
||||||
@@ -734,8 +752,8 @@ impl ShedVi {
|
|||||||
self.writer.clear_rows(layout)?;
|
self.writer.clear_rows(layout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pre_prompt = read_logic(|l| l.get_autocmds(AutoCmdKind::PrePrompt));
|
let pre_prompt = read_logic(|l| l.get_autocmds(AutoCmdKind::PrePrompt));
|
||||||
pre_prompt.exec();
|
pre_prompt.exec();
|
||||||
|
|
||||||
self
|
self
|
||||||
.writer
|
.writer
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -765,10 +784,11 @@ impl ShedVi {
|
|||||||
|
|
||||||
// Save cursor, move up to top row, move right to column, write sequence,
|
// Save cursor, move up to top row, move right to column, write sequence,
|
||||||
// restore cursor
|
// restore cursor
|
||||||
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,19 +801,28 @@ 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() {
|
||||||
let pending_seq = self.mode.pending_seq().unwrap_or_default();
|
let pending_seq = self.mode.pending_seq().unwrap_or_default();
|
||||||
write!(buf, "\n: {pending_seq}").unwrap();
|
write!(buf, "\n: {pending_seq}").unwrap();
|
||||||
new_layout.end.row += 1;
|
new_layout.end.row += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(buf, "{}", &self.mode.cursor_style()).unwrap();
|
write!(buf, "{}", &self.mode.cursor_style()).unwrap();
|
||||||
|
|
||||||
self.writer.flush_write(&buf)?;
|
self.writer.flush_write(&buf)?;
|
||||||
|
|
||||||
// Tell the completer the width of the prompt line above its \n so it can
|
// Tell the completer the width of the prompt line above its \n so it can
|
||||||
// account for wrapping when clearing after a resize.
|
// account for wrapping when clearing after a resize.
|
||||||
@@ -803,30 +832,39 @@ 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);
|
||||||
self.needs_redraw = false;
|
self.needs_redraw = false;
|
||||||
|
|
||||||
let post_prompt = read_logic(|l| l.get_autocmds(AutoCmdKind::PostPrompt));
|
let post_prompt = read_logic(|l| l.get_autocmds(AutoCmdKind::PostPrompt));
|
||||||
post_prompt.exec();
|
post_prompt.exec();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn swap_mode(&mut self, mode: &mut Box<dyn ViMode>) {
|
pub fn swap_mode(&mut self, mode: &mut Box<dyn ViMode>) {
|
||||||
let pre_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PreModeChange));
|
let pre_mode_change = read_logic(|l| l.get_autocmds(AutoCmdKind::PreModeChange));
|
||||||
pre_mode_change.exec();
|
pre_mode_change.exec();
|
||||||
|
|
||||||
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| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
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));
|
||||||
post_mode_change.exec();
|
post_mode_change.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
||||||
let mut select_mode = None;
|
let mut select_mode = None;
|
||||||
@@ -834,63 +872,72 @@ 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!(
|
||||||
if let Some(saved) = self.saved_mode.take() {
|
self.mode.report_mode(),
|
||||||
saved
|
ModeReport::Ex | ModeReport::Verbatim
|
||||||
} else {
|
) && cmd.flags.contains(CmdFlags::EXIT_CUR_MODE)
|
||||||
Box::new(ViNormal::new())
|
{
|
||||||
}
|
if let Some(saved) = self.saved_mode.take() {
|
||||||
} else {
|
saved
|
||||||
match cmd.verb().unwrap().1 {
|
} else {
|
||||||
Verb::Change | Verb::InsertModeLineBreak(_) | Verb::InsertMode => {
|
Box::new(ViNormal::new())
|
||||||
is_insert_mode = true;
|
}
|
||||||
Box::new(ViInsert::new().with_count(count as u16))
|
} else {
|
||||||
}
|
match cmd.verb().unwrap().1 {
|
||||||
|
Verb::Change | Verb::InsertModeLineBreak(_) | Verb::InsertMode => {
|
||||||
|
is_insert_mode = true;
|
||||||
|
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()),
|
||||||
|
|
||||||
Verb::ReplaceMode => Box::new(ViReplace::new()),
|
Verb::ReplaceMode => Box::new(ViReplace::new()),
|
||||||
|
|
||||||
Verb::VisualModeSelectLast => {
|
Verb::VisualModeSelectLast => {
|
||||||
if self.mode.report_mode() != ModeReport::Visual {
|
if self.mode.report_mode() != ModeReport::Visual {
|
||||||
self
|
self
|
||||||
.editor
|
.editor
|
||||||
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
||||||
}
|
}
|
||||||
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
|
let mut mode: Box<dyn ViMode> = Box::new(ViVisual::new());
|
||||||
self.swap_mode(&mut mode);
|
self.swap_mode(&mut mode);
|
||||||
|
|
||||||
return self.editor.exec_cmd(cmd);
|
return self.editor.exec_cmd(cmd);
|
||||||
}
|
}
|
||||||
Verb::VisualMode => {
|
Verb::VisualMode => {
|
||||||
select_mode = Some(SelectMode::Char(SelectAnchor::End));
|
select_mode = Some(SelectMode::Char(SelectAnchor::End));
|
||||||
Box::new(ViVisual::new())
|
Box::new(ViVisual::new())
|
||||||
}
|
}
|
||||||
Verb::VisualModeLine => {
|
Verb::VisualModeLine => {
|
||||||
select_mode = Some(SelectMode::Line(SelectAnchor::End));
|
select_mode = Some(SelectMode::Line(SelectAnchor::End));
|
||||||
Box::new(ViVisual::new())
|
Box::new(ViVisual::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.swap_mode(&mut mode);
|
self.swap_mode(&mut mode);
|
||||||
|
|
||||||
if matches!(self.mode.report_mode(), ModeReport::Ex | ModeReport::Verbatim) {
|
if matches!(
|
||||||
self.saved_mode = Some(mode);
|
self.mode.report_mode(),
|
||||||
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE))?;
|
ModeReport::Ex | ModeReport::Verbatim
|
||||||
self.prompt.refresh();
|
) {
|
||||||
return Ok(());
|
self.saved_mode = Some(mode);
|
||||||
}
|
write_vars(|v| {
|
||||||
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
self.prompt.refresh();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if mode.is_repeatable() {
|
if mode.is_repeatable() {
|
||||||
self.repeat_action = mode.as_replay();
|
self.repeat_action = mode.as_replay();
|
||||||
@@ -912,9 +959,14 @@ 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| {
|
||||||
self.prompt.refresh();
|
v.set_var(
|
||||||
|
"SHED_VI_MODE",
|
||||||
|
VarKind::Str(self.mode.report_mode().to_string()),
|
||||||
|
VarFlags::NONE,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
self.prompt.refresh();
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if cmd.is_cmd_repeat() {
|
} else if cmd.is_cmd_repeat() {
|
||||||
@@ -989,22 +1041,21 @@ 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);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.is_repeatable() {
|
if cmd.is_repeatable() {
|
||||||
if self.mode.report_mode() == ModeReport::Visual {
|
if self.mode.report_mode() == ModeReport::Visual {
|
||||||
// The motion is assigned in the line buffer execution, so we also have to
|
// The motion is assigned in the line buffer execution, so we also have to
|
||||||
// assign it here in order to be able to repeat it
|
// assign it here in order to be able to repeat it
|
||||||
if let Some(range) = self.editor.select_range() {
|
if let Some(range) = self.editor.select_range() {
|
||||||
cmd.motion = Some(MotionCmd(1, Motion::Range(range.0, range.1)))
|
cmd.motion = Some(MotionCmd(1, Motion::Range(range.0, range.1)))
|
||||||
} else {
|
} else {
|
||||||
log::warn!("You're in visual mode with no select range??");
|
log::warn!("You're in visual mode with no select range??");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.repeat_action = Some(CmdReplay::Single(cmd.clone()));
|
self.repeat_action = Some(CmdReplay::Single(cmd.clone()));
|
||||||
}
|
}
|
||||||
@@ -1018,24 +1069,27 @@ impl ShedVi {
|
|||||||
if self.mode.report_mode() == ModeReport::Visual && cmd.verb().is_some_and(|v| v.1.is_edit()) {
|
if self.mode.report_mode() == ModeReport::Visual && cmd.verb().is_some_and(|v| v.1.is_edit()) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode.report_mode() != ModeReport::Visual && self.editor.select_range().is_some() {
|
if self.mode.report_mode() != ModeReport::Visual && self.editor.select_range().is_some() {
|
||||||
self.editor.stop_selecting();
|
self.editor.stop_selecting();
|
||||||
}
|
}
|
||||||
|
|
||||||
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!(
|
||||||
if let Some(saved) = self.saved_mode.take() {
|
self.mode.report_mode(),
|
||||||
saved
|
ModeReport::Ex | ModeReport::Verbatim
|
||||||
} else {
|
) {
|
||||||
Box::new(ViNormal::new())
|
if let Some(saved) = self.saved_mode.take() {
|
||||||
}
|
saved
|
||||||
} else {
|
} else {
|
||||||
Box::new(ViNormal::new())
|
Box::new(ViNormal::new())
|
||||||
};
|
}
|
||||||
self.swap_mode(&mut mode);
|
} else {
|
||||||
|
Box::new(ViNormal::new())
|
||||||
|
};
|
||||||
|
self.swap_mode(&mut mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -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;
|
||||||
@@ -1279,7 +1331,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
|
|
||||||
let span_start = token.span.range().start;
|
let span_start = token.span.range().start;
|
||||||
|
|
||||||
let mut qt_state = QuoteState::default();
|
let mut qt_state = QuoteState::default();
|
||||||
let mut cmd_sub_depth = 0;
|
let mut cmd_sub_depth = 0;
|
||||||
let mut proc_sub_depth = 0;
|
let mut proc_sub_depth = 0;
|
||||||
|
|
||||||
@@ -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 == '}' {
|
||||||
@@ -1370,8 +1423,9 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
// consume the var name
|
// consume the var name
|
||||||
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 {
|
||||||
@@ -1397,12 +1451,12 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
token_chars.next(); // consume the escaped char
|
token_chars.next(); // consume the escaped char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\\' 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'<' | '>' if !qt_state.in_quote() && cmd_sub_depth == 0 && proc_sub_depth == 0 => {
|
'<' | '>' if !qt_state.in_quote() && cmd_sub_depth == 0 && proc_sub_depth == 0 => {
|
||||||
token_chars.next();
|
token_chars.next();
|
||||||
if let Some((_, proc_sub_ch)) = token_chars.peek()
|
if let Some((_, proc_sub_ch)) = token_chars.peek()
|
||||||
@@ -1421,7 +1475,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
} else {
|
} else {
|
||||||
insertions.push((span_start + *i, markers::STRING_DQ));
|
insertions.push((span_start + *i, markers::STRING_DQ));
|
||||||
}
|
}
|
||||||
qt_state.toggle_double();
|
qt_state.toggle_double();
|
||||||
token_chars.next(); // consume the quote
|
token_chars.next(); // consume the quote
|
||||||
}
|
}
|
||||||
'\'' if !qt_state.in_double() => {
|
'\'' if !qt_state.in_double() => {
|
||||||
@@ -1430,7 +1484,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
} else {
|
} else {
|
||||||
insertions.push((span_start + *i, markers::STRING_SQ));
|
insertions.push((span_start + *i, markers::STRING_SQ));
|
||||||
}
|
}
|
||||||
qt_state.toggle_single();
|
qt_state.toggle_single();
|
||||||
token_chars.next(); // consume the quote
|
token_chars.next(); // consume the quote
|
||||||
}
|
}
|
||||||
'[' if !qt_state.in_quote() && !token.flags.contains(TkFlags::ASSIGN) => {
|
'[' if !qt_state.in_quote() && !token.flags.contains(TkFlags::ASSIGN) => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -165,13 +164,13 @@ fn width(s: &str, esc_seq: &mut u8) -> u16 {
|
|||||||
0
|
0
|
||||||
} else if *esc_seq == 2 {
|
} else if *esc_seq == 2 {
|
||||||
if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
|
if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
|
||||||
/*} else if s == "m" {
|
/*} else if s == "m" {
|
||||||
// last
|
// last
|
||||||
*esc_seq = 0;*/
|
*esc_seq = 0;*/
|
||||||
} else {
|
} else {
|
||||||
// not supported
|
// not supported
|
||||||
*esc_seq = 0;
|
*esc_seq = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
0
|
||||||
} else if s == "\x1b" {
|
} else if s == "\x1b" {
|
||||||
@@ -457,27 +456,27 @@ 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)
|
||||||
.map(|&m| Self::parse_modifiers(m))
|
.map(|&m| Self::parse_modifiers(m))
|
||||||
.unwrap_or(ModKeys::empty());
|
.unwrap_or(ModKeys::empty());
|
||||||
let key = match codepoint {
|
let key = match codepoint {
|
||||||
9 => KeyCode::Tab,
|
9 => KeyCode::Tab,
|
||||||
13 => KeyCode::Enter,
|
13 => KeyCode::Enter,
|
||||||
27 => KeyCode::Esc,
|
27 => KeyCode::Esc,
|
||||||
127 => KeyCode::Backspace,
|
127 => KeyCode::Backspace,
|
||||||
_ => {
|
_ => {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
KeyEvent(key, mods)
|
KeyEvent(key, mods)
|
||||||
}
|
}
|
||||||
// SGR mouse: CSI < button;x;y M/m (ignore mouse events for now)
|
// SGR mouse: CSI < button;x;y M/m (ignore mouse events for now)
|
||||||
([b'<'], 'M') | ([b'<'], 'm') => {
|
([b'<'], 'M') | ([b'<'], 'm') => {
|
||||||
return;
|
return;
|
||||||
@@ -516,18 +515,21 @@ 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(
|
||||||
} else if bytes == [b'\x1b'] {
|
KeyCode::Verbatim(Arc::from(seq.as_str())),
|
||||||
|
ModKeys::empty(),
|
||||||
|
));
|
||||||
|
} else if bytes == [b'\x1b'] {
|
||||||
// Single escape byte - user pressed ESC key
|
// Single escape byte - user pressed ESC key
|
||||||
self
|
self
|
||||||
.collector
|
.collector
|
||||||
.push(KeyEvent(KeyCode::Esc, ModKeys::empty()));
|
.push(KeyEvent(KeyCode::Esc, ModKeys::empty()));
|
||||||
} else {
|
} else {
|
||||||
// Feed all bytes through vte parser
|
// Feed all bytes through vte parser
|
||||||
self.parser.advance(&mut self.collector, bytes);
|
self.parser.advance(&mut self.collector, bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,13 +750,9 @@ 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 {
|
||||||
const TAB_STOP: u16 = 8;
|
const TAB_STOP: u16 = 8;
|
||||||
@@ -767,8 +765,8 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
let c_width = if c == "\t" {
|
let c_width = if c == "\t" {
|
||||||
TAB_STOP - (pos.col % TAB_STOP)
|
TAB_STOP - (pos.col % TAB_STOP)
|
||||||
} else if raw_calc && Self::is_ctl_char(c) {
|
} else if raw_calc && Self::is_ctl_char(c) {
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
width(c, &mut esc_seq)
|
width(c, &mut esc_seq)
|
||||||
};
|
};
|
||||||
@@ -867,21 +865,21 @@ impl TermWriter {
|
|||||||
self.t_cols = t_cols;
|
self.t_cols = t_cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called before the prompt is drawn. If we are not on column 1, push a vid-inverted '%' and then a '\n\r'.
|
/// Called before the prompt is drawn. If we are not on column 1, push a vid-inverted '%' and then a '\n\r'.
|
||||||
///
|
///
|
||||||
/// 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(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if c != 1 {
|
if c != 1 {
|
||||||
self.flush_write("\x1b[7m%\x1b[0m\n\r")?;
|
self.flush_write("\x1b[7m%\x1b[0m\n\r")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_cursor_pos(&mut self, rdr: &mut TermReader) -> ShResult<Option<(usize, usize)>> {
|
pub fn get_cursor_pos(&mut self, rdr: &mut TermReader) -> ShResult<Option<(usize, usize)>> {
|
||||||
// Ping the cursor's position
|
// Ping the cursor's position
|
||||||
self.flush_write("\x1b[6n")?;
|
self.flush_write("\x1b[6n")?;
|
||||||
|
|
||||||
@@ -900,14 +898,16 @@ 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
|
||||||
Some((row as usize, col as usize))
|
&& let Some(col) = col
|
||||||
} else {
|
{
|
||||||
None
|
Some((row as usize, col as usize))
|
||||||
};
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(pos)
|
Ok(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_cursor_at_leftmost(
|
pub fn move_cursor_at_leftmost(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -996,7 +996,7 @@ impl LineWriter for TermWriter {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
self.buffer.clear();
|
self.buffer.clear();
|
||||||
self.buffer.push_str("\x1b[J"); // Clear from cursor to end of screen to erase any remnants of the old line after the prompt
|
self.buffer.push_str("\x1b[J"); // Clear from cursor to end of screen to erase any remnants of the old line after the prompt
|
||||||
|
|
||||||
let end = new_layout.end;
|
let end = new_layout.end;
|
||||||
let cursor = new_layout.cursor;
|
let cursor = new_layout.cursor;
|
||||||
|
|||||||
@@ -178,8 +178,8 @@ impl ViCmd {
|
|||||||
matches!(
|
matches!(
|
||||||
v.1,
|
v.1,
|
||||||
Verb::Change
|
Verb::Change
|
||||||
| Verb::VerbatimMode
|
| Verb::VerbatimMode
|
||||||
| Verb::ExMode
|
| Verb::ExMode
|
||||||
| Verb::InsertMode
|
| Verb::InsertMode
|
||||||
| Verb::InsertModeLineBreak(_)
|
| Verb::InsertModeLineBreak(_)
|
||||||
| Verb::NormalMode
|
| Verb::NormalMode
|
||||||
@@ -221,8 +221,8 @@ pub enum Verb {
|
|||||||
ReplaceCharInplace(char, u16), // char to replace with, number of chars to replace
|
ReplaceCharInplace(char, u16), // char to replace with, number of chars to replace
|
||||||
ToggleCaseInplace(u16), // Number of chars to toggle
|
ToggleCaseInplace(u16), // Number of chars to toggle
|
||||||
ToggleCaseRange,
|
ToggleCaseRange,
|
||||||
IncrementNumber(u16),
|
IncrementNumber(u16),
|
||||||
DecrementNumber(u16),
|
DecrementNumber(u16),
|
||||||
ToLower,
|
ToLower,
|
||||||
ToUpper,
|
ToUpper,
|
||||||
Complete,
|
Complete,
|
||||||
@@ -232,7 +232,7 @@ pub enum Verb {
|
|||||||
RepeatLast,
|
RepeatLast,
|
||||||
Put(Anchor),
|
Put(Anchor),
|
||||||
ReplaceMode,
|
ReplaceMode,
|
||||||
VerbatimMode,
|
VerbatimMode,
|
||||||
InsertMode,
|
InsertMode,
|
||||||
InsertModeLineBreak(Anchor),
|
InsertModeLineBreak(Anchor),
|
||||||
NormalMode,
|
NormalMode,
|
||||||
@@ -303,8 +303,8 @@ impl Verb {
|
|||||||
| Self::Insert(_)
|
| Self::Insert(_)
|
||||||
| Self::Rot13
|
| Self::Rot13
|
||||||
| Self::EndOfFile
|
| Self::EndOfFile
|
||||||
| Self::IncrementNumber(_)
|
| Self::IncrementNumber(_)
|
||||||
| Self::DecrementNumber(_)
|
| Self::DecrementNumber(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub fn is_char_insert(&self) -> bool {
|
pub fn is_char_insert(&self) -> bool {
|
||||||
|
|||||||
@@ -9,373 +9,399 @@ 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;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
||||||
pub struct SubFlags: u16 {
|
pub struct SubFlags: u16 {
|
||||||
const GLOBAL = 1 << 0; // g
|
const GLOBAL = 1 << 0; // g
|
||||||
const CONFIRM = 1 << 1; // c (probably not implemented)
|
const CONFIRM = 1 << 1; // c (probably not implemented)
|
||||||
const IGNORE_CASE = 1 << 2; // i
|
const IGNORE_CASE = 1 << 2; // i
|
||||||
const NO_IGNORE_CASE = 1 << 3; // I
|
const NO_IGNORE_CASE = 1 << 3; // I
|
||||||
const SHOW_COUNT = 1 << 4; // n
|
const SHOW_COUNT = 1 << 4; // n
|
||||||
const PRINT_RESULT = 1 << 5; // p
|
const PRINT_RESULT = 1 << 5; // p
|
||||||
const PRINT_NUMBERED = 1 << 6; // #
|
const PRINT_NUMBERED = 1 << 6; // #
|
||||||
const PRINT_LEFT_ALIGN = 1 << 7; // l
|
const PRINT_LEFT_ALIGN = 1 << 7; // l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
struct ExEditor {
|
struct ExEditor {
|
||||||
buf: LineBuf,
|
buf: LineBuf,
|
||||||
mode: ViInsert
|
mode: ViInsert,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExEditor {
|
impl ExEditor {
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
*self = Self::default()
|
*self = Self::default()
|
||||||
}
|
}
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViEx {
|
impl ViEx {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
Ok(cmd) => {
|
||||||
Ok(cmd) => {
|
log::debug!("[ViEx] parse_ex_cmd Ok: {:?}", cmd);
|
||||||
log::debug!("[ViEx] parse_ex_cmd Ok: {:?}", cmd);
|
Ok(cmd)
|
||||||
Ok(cmd)
|
}
|
||||||
}
|
Err(e) => {
|
||||||
Err(e) => {
|
log::debug!("[ViEx] parse_ex_cmd Err: {:?}", e);
|
||||||
log::debug!("[ViEx] parse_ex_cmd Err: {:?}", e);
|
let msg = e.unwrap_or(format!("Not an editor command: {}", input));
|
||||||
let msg = e.unwrap_or(format!("Not an editor command: {}", input));
|
write_meta(|m| m.post_system_message(msg.clone()));
|
||||||
write_meta(|m| m.post_system_message(msg.clone()));
|
Err(ShErr::simple(ShErrKind::ParseErr, msg))
|
||||||
Err(ShErr::simple(ShErrKind::ParseErr, msg))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
E(C::Char('C'), M::CTRL) => {
|
||||||
E(C::Char('C'), M::CTRL) => {
|
log::debug!("[ViEx] Ctrl-C, clearing");
|
||||||
log::debug!("[ViEx] Ctrl-C, clearing");
|
self.pending_cmd.clear();
|
||||||
self.pending_cmd.clear();
|
Ok(None)
|
||||||
Ok(None)
|
}
|
||||||
}
|
E(C::Esc, M::NONE) => {
|
||||||
E(C::Esc, M::NONE) => {
|
log::debug!("[ViEx] Esc, returning to normal mode");
|
||||||
log::debug!("[ViEx] Esc, returning to normal mode");
|
Ok(Some(ViCmd {
|
||||||
Ok(Some(ViCmd {
|
register: RegisterName::default(),
|
||||||
register: RegisterName::default(),
|
verb: Some(VerbCmd(1, Verb::NormalMode)),
|
||||||
verb: Some(VerbCmd(1, Verb::NormalMode)),
|
motion: None,
|
||||||
motion: None,
|
flags: CmdFlags::empty(),
|
||||||
flags: CmdFlags::empty(),
|
raw_seq: "".into(),
|
||||||
raw_seq: "".into(),
|
}))
|
||||||
}))
|
}
|
||||||
}
|
_ => {
|
||||||
_ => {
|
log::debug!("[ViEx] forwarding key to ExEditor");
|
||||||
log::debug!("[ViEx] forwarding key to ExEditor");
|
self.pending_cmd.handle_key(key).map(|_| None)
|
||||||
self.pending_cmd.handle_key(key).map(|_| None)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
fn handle_key(&mut self, key: KeyEvent) -> Option<ViCmd> {
|
||||||
fn handle_key(&mut self, key: KeyEvent) -> Option<ViCmd> {
|
let result = self.handle_key_fallible(key);
|
||||||
let result = self.handle_key_fallible(key);
|
log::debug!("[ViEx] handle_key result: {:?}", result);
|
||||||
log::debug!("[ViEx] handle_key result: {:?}", result);
|
result.ok().flatten()
|
||||||
result.ok().flatten()
|
}
|
||||||
}
|
fn is_repeatable(&self) -> bool {
|
||||||
fn is_repeatable(&self) -> bool {
|
false
|
||||||
false
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn as_replay(&self) -> Option<super::CmdReplay> {
|
fn as_replay(&self) -> Option<super::CmdReplay> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_style(&self) -> String {
|
fn cursor_style(&self) -> String {
|
||||||
"\x1b[3 q".to_string()
|
"\x1b[3 q".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_seq(&self) -> Option<String> {
|
fn pending_seq(&self) -> Option<String> {
|
||||||
Some(self.pending_cmd.buf.as_str().to_string())
|
Some(self.pending_cmd.buf.as_str().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_cursor(&self) -> Option<usize> {
|
fn pending_cursor(&self) -> Option<usize> {
|
||||||
Some(self.pending_cmd.buf.cursor.get())
|
Some(self.pending_cmd.buf.cursor.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_on_undo(&self) -> bool {
|
fn move_cursor_on_undo(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clamp_cursor(&self) -> bool {
|
fn clamp_cursor(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hist_scroll_start_pos(&self) -> Option<To> {
|
fn hist_scroll_start_pos(&self) -> Option<To> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_mode(&self) -> super::ModeReport {
|
fn report_mode(&self) -> super::ModeReport {
|
||||||
ModeReport::Ex
|
ModeReport::Ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) = {
|
||||||
if chars.peek() == Some(&'g') {
|
if chars.peek() == Some(&'g') {
|
||||||
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.is_alphanumeric() {
|
if ch.is_alphanumeric() {
|
||||||
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);
|
||||||
} else {
|
};
|
||||||
(parse_ex_command(&mut chars)?.map(|v| VerbCmd(1, v)), None)
|
(Some(VerbCmd(1, result.1)), Some(MotionCmd(1, result.0)))
|
||||||
}
|
} else {
|
||||||
};
|
(parse_ex_command(&mut chars)?.map(|v| VerbCmd(1, v)), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Some(ViCmd {
|
Ok(Some(ViCmd {
|
||||||
register: RegisterName::default(),
|
register: RegisterName::default(),
|
||||||
verb,
|
verb,
|
||||||
motion,
|
motion,
|
||||||
raw_seq: raw.to_string(),
|
raw_seq: raw.to_string(),
|
||||||
flags: CmdFlags::EXIT_CUR_MODE,
|
flags: CmdFlags::EXIT_CUR_MODE,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unescape shell command arguments
|
/// Unescape shell command arguments
|
||||||
fn unescape_shell_cmd(cmd: &str) -> String {
|
fn unescape_shell_cmd(cmd: &str) -> String {
|
||||||
// The pest grammar uses double quotes for vicut commands
|
// The pest grammar uses double quotes for vicut commands
|
||||||
// So shell commands need to escape double quotes
|
// So shell commands need to escape double quotes
|
||||||
// We will be removing a single layer of escaping from double quotes
|
// We will be removing a single layer of escaping from double quotes
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut chars = cmd.chars().peekable();
|
let mut chars = cmd.chars().peekable();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
if let Some(&'"') = chars.peek() {
|
if let Some(&'"') = chars.peek() {
|
||||||
chars.next();
|
chars.next();
|
||||||
result.push('"');
|
result.push('"');
|
||||||
} else {
|
} else {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
match cmd_name.as_str() {
|
match cmd_name.as_str() {
|
||||||
"!" => {
|
"!" => {
|
||||||
let cmd = chars.collect::<String>();
|
let cmd = chars.collect::<String>();
|
||||||
let cmd = unescape_shell_cmd(&cmd);
|
let cmd = unescape_shell_cmd(&cmd);
|
||||||
Ok(Some(Verb::ShellCmd(cmd)))
|
Ok(Some(Verb::ShellCmd(cmd)))
|
||||||
}
|
}
|
||||||
"normal!" => parse_normal(chars),
|
"normal!" => parse_normal(chars),
|
||||||
_ if "delete".starts_with(&cmd_name) => Ok(Some(Verb::Delete)),
|
_ if "delete".starts_with(&cmd_name) => Ok(Some(Verb::Delete)),
|
||||||
_ if "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)),
|
_ if "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)),
|
||||||
_ if "put".starts_with(&cmd_name) => Ok(Some(Verb::Put(Anchor::After))),
|
_ if "put".starts_with(&cmd_name) => Ok(Some(Verb::Put(Anchor::After))),
|
||||||
_ 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(&'!') {
|
||||||
let arg: String = chars.collect();
|
chars.next();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
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 {
|
||||||
Ok(Some(Verb::Read(ReadSrc::Cmd(arg))))
|
Ok(Some(Verb::Read(ReadSrc::Cmd(arg))))
|
||||||
} else {
|
} else {
|
||||||
let arg_path = get_path(arg.trim());
|
let arg_path = get_path(arg.trim());
|
||||||
Ok(Some(Verb::Read(ReadSrc::File(arg_path))))
|
Ok(Some(Verb::Read(ReadSrc::File(arg_path))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 == "~"
|
}
|
||||||
&& let Some(home) = std::env::var_os("HOME") {
|
if path == "~"
|
||||||
return PathBuf::from(home)
|
&& let Some(home) = std::env::var_os("HOME")
|
||||||
}
|
{
|
||||||
PathBuf::from(path)
|
return PathBuf::from(home);
|
||||||
|
}
|
||||||
|
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 {
|
||||||
chars.next(); // consume '!'
|
chars.next(); // consume '!'
|
||||||
let arg: String = chars.collect();
|
let arg: String = chars.collect();
|
||||||
return Ok(Some(Verb::Write(WriteDest::Cmd(arg))));
|
return Ok(Some(Verb::Write(WriteDest::Cmd(arg))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for >>
|
// Check for >>
|
||||||
let mut append_check = chars.clone();
|
let mut append_check = chars.clone();
|
||||||
let is_file_append = append_check.next() == Some('>') && append_check.next() == Some('>');
|
let is_file_append = append_check.next() == Some('>') && append_check.next() == Some('>');
|
||||||
if is_file_append {
|
if is_file_append {
|
||||||
*chars = append_check;
|
*chars = append_check;
|
||||||
}
|
}
|
||||||
|
|
||||||
let arg: String = chars.collect();
|
let arg: String = chars.collect();
|
||||||
let arg_path = get_path(arg.trim());
|
let arg_path = get_path(arg.trim());
|
||||||
|
|
||||||
let dest = if is_file_append {
|
let dest = if is_file_append {
|
||||||
WriteDest::FileAppend(arg_path)
|
WriteDest::FileAppend(arg_path)
|
||||||
} else {
|
} else {
|
||||||
WriteDest::File(arg_path)
|
WriteDest::File(arg_path)
|
||||||
};
|
};
|
||||||
|
|
||||||
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)))
|
||||||
} else {
|
} else {
|
||||||
Ok(Some((Motion::Global(Val::new_str(global_pat)), command)))
|
Ok(Some((Motion::Global(Val::new_str(global_pat)), command)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)?;
|
||||||
let mut flags = SubFlags::empty();
|
let mut flags = SubFlags::empty();
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'g' => flags |= SubFlags::GLOBAL,
|
'g' => flags |= SubFlags::GLOBAL,
|
||||||
'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(
|
||||||
let mut pat = String::new();
|
chars: &mut Peekable<Chars<'_>>,
|
||||||
let mut closed = false;
|
delimiter: char,
|
||||||
while let Some(ch) = chars.next() {
|
) -> Result<String, Option<String>> {
|
||||||
match ch {
|
let mut pat = String::new();
|
||||||
'\\' => {
|
let mut closed = false;
|
||||||
if chars.peek().is_some_and(|c| *c == delimiter) {
|
while let Some(ch) = chars.next() {
|
||||||
// We escaped the delimiter, so we consume the escape char and continue
|
match ch {
|
||||||
pat.push(chars.next().unwrap());
|
'\\' => {
|
||||||
continue
|
if chars.peek().is_some_and(|c| *c == delimiter) {
|
||||||
} else {
|
// We escaped the delimiter, so we consume the escape char and continue
|
||||||
// The escape char is probably for the regex in the pattern
|
pat.push(chars.next().unwrap());
|
||||||
pat.push(ch);
|
continue;
|
||||||
if let Some(esc_ch) = chars.next() {
|
} else {
|
||||||
pat.push(esc_ch)
|
// The escape char is probably for the regex in the pattern
|
||||||
}
|
pat.push(ch);
|
||||||
}
|
if let Some(esc_ch) = chars.next() {
|
||||||
}
|
pat.push(esc_ch)
|
||||||
_ if ch == delimiter => {
|
}
|
||||||
closed = true;
|
}
|
||||||
break
|
}
|
||||||
}
|
_ if ch == delimiter => {
|
||||||
_ => pat.push(ch)
|
closed = true;
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
if !closed {
|
_ => pat.push(ch),
|
||||||
Err(Some("Unclosed pattern in ex command".into()))
|
}
|
||||||
} else {
|
}
|
||||||
Ok(pat)
|
if !closed {
|
||||||
}
|
Err(Some("Unclosed pattern in ex command".into()))
|
||||||
|
} else {
|
||||||
|
Ok(pat)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -4,47 +4,45 @@ 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 {
|
||||||
Insert,
|
Insert,
|
||||||
Normal,
|
Normal,
|
||||||
Ex,
|
Ex,
|
||||||
Visual,
|
Visual,
|
||||||
Replace,
|
Replace,
|
||||||
Verbatim,
|
Verbatim,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ModeReport {
|
impl Display for ModeReport {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ModeReport::Insert => write!(f, "INSERT"),
|
ModeReport::Insert => write!(f, "INSERT"),
|
||||||
ModeReport::Normal => write!(f, "NORMAL"),
|
ModeReport::Normal => write!(f, "NORMAL"),
|
||||||
ModeReport::Ex => write!(f, "COMMAND"),
|
ModeReport::Ex => write!(f, "COMMAND"),
|
||||||
ModeReport::Visual => write!(f, "VISUAL"),
|
ModeReport::Visual => write!(f, "VISUAL"),
|
||||||
ModeReport::Replace => write!(f, "REPLACE"),
|
ModeReport::Replace => write!(f, "REPLACE"),
|
||||||
ModeReport::Verbatim => write!(f, "VERBATIM"),
|
ModeReport::Verbatim => write!(f, "VERBATIM"),
|
||||||
ModeReport::Unknown => write!(f, "UNKNOWN"),
|
ModeReport::Unknown => write!(f, "UNKNOWN"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -309,15 +309,15 @@ impl ViNormal {
|
|||||||
flags: self.flags(),
|
flags: self.flags(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
':' => {
|
':' => {
|
||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register,
|
register,
|
||||||
verb: Some(VerbCmd(count, Verb::ExMode)),
|
verb: Some(VerbCmd(count, Verb::ExMode)),
|
||||||
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 {
|
||||||
register,
|
register,
|
||||||
@@ -724,7 +724,7 @@ impl ViNormal {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = chars; // suppresses unused warnings, creates an error if we decide to use chars later
|
let _ = chars; // suppresses unused warnings, creates an error if we decide to use chars later
|
||||||
|
|
||||||
let verb_ref = verb.as_ref().map(|v| &v.1);
|
let verb_ref = verb.as_ref().map(|v| &v.1);
|
||||||
let motion_ref = motion.as_ref().map(|m| &m.1);
|
let motion_ref = motion.as_ref().map(|m| &m.1);
|
||||||
@@ -756,28 +756,32 @@ impl ViMode for ViNormal {
|
|||||||
raw_seq: "".into(),
|
raw_seq: "".into(),
|
||||||
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
|
||||||
self.pending_seq.clear();
|
.parse_count(&mut self.pending_seq.chars().peekable())
|
||||||
Some(ViCmd {
|
.unwrap_or(1) as u16;
|
||||||
register: Default::default(),
|
self.pending_seq.clear();
|
||||||
verb: Some(VerbCmd(1, Verb::IncrementNumber(count))),
|
Some(ViCmd {
|
||||||
motion: None,
|
register: Default::default(),
|
||||||
raw_seq: "".into(),
|
verb: Some(VerbCmd(1, Verb::IncrementNumber(count))),
|
||||||
flags: self.flags(),
|
motion: None,
|
||||||
})
|
raw_seq: "".into(),
|
||||||
},
|
flags: self.flags(),
|
||||||
E(K::Char('X'), M::CTRL) => {
|
})
|
||||||
let count = self.parse_count(&mut self.pending_seq.chars().peekable()).unwrap_or(1) as u16;
|
}
|
||||||
self.pending_seq.clear();
|
E(K::Char('X'), M::CTRL) => {
|
||||||
Some(ViCmd {
|
let count = self
|
||||||
register: Default::default(),
|
.parse_count(&mut self.pending_seq.chars().peekable())
|
||||||
verb: Some(VerbCmd(1, Verb::DecrementNumber(count))),
|
.unwrap_or(1) as u16;
|
||||||
motion: None,
|
self.pending_seq.clear();
|
||||||
raw_seq: "".into(),
|
Some(ViCmd {
|
||||||
flags: self.flags(),
|
register: Default::default(),
|
||||||
})
|
verb: Some(VerbCmd(1, Verb::DecrementNumber(count))),
|
||||||
},
|
motion: None,
|
||||||
|
raw_seq: "".into(),
|
||||||
|
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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,39 +1,40 @@
|
|||||||
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 {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
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(),
|
||||||
motion: None,
|
verb: Some(VerbCmd(1, Verb::Insert(seq.to_string()))),
|
||||||
raw_seq: seq.to_string(),
|
motion: None,
|
||||||
flags: CmdFlags::EXIT_CUR_MODE
|
raw_seq: seq.to_string(),
|
||||||
};
|
flags: CmdFlags::EXIT_CUR_MODE,
|
||||||
self.sent_cmd.push(cmd.clone());
|
};
|
||||||
Some(cmd)
|
self.sent_cmd.push(cmd.clone());
|
||||||
}
|
Some(cmd)
|
||||||
|
}
|
||||||
_ => common_cmds(key),
|
_ => common_cmds(key),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -129,15 +129,15 @@ impl ViVisual {
|
|||||||
flags: CmdFlags::empty(),
|
flags: CmdFlags::empty(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
':' => {
|
':' => {
|
||||||
return Some(ViCmd {
|
return Some(ViCmd {
|
||||||
register,
|
register,
|
||||||
verb: Some(VerbCmd(count, Verb::ExMode)),
|
verb: Some(VerbCmd(count, Verb::ExMode)),
|
||||||
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;
|
||||||
break 'verb_parse Some(VerbCmd(count, Verb::Delete));
|
break 'verb_parse Some(VerbCmd(count, Verb::Delete));
|
||||||
@@ -581,7 +581,7 @@ impl ViVisual {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = chars; // suppresses unused warnings, creates an error if we decide to use chars later
|
let _ = chars; // suppresses unused warnings, creates an error if we decide to use chars later
|
||||||
|
|
||||||
let verb_ref = verb.as_ref().map(|v| &v.1);
|
let verb_ref = verb.as_ref().map(|v| &v.1);
|
||||||
let motion_ref = motion.as_ref().map(|m| &m.1);
|
let motion_ref = motion.as_ref().map(|m| &m.1);
|
||||||
@@ -614,28 +614,32 @@ impl ViMode for ViVisual {
|
|||||||
raw_seq: "".into(),
|
raw_seq: "".into(),
|
||||||
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
|
||||||
self.pending_seq.clear();
|
.parse_count(&mut self.pending_seq.chars().peekable())
|
||||||
Some(ViCmd {
|
.unwrap_or(1) as u16;
|
||||||
register: Default::default(),
|
self.pending_seq.clear();
|
||||||
verb: Some(VerbCmd(1, Verb::IncrementNumber(count))),
|
Some(ViCmd {
|
||||||
motion: None,
|
register: Default::default(),
|
||||||
raw_seq: "".into(),
|
verb: Some(VerbCmd(1, Verb::IncrementNumber(count))),
|
||||||
flags: CmdFlags::empty(),
|
motion: None,
|
||||||
})
|
raw_seq: "".into(),
|
||||||
},
|
flags: CmdFlags::empty(),
|
||||||
E(K::Char('X'), M::CTRL) => {
|
})
|
||||||
let count = self.parse_count(&mut self.pending_seq.chars().peekable()).unwrap_or(1) as u16;
|
}
|
||||||
self.pending_seq.clear();
|
E(K::Char('X'), M::CTRL) => {
|
||||||
Some(ViCmd {
|
let count = self
|
||||||
register: Default::default(),
|
.parse_count(&mut self.pending_seq.chars().peekable())
|
||||||
verb: Some(VerbCmd(1, Verb::DecrementNumber(count))),
|
.unwrap_or(1) as u16;
|
||||||
motion: None,
|
self.pending_seq.clear();
|
||||||
raw_seq: "".into(),
|
Some(ViCmd {
|
||||||
flags: CmdFlags::empty(),
|
register: Default::default(),
|
||||||
})
|
verb: Some(VerbCmd(1, Verb::DecrementNumber(count))),
|
||||||
}
|
motion: None,
|
||||||
|
raw_seq: "".into(),
|
||||||
|
flags: CmdFlags::empty(),
|
||||||
|
})
|
||||||
|
}
|
||||||
E(K::Char('R'), M::CTRL) => {
|
E(K::Char('R'), M::CTRL) => {
|
||||||
let mut chars = self.pending_seq.chars().peekable();
|
let mut chars = self.pending_seq.chars().peekable();
|
||||||
let count = self.parse_count(&mut chars).unwrap_or(1);
|
let count = self.parse_count(&mut chars).unwrap_or(1);
|
||||||
|
|||||||
80
src/shopt.rs
80
src/shopt.rs
@@ -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");
|
||||||
ShErrKind::SyntaxErr,
|
output.push_str(&format!("{}", self.line_numbers));
|
||||||
format!("shopt: Unexpected 'prompt' option '{query}'"),
|
Ok(Some(output))
|
||||||
)
|
}
|
||||||
),
|
_ => Err(ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,11 +148,10 @@ 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();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset all signal dispositions to SIG_DFL.
|
/// Reset all signal dispositions to SIG_DFL.
|
||||||
@@ -307,29 +306,30 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
|||||||
{
|
{
|
||||||
if is_fg {
|
if is_fg {
|
||||||
take_term()?;
|
take_term()?;
|
||||||
} else {
|
} else {
|
||||||
JOB_DONE.store(true, Ordering::SeqCst);
|
JOB_DONE.store(true, Ordering::SeqCst);
|
||||||
let job_order = read_jobs(|j| j.order().to_vec());
|
let job_order = read_jobs(|j| j.order().to_vec());
|
||||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||||
if let Some(job) = result {
|
if let Some(job) = result {
|
||||||
let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string();
|
let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string();
|
||||||
|
|
||||||
let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish));
|
let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish));
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write_meta(|m| m.post_system_message(job_complete_msg))
|
write_meta(|m| m.post_system_message(job_complete_msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
639
src/state.rs
639
src/state.rs
@@ -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 {
|
||||||
@@ -71,18 +87,18 @@ impl ShellParam {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_char(c: &char) -> Option<Self> {
|
pub fn from_char(c: &char) -> Option<Self> {
|
||||||
match c {
|
match c {
|
||||||
'?' => Some(Self::Status),
|
'?' => Some(Self::Status),
|
||||||
'$' => Some(Self::ShPid),
|
'$' => Some(Self::ShPid),
|
||||||
'!' => Some(Self::LastJob),
|
'!' => Some(Self::LastJob),
|
||||||
'0' => Some(Self::ShellName),
|
'0' => Some(Self::ShellName),
|
||||||
'@' => Some(Self::AllArgs),
|
'@' => Some(Self::AllArgs),
|
||||||
'*' => Some(Self::AllArgsStr),
|
'*' => Some(Self::AllArgsStr),
|
||||||
'#' => Some(Self::ArgCount),
|
'#' => Some(Self::ArgCount),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShellParam {
|
impl Display for ShellParam {
|
||||||
@@ -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(
|
_ => {
|
||||||
ShErrKind::ExecFail,
|
return Err(ShErr::simple(
|
||||||
format!("Variable '{}' is not an array", var_name),
|
ShErrKind::ExecFail,
|
||||||
)),
|
format!("Variable '{}' is not an array", var_name),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,38 +376,37 @@ impl ScopeStack {
|
|||||||
}
|
}
|
||||||
Ok("".into())
|
Ok("".into())
|
||||||
}
|
}
|
||||||
pub fn remove_map(&mut self, map_name: &str) -> Option<MapNode> {
|
pub fn remove_map(&mut self, map_name: &str) -> Option<MapNode> {
|
||||||
for scope in self.scopes.iter_mut().rev() {
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
if scope.get_map(map_name).is_some() {
|
if scope.get_map(map_name).is_some() {
|
||||||
return scope.remove_map(map_name);
|
return scope.remove_map(map_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
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);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
||||||
// This version of get_var() is mainly used internally
|
// This version of get_var() is mainly used internally
|
||||||
// so that we have access to Option methods
|
// so that we have access to Option methods
|
||||||
@@ -410,11 +427,11 @@ impl ScopeStack {
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
pub fn take_var(&mut self, var_name: &str) -> String {
|
pub fn take_var(&mut self, var_name: &str) -> String {
|
||||||
let var = self.get_var(var_name);
|
let var = self.get_var(var_name);
|
||||||
self.unset_var(var_name).ok();
|
self.unset_var(var_name).ok();
|
||||||
var
|
var
|
||||||
}
|
}
|
||||||
pub fn get_var(&self, var_name: &str) -> String {
|
pub fn get_var(&self, var_name: &str) -> String {
|
||||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
return self.get_param(param);
|
return self.get_param(param);
|
||||||
@@ -480,14 +497,14 @@ 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 {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.body)
|
write!(f, "{}", self.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A shell function
|
/// A shell function
|
||||||
@@ -495,14 +512,14 @@ impl Display for ShAlias {
|
|||||||
/// Wraps the BraceGrp Node that forms the body of the function, and provides some helper methods to extract it from the parse tree
|
/// Wraps the BraceGrp Node that forms the body of the function, and provides some helper methods to extract it from the parse tree
|
||||||
#[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
|
||||||
@@ -524,61 +541,61 @@ impl ShFunc {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub enum AutoCmdKind {
|
pub enum AutoCmdKind {
|
||||||
PreCmd,
|
PreCmd,
|
||||||
PostCmd,
|
PostCmd,
|
||||||
PreChangeDir,
|
PreChangeDir,
|
||||||
PostChangeDir,
|
PostChangeDir,
|
||||||
OnJobFinish,
|
OnJobFinish,
|
||||||
PrePrompt,
|
PrePrompt,
|
||||||
PostPrompt,
|
PostPrompt,
|
||||||
PreModeChange,
|
PreModeChange,
|
||||||
PostModeChange,
|
PostModeChange,
|
||||||
OnExit
|
OnExit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for AutoCmdKind {
|
impl Display for AutoCmdKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::PreCmd => write!(f, "pre-cmd"),
|
Self::PreCmd => write!(f, "pre-cmd"),
|
||||||
Self::PostCmd => write!(f, "post-cmd"),
|
Self::PostCmd => write!(f, "post-cmd"),
|
||||||
Self::PreChangeDir => write!(f, "pre-change-dir"),
|
Self::PreChangeDir => write!(f, "pre-change-dir"),
|
||||||
Self::PostChangeDir => write!(f, "post-change-dir"),
|
Self::PostChangeDir => write!(f, "post-change-dir"),
|
||||||
Self::OnJobFinish => write!(f, "on-job-finish"),
|
Self::OnJobFinish => write!(f, "on-job-finish"),
|
||||||
Self::PrePrompt => write!(f, "pre-prompt"),
|
Self::PrePrompt => write!(f, "pre-prompt"),
|
||||||
Self::PostPrompt => write!(f, "post-prompt"),
|
Self::PostPrompt => write!(f, "post-prompt"),
|
||||||
Self::PreModeChange => write!(f, "pre-mode-change"),
|
Self::PreModeChange => write!(f, "pre-mode-change"),
|
||||||
Self::PostModeChange => write!(f, "post-mode-change"),
|
Self::PostModeChange => write!(f, "post-mode-change"),
|
||||||
Self::OnExit => write!(f, "on-exit"),
|
Self::OnExit => write!(f, "on-exit"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for AutoCmdKind {
|
impl FromStr for AutoCmdKind {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"pre-cmd" => Ok(Self::PreCmd),
|
"pre-cmd" => Ok(Self::PreCmd),
|
||||||
"post-cmd" => Ok(Self::PostCmd),
|
"post-cmd" => Ok(Self::PostCmd),
|
||||||
"pre-change-dir" => Ok(Self::PreChangeDir),
|
"pre-change-dir" => Ok(Self::PreChangeDir),
|
||||||
"post-change-dir" => Ok(Self::PostChangeDir),
|
"post-change-dir" => Ok(Self::PostChangeDir),
|
||||||
"on-job-finish" => Ok(Self::OnJobFinish),
|
"on-job-finish" => Ok(Self::OnJobFinish),
|
||||||
"pre-prompt" => Ok(Self::PrePrompt),
|
"pre-prompt" => Ok(Self::PrePrompt),
|
||||||
"post-prompt" => Ok(Self::PostPrompt),
|
"post-prompt" => Ok(Self::PostPrompt),
|
||||||
"pre-mode-change" => Ok(Self::PreModeChange),
|
"pre-mode-change" => Ok(Self::PreModeChange),
|
||||||
"post-mode-change" => Ok(Self::PostModeChange),
|
"post-mode-change" => Ok(Self::PostModeChange),
|
||||||
"on-exit" => Ok(Self::OnExit),
|
"on-exit" => Ok(Self::OnExit),
|
||||||
_ => Err(ShErr::simple(
|
_ => Err(ShErr::simple(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
format!("Invalid autocmd kind: {}", s),
|
format!("Invalid autocmd kind: {}", s),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AutoCmd {
|
pub struct AutoCmd {
|
||||||
pub pattern: Option<Regex>,
|
pub pattern: Option<Regex>,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logic table for the shell
|
/// The logic table for the shell
|
||||||
@@ -589,58 +606,59 @@ pub struct LogTab {
|
|||||||
functions: HashMap<String, ShFunc>,
|
functions: HashMap<String, ShFunc>,
|
||||||
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 {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn autocmds(&self) -> &HashMap<AutoCmdKind, Vec<AutoCmd>> {
|
pub fn autocmds(&self) -> &HashMap<AutoCmdKind, Vec<AutoCmd>> {
|
||||||
&self.autocmds
|
&self.autocmds
|
||||||
}
|
}
|
||||||
pub fn autocmds_mut(&mut self) -> &mut HashMap<AutoCmdKind, Vec<AutoCmd>> {
|
pub fn autocmds_mut(&mut self) -> &mut HashMap<AutoCmdKind, Vec<AutoCmd>> {
|
||||||
&mut self.autocmds
|
&mut self.autocmds
|
||||||
}
|
}
|
||||||
pub fn insert_autocmd(&mut self, kind: AutoCmdKind, cmd: AutoCmd) {
|
pub fn insert_autocmd(&mut self, kind: AutoCmdKind, cmd: AutoCmd) {
|
||||||
self.autocmds.entry(kind).or_default().push(cmd);
|
self.autocmds.entry(kind).or_default().push(cmd);
|
||||||
}
|
}
|
||||||
pub fn get_autocmds(&self, kind: AutoCmdKind) -> Vec<AutoCmd> {
|
pub fn get_autocmds(&self, kind: AutoCmdKind) -> Vec<AutoCmd> {
|
||||||
self.autocmds.get(&kind).cloned().unwrap_or_default()
|
self.autocmds.get(&kind).cloned().unwrap_or_default()
|
||||||
}
|
}
|
||||||
pub fn clear_autocmds(&mut self, kind: AutoCmdKind) {
|
pub fn clear_autocmds(&mut self, kind: AutoCmdKind) {
|
||||||
self.autocmds.remove(&kind);
|
self.autocmds.remove(&kind);
|
||||||
}
|
}
|
||||||
pub fn keymaps(&self) -> &Vec<KeyMap> {
|
pub fn keymaps(&self) -> &Vec<KeyMap> {
|
||||||
&self.keymaps
|
&self.keymaps
|
||||||
}
|
}
|
||||||
pub fn keymaps_mut(&mut self) -> &mut Vec<KeyMap> {
|
pub fn keymaps_mut(&mut self) -> &mut Vec<KeyMap> {
|
||||||
&mut self.keymaps
|
&mut self.keymaps
|
||||||
}
|
}
|
||||||
pub fn insert_keymap(&mut self, keymap: KeyMap) {
|
pub fn insert_keymap(&mut self, keymap: KeyMap) {
|
||||||
let mut found_dup = false;
|
let mut found_dup = false;
|
||||||
for map in self.keymaps.iter_mut() {
|
for map in self.keymaps.iter_mut() {
|
||||||
if map.keys == keymap.keys {
|
if map.keys == keymap.keys {
|
||||||
*map = keymap.clone();
|
*map = keymap.clone();
|
||||||
found_dup = true;
|
found_dup = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found_dup {
|
if !found_dup {
|
||||||
self.keymaps.push(keymap);
|
self.keymaps.push(keymap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn remove_keymap(&mut self, keys: &str) {
|
pub fn remove_keymap(&mut self, keys: &str) {
|
||||||
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
|
||||||
.iter()
|
.keymaps
|
||||||
.filter(|km| km.flags.intersects(flags) && km.compare(pending) != KeyMapMatch::NoMatch)
|
.iter()
|
||||||
.cloned()
|
.filter(|km| km.flags.intersects(flags) && km.compare(pending) != KeyMapMatch::NoMatch)
|
||||||
.collect()
|
.cloned()
|
||||||
}
|
.collect()
|
||||||
|
}
|
||||||
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
||||||
self.functions.insert(name.into(), src);
|
self.functions.insert(name.into(), src);
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
@@ -751,7 +775,7 @@ impl VarFlags {
|
|||||||
pub enum ArrIndex {
|
pub enum ArrIndex {
|
||||||
Literal(usize),
|
Literal(usize),
|
||||||
FromBack(usize),
|
FromBack(usize),
|
||||||
ArgCount,
|
ArgCount,
|
||||||
AllJoined,
|
AllJoined,
|
||||||
AllSplit,
|
AllSplit,
|
||||||
}
|
}
|
||||||
@@ -762,7 +786,7 @@ impl FromStr for ArrIndex {
|
|||||||
match s {
|
match s {
|
||||||
"@" => Ok(Self::AllSplit),
|
"@" => Ok(Self::AllSplit),
|
||||||
"*" => Ok(Self::AllJoined),
|
"*" => Ok(Self::AllJoined),
|
||||||
"#" => Ok(Self::ArgCount),
|
"#" => Ok(Self::ArgCount),
|
||||||
_ if s.starts_with('-') && s[1..].chars().all(|c| c.is_digit(1)) => {
|
_ if s.starts_with('-') && s[1..].chars().all(|c| c.is_digit(1)) => {
|
||||||
let idx = s[1..].parse::<usize>().unwrap();
|
let idx = s[1..].parse::<usize>().unwrap();
|
||||||
Ok(Self::FromBack(idx))
|
Ok(Self::FromBack(idx))
|
||||||
@@ -879,15 +903,15 @@ impl Display for Var {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Var {
|
impl From<String> for Var {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
Self::new(VarKind::Str(value), VarFlags::NONE)
|
Self::new(VarKind::Str(value), VarFlags::NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Var {
|
impl From<&str> for Var {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
Self::new(VarKind::Str(value.into()), VarFlags::NONE)
|
Self::new(VarKind::Str(value.into()), VarFlags::NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
@@ -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 {
|
||||||
@@ -908,7 +932,7 @@ impl VarTab {
|
|||||||
vars,
|
vars,
|
||||||
params,
|
params,
|
||||||
sh_argv: VecDeque::new(),
|
sh_argv: VecDeque::new(),
|
||||||
maps: HashMap::new(),
|
maps: HashMap::new(),
|
||||||
};
|
};
|
||||||
var_tab.init_sh_argv();
|
var_tab.init_sh_argv();
|
||||||
var_tab
|
var_tab
|
||||||
@@ -1027,24 +1051,24 @@ impl VarTab {
|
|||||||
self.update_arg_params();
|
self.update_arg_params();
|
||||||
arg
|
arg
|
||||||
}
|
}
|
||||||
pub fn set_map(&mut self, map_name: &str, map: MapNode) {
|
pub fn set_map(&mut self, map_name: &str, map: MapNode) {
|
||||||
self.maps.insert(map_name.to_string(), map);
|
self.maps.insert(map_name.to_string(), map);
|
||||||
}
|
}
|
||||||
pub fn remove_map(&mut self, map_name: &str) -> Option<MapNode> {
|
pub fn remove_map(&mut self, map_name: &str) -> Option<MapNode> {
|
||||||
self.maps.remove(map_name)
|
self.maps.remove(map_name)
|
||||||
}
|
}
|
||||||
pub fn get_map(&self, map_name: &str) -> Option<&MapNode> {
|
pub fn get_map(&self, map_name: &str) -> Option<&MapNode> {
|
||||||
self.maps.get(map_name)
|
self.maps.get(map_name)
|
||||||
}
|
}
|
||||||
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> {
|
||||||
self.maps.get_mut(map_name)
|
self.maps.get_mut(map_name)
|
||||||
}
|
}
|
||||||
pub fn maps(&self) -> &HashMap<String, MapNode> {
|
pub fn maps(&self) -> &HashMap<String, MapNode> {
|
||||||
&self.maps
|
&self.maps
|
||||||
}
|
}
|
||||||
pub fn maps_mut(&mut self) -> &mut HashMap<String, MapNode> {
|
pub fn maps_mut(&mut self) -> &mut HashMap<String, MapNode> {
|
||||||
&mut self.maps
|
&mut self.maps
|
||||||
}
|
}
|
||||||
pub fn vars(&self) -> &HashMap<String, Var> {
|
pub fn vars(&self) -> &HashMap<String, Var> {
|
||||||
&self.vars
|
&self.vars
|
||||||
}
|
}
|
||||||
@@ -1160,9 +1184,9 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn map_exists(&self, map_name: &str) -> bool {
|
pub fn map_exists(&self, map_name: &str) -> bool {
|
||||||
self.maps.contains_key(map_name)
|
self.maps.contains_key(map_name)
|
||||||
}
|
}
|
||||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
return self.params.contains_key(¶m);
|
return self.params.contains_key(¶m);
|
||||||
@@ -1205,8 +1229,8 @@ pub struct MetaTab {
|
|||||||
|
|
||||||
// pushd/popd stack
|
// pushd/popd stack
|
||||||
dir_stack: VecDeque<PathBuf>,
|
dir_stack: VecDeque<PathBuf>,
|
||||||
// getopts char offset for opts like -abc
|
// getopts char offset for opts like -abc
|
||||||
getopts_offset: usize,
|
getopts_offset: usize,
|
||||||
|
|
||||||
old_path: Option<String>,
|
old_path: Option<String>,
|
||||||
old_pwd: Option<String>,
|
old_pwd: Option<String>,
|
||||||
@@ -1216,8 +1240,8 @@ pub struct MetaTab {
|
|||||||
// programmable completion specs
|
// programmable completion specs
|
||||||
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 {
|
||||||
@@ -1227,28 +1251,28 @@ impl MetaTab {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_pending_widget_keys(&mut self, keys: &str) {
|
pub fn set_pending_widget_keys(&mut self, keys: &str) {
|
||||||
let exp = expand_keymap(keys);
|
let exp = expand_keymap(keys);
|
||||||
self.pending_widget_keys = exp;
|
self.pending_widget_keys = exp;
|
||||||
}
|
}
|
||||||
pub fn take_pending_widget_keys(&mut self) -> Option<Vec<KeyEvent>> {
|
pub fn take_pending_widget_keys(&mut self) -> Option<Vec<KeyEvent>> {
|
||||||
if self.pending_widget_keys.is_empty() {
|
if self.pending_widget_keys.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(std::mem::take(&mut self.pending_widget_keys))
|
Some(std::mem::take(&mut self.pending_widget_keys))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn getopts_char_offset(&self) -> usize {
|
pub fn getopts_char_offset(&self) -> usize {
|
||||||
self.getopts_offset
|
self.getopts_offset
|
||||||
}
|
}
|
||||||
pub fn inc_getopts_char_offset(&mut self) -> usize {
|
pub fn inc_getopts_char_offset(&mut self) -> usize {
|
||||||
let offset = self.getopts_offset;
|
let offset = self.getopts_offset;
|
||||||
self.getopts_offset += 1;
|
self.getopts_offset += 1;
|
||||||
offset
|
offset
|
||||||
}
|
}
|
||||||
pub fn reset_getopts_char_offset(&mut self) {
|
pub fn reset_getopts_char_offset(&mut self) {
|
||||||
self.getopts_offset = 0;
|
self.getopts_offset = 0;
|
||||||
}
|
}
|
||||||
pub fn get_builtin_comp_specs() -> HashMap<String, Box<dyn CompSpec>> {
|
pub fn get_builtin_comp_specs() -> HashMap<String, Box<dyn CompSpec>> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
@@ -1280,14 +1304,14 @@ impl MetaTab {
|
|||||||
"disown".into(),
|
"disown".into(),
|
||||||
Box::new(BashCompSpec::new().jobs(true)) as Box<dyn CompSpec>,
|
Box::new(BashCompSpec::new().jobs(true)) as Box<dyn CompSpec>,
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
"alias".into(),
|
"alias".into(),
|
||||||
Box::new(BashCompSpec::new().aliases(true)) as Box<dyn CompSpec>,
|
Box::new(BashCompSpec::new().aliases(true)) as Box<dyn CompSpec>,
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
"trap".into(),
|
"trap".into(),
|
||||||
Box::new(BashCompSpec::new().signals(true)) as Box<dyn CompSpec>,
|
Box::new(BashCompSpec::new().signals(true)) as Box<dyn CompSpec>,
|
||||||
);
|
);
|
||||||
|
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
@@ -1312,29 +1336,29 @@ impl MetaTab {
|
|||||||
pub fn remove_comp_spec(&mut self, cmd: &str) -> bool {
|
pub fn remove_comp_spec(&mut self, cmd: &str) -> bool {
|
||||||
self.comp_specs.remove(cmd).is_some()
|
self.comp_specs.remove(cmd).is_some()
|
||||||
}
|
}
|
||||||
pub fn get_cmds_in_path() -> Vec<String> {
|
pub fn get_cmds_in_path() -> Vec<String> {
|
||||||
let path = env::var("PATH").unwrap_or_default();
|
let path = env::var("PATH").unwrap_or_default();
|
||||||
let paths = path.split(":").map(PathBuf::from);
|
let paths = path.split(":").map(PathBuf::from);
|
||||||
let mut cmds = vec![];
|
let mut cmds = vec![];
|
||||||
for path in paths {
|
for path in paths {
|
||||||
if let Ok(entries) = path.read_dir() {
|
if let Ok(entries) = path.read_dir() {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
cmds.push(name.to_string());
|
cmds.push(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmds
|
cmds
|
||||||
}
|
}
|
||||||
pub fn try_rehash_commands(&mut self) {
|
pub fn try_rehash_commands(&mut self) {
|
||||||
let path = env::var("PATH").unwrap_or_default();
|
let path = env::var("PATH").unwrap_or_default();
|
||||||
let cwd = env::var("PWD").unwrap_or_default();
|
let cwd = env::var("PWD").unwrap_or_default();
|
||||||
@@ -1350,10 +1374,10 @@ impl MetaTab {
|
|||||||
self.path_cache.clear();
|
self.path_cache.clear();
|
||||||
self.old_path = Some(path.clone());
|
self.old_path = Some(path.clone());
|
||||||
self.old_pwd = Some(cwd.clone());
|
self.old_pwd = Some(cwd.clone());
|
||||||
let cmds_in_path = Self::get_cmds_in_path();
|
let cmds_in_path = Self::get_cmds_in_path();
|
||||||
for cmd in cmds_in_path {
|
for cmd in cmds_in_path {
|
||||||
self.path_cache.insert(cmd);
|
self.path_cache.insert(cmd);
|
||||||
}
|
}
|
||||||
if let Ok(entries) = Path::new(&cwd).read_dir() {
|
if let Ok(entries) = Path::new(&cwd).read_dir() {
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||||
@@ -1589,63 +1613,86 @@ 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 {
|
||||||
let val = val.into();
|
let val = val.into();
|
||||||
write_vars(|v| v.set_var(&name, val.kind, val.flags).unwrap());
|
write_vars(|v| v.set_var(&name, val.kind, val.flags).unwrap());
|
||||||
}
|
}
|
||||||
let _guard = scopeguard::guard(snapshot, |snap| {
|
let _guard = scopeguard::guard(snapshot, |snap| {
|
||||||
write_vars(|v| *v = snap);
|
write_vars(|v| *v = snap);
|
||||||
});
|
});
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_dir<P: AsRef<Path>>(dir: P) -> ShResult<()> {
|
pub fn change_dir<P: AsRef<Path>>(dir: P) -> ShResult<()> {
|
||||||
let dir = dir.as_ref();
|
let dir = dir.as_ref();
|
||||||
let dir_raw = &dir.display().to_string();
|
let dir_raw = &dir.display().to_string();
|
||||||
let pre_cd = read_logic(|l| l.get_autocmds(AutoCmdKind::PreChangeDir));
|
let pre_cd = read_logic(|l| l.get_autocmds(AutoCmdKind::PreChangeDir));
|
||||||
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(
|
||||||
for cmd in pre_cd {
|
[
|
||||||
let AutoCmd { command, pattern } = cmd;
|
("_NEW_DIR".into(), dir_raw.as_str()),
|
||||||
if let Some(pat) = pattern
|
("_OLD_DIR".into(), current_dir.as_str()),
|
||||||
&& !pat.is_match(dir_raw) {
|
],
|
||||||
continue;
|
|| {
|
||||||
}
|
for cmd in pre_cd {
|
||||||
|
let AutoCmd { command, pattern } = cmd;
|
||||||
|
if let Some(pat) = pattern
|
||||||
|
&& !pat.is_match(dir_raw)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd (pre-changedir)".to_string())) {
|
if let Err(e) = exec_input(
|
||||||
e.print_error();
|
command.clone(),
|
||||||
};
|
None,
|
||||||
}
|
false,
|
||||||
});
|
Some("autocmd (pre-changedir)".to_string()),
|
||||||
|
) {
|
||||||
|
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()),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
for cmd in post_cd {
|
||||||
|
let AutoCmd { command, pattern } = cmd;
|
||||||
|
if let Some(pat) = pattern
|
||||||
|
&& !pat.is_match(dir_raw)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
with_vars([("_NEW_DIR".into(), dir_raw.as_str()), ("_OLD_DIR".into(), current_dir.as_str())], || {
|
if let Err(e) = exec_input(
|
||||||
for cmd in post_cd {
|
command.clone(),
|
||||||
let AutoCmd { command, pattern } = cmd;
|
None,
|
||||||
if let Some(pat) = pattern
|
false,
|
||||||
&& !pat.is_match(dir_raw) {
|
Some("autocmd (post-changedir)".to_string()),
|
||||||
continue;
|
) {
|
||||||
}
|
e.print_error();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd (post-changedir)".to_string())) {
|
Ok(())
|
||||||
e.print_error();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status() -> i32 {
|
pub fn get_status() -> i32 {
|
||||||
@@ -1671,7 +1718,7 @@ pub fn source_rc() -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||||
let source_name = path.to_string_lossy().to_string();
|
let source_name = path.to_string_lossy().to_string();
|
||||||
let mut file = OpenOptions::new().read(true).open(path)?;
|
let mut file = OpenOptions::new().read(true).open(path)?;
|
||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user