Added 'read_key' builtin that allows widget scripts to handle input
This commit is contained in:
@@ -7,7 +7,7 @@ use ariadne::Fmt;
|
||||
|
||||
use crate::{
|
||||
builtin::{
|
||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
|
||||
},
|
||||
expand::{expand_aliases, glob_to_regex},
|
||||
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
||||
@@ -423,13 +423,16 @@ impl Dispatcher {
|
||||
|
||||
'outer: for block in case_blocks {
|
||||
let CaseNode { pattern, body } = block;
|
||||
let block_pattern_raw = pattern.span.as_str().trim_end_matches(')').trim();
|
||||
let block_pattern_raw = pattern.span.as_str().strip_suffix(')').unwrap_or(pattern.span.as_str()).trim();
|
||||
log::debug!("[case] raw block pattern: {:?}", block_pattern_raw);
|
||||
// Split at '|' to allow for multiple patterns like `foo|bar)`
|
||||
let block_patterns = block_pattern_raw.split('|');
|
||||
|
||||
for pattern in block_patterns {
|
||||
let pattern_regex = glob_to_regex(pattern, false);
|
||||
log::debug!("[case] testing input {:?} against pattern {:?} (regex: {:?})", pattern_raw, pattern, pattern_regex);
|
||||
if pattern_regex.is_match(&pattern_raw) {
|
||||
log::debug!("[case] matched pattern {:?}", pattern);
|
||||
for node in &body {
|
||||
s.dispatch_node(node.clone())?;
|
||||
}
|
||||
@@ -824,6 +827,7 @@ impl Dispatcher {
|
||||
"type" => intro::type_builtin(cmd),
|
||||
"getopts" => getopts(cmd),
|
||||
"keymap" => keymap::keymap(cmd),
|
||||
"read_key" => read::read_key(cmd),
|
||||
"true" | ":" => {
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
@@ -836,6 +840,9 @@ impl Dispatcher {
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
if !e.is_flow_control() {
|
||||
state::set_status(1);
|
||||
}
|
||||
Err(e.with_context(context).with_redirs(redir_guard))
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -860,7 +867,6 @@ impl Dispatcher {
|
||||
let no_fork = cmd.flags.contains(NdFlags::NO_FORK);
|
||||
|
||||
if argv.is_empty() {
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,26 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_input(input: &str) -> String {
|
||||
let mut chars = input.chars().peekable();
|
||||
let mut output = String::new();
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' if chars.peek() == Some(&'\n') => {
|
||||
chars.next();
|
||||
}
|
||||
'\r' => {
|
||||
if chars.peek() == Some(&'\n') {
|
||||
chars.next();
|
||||
}
|
||||
output.push('\n');
|
||||
}
|
||||
_ => output.push(ch),
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
impl LexStream {
|
||||
pub fn new(source: Arc<String>, flags: LexFlags) -> Self {
|
||||
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
||||
@@ -825,12 +845,15 @@ impl Iterator for LexStream {
|
||||
self.set_next_is_cmd(true);
|
||||
|
||||
while let Some(ch) = get_char(&self.source, self.cursor) {
|
||||
if is_hard_sep(ch) {
|
||||
// Combine consecutive separators into one, including whitespace
|
||||
self.cursor += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
match ch {
|
||||
'\\' => {
|
||||
self.cursor = (self.cursor + 2).min(self.source.len());
|
||||
}
|
||||
_ if is_hard_sep(ch) => {
|
||||
self.cursor += 1;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
self.get_token(ch_idx..self.cursor, TkRule::Sep)
|
||||
}
|
||||
@@ -1060,6 +1083,7 @@ pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
|
||||
|
||||
pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
||||
let mut pos = 0;
|
||||
let mut qt_state = QuoteState::default();
|
||||
while let Some(ch) = chars.next() {
|
||||
pos += ch.len_utf8();
|
||||
match ch {
|
||||
@@ -1069,8 +1093,14 @@ pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
|
||||
pos += esc.len_utf8();
|
||||
}
|
||||
}
|
||||
')' => return Some(pos),
|
||||
'(' => return None,
|
||||
'\'' => {
|
||||
qt_state.toggle_single();
|
||||
}
|
||||
'"' => {
|
||||
qt_state.toggle_double();
|
||||
}
|
||||
')' if qt_state.outside() => return Some(pos),
|
||||
'(' if qt_state.outside() => return None,
|
||||
_ => { /* continue */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ use crate::{
|
||||
libsh::{
|
||||
error::{ShErr, ShErrKind, ShResult, last_color, next_color},
|
||||
utils::{NodeVecUtils, TkVecUtils},
|
||||
},
|
||||
prelude::*,
|
||||
procio::IoMode,
|
||||
}, parse::lex::clean_input, prelude::*, procio::IoMode
|
||||
};
|
||||
|
||||
pub mod execute;
|
||||
@@ -52,6 +50,11 @@ pub struct ParsedSrc {
|
||||
|
||||
impl ParsedSrc {
|
||||
pub fn new(src: Arc<String>) -> Self {
|
||||
let src = if src.contains("\\\n") || src.contains('\r') {
|
||||
Arc::new(clean_input(&src))
|
||||
} else {
|
||||
src
|
||||
};
|
||||
Self {
|
||||
src,
|
||||
name: "<stdin>".into(),
|
||||
|
||||
Reference in New Issue
Block a user