implemented read command for ex mode
This commit is contained in:
@@ -4,14 +4,25 @@ use ariadne::Span as ASpan;
|
|||||||
use nix::libc::STDIN_FILENO;
|
use nix::libc::STDIN_FILENO;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{error::{ShErr, ShErrKind, ShResult}, guards::RawModeGuard}, parse::{NdRule, Node, Redir, RedirType, execute::{exec_input, prepare_argv}, lex::{QuoteState, Span}}, procio::{IoFrame, IoMode}, readline::{complete::ScoredCandidate, markers}, state
|
libsh::{
|
||||||
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
|
guards::RawModeGuard,
|
||||||
|
},
|
||||||
|
parse::{
|
||||||
|
NdRule, Node, Redir, RedirType,
|
||||||
|
execute::{exec_input, prepare_argv},
|
||||||
|
lex::{QuoteState, Span},
|
||||||
|
},
|
||||||
|
procio::{IoFrame, IoMode},
|
||||||
|
readline::{complete::ScoredCandidate, markers},
|
||||||
|
state,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TAG_SEQ: &str = "\x1b[1;33m"; // bold yellow — searchable tags
|
const TAG_SEQ: &str = "\x1b[1;33m"; // bold yellow — searchable tags
|
||||||
const REF_SEQ: &str = "\x1b[4;36m"; // underline cyan — cross-references
|
const REF_SEQ: &str = "\x1b[4;36m"; // underline cyan — cross-references
|
||||||
const RESET_SEQ: &str = "\x1b[0m";
|
const RESET_SEQ: &str = "\x1b[0m";
|
||||||
const HEADER_SEQ: &str = "\x1b[1;35m"; // bold magenta — section headers
|
const HEADER_SEQ: &str = "\x1b[1;35m"; // bold magenta — section headers
|
||||||
const CODE_SEQ: &str = "\x1b[32m"; // green — inline code
|
const CODE_SEQ: &str = "\x1b[32m"; // green — inline code
|
||||||
const KEYWORD_2_SEQ: &str = "\x1b[1;32m"; // bold green — {keyword}
|
const KEYWORD_2_SEQ: &str = "\x1b[1;32m"; // bold green — {keyword}
|
||||||
const KEYWORD_3_SEQ: &str = "\x1b[3;37m"; // italic white — [optional]
|
const KEYWORD_3_SEQ: &str = "\x1b[3;37m"; // italic white — [optional]
|
||||||
|
|
||||||
@@ -27,265 +38,263 @@ pub fn help(node: Node) -> ShResult<()> {
|
|||||||
let mut argv = prepare_argv(argv)?.into_iter().peekable();
|
let mut argv = prepare_argv(argv)?.into_iter().peekable();
|
||||||
let help = argv.next().unwrap(); // drop 'help'
|
let help = argv.next().unwrap(); // drop 'help'
|
||||||
|
|
||||||
// Join all of the word-split arguments into a single string
|
// Join all of the word-split arguments into a single string
|
||||||
// Preserve the span too
|
// Preserve the span too
|
||||||
let (topic, span) = if argv.peek().is_none() {
|
let (topic, span) = if argv.peek().is_none() {
|
||||||
("help.txt".to_string(), help.1)
|
("help.txt".to_string(), help.1)
|
||||||
} else {
|
} else {
|
||||||
argv.fold((String::new(), Span::default()), |mut acc, arg| {
|
argv.fold((String::new(), Span::default()), |mut acc, arg| {
|
||||||
if acc.1 == Span::default() {
|
if acc.1 == Span::default() {
|
||||||
acc.1 = arg.1.clone();
|
acc.1 = arg.1.clone();
|
||||||
} else {
|
} else {
|
||||||
let new_end = arg.1.end();
|
let new_end = arg.1.end();
|
||||||
let start = acc.1.start();
|
let start = acc.1.start();
|
||||||
acc.1.set_range(start..new_end);
|
acc.1.set_range(start..new_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
if acc.0.is_empty() {
|
if acc.0.is_empty() {
|
||||||
acc.0 = arg.0;
|
acc.0 = arg.0;
|
||||||
} else {
|
} else {
|
||||||
acc.0 = acc.0 + &format!(" {}",arg.0);
|
acc.0 = acc.0 + &format!(" {}", arg.0);
|
||||||
}
|
}
|
||||||
acc
|
acc
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let hpath = env::var("SHED_HPATH").unwrap_or_default();
|
let hpath = env::var("SHED_HPATH").unwrap_or_default();
|
||||||
|
|
||||||
for path in hpath.split(':') {
|
for path in hpath.split(':') {
|
||||||
let path = Path::new(&path).join(&topic);
|
let path = Path::new(&path).join(&topic);
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let Ok(contents) = std::fs::read_to_string(&path) else {
|
let Ok(contents) = std::fs::read_to_string(&path) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let filename = path.file_stem()
|
let filename = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let unescaped = unescape_help(&contents);
|
let unescaped = unescape_help(&contents);
|
||||||
let expanded = expand_help(&unescaped);
|
let expanded = expand_help(&unescaped);
|
||||||
open_help(&expanded, None, Some(filename))?;
|
open_help(&expanded, None, Some(filename))?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// didn't find an exact filename match, its probably a tag search
|
// didn't find an exact filename match, its probably a tag search
|
||||||
for path in hpath.split(':') {
|
for path in hpath.split(':') {
|
||||||
let path = Path::new(path);
|
let path = Path::new(path);
|
||||||
if let Ok(entries) = path.read_dir() {
|
if let Ok(entries) = path.read_dir() {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let Ok(entry) = entry else { continue };
|
let Ok(entry) = entry else { continue };
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let filename = path.file_stem()
|
let filename = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(contents) = std::fs::read_to_string(&path) else {
|
let Ok(contents) = std::fs::read_to_string(&path) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let unescaped = unescape_help(&contents);
|
let unescaped = unescape_help(&contents);
|
||||||
let expanded = expand_help(&unescaped);
|
let expanded = expand_help(&unescaped);
|
||||||
let tags = read_tags(&expanded);
|
let tags = read_tags(&expanded);
|
||||||
|
|
||||||
for (tag, line) in &tags {
|
for (tag, line) in &tags {}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((matched_tag, line)) = get_best_match(&topic, &tags) {
|
if let Some((matched_tag, line)) = get_best_match(&topic, &tags) {
|
||||||
open_help(&expanded, Some(line), Some(filename))?;
|
open_help(&expanded, Some(line), Some(filename))?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
Err(ShErr::at(
|
Err(ShErr::at(
|
||||||
ShErrKind::NotFound,
|
ShErrKind::NotFound,
|
||||||
span,
|
span,
|
||||||
"No relevant help page found for this topic",
|
"No relevant help page found for this topic",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_help(content: &str, line: Option<usize>, file_name: Option<String>) -> ShResult<()> {
|
pub fn open_help(content: &str, line: Option<usize>, file_name: Option<String>) -> ShResult<()> {
|
||||||
let pager = env::var("PAGER").unwrap_or("less -R".into());
|
let pager = env::var("PAGER").unwrap_or("less -R".into());
|
||||||
let line_arg = line.map(|ln| format!("+{ln}")).unwrap_or_default();
|
let line_arg = line.map(|ln| format!("+{ln}")).unwrap_or_default();
|
||||||
let prompt_arg = file_name.map(|name| format!("-Ps'{name}'")).unwrap_or_default();
|
let prompt_arg = file_name
|
||||||
|
.map(|name| format!("-Ps'{name}'"))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut tmp = tempfile::NamedTempFile::new()?;
|
let mut tmp = tempfile::NamedTempFile::new()?;
|
||||||
let tmp_path = tmp.path().to_string_lossy().to_string();
|
let tmp_path = tmp.path().to_string_lossy().to_string();
|
||||||
tmp.write_all(content.as_bytes())?;
|
tmp.write_all(content.as_bytes())?;
|
||||||
tmp.flush()?;
|
tmp.flush()?;
|
||||||
|
|
||||||
RawModeGuard::with_cooked_mode(|| {
|
RawModeGuard::with_cooked_mode(|| {
|
||||||
exec_input(
|
exec_input(
|
||||||
format!("{pager} {line_arg} {prompt_arg} {tmp_path}"),
|
format!("{pager} {line_arg} {prompt_arg} {tmp_path}"),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
Some("help".into()),
|
Some("help".into()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_best_match(topic: &str, tags: &[(String, usize)]) -> Option<(String, usize)> {
|
pub fn get_best_match(topic: &str, tags: &[(String, usize)]) -> Option<(String, usize)> {
|
||||||
let mut candidates: Vec<_> = tags.iter()
|
let mut candidates: Vec<_> = tags
|
||||||
.map(|(tag,line)| (ScoredCandidate::new(tag.to_string()), *line))
|
.iter()
|
||||||
.collect();
|
.map(|(tag, line)| (ScoredCandidate::new(tag.to_string()), *line))
|
||||||
|
.collect();
|
||||||
|
|
||||||
for (cand,_) in candidates.iter_mut() {
|
for (cand, _) in candidates.iter_mut() {
|
||||||
cand.fuzzy_score(topic);
|
cand.fuzzy_score(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates.retain(|(c,_)| c.score.unwrap_or(i32::MIN) > i32::MIN);
|
candidates.retain(|(c, _)| c.score.unwrap_or(i32::MIN) > i32::MIN);
|
||||||
candidates.sort_by_key(|(c,_)| c.score.unwrap_or(i32::MIN));
|
candidates.sort_by_key(|(c, _)| c.score.unwrap_or(i32::MIN));
|
||||||
|
|
||||||
candidates.first().map(|(c,line)| (c.content.clone(), *line))
|
candidates
|
||||||
|
.first()
|
||||||
|
.map(|(c, line)| (c.content.clone(), *line))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_tags(raw: &str) -> Vec<(String, usize)> {
|
pub fn read_tags(raw: &str) -> Vec<(String, usize)> {
|
||||||
let mut tags = vec![];
|
let mut tags = vec![];
|
||||||
|
|
||||||
for (line_num, line) in raw.lines().enumerate() {
|
for (line_num, line) in raw.lines().enumerate() {
|
||||||
let mut rest = line;
|
let mut rest = line;
|
||||||
|
|
||||||
while let Some(pos) = rest.find(TAG_SEQ) {
|
while let Some(pos) = rest.find(TAG_SEQ) {
|
||||||
let after_seq = &rest[pos + TAG_SEQ.len()..];
|
let after_seq = &rest[pos + TAG_SEQ.len()..];
|
||||||
if let Some(end) = after_seq.find(RESET_SEQ) {
|
if let Some(end) = after_seq.find(RESET_SEQ) {
|
||||||
let tag = &after_seq[..end];
|
let tag = &after_seq[..end];
|
||||||
tags.push((tag.to_string(), line_num + 1));
|
tags.push((tag.to_string(), line_num + 1));
|
||||||
rest = &after_seq[end + RESET_SEQ.len()..];
|
rest = &after_seq[end + RESET_SEQ.len()..];
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags
|
tags
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_help(raw: &str) -> String {
|
pub fn expand_help(raw: &str) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut chars = raw.chars();
|
let mut chars = raw.chars();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
markers::RESET => result.push_str(RESET_SEQ),
|
markers::RESET => result.push_str(RESET_SEQ),
|
||||||
markers::TAG => result.push_str(TAG_SEQ),
|
markers::TAG => result.push_str(TAG_SEQ),
|
||||||
markers::REFERENCE => result.push_str(REF_SEQ),
|
markers::REFERENCE => result.push_str(REF_SEQ),
|
||||||
markers::HEADER => result.push_str(HEADER_SEQ),
|
markers::HEADER => result.push_str(HEADER_SEQ),
|
||||||
markers::CODE => result.push_str(CODE_SEQ),
|
markers::CODE => result.push_str(CODE_SEQ),
|
||||||
markers::KEYWORD_2 => result.push_str(KEYWORD_2_SEQ),
|
markers::KEYWORD_2 => result.push_str(KEYWORD_2_SEQ),
|
||||||
markers::KEYWORD_3 => result.push_str(KEYWORD_3_SEQ),
|
markers::KEYWORD_3 => result.push_str(KEYWORD_3_SEQ),
|
||||||
_ => result.push(ch),
|
_ => result.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unescape_help(raw: &str) -> String {
|
pub fn unescape_help(raw: &str) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut chars = raw.chars().peekable();
|
let mut chars = raw.chars().peekable();
|
||||||
let mut qt_state = QuoteState::default();
|
let mut qt_state = QuoteState::default();
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
if let Some(next_ch) = chars.next() {
|
if let Some(next_ch) = chars.next() {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'\n' => {
|
'\n' => {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
qt_state = QuoteState::default();
|
qt_state = QuoteState::default();
|
||||||
}
|
}
|
||||||
'"' => {
|
'"' => {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
qt_state.toggle_double();
|
qt_state.toggle_double();
|
||||||
}
|
}
|
||||||
'\'' => {
|
'\'' => {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
qt_state.toggle_single();
|
qt_state.toggle_single();
|
||||||
}
|
}
|
||||||
_ if qt_state.in_quote() || chars.peek().is_none_or(|ch| ch.is_whitespace()) => {
|
_ if qt_state.in_quote() || chars.peek().is_none_or(|ch| ch.is_whitespace()) => {
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
}
|
}
|
||||||
'*' => {
|
'*' => {
|
||||||
result.push(markers::TAG);
|
result.push(markers::TAG);
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
if next_ch == '*' {
|
if next_ch == '*' {
|
||||||
result.push(markers::RESET);
|
result.push(markers::RESET);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'|' => {
|
'|' => {
|
||||||
result.push(markers::REFERENCE);
|
result.push(markers::REFERENCE);
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
if next_ch == '|' {
|
if next_ch == '|' {
|
||||||
result.push(markers::RESET);
|
result.push(markers::RESET);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'#' => {
|
'#' => {
|
||||||
result.push(markers::HEADER);
|
result.push(markers::HEADER);
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
if next_ch == '#' {
|
if next_ch == '#' {
|
||||||
result.push(markers::RESET);
|
result.push(markers::RESET);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'`' => {
|
'`' => {
|
||||||
result.push(markers::CODE);
|
result.push(markers::CODE);
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
if next_ch == '`' {
|
if next_ch == '`' {
|
||||||
result.push(markers::RESET);
|
result.push(markers::RESET);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'{' => {
|
'{' => {
|
||||||
result.push(markers::KEYWORD_2);
|
result.push(markers::KEYWORD_2);
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
if next_ch == '}' {
|
if next_ch == '}' {
|
||||||
result.push(markers::RESET);
|
result.push(markers::RESET);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'[' => {
|
'[' => {
|
||||||
result.push(markers::KEYWORD_3);
|
result.push(markers::KEYWORD_3);
|
||||||
while let Some(next_ch) = chars.next() {
|
while let Some(next_ch) = chars.next() {
|
||||||
if next_ch == ']' {
|
if next_ch == ']' {
|
||||||
result.push(markers::RESET);
|
result.push(markers::RESET);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
result.push(next_ch);
|
result.push(next_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => result.push(ch),
|
_ => result.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub mod eval;
|
|||||||
pub mod exec;
|
pub mod exec;
|
||||||
pub mod flowctl;
|
pub mod flowctl;
|
||||||
pub mod getopts;
|
pub mod getopts;
|
||||||
|
pub mod help;
|
||||||
pub mod intro;
|
pub mod intro;
|
||||||
pub mod jobctl;
|
pub mod jobctl;
|
||||||
pub mod keymap;
|
pub mod keymap;
|
||||||
@@ -25,7 +26,6 @@ pub mod source;
|
|||||||
pub mod test; // [[ ]] thing
|
pub mod test; // [[ ]] thing
|
||||||
pub mod trap;
|
pub mod trap;
|
||||||
pub mod varcmds;
|
pub mod varcmds;
|
||||||
pub mod help;
|
|
||||||
|
|
||||||
pub const BUILTINS: [&str; 51] = [
|
pub const BUILTINS: [&str; 51] = [
|
||||||
"echo", "cd", "read", "export", "local", "pwd", "source", ".", "shift", "jobs", "fg", "bg",
|
"echo", "cd", "read", "export", "local", "pwd", "source", ".", "shift", "jobs", "fg", "bg",
|
||||||
|
|||||||
125
src/expand.rs
125
src/expand.rs
@@ -474,32 +474,31 @@ pub fn expand_raw(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
|
|||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
markers::TILDE_SUB => {
|
markers::TILDE_SUB => {
|
||||||
let mut username = String::new();
|
let mut username = String::new();
|
||||||
while chars.peek().is_some_and(|ch| *ch != '/') {
|
while chars.peek().is_some_and(|ch| *ch != '/') {
|
||||||
let ch = chars.next().unwrap();
|
let ch = chars.next().unwrap();
|
||||||
username.push(ch);
|
username.push(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
let home = if username.is_empty() {
|
let home = if username.is_empty() {
|
||||||
// standard '~' expansion
|
// standard '~' expansion
|
||||||
env::var("HOME").unwrap_or_default()
|
env::var("HOME").unwrap_or_default()
|
||||||
}
|
} else if let Ok(result) = User::from_name(&username)
|
||||||
else if let Ok(result) = User::from_name(&username)
|
&& let Some(user) = result
|
||||||
&& let Some(user) = result {
|
{
|
||||||
// username expansion like '~user'
|
// username expansion like '~user'
|
||||||
user.dir.to_string_lossy().to_string()
|
user.dir.to_string_lossy().to_string()
|
||||||
}
|
} else if let Ok(id) = username.parse::<u32>()
|
||||||
else if let Ok(id) = username.parse::<u32>()
|
&& let Ok(result) = User::from_uid(Uid::from_raw(id))
|
||||||
&& let Ok(result) = User::from_uid(Uid::from_raw(id))
|
&& let Some(user) = result
|
||||||
&& let Some(user) = result {
|
{
|
||||||
// uid expansion like '~1000'
|
// uid expansion like '~1000'
|
||||||
// shed only feature btw B)
|
// shed only feature btw B)
|
||||||
user.dir.to_string_lossy().to_string()
|
user.dir.to_string_lossy().to_string()
|
||||||
}
|
} else {
|
||||||
else {
|
// no match, use literal
|
||||||
// no match, use literal
|
format!("~{username}")
|
||||||
format!("~{username}")
|
};
|
||||||
};
|
|
||||||
|
|
||||||
result.push_str(&home);
|
result.push_str(&home);
|
||||||
}
|
}
|
||||||
@@ -1584,10 +1583,10 @@ pub fn unescape_math(raw: &str) -> String {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ParamExp {
|
pub enum ParamExp {
|
||||||
Len, // #var_name
|
Len, // #var_name
|
||||||
ToUpperFirst, // ^var_name
|
ToUpperFirst, // ^var_name
|
||||||
ToUpperAll, // ^^var_name
|
ToUpperAll, // ^^var_name
|
||||||
ToLowerFirst, // ,var_name
|
ToLowerFirst, // ,var_name
|
||||||
ToLowerAll, // ,,var_name
|
ToLowerAll, // ,,var_name
|
||||||
DefaultUnsetOrNull(String), // :-
|
DefaultUnsetOrNull(String), // :-
|
||||||
DefaultUnset(String), // -
|
DefaultUnset(String), // -
|
||||||
SetDefaultUnsetOrNull(String), // :=
|
SetDefaultUnsetOrNull(String), // :=
|
||||||
@@ -1623,10 +1622,18 @@ impl FromStr for ParamExp {
|
|||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
if s == "^^" { return Ok(ToUpperAll) }
|
if s == "^^" {
|
||||||
if s == "^" { return Ok(ToUpperFirst) }
|
return Ok(ToUpperAll);
|
||||||
if s == ",," { return Ok(ToLowerAll) }
|
}
|
||||||
if s == "," { return Ok(ToLowerFirst) }
|
if s == "^" {
|
||||||
|
return Ok(ToUpperFirst);
|
||||||
|
}
|
||||||
|
if s == ",," {
|
||||||
|
return Ok(ToLowerAll);
|
||||||
|
}
|
||||||
|
if s == "," {
|
||||||
|
return Ok(ToLowerFirst);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle indirect var expansion: ${!var}
|
// Handle indirect var expansion: ${!var}
|
||||||
if let Some(var) = s.strip_prefix('!') {
|
if let Some(var) = s.strip_prefix('!') {
|
||||||
@@ -1745,32 +1752,32 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
if let Ok(expansion) = rest.parse::<ParamExp>() {
|
if let Ok(expansion) = rest.parse::<ParamExp>() {
|
||||||
match expansion {
|
match expansion {
|
||||||
ParamExp::Len => unreachable!(),
|
ParamExp::Len => unreachable!(),
|
||||||
ParamExp::ToUpperAll => {
|
ParamExp::ToUpperAll => {
|
||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
Ok(value.to_uppercase())
|
Ok(value.to_uppercase())
|
||||||
}
|
}
|
||||||
ParamExp::ToUpperFirst => {
|
ParamExp::ToUpperFirst => {
|
||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let mut chars = value.chars();
|
let mut chars = value.chars();
|
||||||
let first = chars.next()
|
let first = chars
|
||||||
.map(|c| c.to_uppercase()
|
.next()
|
||||||
.to_string())
|
.map(|c| c.to_uppercase().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(first + chars.as_str())
|
Ok(first + chars.as_str())
|
||||||
|
}
|
||||||
}
|
ParamExp::ToLowerAll => {
|
||||||
ParamExp::ToLowerAll => {
|
let value = vars.get_var(&var_name);
|
||||||
let value = vars.get_var(&var_name);
|
Ok(value.to_lowercase())
|
||||||
Ok(value.to_lowercase())
|
}
|
||||||
}
|
ParamExp::ToLowerFirst => {
|
||||||
ParamExp::ToLowerFirst => {
|
let value = vars.get_var(&var_name);
|
||||||
let value = vars.get_var(&var_name);
|
let mut chars = value.chars();
|
||||||
let mut chars = value.chars();
|
let first = chars
|
||||||
let first = chars.next()
|
.next()
|
||||||
.map(|c| c.to_lowercase().to_string())
|
.map(|c| c.to_lowercase().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(first + chars.as_str())
|
Ok(first + chars.as_str())
|
||||||
}
|
}
|
||||||
ParamExp::DefaultUnsetOrNull(default) => {
|
ParamExp::DefaultUnsetOrNull(default) => {
|
||||||
match vars.try_get_var(&var_name).filter(|v| !v.is_empty()) {
|
match vars.try_get_var(&var_name).filter(|v| !v.is_empty()) {
|
||||||
Some(val) => Ok(val),
|
Some(val) => Ok(val),
|
||||||
|
|||||||
57
src/jobs.rs
57
src/jobs.rs
@@ -598,26 +598,29 @@ impl Job {
|
|||||||
.map(|chld| chld.stat())
|
.map(|chld| chld.stat())
|
||||||
.collect::<Vec<WtStat>>()
|
.collect::<Vec<WtStat>>()
|
||||||
}
|
}
|
||||||
pub fn pipe_status(stats: &[WtStat]) -> Option<Vec<i32>> {
|
pub fn pipe_status(stats: &[WtStat]) -> Option<Vec<i32>> {
|
||||||
if stats.iter()
|
if stats.iter().any(|stat| {
|
||||||
.any(|stat| matches!(stat, WtStat::StillAlive | WtStat::Continued(_) | WtStat::PtraceSyscall(_)))
|
matches!(
|
||||||
|| stats.len() <= 1 {
|
stat,
|
||||||
return None;
|
WtStat::StillAlive | WtStat::Continued(_) | WtStat::PtraceSyscall(_)
|
||||||
}
|
)
|
||||||
Some(stats.iter()
|
}) || stats.len() <= 1
|
||||||
.map(|stat| {
|
{
|
||||||
match stat {
|
return None;
|
||||||
WtStat::Exited(_, code) => *code,
|
}
|
||||||
WtStat::Signaled(_, signal, _) => SIG_EXIT_OFFSET + *signal as i32,
|
Some(
|
||||||
WtStat::Stopped(_, signal) => SIG_EXIT_OFFSET + *signal as i32,
|
stats
|
||||||
WtStat::PtraceEvent(_, signal, _) => SIG_EXIT_OFFSET + *signal as i32,
|
.iter()
|
||||||
WtStat::PtraceSyscall(_) |
|
.map(|stat| match stat {
|
||||||
WtStat::Continued(_) |
|
WtStat::Exited(_, code) => *code,
|
||||||
WtStat::StillAlive => unreachable!()
|
WtStat::Signaled(_, signal, _) => SIG_EXIT_OFFSET + *signal as i32,
|
||||||
}
|
WtStat::Stopped(_, signal) => SIG_EXIT_OFFSET + *signal as i32,
|
||||||
})
|
WtStat::PtraceEvent(_, signal, _) => SIG_EXIT_OFFSET + *signal as i32,
|
||||||
.collect())
|
WtStat::PtraceSyscall(_) | WtStat::Continued(_) | WtStat::StillAlive => unreachable!(),
|
||||||
}
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
pub fn get_pids(&self) -> Vec<Pid> {
|
pub fn get_pids(&self) -> Vec<Pid> {
|
||||||
self
|
self
|
||||||
.children
|
.children
|
||||||
@@ -877,14 +880,14 @@ pub fn wait_fg(job: Job, interactive: bool) -> ShResult<()> {
|
|||||||
_ => { /* Do nothing */ }
|
_ => { /* Do nothing */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(pipe_status) = Job::pipe_status(&statuses) {
|
if let Some(pipe_status) = Job::pipe_status(&statuses) {
|
||||||
let pipe_status = pipe_status
|
let pipe_status = pipe_status
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect::<VecDeque<String>>();
|
.collect::<VecDeque<String>>();
|
||||||
|
|
||||||
write_vars(|v| v.set_var("PIPESTATUS", VarKind::Arr(pipe_status), VarFlags::NONE))?;
|
write_vars(|v| v.set_var("PIPESTATUS", VarKind::Arr(pipe_status), VarFlags::NONE))?;
|
||||||
}
|
}
|
||||||
// If job wasn't stopped (moved to bg), clear the fg slot
|
// If job wasn't stopped (moved to bg), clear the fg slot
|
||||||
if !was_stopped {
|
if !was_stopped {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
|
|||||||
64
src/main.rs
64
src/main.rs
@@ -40,7 +40,8 @@ use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
|
|||||||
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
use crate::readline::{Prompt, ReadlineEvent, ShedVi};
|
||||||
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
AutoCmdKind, read_logic, read_shopts, source_env, source_login, source_rc, write_jobs, write_meta, write_shopts
|
AutoCmdKind, read_logic, read_shopts, source_env, source_login, source_rc, write_jobs,
|
||||||
|
write_meta, write_shopts,
|
||||||
};
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use state::write_vars;
|
use state::write_vars;
|
||||||
@@ -59,8 +60,8 @@ struct ShedArgs {
|
|||||||
#[arg(short)]
|
#[arg(short)]
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
|
|
||||||
#[arg(short)]
|
#[arg(short)]
|
||||||
stdin: bool,
|
stdin: bool,
|
||||||
|
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
login_shell: bool,
|
login_shell: bool,
|
||||||
@@ -128,16 +129,16 @@ fn main() -> ExitCode {
|
|||||||
unsafe { env::set_var("SHLVL", "1") };
|
unsafe { env::set_var("SHLVL", "1") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = source_env() {
|
if let Err(e) = source_env() {
|
||||||
e.print_error();
|
e.print_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = if let Some(cmd) = args.command {
|
if let Err(e) = if let Some(cmd) = args.command {
|
||||||
exec_dash_c(cmd)
|
exec_dash_c(cmd)
|
||||||
} else if args.stdin || !isatty(STDIN_FILENO).unwrap_or(false) {
|
} else if args.stdin || !isatty(STDIN_FILENO).unwrap_or(false) {
|
||||||
read_commands(args.script_args)
|
read_commands(args.script_args)
|
||||||
} else if !args.script_args.is_empty() {
|
} else if !args.script_args.is_empty() {
|
||||||
let path = args.script_args.remove(0);
|
let path = args.script_args.remove(0);
|
||||||
run_script(path, args.script_args)
|
run_script(path, args.script_args)
|
||||||
} else {
|
} else {
|
||||||
let res = shed_interactive(args);
|
let res = shed_interactive(args);
|
||||||
@@ -161,29 +162,29 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn read_commands(args: Vec<String>) -> ShResult<()> {
|
fn read_commands(args: Vec<String>) -> ShResult<()> {
|
||||||
let mut input = vec![];
|
let mut input = vec![];
|
||||||
let mut read_buf = [0u8;4096];
|
let mut read_buf = [0u8; 4096];
|
||||||
loop {
|
loop {
|
||||||
match read(STDIN_FILENO, &mut read_buf) {
|
match read(STDIN_FILENO, &mut read_buf) {
|
||||||
Ok(0) => break,
|
Ok(0) => break,
|
||||||
Ok(n) => input.extend_from_slice(&read_buf[..n]),
|
Ok(n) => input.extend_from_slice(&read_buf[..n]),
|
||||||
Err(Errno::EINTR) => continue,
|
Err(Errno::EINTR) => continue,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
QUIT_CODE.store(1, Ordering::SeqCst);
|
QUIT_CODE.store(1, Ordering::SeqCst);
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::CleanExit(1),
|
ShErrKind::CleanExit(1),
|
||||||
format!("error reading from stdin: {e}"),
|
format!("error reading from stdin: {e}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let commands = String::from_utf8_lossy(&input).to_string();
|
let commands = String::from_utf8_lossy(&input).to_string();
|
||||||
for arg in args {
|
for arg in args {
|
||||||
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
exec_input(commands, None, false, None)
|
exec_input(commands, None, false, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) -> ShResult<()> {
|
||||||
@@ -221,10 +222,11 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
|||||||
let _raw_mode = raw_mode(); // sets raw mode, restores termios on drop
|
let _raw_mode = raw_mode(); // sets raw mode, restores termios on drop
|
||||||
sig_setup(args.login_shell);
|
sig_setup(args.login_shell);
|
||||||
|
|
||||||
if args.login_shell
|
if args.login_shell
|
||||||
&& let Err(e) = source_login() {
|
&& let Err(e) = source_login()
|
||||||
e.print_error();
|
{
|
||||||
}
|
e.print_error();
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = source_rc() {
|
if let Err(e) = source_rc() {
|
||||||
e.print_error();
|
e.print_error();
|
||||||
|
|||||||
@@ -8,7 +8,30 @@ use ariadne::Fmt;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, autocmd::autocmd, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, help::help, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, resource::{ulimit, umask_builtin}, seek::seek, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}
|
alias::{alias, unalias},
|
||||||
|
arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate},
|
||||||
|
autocmd::autocmd,
|
||||||
|
cd::cd,
|
||||||
|
complete::{compgen_builtin, complete_builtin},
|
||||||
|
dirstack::{dirs, popd, pushd},
|
||||||
|
echo::echo,
|
||||||
|
eval, exec,
|
||||||
|
flowctl::flowctl,
|
||||||
|
getopts::getopts,
|
||||||
|
help::help,
|
||||||
|
intro,
|
||||||
|
jobctl::{self, JobBehavior, continue_job, disown, jobs},
|
||||||
|
keymap, map,
|
||||||
|
pwd::pwd,
|
||||||
|
read::{self, read_builtin},
|
||||||
|
resource::{ulimit, umask_builtin},
|
||||||
|
seek::seek,
|
||||||
|
shift::shift,
|
||||||
|
shopt::shopt,
|
||||||
|
source::source,
|
||||||
|
test::double_bracket_test,
|
||||||
|
trap::{TrapTarget, trap},
|
||||||
|
varcmds::{export, local, readonly, unset},
|
||||||
},
|
},
|
||||||
expand::{expand_aliases, expand_case_pattern, glob_to_regex},
|
expand::{expand_aliases, expand_case_pattern, glob_to_regex},
|
||||||
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
|
||||||
@@ -995,7 +1018,7 @@ impl Dispatcher {
|
|||||||
"ulimit" => ulimit(cmd),
|
"ulimit" => ulimit(cmd),
|
||||||
"umask" => umask_builtin(cmd),
|
"umask" => umask_builtin(cmd),
|
||||||
"seek" => seek(cmd),
|
"seek" => seek(cmd),
|
||||||
"help" => help(cmd),
|
"help" => help(cmd),
|
||||||
"true" | ":" => {
|
"true" | ":" => {
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1084,12 +1084,10 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if !from_func_def {
|
if !from_func_def {
|
||||||
self.parse_redir(&mut redirs, &mut node_tks)?;
|
self.parse_redir(&mut redirs, &mut node_tks)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
class: NdRule::BraceGrp { body },
|
class: NdRule::BraceGrp { body },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
|
|||||||
@@ -115,11 +115,14 @@ impl IoMode {
|
|||||||
pub fn buffer(tgt_fd: RawFd, buf: String, flags: TkFlags) -> ShResult<Self> {
|
pub fn buffer(tgt_fd: RawFd, buf: String, flags: TkFlags) -> ShResult<Self> {
|
||||||
Ok(Self::Buffer { tgt_fd, buf, flags })
|
Ok(Self::Buffer { tgt_fd, buf, flags })
|
||||||
}
|
}
|
||||||
pub fn loaded_pipe(tgt_fd: RawFd, buf: &[u8]) -> ShResult<Self> {
|
pub fn loaded_pipe(tgt_fd: RawFd, buf: &[u8]) -> ShResult<Self> {
|
||||||
let (rpipe, wpipe) = nix::unistd::pipe()?;
|
let (rpipe, wpipe) = nix::unistd::pipe()?;
|
||||||
write(wpipe, buf)?;
|
write(wpipe, buf)?;
|
||||||
Ok(Self::Pipe { tgt_fd, pipe: rpipe.into() })
|
Ok(Self::Pipe {
|
||||||
}
|
tgt_fd,
|
||||||
|
pipe: rpipe.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
pub fn get_pipes() -> (Self, Self) {
|
pub fn get_pipes() -> (Self, Self) {
|
||||||
let (rpipe, wpipe) = nix::unistd::pipe2(OFlag::O_CLOEXEC).unwrap();
|
let (rpipe, wpipe) = nix::unistd::pipe2(OFlag::O_CLOEXEC).unwrap();
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ fn dedupe_entries(entries: &[HistEntry]) -> Vec<HistEntry> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Clone,Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct History {
|
pub struct History {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
pub pending: Option<LineBuf>, // command, cursor_pos
|
pub pending: Option<LineBuf>, // command, cursor_pos
|
||||||
@@ -215,7 +215,7 @@ pub struct History {
|
|||||||
//search_direction: Direction,
|
//search_direction: Direction,
|
||||||
ignore_dups: bool,
|
ignore_dups: bool,
|
||||||
max_size: Option<u32>,
|
max_size: Option<u32>,
|
||||||
stateless: bool
|
stateless: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History {
|
impl History {
|
||||||
@@ -231,7 +231,7 @@ impl History {
|
|||||||
//search_direction: Direction::Backward,
|
//search_direction: Direction::Backward,
|
||||||
ignore_dups: false,
|
ignore_dups: false,
|
||||||
max_size: None,
|
max_size: None,
|
||||||
stateless: true,
|
stateless: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new() -> ShResult<Self> {
|
pub fn new() -> ShResult<Self> {
|
||||||
@@ -269,7 +269,7 @@ impl History {
|
|||||||
//search_direction: Direction::Backward,
|
//search_direction: Direction::Backward,
|
||||||
ignore_dups,
|
ignore_dups,
|
||||||
max_size,
|
max_size,
|
||||||
stateless: false,
|
stateless: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,9 +454,9 @@ impl History {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&mut self) -> ShResult<()> {
|
pub fn save(&mut self) -> ShResult<()> {
|
||||||
if self.stateless {
|
if self.stateless {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use super::vicmd::{
|
|||||||
ViCmd, Word,
|
ViCmd, Word,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
expand::expand_cmd_sub,
|
||||||
libsh::{error::ShResult, guards::var_ctx_guard},
|
libsh::{error::ShResult, guards::var_ctx_guard},
|
||||||
parse::{
|
parse::{
|
||||||
execute::exec_input,
|
execute::exec_input,
|
||||||
@@ -23,6 +24,7 @@ use crate::{
|
|||||||
markers,
|
markers,
|
||||||
register::{RegisterContent, write_register},
|
register::{RegisterContent, write_register},
|
||||||
term::RawModeGuard,
|
term::RawModeGuard,
|
||||||
|
vicmd::ReadSrc,
|
||||||
},
|
},
|
||||||
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
|
||||||
};
|
};
|
||||||
@@ -441,33 +443,30 @@ pub struct LineBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LineBuf {
|
impl Default for LineBuf {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer: String::new(),
|
buffer: String::new(),
|
||||||
hint: None,
|
hint: None,
|
||||||
grapheme_indices: Some(vec![]),
|
grapheme_indices: Some(vec![]),
|
||||||
cursor: ClampedUsize::new(0, 0, false),
|
cursor: ClampedUsize::new(0, 0, false),
|
||||||
|
|
||||||
select_mode: None,
|
select_mode: None,
|
||||||
select_range: None,
|
select_range: None,
|
||||||
last_selection: None,
|
last_selection: None,
|
||||||
|
|
||||||
insert_mode_start_pos: None,
|
insert_mode_start_pos: None,
|
||||||
saved_col: None,
|
saved_col: None,
|
||||||
indent_ctx: IndentCtx::new(),
|
indent_ctx: IndentCtx::new(),
|
||||||
|
|
||||||
undo_stack: vec![],
|
undo_stack: vec![],
|
||||||
redo_stack: vec![],
|
redo_stack: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LineBuf {
|
impl LineBuf {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut new = Self {
|
let mut new = Self::default();
|
||||||
grapheme_indices: Some(vec![]), // We know the buffer is empty, so this keeps us safe from unwrapping None
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
new.update_graphemes();
|
new.update_graphemes();
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
@@ -3329,12 +3328,39 @@ impl LineBuf {
|
|||||||
| Verb::CompleteBackward
|
| Verb::CompleteBackward
|
||||||
| Verb::VisualModeSelectLast => self.apply_motion(motion),
|
| Verb::VisualModeSelectLast => self.apply_motion(motion),
|
||||||
Verb::ShellCmd(cmd) => self.verb_shell_cmd(cmd)?,
|
Verb::ShellCmd(cmd) => self.verb_shell_cmd(cmd)?,
|
||||||
Verb::Normal(_)
|
Verb::Read(src) => match src {
|
||||||
| Verb::Read(_)
|
ReadSrc::File(path_buf) => {
|
||||||
| Verb::Write(_)
|
if !path_buf.is_file() {
|
||||||
| Verb::Substitute(..)
|
write_meta(|m| m.post_system_message(format!("{} is not a file", path_buf.display())));
|
||||||
| Verb::RepeatSubstitute
|
return Ok(());
|
||||||
| Verb::RepeatGlobal => {}
|
}
|
||||||
|
let Ok(contents) = std::fs::read_to_string(&path_buf) else {
|
||||||
|
write_meta(|m| {
|
||||||
|
m.post_system_message(format!("Failed to read file {}", path_buf.display()))
|
||||||
|
});
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let grapheme_count = contents.graphemes(true).count();
|
||||||
|
self.insert_str_at_cursor(&contents);
|
||||||
|
self.cursor.add(grapheme_count);
|
||||||
|
}
|
||||||
|
ReadSrc::Cmd(cmd) => {
|
||||||
|
let output = match expand_cmd_sub(&cmd) {
|
||||||
|
Ok(out) => out,
|
||||||
|
Err(e) => {
|
||||||
|
e.print_error();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let grapheme_count = output.graphemes(true).count();
|
||||||
|
self.insert_str_at_cursor(&output);
|
||||||
|
self.cursor.add(grapheme_count);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Verb::Write(dest) => {}
|
||||||
|
Verb::Edit(path) => {}
|
||||||
|
Verb::Normal(_) | Verb::Substitute(..) | Verb::RepeatSubstitute | Verb::RepeatGlobal => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,17 +133,17 @@ pub mod markers {
|
|||||||
('\u{e000}'..'\u{efff}').contains(&c)
|
('\u{e000}'..'\u{efff}').contains(&c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help command formatting markers
|
// Help command formatting markers
|
||||||
pub const TAG: Marker = '\u{e180}';
|
pub const TAG: Marker = '\u{e180}';
|
||||||
pub const REFERENCE: Marker = '\u{e181}';
|
pub const REFERENCE: Marker = '\u{e181}';
|
||||||
pub const HEADER: Marker = '\u{e182}';
|
pub const HEADER: Marker = '\u{e182}';
|
||||||
pub const CODE: Marker = '\u{e183}';
|
pub const CODE: Marker = '\u{e183}';
|
||||||
/// angle brackets
|
/// angle brackets
|
||||||
pub const KEYWORD_1: Marker = '\u{e184}';
|
pub const KEYWORD_1: Marker = '\u{e184}';
|
||||||
/// curly brackets
|
/// curly brackets
|
||||||
pub const KEYWORD_2: Marker = '\u{e185}';
|
pub const KEYWORD_2: Marker = '\u{e185}';
|
||||||
/// square brackets
|
/// square brackets
|
||||||
pub const KEYWORD_3: Marker = '\u{e186}';
|
pub const KEYWORD_3: Marker = '\u{e186}';
|
||||||
}
|
}
|
||||||
type Marker = char;
|
type Marker = char;
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ pub struct ShedVi {
|
|||||||
|
|
||||||
pub old_layout: Option<Layout>,
|
pub old_layout: Option<Layout>,
|
||||||
pub history: History,
|
pub history: History,
|
||||||
pub ex_history: History,
|
pub ex_history: History,
|
||||||
|
|
||||||
pub needs_redraw: bool,
|
pub needs_redraw: bool,
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ impl ShedVi {
|
|||||||
repeat_motion: None,
|
repeat_motion: None,
|
||||||
editor: LineBuf::new(),
|
editor: LineBuf::new(),
|
||||||
history: History::new()?,
|
history: History::new()?,
|
||||||
ex_history: History::empty(),
|
ex_history: History::empty(),
|
||||||
needs_redraw: true,
|
needs_redraw: true,
|
||||||
};
|
};
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
@@ -322,7 +322,7 @@ impl ShedVi {
|
|||||||
repeat_motion: None,
|
repeat_motion: None,
|
||||||
editor: LineBuf::new(),
|
editor: LineBuf::new(),
|
||||||
history: History::empty(),
|
history: History::empty(),
|
||||||
ex_history: History::empty(),
|
ex_history: History::empty(),
|
||||||
needs_redraw: true,
|
needs_redraw: true,
|
||||||
};
|
};
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
@@ -861,16 +861,16 @@ impl ShedVi {
|
|||||||
|
|
||||||
let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit());
|
let has_edit_verb = cmd.verb().is_some_and(|v| v.1.is_edit());
|
||||||
let is_shell_cmd = cmd.verb().is_some_and(|v| matches!(v.1, Verb::ShellCmd(_)));
|
let is_shell_cmd = cmd.verb().is_some_and(|v| matches!(v.1, Verb::ShellCmd(_)));
|
||||||
let is_ex_cmd = cmd.flags.contains(CmdFlags::IS_EX_CMD);
|
let is_ex_cmd = cmd.flags.contains(CmdFlags::IS_EX_CMD);
|
||||||
log::debug!("is_ex_cmd: {is_ex_cmd}");
|
log::debug!("is_ex_cmd: {is_ex_cmd}");
|
||||||
if is_shell_cmd {
|
if is_shell_cmd {
|
||||||
self.old_layout = None;
|
self.old_layout = None;
|
||||||
}
|
}
|
||||||
if is_ex_cmd {
|
if is_ex_cmd {
|
||||||
self.ex_history.push(cmd.raw_seq.clone());
|
self.ex_history.push(cmd.raw_seq.clone());
|
||||||
self.ex_history.reset();
|
self.ex_history.reset();
|
||||||
log::debug!("ex_history: {:?}", self.ex_history.entries());
|
log::debug!("ex_history: {:?}", self.ex_history.entries());
|
||||||
}
|
}
|
||||||
|
|
||||||
let before = self.editor.buffer.clone();
|
let before = self.editor.buffer.clone();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
use crate::readline::vimode::ex::SubFlags;
|
||||||
|
|
||||||
use super::register::{RegisterContent, append_register, read_register, write_register};
|
use super::register::{RegisterContent, append_register, read_register, write_register};
|
||||||
|
|
||||||
//TODO: write tests that take edit results and cursor positions from actual
|
//TODO: write tests that take edit results and cursor positions from actual
|
||||||
@@ -64,7 +68,7 @@ bitflags! {
|
|||||||
const VISUAL_LINE = 1<<1;
|
const VISUAL_LINE = 1<<1;
|
||||||
const VISUAL_BLOCK = 1<<2;
|
const VISUAL_BLOCK = 1<<2;
|
||||||
const EXIT_CUR_MODE = 1<<3;
|
const EXIT_CUR_MODE = 1<<3;
|
||||||
const IS_EX_CMD = 1<<4;
|
const IS_EX_CMD = 1<<4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +260,8 @@ pub enum Verb {
|
|||||||
Normal(String),
|
Normal(String),
|
||||||
Read(ReadSrc),
|
Read(ReadSrc),
|
||||||
Write(WriteDest),
|
Write(WriteDest),
|
||||||
Substitute(String, String, super::vimode::ex::SubFlags),
|
Edit(PathBuf),
|
||||||
|
Substitute(String, String, SubFlags),
|
||||||
RepeatSubstitute,
|
RepeatSubstitute,
|
||||||
RepeatGlobal,
|
RepeatGlobal,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::str::Chars;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::bitflags;
|
use crate::bitflags;
|
||||||
|
use crate::expand::{Expander, expand_raw};
|
||||||
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
||||||
use crate::readline::history::History;
|
use crate::readline::history::History;
|
||||||
use crate::readline::keys::KeyEvent;
|
use crate::readline::keys::KeyEvent;
|
||||||
@@ -14,7 +15,7 @@ use crate::readline::vicmd::{
|
|||||||
WriteDest,
|
WriteDest,
|
||||||
};
|
};
|
||||||
use crate::readline::vimode::{ModeReport, ViInsert, ViMode};
|
use crate::readline::vimode::{ModeReport, ViInsert, ViMode};
|
||||||
use crate::state::write_meta;
|
use crate::state::{get_home, write_meta};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
||||||
@@ -34,18 +35,18 @@ bitflags! {
|
|||||||
struct ExEditor {
|
struct ExEditor {
|
||||||
buf: LineBuf,
|
buf: LineBuf,
|
||||||
mode: ViInsert,
|
mode: ViInsert,
|
||||||
history: History
|
history: History,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExEditor {
|
impl ExEditor {
|
||||||
pub fn new(history: History) -> Self {
|
pub fn new(history: History) -> Self {
|
||||||
let mut new = Self {
|
let mut new = Self {
|
||||||
history,
|
history,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
new.buf.update_graphemes();
|
new.buf.update_graphemes();
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
*self = Self::default()
|
*self = Self::default()
|
||||||
}
|
}
|
||||||
@@ -85,13 +86,13 @@ impl ExEditor {
|
|||||||
let Some(mut cmd) = self.mode.handle_key(key) else {
|
let Some(mut cmd) = self.mode.handle_key(key) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
cmd.alter_line_motion_if_no_verb();
|
cmd.alter_line_motion_if_no_verb();
|
||||||
log::debug!("ExEditor got cmd: {:?}", cmd);
|
log::debug!("ExEditor got cmd: {:?}", cmd);
|
||||||
if self.should_grab_history(&cmd) {
|
if self.should_grab_history(&cmd) {
|
||||||
log::debug!("Grabbing history for cmd: {:?}", cmd);
|
log::debug!("Grabbing history for cmd: {:?}", cmd);
|
||||||
self.scroll_history(cmd);
|
self.scroll_history(cmd);
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
self.buf.exec_cmd(cmd)
|
self.buf.exec_cmd(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,9 @@ pub struct ViEx {
|
|||||||
|
|
||||||
impl ViEx {
|
impl ViEx {
|
||||||
pub fn new(history: History) -> Self {
|
pub fn new(history: History) -> Self {
|
||||||
Self { pending_cmd: ExEditor::new(history) }
|
Self {
|
||||||
|
pending_cmd: ExEditor::new(history),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +118,7 @@ impl ViMode for ViEx {
|
|||||||
E(C::Char('\r'), M::NONE) | E(C::Enter, M::NONE) => {
|
E(C::Char('\r'), M::NONE) | E(C::Enter, M::NONE) => {
|
||||||
let input = self.pending_cmd.buf.as_str();
|
let input = self.pending_cmd.buf.as_str();
|
||||||
match parse_ex_cmd(input) {
|
match parse_ex_cmd(input) {
|
||||||
Ok(cmd) => {
|
Ok(cmd) => Ok(cmd),
|
||||||
Ok(cmd)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = e.unwrap_or(format!("Not an editor command: {}", input));
|
let msg = e.unwrap_or(format!("Not an editor command: {}", input));
|
||||||
write_meta(|m| m.post_system_message(msg.clone()));
|
write_meta(|m| m.post_system_message(msg.clone()));
|
||||||
@@ -129,18 +130,14 @@ impl ViMode for ViEx {
|
|||||||
self.pending_cmd.clear();
|
self.pending_cmd.clear();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
E(C::Esc, M::NONE) => {
|
E(C::Esc, M::NONE) => Ok(Some(ViCmd {
|
||||||
Ok(Some(ViCmd {
|
register: RegisterName::default(),
|
||||||
register: RegisterName::default(),
|
verb: Some(VerbCmd(1, Verb::NormalMode)),
|
||||||
verb: Some(VerbCmd(1, Verb::NormalMode)),
|
motion: None,
|
||||||
motion: None,
|
flags: CmdFlags::empty(),
|
||||||
flags: CmdFlags::empty(),
|
raw_seq: "".into(),
|
||||||
raw_seq: "".into(),
|
})),
|
||||||
}))
|
_ => self.pending_cmd.handle_key(key).map(|_| None),
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.pending_cmd.handle_key(key).map(|_| None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn handle_key(&mut self, key: KeyEvent) -> Option<ViCmd> {
|
fn handle_key(&mut self, key: KeyEvent) -> Option<ViCmd> {
|
||||||
@@ -248,7 +245,7 @@ fn parse_ex_command(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Opt
|
|||||||
let mut cmd_name = String::new();
|
let mut cmd_name = String::new();
|
||||||
|
|
||||||
while let Some(ch) = chars.peek() {
|
while let Some(ch) = chars.peek() {
|
||||||
if ch == &'!' {
|
if cmd_name.is_empty() && ch == &'!' {
|
||||||
cmd_name.push(*ch);
|
cmd_name.push(*ch);
|
||||||
chars.next();
|
chars.next();
|
||||||
break;
|
break;
|
||||||
@@ -265,16 +262,17 @@ fn parse_ex_command(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Opt
|
|||||||
let cmd = unescape_shell_cmd(&cmd);
|
let cmd = unescape_shell_cmd(&cmd);
|
||||||
Ok(Some(Verb::ShellCmd(cmd)))
|
Ok(Some(Verb::ShellCmd(cmd)))
|
||||||
}
|
}
|
||||||
_ if "help".starts_with(&cmd_name) => {
|
_ if "help".starts_with(&cmd_name) => {
|
||||||
let cmd = "help ".to_string() + chars.collect::<String>().trim();
|
let cmd = "help ".to_string() + chars.collect::<String>().trim();
|
||||||
Ok(Some(Verb::ShellCmd(cmd)))
|
Ok(Some(Verb::ShellCmd(cmd)))
|
||||||
}
|
}
|
||||||
"normal!" => parse_normal(chars),
|
"normal!" => parse_normal(chars),
|
||||||
_ if "delete".starts_with(&cmd_name) => Ok(Some(Verb::Delete)),
|
_ if "delete".starts_with(&cmd_name) => Ok(Some(Verb::Delete)),
|
||||||
_ if "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)),
|
_ if "yank".starts_with(&cmd_name) => Ok(Some(Verb::Yank)),
|
||||||
_ if "put".starts_with(&cmd_name) => Ok(Some(Verb::Put(Anchor::After))),
|
_ if "put".starts_with(&cmd_name) => Ok(Some(Verb::Put(Anchor::After))),
|
||||||
_ if "read".starts_with(&cmd_name) => parse_read(chars),
|
_ if "read".starts_with(&cmd_name) => parse_read(chars),
|
||||||
_ if "write".starts_with(&cmd_name) => parse_write(chars),
|
_ if "write".starts_with(&cmd_name) => parse_write(chars),
|
||||||
|
_ if "edit".starts_with(&cmd_name) => parse_edit(chars),
|
||||||
_ if "substitute".starts_with(&cmd_name) => parse_substitute(chars),
|
_ if "substitute".starts_with(&cmd_name) => parse_substitute(chars),
|
||||||
_ => Err(None),
|
_ => Err(None),
|
||||||
}
|
}
|
||||||
@@ -289,6 +287,19 @@ fn parse_normal(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<
|
|||||||
Ok(Some(Verb::Normal(seq)))
|
Ok(Some(Verb::Normal(seq)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_edit(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
|
||||||
|
chars
|
||||||
|
.peeking_take_while(|c| c.is_whitespace())
|
||||||
|
.for_each(drop);
|
||||||
|
|
||||||
|
let arg: String = chars.collect();
|
||||||
|
if arg.trim().is_empty() {
|
||||||
|
return Err(Some("Expected file path after ':edit'".into()));
|
||||||
|
}
|
||||||
|
let arg_path = get_path(arg.trim())?;
|
||||||
|
Ok(Some(Verb::Edit(arg_path)))
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
|
fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
|
||||||
chars
|
chars
|
||||||
.peeking_take_while(|c| c.is_whitespace())
|
.peeking_take_while(|c| c.is_whitespace())
|
||||||
@@ -311,23 +322,15 @@ fn parse_read(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<St
|
|||||||
if is_shell_read {
|
if is_shell_read {
|
||||||
Ok(Some(Verb::Read(ReadSrc::Cmd(arg))))
|
Ok(Some(Verb::Read(ReadSrc::Cmd(arg))))
|
||||||
} else {
|
} else {
|
||||||
let arg_path = get_path(arg.trim());
|
let arg_path = get_path(arg.trim())?;
|
||||||
Ok(Some(Verb::Read(ReadSrc::File(arg_path))))
|
Ok(Some(Verb::Read(ReadSrc::File(arg_path))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_path(path: &str) -> PathBuf {
|
fn get_path(path: &str) -> Result<PathBuf, Option<String>> {
|
||||||
if let Some(stripped) = path.strip_prefix("~/")
|
let expanded = expand_raw(&mut path.chars().peekable())
|
||||||
&& let Some(home) = std::env::var_os("HOME")
|
.map_err(|e| Some(format!("Error expanding path: {}", e)))?;
|
||||||
{
|
Ok(PathBuf::from(&expanded))
|
||||||
return PathBuf::from(home).join(stripped);
|
|
||||||
}
|
|
||||||
if path == "~"
|
|
||||||
&& let Some(home) = std::env::var_os("HOME")
|
|
||||||
{
|
|
||||||
return PathBuf::from(home);
|
|
||||||
}
|
|
||||||
PathBuf::from(path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_write(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
|
fn parse_write(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<String>> {
|
||||||
@@ -350,7 +353,7 @@ fn parse_write(chars: &mut Peekable<Chars<'_>>) -> Result<Option<Verb>, Option<S
|
|||||||
}
|
}
|
||||||
|
|
||||||
let arg: String = chars.collect();
|
let arg: String = chars.collect();
|
||||||
let arg_path = get_path(arg.trim());
|
let arg_path = get_path(arg.trim())?;
|
||||||
|
|
||||||
let dest = if is_file_append {
|
let dest = if is_file_append {
|
||||||
WriteDest::FileAppend(arg_path)
|
WriteDest::FileAppend(arg_path)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::{collections::VecDeque, sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}};
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
use nix::sys::signal::{SaFlags, SigAction, sigaction};
|
use nix::sys::signal::{SaFlags, SigAction, sigaction};
|
||||||
|
|
||||||
@@ -8,7 +11,10 @@ use crate::{
|
|||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
parse::execute::exec_input,
|
parse::execute::exec_input,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
state::{AutoCmd, AutoCmdKind, VarFlags, VarKind, read_jobs, read_logic, write_jobs, write_meta, write_vars},
|
state::{
|
||||||
|
AutoCmd, AutoCmdKind, VarFlags, VarKind, read_jobs, read_logic, write_jobs, write_meta,
|
||||||
|
write_vars,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static SIGNALS: AtomicU64 = AtomicU64::new(0);
|
static SIGNALS: AtomicU64 = AtomicU64::new(0);
|
||||||
@@ -316,16 +322,16 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
|||||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||||
if let Some(job) = result {
|
if let Some(job) = result {
|
||||||
let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string();
|
let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string();
|
||||||
let statuses = job.get_stats();
|
let statuses = job.get_stats();
|
||||||
|
|
||||||
if let Some(pipe_status) = Job::pipe_status(&statuses) {
|
if let Some(pipe_status) = Job::pipe_status(&statuses) {
|
||||||
let pipe_status = pipe_status
|
let pipe_status = pipe_status
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.collect::<VecDeque<String>>();
|
.collect::<VecDeque<String>>();
|
||||||
|
|
||||||
write_vars(|v| v.set_var("PIPESTATUS", VarKind::Arr(pipe_status), VarFlags::NONE))?;
|
write_vars(|v| v.set_var("PIPESTATUS", VarKind::Arr(pipe_status), VarFlags::NONE))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish));
|
let post_job_hooks = read_logic(|l| l.get_autocmds(AutoCmdKind::OnJobFinish));
|
||||||
for cmd in post_job_hooks {
|
for cmd in post_job_hooks {
|
||||||
|
|||||||
76
src/state.rs
76
src/state.rs
@@ -1098,7 +1098,7 @@ impl VarTab {
|
|||||||
.map(|hname| hname.to_string_lossy().to_string())
|
.map(|hname| hname.to_string_lossy().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let help_paths = format!("/usr/share/shed/doc:{home}/.local/share/shed/doc");
|
let help_paths = format!("/usr/share/shed/doc:{home}/.local/share/shed/doc");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
env::set_var("IFS", " \t\n");
|
env::set_var("IFS", " \t\n");
|
||||||
@@ -1116,7 +1116,7 @@ impl VarTab {
|
|||||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||||
env::set_var("SHED_HIST", format!("{}/.shedhist", home));
|
env::set_var("SHED_HIST", format!("{}/.shedhist", home));
|
||||||
env::set_var("SHED_RC", format!("{}/.shedrc", home));
|
env::set_var("SHED_RC", format!("{}/.shedrc", home));
|
||||||
env::set_var("SHED_HPATH", help_paths);
|
env::set_var("SHED_HPATH", help_paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn init_sh_argv(&mut self) {
|
pub fn init_sh_argv(&mut self) {
|
||||||
@@ -1873,36 +1873,41 @@ pub fn set_status(code: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_runtime_file(name: &str, env_var_name: Option<&str>) -> ShResult<()> {
|
pub fn source_runtime_file(name: &str, env_var_name: Option<&str>) -> ShResult<()> {
|
||||||
let etc_path = PathBuf::from(format!("/etc/shed/{name}"));
|
let etc_path = PathBuf::from(format!("/etc/shed/{name}"));
|
||||||
if etc_path.is_file()
|
if etc_path.is_file()
|
||||||
&& let Err(e) = source_file(etc_path) {
|
&& let Err(e) = source_file(etc_path)
|
||||||
e.print_error();
|
{
|
||||||
}
|
e.print_error();
|
||||||
|
}
|
||||||
|
|
||||||
let path = if let Some(name) = env_var_name
|
let path = if let Some(name) = env_var_name
|
||||||
&& let Ok(path) = env::var(name) {
|
&& let Ok(path) = env::var(name)
|
||||||
|
{
|
||||||
PathBuf::from(&path)
|
PathBuf::from(&path)
|
||||||
} else if let Some(home) = get_home() {
|
} else if let Some(home) = get_home() {
|
||||||
home.join(format!(".{name}"))
|
home.join(format!(".{name}"))
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::simple(ShErrKind::InternalErr, "could not determine home path"));
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::InternalErr,
|
||||||
|
"could not determine home path",
|
||||||
|
));
|
||||||
};
|
};
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
source_file(path)
|
source_file(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_rc() -> ShResult<()> {
|
pub fn source_rc() -> ShResult<()> {
|
||||||
source_runtime_file("shedrc", Some("SHED_RC"))
|
source_runtime_file("shedrc", Some("SHED_RC"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_login() -> ShResult<()> {
|
pub fn source_login() -> ShResult<()> {
|
||||||
source_runtime_file("shed_profile", Some("SHED_PROFILE"))
|
source_runtime_file("shed_profile", Some("SHED_PROFILE"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_env() -> ShResult<()> {
|
pub fn source_env() -> ShResult<()> {
|
||||||
source_runtime_file("shedenv", Some("SHED_ENV"))
|
source_runtime_file("shedenv", Some("SHED_ENV"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||||
@@ -1917,30 +1922,39 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
|
|||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn get_home_unchecked() -> PathBuf {
|
pub fn get_home_unchecked() -> PathBuf {
|
||||||
if let Some(home) = get_home() {
|
if let Some(home) = get_home() {
|
||||||
home
|
home
|
||||||
} else {
|
} else {
|
||||||
let caller = std::panic::Location::caller();
|
let caller = std::panic::Location::caller();
|
||||||
panic!("get_home_unchecked: could not determine home directory (called from {}:{})", caller.file(), caller.line())
|
panic!(
|
||||||
}
|
"get_home_unchecked: could not determine home directory (called from {}:{})",
|
||||||
|
caller.file(),
|
||||||
|
caller.line()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn get_home_str_unchecked() -> String {
|
pub fn get_home_str_unchecked() -> String {
|
||||||
if let Some(home) = get_home() {
|
if let Some(home) = get_home() {
|
||||||
home.to_string_lossy().to_string()
|
home.to_string_lossy().to_string()
|
||||||
} else {
|
} else {
|
||||||
let caller = std::panic::Location::caller();
|
let caller = std::panic::Location::caller();
|
||||||
panic!("get_home_str_unchecked: could not determine home directory (called from {}:{})", caller.file(), caller.line())
|
panic!(
|
||||||
}
|
"get_home_str_unchecked: could not determine home directory (called from {}:{})",
|
||||||
|
caller.file(),
|
||||||
|
caller.line()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_home() -> Option<PathBuf> {
|
pub fn get_home() -> Option<PathBuf> {
|
||||||
env::var("HOME").ok().map(PathBuf::from).or_else(|| {
|
env::var("HOME")
|
||||||
User::from_uid(getuid()).ok().flatten().map(|u| u.dir)
|
.ok()
|
||||||
})
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| User::from_uid(getuid()).ok().flatten().map(|u| u.dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_home_str() -> Option<String> {
|
pub fn get_home_str() -> Option<String> {
|
||||||
get_home().map(|h| h.to_string_lossy().to_string())
|
get_home().map(|h| h.to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user