Add array support for local/export/readonly builtins
Add array length syntax ${arr[#]}
Map read path now expands variables before splitting on ., fixing map "$node" with dotted paths
Map assignment path uses quote-aware token splitting, enabling quoted keys like "--type="
Completion errors now display above prompt instead of being overwritten
Fix nested if/fi parser bug when closing keywords appear on separate lines
Add QuoteState enum, replacing ad-hoc quote tracking booleans across lexer, highlighter, and expansion
Add split_tk_at/split_tk for quote-aware token splitting with span preservation
Refactor setup_builtin to accept optional argv for deferred expansion
Add ariadne dependency (not yet wired up)
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -61,6 +61,16 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ariadne"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8454c8a44ce2cb9cc7e7fae67fc6128465b343b92c6631e94beca3c8d1524ea5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -451,6 +461,7 @@ dependencies = [
|
|||||||
name = "shed"
|
name = "shed"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ariadne",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ edition = "2024"
|
|||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ariadne = "0.6.0"
|
||||||
bitflags = "2.8.0"
|
bitflags = "2.8.0"
|
||||||
clap = { version = "4.5.38", features = ["derive"] }
|
clap = { version = "4.5.38", features = ["derive"] }
|
||||||
env_logger = "0.11.9"
|
env_logger = "0.11.9"
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -67,7 +68,8 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::iter::Peekable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, write_vars}
|
getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state::{self, VarFlags, VarKind, write_vars}
|
||||||
};
|
};
|
||||||
@@ -51,7 +53,8 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
|
|||||||
|
|
||||||
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 (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
let mut status = 0;
|
let mut status = 0;
|
||||||
|
|
||||||
@@ -91,7 +94,8 @@ fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: En
|
|||||||
|
|
||||||
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 (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
let Some((name, _)) = argv.next() else {
|
let Some((name, _)) = argv.next() else {
|
||||||
@@ -140,7 +144,8 @@ pub fn arr_rotate(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShRe
|
|||||||
|
|
||||||
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 (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
for (arg, _) in argv {
|
for (arg, _) in argv {
|
||||||
write_vars(|v| -> ShResult<()> {
|
write_vars(|v| -> ShResult<()> {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let new_dir = if let Some((arg, _)) = argv.into_iter().next() {
|
let new_dir = if let Some((arg, _)) = argv.into_iter().next() {
|
||||||
PathBuf::from(arg)
|
PathBuf::from(arg)
|
||||||
|
|||||||
@@ -168,7 +168,8 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
|
|||||||
|
|
||||||
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 (argv, _) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if comp_opts.flags.contains(CompFlags::PRINT) {
|
if comp_opts.flags.contains(CompFlags::PRINT) {
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
@@ -242,7 +243,7 @@ pub fn compgen_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) ->
|
|||||||
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
|
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
|
||||||
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
|
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
|
||||||
let comp_opts = get_comp_opts(opts)?;
|
let comp_opts = get_comp_opts(opts)?;
|
||||||
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,8 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut dir = None;
|
let mut dir = None;
|
||||||
let mut rotate_idx = None;
|
let mut rotate_idx = None;
|
||||||
@@ -213,7 +214,8 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut remove_idx = None;
|
let mut remove_idx = None;
|
||||||
let mut no_cd = false;
|
let mut no_cd = false;
|
||||||
@@ -316,7 +318,8 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut abbreviate_home = true;
|
let mut abbreviate_home = true;
|
||||||
let mut one_per_line = false;
|
let mut one_per_line = false;
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
assert!(!argv.is_empty());
|
assert!(!argv.is_empty());
|
||||||
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 (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expanded_argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (expanded_argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let expanded_argv = expanded_argv.unwrap();
|
||||||
|
|
||||||
if expanded_argv.is_empty() {
|
if expanded_argv.is_empty() {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expanded_argv, guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (expanded_argv, guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let expanded_argv = expanded_argv.unwrap();
|
||||||
if let Some(g) = guard {
|
if let Some(g) = guard {
|
||||||
// Persist redirections so they affect the entire shell,
|
// Persist redirections so they affect the entire shell,
|
||||||
// not just this command call
|
// not just this command call
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
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()) {
|
||||||
@@ -143,7 +144,8 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
let mut flags = JobCmdFlags::empty();
|
let mut flags = JobCmdFlags::empty();
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
@@ -190,7 +192,8 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
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()) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_all_unescaped, split_at_unescaped}}, procio::{IoStack, borrow_fd}, state::{self, read_vars, write_vars}
|
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_tk, split_tk_at}}, procio::{IoStack, borrow_fd}, state::{self, read_vars, write_vars}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -252,17 +252,23 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (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);
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
if !argv.is_empty() {
|
||||||
|
argv.remove(0); // remove "map" command from argv
|
||||||
|
}
|
||||||
|
|
||||||
for (arg,_) in argv {
|
for arg in argv {
|
||||||
if let Some((lhs,rhs)) = split_at_unescaped(&arg, "=") {
|
if let Some((lhs,rhs)) = split_tk_at(&arg, "=") {
|
||||||
let path = split_all_unescaped(&lhs, ".");
|
let path = split_tk(&lhs, ".")
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.expand().map(|exp| exp.get_words().join(" ")))
|
||||||
|
.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)
|
format!("invalid map path: {}", lhs.as_str())
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,39 +277,42 @@ pub fn map(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
let make_leaf = |s: String| {
|
let make_leaf = |s: String| {
|
||||||
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) }
|
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) }
|
||||||
};
|
};
|
||||||
let found = write_vars(|v| {
|
let expanded = rhs.expand()?.get_words().join(" ");
|
||||||
|
let found = write_vars(|v| -> ShResult<bool> {
|
||||||
if let Some(map) = v.get_map_mut(name) {
|
if let Some(map) = v.get_map_mut(name) {
|
||||||
if is_json {
|
if is_json {
|
||||||
if let Ok(parsed) = serde_json::from_str::<Value>(&rhs) {
|
if let Ok(parsed) = serde_json::from_str::<Value>(expanded.as_str()) {
|
||||||
map.set(&path[1..], parsed.into());
|
map.set(&path[1..], parsed.into());
|
||||||
} else {
|
} else {
|
||||||
map.set(&path[1..], make_leaf(rhs.clone()));
|
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
map.set(&path[1..], make_leaf(rhs.clone()));
|
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||||
}
|
}
|
||||||
true
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
false
|
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) {
|
if is_json /*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */{
|
||||||
|
let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
|
||||||
let node: MapNode = parsed.into();
|
let node: MapNode = parsed.into();
|
||||||
new.set(&path[1..], node);
|
new.set(&path[1..], node);
|
||||||
} else {
|
} else {
|
||||||
new.set(&path[1..], make_leaf(rhs));
|
new.set(&path[1..], make_leaf(expanded));
|
||||||
}
|
}
|
||||||
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let path = split_all_unescaped(&arg, ".");
|
let expanded = arg.expand()?.get_words().join(" ");
|
||||||
|
let path: Vec<String> = expanded.split('.').map(|s| s.to_string()).collect();
|
||||||
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: {}", &arg)
|
format!("invalid map path: {}", expanded)
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,13 +67,13 @@ pub const BUILTINS: [&str; 41] = [
|
|||||||
/// * If redirections are given, the second field of the resulting tuple will
|
/// * If redirections are given, the second field of the resulting tuple will
|
||||||
/// *always* be `Some()`
|
/// *always* be `Some()`
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
type SetupReturns = ShResult<(Vec<(String, Span)>, Option<RedirGuard>)>;
|
type SetupReturns = ShResult<(Option<Vec<(String, Span)>>, Option<RedirGuard>)>;
|
||||||
pub fn setup_builtin(
|
pub fn setup_builtin(
|
||||||
argv: Vec<Tk>,
|
argv: Option<Vec<Tk>>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
|
io_mode: Option<(&mut IoStack, Vec<Redir>)>,
|
||||||
) -> SetupReturns {
|
) -> SetupReturns {
|
||||||
let mut argv: Vec<(String, Span)> = prepare_argv(argv)?;
|
let mut argv = argv.map(|argv| prepare_argv(argv)).transpose()?;
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
pgid
|
pgid
|
||||||
@@ -81,18 +81,22 @@ pub fn setup_builtin(
|
|||||||
job.set_pgid(Pid::this());
|
job.set_pgid(Pid::this());
|
||||||
Pid::this()
|
Pid::this()
|
||||||
};
|
};
|
||||||
let cmd_name = argv.remove(0).0;
|
let cmd_name = argv
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|argv| {
|
||||||
|
if argv.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(argv.remove(0).0)
|
||||||
|
}
|
||||||
|
}).unwrap_or_else(|| String::new());
|
||||||
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
||||||
job.push_child(child);
|
job.push_child(child);
|
||||||
|
|
||||||
let guard = if let Some((io_stack, redirs)) = io_mode {
|
let guard = io_mode.map(|(io,rdrs)| {
|
||||||
io_stack.append_to_frame(redirs);
|
io.append_to_frame(rdrs);
|
||||||
let io_frame = io_stack.pop_frame();
|
io.pop_frame().redirect()
|
||||||
let guard = io_frame.redirect()?;
|
}).transpose()?;
|
||||||
Some(guard)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// We return the io_frame because the caller needs to also call
|
// We return the io_frame because the caller needs to also call
|
||||||
// io_frame.restore()
|
// io_frame.restore()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
|
|||||||
|
|
||||||
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 (argv, _) = setup_builtin(argv, job, None).blame(blame.clone())?;
|
let (argv, _) = setup_builtin(Some(argv), job, None).blame(blame.clone())?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
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())?;
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
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() {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
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())?;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _) = setup_builtin(argv, job, None)?;
|
let (argv, _) = setup_builtin(Some(argv), job, None)?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let path = PathBuf::from(arg);
|
let path = PathBuf::from(arg);
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ pub fn trap(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node, lex::split_tk_at},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoStack, borrow_fd},
|
procio::{IoStack, borrow_fd},
|
||||||
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
||||||
@@ -18,7 +18,10 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
// Remove "readonly" from 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
|
||||||
@@ -38,10 +41,17 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, vars_output.as_bytes())?; // Write it
|
write(stdout, vars_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for tk in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var_tk, val_tk)) = split_tk_at(tk, "=") {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::READONLY))?;
|
let var = var_tk.expand()?.get_words().join(" ");
|
||||||
|
let val = if val_tk.as_str().starts_with('(') && val_tk.as_str().ends_with(')') {
|
||||||
|
VarKind::arr_from_tk(val_tk.clone())?
|
||||||
} else {
|
} else {
|
||||||
|
VarKind::Str(val_tk.expand()?.get_words().join(" "))
|
||||||
|
};
|
||||||
|
write_vars(|v| v.set_var(&var, val, VarFlags::READONLY))?;
|
||||||
|
} else {
|
||||||
|
let arg = tk.clone().expand()?.get_words().join(" ");
|
||||||
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::READONLY))?;
|
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::READONLY))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +71,8 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::full(
|
||||||
@@ -95,7 +106,10 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
// Remove "export" from 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
|
||||||
@@ -109,12 +123,18 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, env_output.as_bytes())?; // Write it
|
write(stdout, env_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for tk in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var_tk, val_tk)) = split_tk_at(tk, "=") {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::EXPORT))?;
|
let var = var_tk.expand()?.get_words().join(" ");
|
||||||
|
let val = if val_tk.as_str().starts_with('(') && val_tk.as_str().ends_with(')') {
|
||||||
|
VarKind::arr_from_tk(val_tk.clone())?
|
||||||
} else {
|
} else {
|
||||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
VarKind::Str(val_tk.expand()?.get_words().join(" "))
|
||||||
// any
|
};
|
||||||
|
write_vars(|v| v.set_var(&var, val, VarFlags::EXPORT))?;
|
||||||
|
} else {
|
||||||
|
let arg = tk.clone().expand()?.get_words().join(" ");
|
||||||
|
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +151,10 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(None, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
|
// Remove "local" from 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
|
||||||
@@ -150,10 +173,17 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
write(stdout, vars_output.as_bytes())?; // Write it
|
write(stdout, vars_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for tk in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var_tk, val_tk)) = split_tk_at(tk, "=") {
|
||||||
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::LOCAL))?;
|
let var = var_tk.expand()?.get_words().join(" ");
|
||||||
|
let val = if val_tk.as_str().starts_with('(') && val_tk.as_str().ends_with(')') {
|
||||||
|
VarKind::arr_from_tk(val_tk.clone())?
|
||||||
} else {
|
} else {
|
||||||
|
VarKind::Str(val_tk.expand()?.get_words().join(" "))
|
||||||
|
};
|
||||||
|
write_vars(|v| v.set_var(&var, val, VarFlags::LOCAL))?;
|
||||||
|
} else {
|
||||||
|
let arg = tk.clone().expand()?.get_words().join(" ");
|
||||||
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::LOCAL))?;
|
write_vars(|v| v.set_var(&arg, VarKind::Str(String::new()), VarFlags::LOCAL))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(Some(argv), job, Some((io_stack, node.redirs)))?;
|
||||||
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use regex::Regex;
|
|||||||
|
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::parse::lex::{LexFlags, LexStream, 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::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
||||||
use crate::readline::markers;
|
use crate::readline::markers;
|
||||||
@@ -130,18 +130,16 @@ fn has_braces(s: &str) -> bool {
|
|||||||
let mut found_open = false;
|
let mut found_open = false;
|
||||||
let mut has_comma = false;
|
let mut has_comma = false;
|
||||||
let mut has_range = false;
|
let mut has_range = false;
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
chars.next();
|
chars.next();
|
||||||
} // skip escaped char
|
} // skip escaped char
|
||||||
'\'' if cur_quote.is_none() => cur_quote = Some('\''),
|
'\'' => qt_state.toggle_single(),
|
||||||
'\'' if cur_quote == Some('\'') => cur_quote = None,
|
'"' => qt_state.toggle_double(),
|
||||||
'"' if cur_quote.is_none() => cur_quote = Some('"'),
|
'{' if qt_state.in_quote() => {
|
||||||
'"' if cur_quote == Some('"') => cur_quote = None,
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
found_open = true;
|
found_open = true;
|
||||||
has_comma = false;
|
has_comma = false;
|
||||||
@@ -149,16 +147,16 @@ fn has_braces(s: &str) -> bool {
|
|||||||
}
|
}
|
||||||
depth += 1;
|
depth += 1;
|
||||||
}
|
}
|
||||||
'}' if cur_quote.is_none() && depth > 0 => {
|
'}' if qt_state.outside() && depth > 0 => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
if depth == 0 && found_open && (has_comma || has_range) {
|
if depth == 0 && found_open && (has_comma || has_range) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
',' if cur_quote.is_none() && depth == 1 => {
|
',' if qt_state.outside() && depth == 1 => {
|
||||||
has_comma = true;
|
has_comma = true;
|
||||||
}
|
}
|
||||||
'.' if cur_quote.is_none() && depth == 1 => {
|
'.' if qt_state.outside() && depth == 1 => {
|
||||||
if chars.peek() == Some(&'.') {
|
if chars.peek() == Some(&'.') {
|
||||||
chars.next();
|
chars.next();
|
||||||
has_range = true;
|
has_range = true;
|
||||||
@@ -239,7 +237,7 @@ fn expand_one_brace(word: &str) -> ShResult<Vec<String>> {
|
|||||||
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
||||||
let mut chars = word.chars().peekable();
|
let mut chars = word.chars().peekable();
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
// Find the opening brace
|
// Find the opening brace
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
@@ -250,23 +248,15 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
prefix.push(next);
|
prefix.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' if cur_quote.is_none() => {
|
'\'' => {
|
||||||
cur_quote = Some('\'');
|
qt_state.toggle_single();
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
}
|
}
|
||||||
'\'' if cur_quote == Some('\'') => {
|
'"' => {
|
||||||
cur_quote = None;
|
qt_state.toggle_double();
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
}
|
}
|
||||||
'"' if cur_quote.is_none() => {
|
'{' if qt_state.outside() => {
|
||||||
cur_quote = Some('"');
|
|
||||||
prefix.push(ch);
|
|
||||||
}
|
|
||||||
'"' if cur_quote == Some('"') => {
|
|
||||||
cur_quote = None;
|
|
||||||
prefix.push(ch);
|
|
||||||
}
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => prefix.push(ch),
|
_ => prefix.push(ch),
|
||||||
@@ -276,7 +266,7 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
// Find matching closing brace
|
// Find matching closing brace
|
||||||
let mut depth = 1;
|
let mut depth = 1;
|
||||||
let mut inner = String::new();
|
let mut inner = String::new();
|
||||||
cur_quote = None;
|
qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
@@ -286,27 +276,19 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
inner.push(next);
|
inner.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' if cur_quote.is_none() => {
|
'\'' => {
|
||||||
cur_quote = Some('\'');
|
qt_state.toggle_single();
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'\'' if cur_quote == Some('\'') => {
|
'"' => {
|
||||||
cur_quote = None;
|
qt_state.toggle_double();
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'"' if cur_quote.is_none() => {
|
'{' if qt_state.outside() => {
|
||||||
cur_quote = Some('"');
|
|
||||||
inner.push(ch);
|
|
||||||
}
|
|
||||||
'"' if cur_quote == Some('"') => {
|
|
||||||
cur_quote = None;
|
|
||||||
inner.push(ch);
|
|
||||||
}
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
depth += 1;
|
depth += 1;
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
}
|
}
|
||||||
'}' if cur_quote.is_none() => {
|
'}' if qt_state.outside() => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
break;
|
break;
|
||||||
@@ -335,7 +317,7 @@ fn split_brace_inner(inner: &str) -> Vec<String> {
|
|||||||
let mut current = String::new();
|
let mut current = String::new();
|
||||||
let mut chars = inner.chars().peekable();
|
let mut chars = inner.chars().peekable();
|
||||||
let mut depth = 0;
|
let mut depth = 0;
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
@@ -345,31 +327,23 @@ fn split_brace_inner(inner: &str) -> Vec<String> {
|
|||||||
current.push(next);
|
current.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\'' if cur_quote.is_none() => {
|
'\'' => {
|
||||||
cur_quote = Some('\'');
|
qt_state.toggle_single();
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'\'' if cur_quote == Some('\'') => {
|
'"' => {
|
||||||
cur_quote = None;
|
qt_state.toggle_double();
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'"' if cur_quote.is_none() => {
|
'{' if qt_state.outside() => {
|
||||||
cur_quote = Some('"');
|
|
||||||
current.push(ch);
|
|
||||||
}
|
|
||||||
'"' if cur_quote == Some('"') => {
|
|
||||||
cur_quote = None;
|
|
||||||
current.push(ch);
|
|
||||||
}
|
|
||||||
'{' if cur_quote.is_none() => {
|
|
||||||
depth += 1;
|
depth += 1;
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
'}' if cur_quote.is_none() => {
|
'}' if qt_state.outside() => {
|
||||||
depth -= 1;
|
depth -= 1;
|
||||||
current.push(ch);
|
current.push(ch);
|
||||||
}
|
}
|
||||||
',' if cur_quote.is_none() && depth == 0 => {
|
',' if qt_state.outside() && depth == 0 => {
|
||||||
parts.push(std::mem::take(&mut current));
|
parts.push(std::mem::take(&mut current));
|
||||||
}
|
}
|
||||||
_ => current.push(ch),
|
_ => current.push(ch),
|
||||||
@@ -556,6 +530,11 @@ 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 => {
|
||||||
|
read_vars(|v| v.get_arr_elems(&var_name))
|
||||||
|
.map(|elems| elems.len().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())
|
||||||
|
|||||||
279
src/parse/lex.rs
279
src/parse/lex.rs
@@ -24,6 +24,46 @@ 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
|
||||||
|
#[derive(Default,Debug)]
|
||||||
|
pub enum QuoteState {
|
||||||
|
#[default]
|
||||||
|
Outside,
|
||||||
|
Single,
|
||||||
|
Double
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuoteState {
|
||||||
|
pub fn outside(&self) -> bool {
|
||||||
|
matches!(self, QuoteState::Outside)
|
||||||
|
}
|
||||||
|
pub fn in_single(&self) -> bool {
|
||||||
|
matches!(self, QuoteState::Single)
|
||||||
|
}
|
||||||
|
pub fn in_double(&self) -> bool {
|
||||||
|
matches!(self, QuoteState::Double)
|
||||||
|
}
|
||||||
|
pub fn in_quote(&self) -> bool {
|
||||||
|
!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
|
||||||
|
pub fn toggle_double(&mut self) {
|
||||||
|
match self {
|
||||||
|
QuoteState::Outside => *self = QuoteState::Double,
|
||||||
|
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
|
||||||
|
pub fn toggle_single(&mut self) {
|
||||||
|
match self {
|
||||||
|
QuoteState::Outside => *self = QuoteState::Single,
|
||||||
|
QuoteState::Single => *self = QuoteState::Outside,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Span::new(10..20)
|
/// Span::new(10..20)
|
||||||
#[derive(Clone, PartialEq, Default, Debug)]
|
#[derive(Clone, PartialEq, Default, Debug)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
@@ -150,7 +190,7 @@ bitflags! {
|
|||||||
pub struct LexStream {
|
pub struct LexStream {
|
||||||
source: Arc<String>,
|
source: Arc<String>,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
in_quote: bool,
|
quote_state: QuoteState,
|
||||||
brc_grp_start: Option<usize>,
|
brc_grp_start: Option<usize>,
|
||||||
flags: LexFlags,
|
flags: LexFlags,
|
||||||
}
|
}
|
||||||
@@ -183,11 +223,11 @@ impl LexStream {
|
|||||||
pub fn new(source: Arc<String>, flags: LexFlags) -> Self {
|
pub fn new(source: Arc<String>, flags: LexFlags) -> Self {
|
||||||
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
||||||
Self {
|
Self {
|
||||||
|
flags,
|
||||||
source,
|
source,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
in_quote: false,
|
quote_state: QuoteState::default(),
|
||||||
brc_grp_start: None,
|
brc_grp_start: None,
|
||||||
flags,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns a slice of the source input using the given range
|
/// Returns a slice of the source input using the given range
|
||||||
@@ -352,6 +392,47 @@ impl LexStream {
|
|||||||
if let Some(ch) = chars.next() {
|
if let Some(ch) = chars.next() {
|
||||||
pos += ch.len_utf8();
|
pos += ch.len_utf8();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
'\'' => {
|
||||||
|
pos += 1;
|
||||||
|
self.quote_state.toggle_single();
|
||||||
|
}
|
||||||
|
_ if self.quote_state.in_single() => pos += ch.len_utf8(),
|
||||||
|
'$' if chars.peek() == Some(&'(') => {
|
||||||
|
pos += 2;
|
||||||
|
chars.next();
|
||||||
|
let mut paren_count = 1;
|
||||||
|
let paren_pos = pos;
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
pos += 1;
|
||||||
|
if let Some(next_ch) = chars.next() {
|
||||||
|
pos += next_ch.len_utf8();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
pos += 1;
|
||||||
|
paren_count += 1;
|
||||||
|
}
|
||||||
|
')' => {
|
||||||
|
pos += 1;
|
||||||
|
paren_count -= 1;
|
||||||
|
if paren_count <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => pos += ch.len_utf8(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
|
self.cursor = pos;
|
||||||
|
return Err(ShErr::full(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
"Unclosed subshell",
|
||||||
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'$' if chars.peek() == Some(&'{') => {
|
'$' if chars.peek() == Some(&'{') => {
|
||||||
pos += 2;
|
pos += 2;
|
||||||
@@ -380,6 +461,11 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'"' => {
|
||||||
|
pos += 1;
|
||||||
|
self.quote_state.toggle_double();
|
||||||
|
}
|
||||||
|
_ 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();
|
||||||
@@ -452,42 +538,6 @@ impl LexStream {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
|
||||||
pos += 2;
|
|
||||||
chars.next();
|
|
||||||
let mut paren_count = 1;
|
|
||||||
let paren_pos = pos;
|
|
||||||
while let Some(ch) = chars.next() {
|
|
||||||
match ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if let Some(next_ch) = chars.next() {
|
|
||||||
pos += next_ch.len_utf8();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'(' => {
|
|
||||||
pos += 1;
|
|
||||||
paren_count += 1;
|
|
||||||
}
|
|
||||||
')' => {
|
|
||||||
pos += 1;
|
|
||||||
paren_count -= 1;
|
|
||||||
if paren_count <= 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => pos += ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
|
||||||
self.cursor = pos;
|
|
||||||
return Err(ShErr::full(
|
|
||||||
ShErrKind::ParseErr,
|
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'(' if self.next_is_cmd() && can_be_subshell => {
|
'(' if self.next_is_cmd() && can_be_subshell => {
|
||||||
pos += 1;
|
pos += 1;
|
||||||
let mut paren_count = 1;
|
let mut paren_count = 1;
|
||||||
@@ -547,82 +597,6 @@ impl LexStream {
|
|||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Ok(tk);
|
return Ok(tk);
|
||||||
}
|
}
|
||||||
'\'' => {
|
|
||||||
self.in_quote = true;
|
|
||||||
pos += 1;
|
|
||||||
while let Some(q_ch) = chars.next() {
|
|
||||||
match q_ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if chars.next().is_some() {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ if q_ch == '\'' => {
|
|
||||||
pos += 1;
|
|
||||||
self.in_quote = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Any time an ambiguous character is found
|
|
||||||
// we must push the cursor by the length of the character
|
|
||||||
// instead of just assuming a length of 1.
|
|
||||||
// Allows spans to work for wide characters
|
|
||||||
_ => pos += q_ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'"' => {
|
|
||||||
self.in_quote = true;
|
|
||||||
pos += 1;
|
|
||||||
while let Some(q_ch) = chars.next() {
|
|
||||||
match q_ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if chars.next().is_some() {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
|
||||||
pos += 2;
|
|
||||||
chars.next();
|
|
||||||
let mut cmdsub_count = 1;
|
|
||||||
while let Some(cmdsub_ch) = chars.next() {
|
|
||||||
match cmdsub_ch {
|
|
||||||
'\\' => {
|
|
||||||
pos += 1;
|
|
||||||
if chars.next().is_some() {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'$' if chars.peek() == Some(&'(') => {
|
|
||||||
cmdsub_count += 1;
|
|
||||||
pos += 2;
|
|
||||||
chars.next();
|
|
||||||
}
|
|
||||||
')' => {
|
|
||||||
cmdsub_count -= 1;
|
|
||||||
pos += 1;
|
|
||||||
if cmdsub_count <= 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => pos += cmdsub_ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ if q_ch == '"' => {
|
|
||||||
pos += 1;
|
|
||||||
self.in_quote = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Any time an ambiguous character is found
|
|
||||||
// we must push the cursor by the length of the character
|
|
||||||
// instead of just assuming a length of 1.
|
|
||||||
// Allows spans to work for wide characters
|
|
||||||
_ => pos += q_ch.len_utf8(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'=' if chars.peek() == Some(&'(') => {
|
'=' if chars.peek() == Some(&'(') => {
|
||||||
pos += 1; // '='
|
pos += 1; // '='
|
||||||
let mut depth = 1;
|
let mut depth = 1;
|
||||||
@@ -652,13 +626,12 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if !self.in_quote && is_op(ch) => break,
|
|
||||||
_ if is_hard_sep(ch) => break,
|
_ if is_hard_sep(ch) => break,
|
||||||
_ => pos += ch.len_utf8(),
|
_ => pos += ch.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||||
if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.quote_state.in_quote() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
@@ -912,6 +885,8 @@ pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
|
|||||||
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits a string by a pattern, but only if the pattern is not escaped by a backslash
|
||||||
|
/// 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![];
|
||||||
@@ -925,19 +900,71 @@ pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
|
|||||||
splits
|
splits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
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 window_start = 0;
|
let mut chars = slice.char_indices().peekable();
|
||||||
let mut window_end = pat.len();
|
let mut qt_state = QuoteState::default();
|
||||||
if window_end > slice.len() {
|
|
||||||
return None;
|
while let Some((i, ch)) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => { chars.next(); continue; }
|
||||||
|
'\'' => qt_state.toggle_single(),
|
||||||
|
'"' => qt_state.toggle_double(),
|
||||||
|
_ if qt_state.in_quote() => continue,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
while window_end <= slice.len() {
|
|
||||||
if &slice[window_start..window_end] == pat && !pos_is_escaped(slice, window_start) {
|
if slice[i..].starts_with(pat) {
|
||||||
return Some((slice[..window_start].to_string(), slice[window_end..].to_string()));
|
let before = slice[..i].to_string();
|
||||||
|
let after = slice[i + pat.len()..].to_string();
|
||||||
|
return Some((before, after));
|
||||||
}
|
}
|
||||||
window_start += 1;
|
|
||||||
window_end += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_tk(tk: &Tk, pat: &str) -> Vec<Tk> {
|
||||||
|
let slice = tk.as_str();
|
||||||
|
let mut cursor = 0;
|
||||||
|
let mut splits = vec![];
|
||||||
|
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
|
||||||
|
let before_span = Span::new(tk.span.start + cursor..tk.span.start + cursor + split.0.len(), tk.source().clone());
|
||||||
|
splits.push(Tk::new(tk.class.clone(), before_span));
|
||||||
|
cursor += split.0.len() + pat.len();
|
||||||
|
}
|
||||||
|
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
|
||||||
|
let remaining_span = Span::new(tk.span.start + cursor..tk.span.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)> {
|
||||||
|
let slice = tk.as_str();
|
||||||
|
let mut chars = slice.char_indices().peekable();
|
||||||
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
|
while let Some((i, ch)) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => { chars.next(); continue; }
|
||||||
|
'\'' => qt_state.toggle_single(),
|
||||||
|
'"' => qt_state.toggle_double(),
|
||||||
|
_ if qt_state.in_quote() => continue,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if slice[i..].starts_with(pat) {
|
||||||
|
let before_span = Span::new(tk.span.start..tk.span.start + i, tk.source().clone());
|
||||||
|
let after_span = Span::new(tk.span.start + i + pat.len()..tk.span.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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1035,6 +1035,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
cond_nodes.push(cond_node);
|
cond_nodes.push(cond_node);
|
||||||
|
|
||||||
|
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 {
|
||||||
@@ -1043,6 +1044,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVis
|
|||||||
|
|
||||||
use crate::expand::expand_prompt;
|
use crate::expand::expand_prompt;
|
||||||
use crate::libsh::sys::TTY_FILENO;
|
use crate::libsh::sys::TTY_FILENO;
|
||||||
use crate::parse::lex::LexStream;
|
use crate::parse::lex::{LexStream, QuoteState};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::readline::term::{Pos, calc_str_width};
|
use crate::readline::term::{Pos, calc_str_width};
|
||||||
use crate::state::read_shopts;
|
use crate::state::read_shopts;
|
||||||
@@ -339,8 +339,14 @@ impl ShedVi {
|
|||||||
let line = self.editor.as_str().to_string();
|
let line = self.editor.as_str().to_string();
|
||||||
let cursor_pos = self.editor.cursor_byte_pos();
|
let cursor_pos = self.editor.cursor_byte_pos();
|
||||||
|
|
||||||
match self.completer.complete(line, cursor_pos, direction)? {
|
match self.completer.complete(line, cursor_pos, direction) {
|
||||||
Some(line) => {
|
Err(e) => {
|
||||||
|
self.writer.flush_write(&format!("\n{e}\n\n"))?;
|
||||||
|
|
||||||
|
// Printing the error invalidates the layout
|
||||||
|
self.old_layout = None;
|
||||||
|
}
|
||||||
|
Ok(Some(line)) => {
|
||||||
let span_start = self.completer.token_span.0;
|
let span_start = self.completer.token_span.0;
|
||||||
let new_cursor = span_start
|
let new_cursor = span_start
|
||||||
+ self
|
+ self
|
||||||
@@ -361,7 +367,7 @@ impl ShedVi {
|
|||||||
let hint = self.history.get_hint();
|
let hint = self.history.get_hint();
|
||||||
self.editor.set_hint(hint);
|
self.editor.set_hint(hint);
|
||||||
}
|
}
|
||||||
None => {
|
Ok(None) => {
|
||||||
self.writer.send_bell().ok();
|
self.writer.send_bell().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1005,8 +1011,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
|
|
||||||
let span_start = token.span.start;
|
let span_start = token.span.start;
|
||||||
|
|
||||||
let mut in_dub_qt = false;
|
let mut qt_state = QuoteState::default();
|
||||||
let mut in_sng_qt = false;
|
|
||||||
let mut cmd_sub_depth = 0;
|
let mut cmd_sub_depth = 0;
|
||||||
let mut proc_sub_depth = 0;
|
let mut proc_sub_depth = 0;
|
||||||
|
|
||||||
@@ -1045,7 +1050,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'$' if !in_sng_qt => {
|
'$' if !qt_state.in_single() => {
|
||||||
let dollar_pos = index;
|
let dollar_pos = index;
|
||||||
token_chars.next(); // consume the dollar
|
token_chars.next(); // consume the dollar
|
||||||
if let Some((_, dollar_ch)) = token_chars.peek() {
|
if let Some((_, dollar_ch)) = token_chars.peek() {
|
||||||
@@ -1115,13 +1120,13 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
token_chars.next(); // consume the char with no special handling
|
token_chars.next(); // consume the char with no special handling
|
||||||
}
|
}
|
||||||
|
|
||||||
'\\' if !in_sng_qt => {
|
'\\' if !qt_state.in_single() => {
|
||||||
token_chars.next(); // consume the backslash
|
token_chars.next(); // consume the backslash
|
||||||
if token_chars.peek().is_some() {
|
if token_chars.peek().is_some() {
|
||||||
token_chars.next(); // consume the escaped char
|
token_chars.next(); // consume the escaped char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'<' | '>' if !in_dub_qt && !in_sng_qt && 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()
|
||||||
&& *proc_sub_ch == '('
|
&& *proc_sub_ch == '('
|
||||||
@@ -1133,25 +1138,25 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'"' if !in_sng_qt => {
|
'"' if !qt_state.in_single() => {
|
||||||
if in_dub_qt {
|
if qt_state.in_double() {
|
||||||
insertions.push((span_start + *i + 1, markers::STRING_DQ_END));
|
insertions.push((span_start + *i + 1, markers::STRING_DQ_END));
|
||||||
} else {
|
} else {
|
||||||
insertions.push((span_start + *i, markers::STRING_DQ));
|
insertions.push((span_start + *i, markers::STRING_DQ));
|
||||||
}
|
}
|
||||||
in_dub_qt = !in_dub_qt;
|
qt_state.toggle_double();
|
||||||
token_chars.next(); // consume the quote
|
token_chars.next(); // consume the quote
|
||||||
}
|
}
|
||||||
'\'' if !in_dub_qt => {
|
'\'' if !qt_state.in_double() => {
|
||||||
if in_sng_qt {
|
if qt_state.in_single() {
|
||||||
insertions.push((span_start + *i + 1, markers::STRING_SQ_END));
|
insertions.push((span_start + *i + 1, markers::STRING_SQ_END));
|
||||||
} else {
|
} else {
|
||||||
insertions.push((span_start + *i, markers::STRING_SQ));
|
insertions.push((span_start + *i, markers::STRING_SQ));
|
||||||
}
|
}
|
||||||
in_sng_qt = !in_sng_qt;
|
qt_state.toggle_single();
|
||||||
token_chars.next(); // consume the quote
|
token_chars.next(); // consume the quote
|
||||||
}
|
}
|
||||||
'[' if !in_dub_qt && !in_sng_qt && !token.flags.contains(TkFlags::ASSIGN) => {
|
'[' if !qt_state.in_quote() && !token.flags.contains(TkFlags::ASSIGN) => {
|
||||||
token_chars.next(); // consume the opening bracket
|
token_chars.next(); // consume the opening bracket
|
||||||
let start_pos = span_start + index;
|
let start_pos = span_start + index;
|
||||||
let mut is_glob_pat = false;
|
let mut is_glob_pat = false;
|
||||||
@@ -1177,7 +1182,7 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
insertions.push((start_pos, markers::GLOB));
|
insertions.push((start_pos, markers::GLOB));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'*' | '?' if (!in_dub_qt && !in_sng_qt) => {
|
'*' | '?' if !qt_state.in_quote() => {
|
||||||
let glob_ch = *ch;
|
let glob_ch = *ch;
|
||||||
token_chars.next(); // consume the first glob char
|
token_chars.next(); // consume the first glob char
|
||||||
if !in_context(markers::COMMAND, &insertions) {
|
if !in_context(markers::COMMAND, &insertions) {
|
||||||
|
|||||||
@@ -620,6 +620,7 @@ impl VarFlags {
|
|||||||
pub enum ArrIndex {
|
pub enum ArrIndex {
|
||||||
Literal(usize),
|
Literal(usize),
|
||||||
FromBack(usize),
|
FromBack(usize),
|
||||||
|
ArgCount,
|
||||||
AllJoined,
|
AllJoined,
|
||||||
AllSplit,
|
AllSplit,
|
||||||
}
|
}
|
||||||
@@ -630,6 +631,7 @@ impl FromStr for ArrIndex {
|
|||||||
match s {
|
match s {
|
||||||
"@" => Ok(Self::AllSplit),
|
"@" => Ok(Self::AllSplit),
|
||||||
"*" => Ok(Self::AllJoined),
|
"*" => Ok(Self::AllJoined),
|
||||||
|
"#" => 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))
|
||||||
|
|||||||
Reference in New Issue
Block a user