Refactored internals for builtins inside of pipelines
This commit is contained in:
@@ -50,7 +50,7 @@ in
|
|||||||
};
|
};
|
||||||
interactiveComments = lib.mkOption {
|
interactiveComments = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = true;
|
||||||
description = "Whether to allow comments in interactive mode";
|
description = "Whether to allow comments in interactive mode";
|
||||||
};
|
};
|
||||||
autoHistory = lib.mkOption {
|
autoHistory = lib.mkOption {
|
||||||
@@ -84,6 +84,11 @@ in
|
|||||||
default = true;
|
default = true;
|
||||||
description = "Whether to enable syntax highlighting in the shell";
|
description = "Whether to enable syntax highlighting in the shell";
|
||||||
};
|
};
|
||||||
|
linebreakOnIncomplete = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to automatically insert a newline when the input is incomplete";
|
||||||
|
};
|
||||||
extraPostConfig = lib.mkOption {
|
extraPostConfig = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "";
|
default = "";
|
||||||
@@ -117,6 +122,7 @@ in
|
|||||||
"shopt prompt.trunc_prompt_path=${toString cfg.settings.promptPathSegments}"
|
"shopt prompt.trunc_prompt_path=${toString cfg.settings.promptPathSegments}"
|
||||||
"shopt prompt.comp_limit=${toString cfg.settings.completionLimit}"
|
"shopt prompt.comp_limit=${toString cfg.settings.completionLimit}"
|
||||||
"shopt prompt.highlight=${boolToString cfg.settings.syntaxHighlighting}"
|
"shopt prompt.highlight=${boolToString cfg.settings.syntaxHighlighting}"
|
||||||
|
"shopt prompt.linebreak_on_incomplete=${boolToString cfg.settings.linebreakOnIncomplete}"
|
||||||
])
|
])
|
||||||
cfg.settings.extraPostConfig
|
cfg.settings.extraPostConfig
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub const ECHO_OPTS: [OptSpec; 4] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct EchoFlags: u32 {
|
pub struct EchoFlags: u32 {
|
||||||
const NO_NEWLINE = 0b000001;
|
const NO_NEWLINE = 0b000001;
|
||||||
const USE_STDERR = 0b000010;
|
const USE_STDERR = 0b000010;
|
||||||
@@ -60,6 +61,7 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
borrow_fd(STDOUT_FILENO)
|
borrow_fd(STDOUT_FILENO)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let mut echo_output = prepare_echo_args(
|
let mut echo_output = prepare_echo_args(
|
||||||
argv
|
argv
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -197,6 +199,7 @@ pub fn prepare_echo_args(
|
|||||||
prepared_args.push(prepared_arg);
|
prepared_args.push(prepared_arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(prepared_args)
|
Ok(prepared_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -113,7 +113,13 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
|
|||||||
input.push(buf[0]);
|
input.push(buf[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Errno::EINTR) => continue,
|
Err(Errno::EINTR) => {
|
||||||
|
if crate::signal::sigint_pending() {
|
||||||
|
state::set_status(130);
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
@@ -137,19 +143,32 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
|
|||||||
let mut input: Vec<u8> = vec![];
|
let mut input: Vec<u8> = vec![];
|
||||||
loop {
|
loop {
|
||||||
let mut buf = [0u8; 1];
|
let mut buf = [0u8; 1];
|
||||||
|
log::info!("read: about to call read()");
|
||||||
match read(STDIN_FILENO, &mut buf) {
|
match read(STDIN_FILENO, &mut buf) {
|
||||||
Ok(0) => {
|
Ok(0) => {
|
||||||
|
log::info!("read: got EOF");
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
break; // EOF
|
break; // EOF
|
||||||
}
|
}
|
||||||
Ok(_) => {
|
Ok(n) => {
|
||||||
|
log::info!("read: got {} bytes: {:?}", n, &buf[..1]);
|
||||||
if buf[0] == read_opts.delim {
|
if buf[0] == read_opts.delim {
|
||||||
|
state::set_status(0);
|
||||||
break; // Delimiter reached, stop reading
|
break; // Delimiter reached, stop reading
|
||||||
}
|
}
|
||||||
input.push(buf[0]);
|
input.push(buf[0]);
|
||||||
}
|
}
|
||||||
Err(Errno::EINTR) => continue,
|
Err(Errno::EINTR) => {
|
||||||
|
let pending = crate::signal::sigint_pending();
|
||||||
|
log::info!("read: got EINTR, sigint_pending={}", pending);
|
||||||
|
if pending {
|
||||||
|
state::set_status(130);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
log::info!("read: got error: {}", e);
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
format!("read: Failed to read from stdin: {e}"),
|
format!("read: Failed to read from stdin: {e}"),
|
||||||
@@ -202,7 +221,6 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(0);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1576,7 +1576,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glob_to_regex(glob: &str, anchored: bool) -> Regex {
|
pub fn glob_to_regex(glob: &str, anchored: bool) -> Regex {
|
||||||
let mut regex = String::new();
|
let mut regex = String::new();
|
||||||
if anchored {
|
if anchored {
|
||||||
regex.push('^');
|
regex.push('^');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{
|
|||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, export::{export, local}, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak
|
alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, export::{export, local}, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak
|
||||||
},
|
},
|
||||||
expand::expand_aliases,
|
expand::{expand_aliases, glob_to_regex},
|
||||||
jobs::{ChildProc, JobStack, dispatch_job},
|
jobs::{ChildProc, JobStack, dispatch_job},
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -376,20 +376,37 @@ impl Dispatcher {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
fn exec_brc_grp(&mut self, brc_grp: Node) -> ShResult<()> {
|
fn exec_brc_grp(&mut self, brc_grp: Node) -> ShResult<()> {
|
||||||
|
let blame = brc_grp.get_span().clone();
|
||||||
let NdRule::BraceGrp { body } = brc_grp.class else {
|
let NdRule::BraceGrp { body } = brc_grp.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
let fork_builtins = brc_grp.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(brc_grp.redirs);
|
self.io_stack.append_to_frame(brc_grp.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
let brc_grp_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
|
|
||||||
for node in body {
|
for node in body {
|
||||||
let blame = node.get_span();
|
let blame = node.get_span();
|
||||||
self.dispatch_node(node).try_blame(blame)?;
|
s.dispatch_node(node).try_blame(blame)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if fork_builtins {
|
||||||
|
log::trace!("Forking brace group");
|
||||||
|
self.run_fork("brace group", |s| {
|
||||||
|
if let Err(e) = brc_grp_logic(s) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
brc_grp_logic(self).try_blame(blame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn exec_case(&mut self, case_stmt: Node) -> ShResult<()> {
|
fn exec_case(&mut self, case_stmt: Node) -> ShResult<()> {
|
||||||
|
let blame = case_stmt.get_span().clone();
|
||||||
let NdRule::CaseNode {
|
let NdRule::CaseNode {
|
||||||
pattern,
|
pattern,
|
||||||
case_blocks,
|
case_blocks,
|
||||||
@@ -398,9 +415,12 @@ impl Dispatcher {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fork_builtins = case_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(case_stmt.redirs);
|
self.io_stack.append_to_frame(case_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
|
let case_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
let exp_pattern = pattern.clone().expand()?;
|
let exp_pattern = pattern.clone().expand()?;
|
||||||
let pattern_raw = exp_pattern
|
let pattern_raw = exp_pattern
|
||||||
.get_words()
|
.get_words()
|
||||||
@@ -415,9 +435,10 @@ impl Dispatcher {
|
|||||||
let block_patterns = block_pattern_raw.split('|');
|
let block_patterns = block_pattern_raw.split('|');
|
||||||
|
|
||||||
for pattern in block_patterns {
|
for pattern in block_patterns {
|
||||||
if pattern_raw == pattern || pattern == "*" {
|
let pattern_regex = glob_to_regex(pattern, false);
|
||||||
|
if pattern_regex.is_match(&pattern_raw) {
|
||||||
for node in &body {
|
for node in &body {
|
||||||
self.dispatch_node(node.clone())?;
|
s.dispatch_node(node.clone())?;
|
||||||
}
|
}
|
||||||
break 'outer;
|
break 'outer;
|
||||||
}
|
}
|
||||||
@@ -425,8 +446,21 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if fork_builtins {
|
||||||
|
log::trace!("Forking builtin: case");
|
||||||
|
self.run_fork("case", |s| {
|
||||||
|
if let Err(e) = case_logic(s) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
case_logic(self).try_blame(blame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
|
fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
|
||||||
|
let blame = loop_stmt.get_span().clone();
|
||||||
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
|
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
@@ -437,12 +471,15 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fork_builtins = loop_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(loop_stmt.redirs);
|
self.io_stack.append_to_frame(loop_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
|
let loop_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
let CondNode { cond, body } = cond_node;
|
let CondNode { cond, body } = cond_node;
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
if let Err(e) = s.dispatch_node(*cond.clone()) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
@@ -450,7 +487,7 @@ impl Dispatcher {
|
|||||||
let status = state::get_status();
|
let status = state::get_status();
|
||||||
if keep_going(kind, status) {
|
if keep_going(kind, status) {
|
||||||
for node in &body {
|
for node in &body {
|
||||||
if let Err(e) = self.dispatch_node(node.clone()) {
|
if let Err(e) = s.dispatch_node(node.clone()) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::LoopBreak(code) => {
|
ShErrKind::LoopBreak(code) => {
|
||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
@@ -472,12 +509,27 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if fork_builtins {
|
||||||
|
log::trace!("Forking builtin: loop");
|
||||||
|
self.run_fork("loop", |s| {
|
||||||
|
if let Err(e) = loop_logic(s) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
loop_logic(self).try_blame(blame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
||||||
|
let blame = for_stmt.get_span().clone();
|
||||||
let NdRule::ForNode { vars, arr, body } = for_stmt.class else {
|
let NdRule::ForNode { vars, arr, body } = for_stmt.class else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fork_builtins = for_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
let to_expanded_strings = |tks: Vec<Tk>| -> ShResult<Vec<String>> {
|
let to_expanded_strings = |tks: Vec<Tk>| -> ShResult<Vec<String>> {
|
||||||
Ok(
|
Ok(
|
||||||
tks
|
tks
|
||||||
@@ -490,15 +542,16 @@ impl Dispatcher {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.io_stack.append_to_frame(for_stmt.redirs);
|
||||||
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
|
let for_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
// Expand all array variables
|
// Expand all array variables
|
||||||
let arr: Vec<String> = to_expanded_strings(arr)?;
|
let arr: Vec<String> = to_expanded_strings(arr)?;
|
||||||
let vars: Vec<String> = to_expanded_strings(vars)?;
|
let vars: Vec<String> = to_expanded_strings(vars)?;
|
||||||
|
|
||||||
let mut for_guard = VarCtxGuard::new(vars.iter().map(|v| v.to_string()).collect());
|
let mut for_guard = VarCtxGuard::new(vars.iter().map(|v| v.to_string()).collect());
|
||||||
|
|
||||||
self.io_stack.append_to_frame(for_stmt.redirs);
|
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
|
||||||
|
|
||||||
'outer: for chunk in arr.chunks(vars.len()) {
|
'outer: for chunk in arr.chunks(vars.len()) {
|
||||||
let empty = String::new();
|
let empty = String::new();
|
||||||
let chunk_iter = vars
|
let chunk_iter = vars
|
||||||
@@ -511,7 +564,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for node in body.clone() {
|
for node in body.clone() {
|
||||||
if let Err(e) = self.dispatch_node(node) {
|
if let Err(e) = s.dispatch_node(node) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::LoopBreak(code) => {
|
ShErrKind::LoopBreak(code) => {
|
||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
@@ -528,8 +581,21 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if fork_builtins {
|
||||||
|
log::trace!("Forking builtin: for");
|
||||||
|
self.run_fork("for", |s| {
|
||||||
|
if let Err(e) = for_logic(s) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for_logic(self).try_blame(blame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
||||||
|
let blame = if_stmt.get_span().clone();
|
||||||
let NdRule::IfNode {
|
let NdRule::IfNode {
|
||||||
cond_nodes,
|
cond_nodes,
|
||||||
else_block,
|
else_block,
|
||||||
@@ -537,15 +603,17 @@ impl Dispatcher {
|
|||||||
else {
|
else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
let fork_builtins = if_stmt.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
|
||||||
self.io_stack.append_to_frame(if_stmt.redirs);
|
self.io_stack.append_to_frame(if_stmt.redirs);
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
|
let if_logic = |s: &mut Self| -> ShResult<()> {
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
for node in cond_nodes {
|
for node in cond_nodes {
|
||||||
let CondNode { cond, body } = node;
|
let CondNode { cond, body } = node;
|
||||||
|
|
||||||
if let Err(e) = self.dispatch_node(*cond) {
|
if let Err(e) = s.dispatch_node(*cond) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
@@ -554,7 +622,7 @@ impl Dispatcher {
|
|||||||
0 => {
|
0 => {
|
||||||
matched = true;
|
matched = true;
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
self.dispatch_node(body_node)?;
|
s.dispatch_node(body_node)?;
|
||||||
}
|
}
|
||||||
break; // Don't check remaining elif conditions
|
break; // Don't check remaining elif conditions
|
||||||
}
|
}
|
||||||
@@ -564,27 +632,46 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if !matched && !else_block.is_empty() {
|
if !matched && !else_block.is_empty() {
|
||||||
for node in else_block {
|
for node in else_block {
|
||||||
self.dispatch_node(node)?;
|
s.dispatch_node(node)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
if fork_builtins {
|
||||||
|
log::trace!("Forking builtin: if");
|
||||||
|
self.run_fork("if", |s| {
|
||||||
|
if let Err(e) = if_logic(s) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
state::set_status(1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if_logic(self).try_blame(blame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
|
fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
|
||||||
let NdRule::Pipeline { cmds, pipe_err: _ } = pipeline.class else {
|
let NdRule::Pipeline { cmds, pipe_err: _ } = pipeline.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
self.job_stack.new_job();
|
self.job_stack.new_job();
|
||||||
|
let fork_builtin = cmds.len() > 1; // If there's more than one command, we need to fork builtins
|
||||||
|
|
||||||
// Zip the commands and their respective pipes into an iterator
|
// Zip the commands and their respective pipes into an iterator
|
||||||
let pipes_and_cmds = get_pipe_stack(cmds.len()).into_iter().zip(cmds);
|
let pipes_and_cmds = get_pipe_stack(cmds.len()).into_iter().zip(cmds);
|
||||||
|
|
||||||
for ((rpipe, wpipe), cmd) in pipes_and_cmds {
|
for ((rpipe, wpipe), mut cmd) in pipes_and_cmds {
|
||||||
if let Some(pipe) = rpipe {
|
if let Some(pipe) = rpipe {
|
||||||
self.io_stack.push_to_frame(pipe);
|
self.io_stack.push_to_frame(pipe);
|
||||||
}
|
}
|
||||||
if let Some(pipe) = wpipe {
|
if let Some(pipe) = wpipe {
|
||||||
self.io_stack.push_to_frame(pipe);
|
self.io_stack.push_to_frame(pipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fork_builtin {
|
||||||
|
cmd.flags |= NdFlags::FORK_BUILTINS;
|
||||||
|
}
|
||||||
self.dispatch_node(cmd)?;
|
self.dispatch_node(cmd)?;
|
||||||
}
|
}
|
||||||
let job = self.job_stack.finalize_job().unwrap();
|
let job = self.job_stack.finalize_job().unwrap();
|
||||||
@@ -592,17 +679,41 @@ impl Dispatcher {
|
|||||||
dispatch_job(job, is_bg)?;
|
dispatch_job(job, is_bg)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn exec_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
fn exec_builtin(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
|
let fork_builtins = cmd.flags.contains(NdFlags::FORK_BUILTINS);
|
||||||
|
let cmd_raw = cmd.get_command().unwrap().to_string();
|
||||||
|
|
||||||
|
if fork_builtins {
|
||||||
|
log::trace!("Forking builtin: {}", cmd_raw);
|
||||||
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
self.run_fork(&cmd_raw, |s| {
|
||||||
|
if let Err(e) = s.dispatch_builtin(cmd) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let result = self.dispatch_builtin(cmd);
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
let code = state::get_status();
|
||||||
|
if code == 0 {
|
||||||
|
state::set_status(1);
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||||
|
let cmd_raw = cmd.get_command().unwrap().to_string();
|
||||||
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let env_vars = self.set_assignments(mem::take(assignments), AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(mem::take(assignments), AssignBehavior::Export)?;
|
||||||
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
||||||
|
|
||||||
let cmd_raw = argv.first().unwrap();
|
|
||||||
let curr_job_mut = self.job_stack.curr_job_mut().unwrap();
|
let curr_job_mut = self.job_stack.curr_job_mut().unwrap();
|
||||||
let io_stack_mut = &mut self.io_stack;
|
let io_stack_mut = &mut self.io_stack;
|
||||||
|
|
||||||
if cmd_raw.as_str() == "builtin" {
|
if cmd_raw.as_str() == "builtin" {
|
||||||
*argv = argv
|
*argv = argv
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@@ -616,10 +727,12 @@ impl Dispatcher {
|
|||||||
.skip(1)
|
.skip(1)
|
||||||
.map(|tk| tk.clone())
|
.map(|tk| tk.clone())
|
||||||
.collect::<Vec<Tk>>();
|
.collect::<Vec<Tk>>();
|
||||||
|
if cmd.flags.contains(NdFlags::FORK_BUILTINS) {
|
||||||
|
cmd.flags |= NdFlags::NO_FORK;
|
||||||
|
}
|
||||||
return self.dispatch_cmd(cmd);
|
return self.dispatch_cmd(cmd);
|
||||||
}
|
}
|
||||||
|
match cmd_raw.as_str() {
|
||||||
let result = match cmd_raw.span.as_str() {
|
|
||||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
||||||
"cd" => cd(cmd, curr_job_mut),
|
"cd" => cd(cmd, curr_job_mut),
|
||||||
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
||||||
@@ -648,15 +761,9 @@ impl Dispatcher {
|
|||||||
"eval" => eval::eval(cmd, io_stack_mut, curr_job_mut),
|
"eval" => eval::eval(cmd, io_stack_mut, curr_job_mut),
|
||||||
_ => unimplemented!(
|
_ => unimplemented!(
|
||||||
"Have not yet added support for builtin '{}'",
|
"Have not yet added support for builtin '{}'",
|
||||||
cmd_raw.span.as_str()
|
cmd_raw
|
||||||
),
|
),
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = result {
|
|
||||||
state::set_status(1);
|
|
||||||
return Err(e);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||||
@@ -672,6 +779,8 @@ impl Dispatcher {
|
|||||||
env_vars_to_unset = self.set_assignments(assignments, assign_behavior)?;
|
env_vars_to_unset = self.set_assignments(assignments, assign_behavior)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let no_fork = cmd.flags.contains(NdFlags::NO_FORK);
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -679,13 +788,10 @@ impl Dispatcher {
|
|||||||
self.io_stack.append_to_frame(cmd.redirs);
|
self.io_stack.append_to_frame(cmd.redirs);
|
||||||
|
|
||||||
let exec_args = ExecArgs::new(argv)?;
|
let exec_args = ExecArgs::new(argv)?;
|
||||||
|
|
||||||
let _guard = self.io_stack.pop_frame().redirect()?;
|
let _guard = self.io_stack.pop_frame().redirect()?;
|
||||||
|
|
||||||
let job = self.job_stack.curr_job_mut().unwrap();
|
let job = self.job_stack.curr_job_mut().unwrap();
|
||||||
|
|
||||||
match unsafe { fork()? } {
|
let child_logic = || -> ! {
|
||||||
ForkResult::Child => {
|
|
||||||
let cmd = &exec_args.cmd.0;
|
let cmd = &exec_args.cmd.0;
|
||||||
let span = exec_args.cmd.1;
|
let span = exec_args.cmd.1;
|
||||||
|
|
||||||
@@ -704,7 +810,14 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(e as i32)
|
exit(e as i32)
|
||||||
|
};
|
||||||
|
|
||||||
|
if no_fork {
|
||||||
|
child_logic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match unsafe { fork()? } {
|
||||||
|
ForkResult::Child => child_logic(),
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => {
|
||||||
// Close proc sub pipe fds - the child has inherited them
|
// Close proc sub pipe fds - the child has inherited them
|
||||||
// and will access them via /proc/self/fd/N. Keeping them
|
// and will access them via /proc/self/fd/N. Keeping them
|
||||||
@@ -730,6 +843,27 @@ impl Dispatcher {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn run_fork(&mut self, name: &str, f: impl FnOnce(&mut Self)) -> ShResult<()> {
|
||||||
|
match unsafe { fork()? } {
|
||||||
|
ForkResult::Child => {
|
||||||
|
f(self);
|
||||||
|
exit(state::get_status())
|
||||||
|
}
|
||||||
|
ForkResult::Parent { child } => {
|
||||||
|
write_jobs(|j| j.drain_registered_fds());
|
||||||
|
let job = self.job_stack.curr_job_mut().unwrap();
|
||||||
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
|
pgid
|
||||||
|
} else {
|
||||||
|
job.set_pgid(child);
|
||||||
|
child
|
||||||
|
};
|
||||||
|
let child_proc = ChildProc::new(child, Some(name), Some(child_pgid))?;
|
||||||
|
job.push_child(child_proc);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fn set_assignments(&self, assigns: Vec<Node>, behavior: AssignBehavior) -> ShResult<Vec<String>> {
|
fn set_assignments(&self, assigns: Vec<Node>, behavior: AssignBehavior) -> ShResult<Vec<String>> {
|
||||||
let mut new_env_vars = vec![];
|
let mut new_env_vars = vec![];
|
||||||
match behavior {
|
match behavior {
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ pub struct LexStream {
|
|||||||
source: Arc<String>,
|
source: Arc<String>,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
in_quote: bool,
|
in_quote: bool,
|
||||||
|
brc_grp_start: Option<usize>,
|
||||||
flags: LexFlags,
|
flags: LexFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ impl LexStream {
|
|||||||
source,
|
source,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
in_quote: false,
|
in_quote: false,
|
||||||
|
brc_grp_start: None,
|
||||||
flags,
|
flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,8 +222,10 @@ impl LexStream {
|
|||||||
pub fn set_in_brc_grp(&mut self, is: bool) {
|
pub fn set_in_brc_grp(&mut self, is: bool) {
|
||||||
if is {
|
if is {
|
||||||
self.flags |= LexFlags::IN_BRC_GRP;
|
self.flags |= LexFlags::IN_BRC_GRP;
|
||||||
|
self.brc_grp_start = Some(self.cursor);
|
||||||
} else {
|
} else {
|
||||||
self.flags &= !LexFlags::IN_BRC_GRP;
|
self.flags &= !LexFlags::IN_BRC_GRP;
|
||||||
|
self.brc_grp_start = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn next_is_cmd(&self) -> bool {
|
pub fn next_is_cmd(&self) -> bool {
|
||||||
@@ -698,6 +702,15 @@ impl Iterator for LexStream {
|
|||||||
return None;
|
return None;
|
||||||
} else {
|
} else {
|
||||||
// Return the EOI token
|
// Return the EOI token
|
||||||
|
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
|
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||||
|
self.flags |= LexFlags::STALE;
|
||||||
|
return Err(ShErr::full(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
"Unclosed brace group",
|
||||||
|
Span::new(start..self.cursor, self.source.clone()),
|
||||||
|
)).into();
|
||||||
|
}
|
||||||
let token = self.get_token(self.cursor..self.cursor, TkRule::EOI);
|
let token = self.get_token(self.cursor..self.cursor, TkRule::EOI);
|
||||||
self.flags |= LexFlags::STALE;
|
self.flags |= LexFlags::STALE;
|
||||||
return Some(Ok(token));
|
return Some(Ok(token));
|
||||||
@@ -728,6 +741,14 @@ impl Iterator for LexStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.cursor == self.source.len() {
|
if self.cursor == self.source.len() {
|
||||||
|
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
|
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||||
|
return Err(ShErr::full(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
"Unclosed brace group",
|
||||||
|
Span::new(start..self.cursor, self.source.clone()),
|
||||||
|
)).into();
|
||||||
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ bitflags! {
|
|||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct NdFlags: u32 {
|
pub struct NdFlags: u32 {
|
||||||
const BACKGROUND = 0b000001;
|
const BACKGROUND = 0b000001;
|
||||||
|
const FORK_BUILTINS = 0b000010;
|
||||||
|
const NO_FORK = 0b000100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1378,6 +1380,7 @@ impl ParseStream {
|
|||||||
redirs.push(redir);
|
redirs.push(redir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TkRule::Comment => { /* Skip comments in command position */ }
|
||||||
_ => unimplemented!("Unexpected token rule `{:?}` in parse_cmd()", tk.class),
|
_ => unimplemented!("Unexpected token rule `{:?}` in parse_cmd()", tk.class),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,6 +342,12 @@ impl DerefMut for IoStack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Vec<IoFrame>> for IoStack {
|
||||||
|
fn from(frames: Vec<IoFrame>) -> Self {
|
||||||
|
Self { stack: frames }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn borrow_fd<'f>(fd: i32) -> BorrowedFd<'f> {
|
pub fn borrow_fd<'f>(fd: i32) -> BorrowedFd<'f> {
|
||||||
unsafe { BorrowedFd::borrow_raw(fd) }
|
unsafe { BorrowedFd::borrow_raw(fd) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2790,6 +2790,12 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Verb::AcceptLineOrNewline => {
|
||||||
|
// If this verb has reached this function, it means we have incomplete input
|
||||||
|
// and therefore must insert a newline instead of accepting the input
|
||||||
|
self.push('\n');
|
||||||
|
self.cursor.add(1);
|
||||||
|
}
|
||||||
|
|
||||||
Verb::Complete
|
Verb::Complete
|
||||||
| Verb::EndOfFile
|
| Verb::EndOfFile
|
||||||
@@ -2800,7 +2806,6 @@ impl LineBuf {
|
|||||||
| Verb::VisualModeLine
|
| Verb::VisualModeLine
|
||||||
| Verb::VisualModeBlock
|
| Verb::VisualModeBlock
|
||||||
| Verb::CompleteBackward
|
| Verb::CompleteBackward
|
||||||
| Verb::AcceptLineOrNewline
|
|
||||||
| Verb::VisualModeSelectLast => self.apply_motion(motion), // Already handled logic for these
|
| Verb::VisualModeSelectLast => self.apply_motion(motion), // Already handled logic for these
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use vicmd::{CmdFlags, Motion, MotionCmd, RegisterName, Verb, VerbCmd, ViCmd};
|
|||||||
use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
|
use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVisual};
|
||||||
|
|
||||||
use crate::libsh::sys::TTY_FILENO;
|
use crate::libsh::sys::TTY_FILENO;
|
||||||
|
use crate::parse::lex::LexStream;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::state::read_shopts;
|
use crate::state::read_shopts;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -170,6 +171,31 @@ impl FernVi {
|
|||||||
self.history.reset();
|
self.history.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_submit(&mut self) -> ShResult<bool> {
|
||||||
|
let input = Arc::new(self.editor.buffer.clone());
|
||||||
|
self.editor.calc_indent_level();
|
||||||
|
let lex_result1 = LexStream::new(Arc::clone(&input), LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<_>>>();
|
||||||
|
let lex_result2 = LexStream::new(Arc::clone(&input), LexFlags::empty()).collect::<ShResult<Vec<_>>>();
|
||||||
|
let is_top_level = self.editor.auto_indent_level == 0;
|
||||||
|
|
||||||
|
let is_complete = match (lex_result1.is_err(), lex_result2.is_err()) {
|
||||||
|
(true, true) => {
|
||||||
|
return Err(lex_result2.unwrap_err());
|
||||||
|
}
|
||||||
|
(true, false) => {
|
||||||
|
return Err(lex_result1.unwrap_err());
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
(false, false) => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(is_complete && is_top_level)
|
||||||
|
}
|
||||||
|
|
||||||
/// Process any available input and return readline event
|
/// Process any available input and return readline event
|
||||||
/// This is non-blocking - returns Pending if no complete line yet
|
/// This is non-blocking - returns Pending if no complete line yet
|
||||||
pub fn process_input(&mut self) -> ShResult<ReadlineEvent> {
|
pub fn process_input(&mut self) -> ShResult<ReadlineEvent> {
|
||||||
@@ -247,7 +273,7 @@ impl FernVi {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.should_submit() {
|
if cmd.is_submit_action() && (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()); // Move the cursor to the very end
|
self.editor.cursor.set(self.editor.cursor_max()); // Move the cursor to the very end
|
||||||
self.print_line()?; // Redraw
|
self.print_line()?; // Redraw
|
||||||
@@ -686,9 +712,8 @@ pub fn marker_for(class: &TkRule) -> Option<Marker> {
|
|||||||
}
|
}
|
||||||
TkRule::Sep => Some(markers::CMD_SEP),
|
TkRule::Sep => Some(markers::CMD_SEP),
|
||||||
TkRule::Redir => Some(markers::REDIRECT),
|
TkRule::Redir => Some(markers::REDIRECT),
|
||||||
TkRule::CasePattern => Some(markers::CASE_PAT),
|
|
||||||
TkRule::Comment => Some(markers::COMMENT),
|
TkRule::Comment => Some(markers::COMMENT),
|
||||||
TkRule::Expanded { exp: _ } | TkRule::EOI | TkRule::SOI | TkRule::Null | TkRule::Str => None,
|
TkRule::Expanded { exp: _ } | TkRule::EOI | TkRule::SOI | TkRule::Null | TkRule::Str | TkRule::CasePattern => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,6 +807,11 @@ pub fn annotate_token(token: Tk) -> Vec<(usize, Marker)> {
|
|||||||
}
|
}
|
||||||
insertions.push((token.span.start, markers::SUBSH));
|
insertions.push((token.span.start, markers::SUBSH));
|
||||||
return insertions;
|
return insertions;
|
||||||
|
} else if token.class == TkRule::CasePattern {
|
||||||
|
insertions.push((token.span.end, markers::RESET));
|
||||||
|
insertions.push((token.span.end - 1, markers::CASE_PAT));
|
||||||
|
insertions.push((token.span.start, markers::OPERATOR));
|
||||||
|
return insertions;
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_raw = token.span.as_str();
|
let token_raw = token.span.as_str();
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ impl ViCmd {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|m| matches!(m.1, Motion::CharSearch(..)))
|
.is_some_and(|m| matches!(m.1, Motion::CharSearch(..)))
|
||||||
}
|
}
|
||||||
pub fn should_submit(&self) -> bool {
|
pub fn is_submit_action(&self) -> bool {
|
||||||
self
|
self
|
||||||
.verb
|
.verb
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
20
src/shopt.rs
20
src/shopt.rs
@@ -370,6 +370,7 @@ pub struct ShOptPrompt {
|
|||||||
pub comp_limit: usize,
|
pub comp_limit: usize,
|
||||||
pub highlight: bool,
|
pub highlight: bool,
|
||||||
pub auto_indent: bool,
|
pub auto_indent: bool,
|
||||||
|
pub linebreak_on_incomplete: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShOptPrompt {
|
impl ShOptPrompt {
|
||||||
@@ -420,6 +421,15 @@ impl ShOptPrompt {
|
|||||||
};
|
};
|
||||||
self.auto_indent = val;
|
self.auto_indent = val;
|
||||||
}
|
}
|
||||||
|
"linebreak_on_incomplete" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for linebreak_on_incomplete value",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
self.linebreak_on_incomplete = val;
|
||||||
|
}
|
||||||
"custom" => {
|
"custom" => {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
@@ -439,6 +449,7 @@ impl ShOptPrompt {
|
|||||||
"comp_limit",
|
"comp_limit",
|
||||||
"highlight",
|
"highlight",
|
||||||
"auto_indent",
|
"auto_indent",
|
||||||
|
"linebreak_on_incomplete",
|
||||||
"custom",
|
"custom",
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
@@ -484,6 +495,12 @@ impl ShOptPrompt {
|
|||||||
output.push_str(&format!("{}", self.auto_indent));
|
output.push_str(&format!("{}", self.auto_indent));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
|
"linebreak_on_incomplete" => {
|
||||||
|
let mut output =
|
||||||
|
String::from("Whether to automatically insert a newline when the input is incomplete\n");
|
||||||
|
output.push_str(&format!("{}", self.linebreak_on_incomplete));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
_ => Err(
|
_ => Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
@@ -499,6 +516,7 @@ impl ShOptPrompt {
|
|||||||
"comp_limit",
|
"comp_limit",
|
||||||
"highlight",
|
"highlight",
|
||||||
"auto_indent",
|
"auto_indent",
|
||||||
|
"linebreak_on_incomplete",
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -515,6 +533,7 @@ impl Display for ShOptPrompt {
|
|||||||
output.push(format!("comp_limit = {}", self.comp_limit));
|
output.push(format!("comp_limit = {}", self.comp_limit));
|
||||||
output.push(format!("highlight = {}", self.highlight));
|
output.push(format!("highlight = {}", self.highlight));
|
||||||
output.push(format!("auto_indent = {}", self.auto_indent));
|
output.push(format!("auto_indent = {}", self.auto_indent));
|
||||||
|
output.push(format!("linebreak_on_incomplete = {}", self.linebreak_on_incomplete));
|
||||||
|
|
||||||
let final_output = output.join("\n");
|
let final_output = output.join("\n");
|
||||||
|
|
||||||
@@ -530,6 +549,7 @@ impl Default for ShOptPrompt {
|
|||||||
comp_limit: 100,
|
comp_limit: 100,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
auto_indent: true,
|
auto_indent: true,
|
||||||
|
linebreak_on_incomplete: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ pub fn signals_pending() -> bool {
|
|||||||
SIGNALS.load(Ordering::SeqCst) != 0 || SHOULD_QUIT.load(Ordering::SeqCst)
|
SIGNALS.load(Ordering::SeqCst) != 0 || SHOULD_QUIT.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sigint_pending() -> bool {
|
||||||
|
SIGNALS.load(Ordering::SeqCst) & (1 << Signal::SIGINT as u64) != 0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_signals() -> ShResult<()> {
|
pub fn check_signals() -> ShResult<()> {
|
||||||
let pending = SIGNALS.swap(0, Ordering::SeqCst);
|
let pending = SIGNALS.swap(0, Ordering::SeqCst);
|
||||||
let got_signal = |sig: Signal| -> bool { pending & (1 << sig as u64) != 0 };
|
let got_signal = |sig: Signal| -> bool { pending & (1 << sig as u64) != 0 };
|
||||||
|
|||||||
Reference in New Issue
Block a user