Implemented functions and aliases

This commit is contained in:
2025-03-02 22:49:36 -05:00
parent e7a84f1edd
commit cd216ef1cc
20 changed files with 424 additions and 95 deletions

19
src/builtin/alias.rs Normal file
View File

@@ -0,0 +1,19 @@
use crate::prelude::*;
pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs: _ } = rule {
let argv = argv.drop_first();
let mut argv_iter = argv.into_iter();
while let Some(arg) = argv_iter.next() {
let arg_raw = arg.to_string();
if let Some((alias,body)) = arg_raw.split_once('=') {
let clean_body = trim_quotes(&body);
shenv.logic_mut().set_alias(alias, &clean_body);
} else {
return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", arg.span().clone()))
}
}
} else { unreachable!() }
Ok(())
}

View File

@@ -22,7 +22,7 @@ pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> {
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
id
} else {
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found".into(), blame))
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", blame))
};
let tabid = match argv_s.next() {
@@ -66,7 +66,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
});
match result {
Some(id) => Ok(id),
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame))
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()",blame))
}
}
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
@@ -86,7 +86,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
match result {
Some(id) => Ok(id),
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame))
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()",blame))
}
} else {
Err(ShErr::full(ShErrKind::SyntaxErr,format!("Invalid fd arg: {}", arg),blame))
@@ -103,7 +103,7 @@ pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let arg_s = arg.to_string();
let mut chars = arg_s.chars().peekable();
if chars.peek().is_none_or(|ch| *ch != '-') {
return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone()))
return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call", arg.span().clone()))
}
chars.next();
while let Some(ch) = chars.next() {
@@ -113,7 +113,7 @@ pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
'n' => JobCmdFlags::NEW_ONLY,
'r' => JobCmdFlags::RUNNING,
's' => JobCmdFlags::STOPPED,
_ => return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone()))
_ => return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call", arg.span().clone()))
};
flags |= flag

View File

@@ -4,8 +4,9 @@ pub mod pwd;
pub mod export;
pub mod jobctl;
pub mod read;
pub mod alias;
pub const BUILTINS: [&str;8] = [
pub const BUILTINS: [&str;9] = [
"echo",
"cd",
"pwd",
@@ -13,5 +14,6 @@ pub const BUILTINS: [&str;8] = [
"fg",
"bg",
"jobs",
"read"
"read",
"alias"
];

View File

@@ -47,9 +47,9 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
}
log!(TRACE, "{:?}", *cmd.rule());
match *cmd.rule() {
NdRule::Command {..} if cmd.flags().contains(NdFlag::BUILTIN) => exec_builtin(cmd,shenv).try_blame(span)?,
NdRule::Command {..} => exec_cmd(cmd,shenv).try_blame(span)?,
NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(span)?,
NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(span)?,
NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(span)?,
NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(span)?,
NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(span)?,
_ => unimplemented!()
@@ -58,6 +58,83 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
Ok(())
}
fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let mut is_builtin = false;
let mut is_func = false;
let mut is_subsh = false;
if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() {
*argv = expand_argv(argv.to_vec(), shenv);
let cmd = argv.first().unwrap().to_string();
if shenv.logic().get_function(&cmd).is_some() {
is_func = true;
} else if node.flags().contains(NdFlag::BUILTIN) {
is_builtin = true;
}
} else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() {
*argv = expand_argv(argv.to_vec(), shenv);
is_subsh = true;
} else { unreachable!() }
if is_builtin {
exec_builtin(node, shenv)?;
} else if is_func {
exec_func(node, shenv)?;
} else if is_subsh {
exec_subshell(node, shenv)?;
} else {
exec_cmd(node, shenv)?;
}
Ok(())
}
fn exec_func(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs } = rule {
let mut argv_iter = argv.into_iter();
let func_name = argv_iter.next().unwrap().to_string();
let body = shenv.logic().get_function(&func_name).unwrap().to_string();
let snapshot = shenv.clone();
shenv.vars_mut().reset_params();
while let Some(arg) = argv_iter.next() {
shenv.vars_mut().bpush_arg(&arg.to_string());
}
shenv.collect_redirs(redirs);
let lex_input = Rc::new(body);
let tokens = Lexer::new(lex_input).lex();
match Parser::new(tokens).parse() {
Ok(syn_tree) => {
match Executor::new(syn_tree, shenv).walk() {
Ok(_) => { /* yippee */ }
Err(e) => {
*shenv = snapshot;
return Err(e.into())
}
}
}
Err(e) => {
*shenv = snapshot;
return Err(e.into())
}
}
*shenv = snapshot;
}
Ok(())
}
fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::FuncDef { name, body } = rule {
let name_raw = name.to_string();
let name = name_raw.trim_end_matches("()");
let body_raw = body.to_string();
let body = body_raw[1..body_raw.len() - 1].trim();
shenv.logic_mut().set_function(name, body);
} else { unreachable!() }
Ok(())
}
fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let snapshot = shenv.clone();
shenv.vars_mut().reset_params();
@@ -154,6 +231,7 @@ fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
"fg" => continue_job(node, shenv, true)?,
"bg" => continue_job(node, shenv, false)?,
"read" => read_builtin(node, shenv)?,
"alias" => alias(node, shenv)?,
_ => unimplemented!("Have not yet implemented support for builtin `{}'",command)
}
log!(TRACE, "done");
@@ -212,6 +290,8 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
let cmd_name = argv.first().unwrap().span().get_slice().to_string();
cmd_names.push(cmd_name);
} else if let NdRule::Subshell {..} = cmd.rule() {
cmd_names.push("subshell".to_string());
} else { unimplemented!() }
match unsafe { fork()? } {
@@ -234,11 +314,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
shenv.ctx_mut().push_rdr(rpipe_redir);
}
if cmd.flags().contains(NdFlag::BUILTIN) {
exec_builtin(cmd, shenv).unwrap();
} else {
exec_cmd(cmd, shenv).unwrap();
}
dispatch_command(cmd, shenv)?;
exit(0);
}
Parent { child } => {

75
src/expand/alias.rs Normal file
View File

@@ -0,0 +1,75 @@
use crate::{parse::lex::SEPARATORS, prelude::*};
pub fn expand_aliases(input: &str, shenv: &mut ShEnv) -> Option<String> {
let mut result = input.to_string();
let mut expanded_aliases = Vec::new();
let mut found_in_iteration = true;
// Loop until no new alias expansion happens.
while found_in_iteration {
found_in_iteration = false;
let mut new_result = String::new();
let mut chars = result.chars().peekable();
let mut alias_cand = String::new();
let mut is_cmd = true;
while let Some(ch) = chars.next() {
match ch {
';' | '\n' => {
new_result.push(ch);
is_cmd = true;
// Consume any extra whitespace or delimiters.
while let Some(&next_ch) = chars.peek() {
if matches!(next_ch, ' ' | '\t' | ';' | '\n') {
new_result.push(next_ch);
chars.next();
} else {
break;
}
}
}
' ' | '\t' => {
is_cmd = false;
new_result.push(ch);
}
_ if is_cmd => {
// Accumulate token characters.
alias_cand.push(ch);
while let Some(&next_ch) = chars.peek() {
if matches!(next_ch, ' ' | '\t' | ';' | '\n') {
break;
} else {
alias_cand.push(next_ch);
chars.next();
}
}
// Check for an alias expansion.
if let Some(alias) = shenv.logic().get_alias(&alias_cand) {
// Only expand if we haven't already done so.
if !expanded_aliases.contains(&alias_cand) {
new_result.push_str(alias);
expanded_aliases.push(alias_cand.clone());
found_in_iteration = true;
} else {
new_result.push_str(&alias_cand);
}
} else {
new_result.push_str(&alias_cand);
}
alias_cand.clear();
}
_ => {
new_result.push(ch);
}
}
}
result = new_result;
log!(DEBUG, result);
}
if expanded_aliases.is_empty() {
None
} else {
Some(result)
}
}

View File

@@ -11,11 +11,11 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
Lexer::new(value).lex() // Automatically handles word splitting for us
}
pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String {
pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token {
let dquote_raw = dquote.to_string();
let mut result = String::new();
let mut var_name = String::new();
let mut chars = dquote_raw.chars();
let mut chars = dquote_raw.chars().peekable();
let mut in_brace = false;
while let Some(ch) = chars.next() {
@@ -25,11 +25,13 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String {
result.push(next_ch)
}
}
'"' => continue,
'$' => {
while let Some(ch) = chars.next() {
while let Some(ch) = chars.peek() {
if *ch == '"' {
break
}
let ch = chars.next().unwrap();
match ch {
'"' => continue,
'{' => {
in_brace = true;
}
@@ -59,5 +61,7 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String {
}
}
result
log!(DEBUG, result);
Lexer::new(Rc::new(result)).lex().pop().unwrap_or(dquote)
}

View File

@@ -1,2 +1,38 @@
pub mod expand_vars;
pub mod tilde;
pub mod alias;
use alias::expand_aliases;
use expand_vars::{expand_dquote, expand_var};
use tilde::expand_tilde;
use crate::prelude::*;
pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
let mut processed = vec![];
for arg in argv {
log!(DEBUG, arg);
log!(DEBUG, processed);
match arg.rule() {
TkRule::DQuote => {
let dquote_exp = expand_dquote(arg.clone(), shenv);
processed.push(dquote_exp);
}
TkRule::VarSub => {
let mut varsub_exp = expand_var(arg.clone(), shenv);
processed.append(&mut varsub_exp);
}
TkRule::TildeSub => {
let tilde_exp = expand_tilde(arg.clone());
processed.push(tilde_exp);
}
_ => {
if arg.rule() != TkRule::Ident {
log!(WARN, "found this in expand_argv: {:?}", arg.rule());
}
processed.push(arg.clone())
}
}
}
processed
}

View File

@@ -1,11 +1,14 @@
use crate::prelude::*;
pub fn expand_tilde(tilde_sub: Token) -> String {
pub fn expand_tilde(tilde_sub: Token) -> Token {
let tilde_sub_raw = tilde_sub.to_string();
if tilde_sub_raw.starts_with('~') {
let home = std::env::var("HOME").unwrap_or_default();
tilde_sub_raw.replacen('~', &home, 1)
tilde_sub_raw.replacen('~', &home, 1);
let lex_input = Rc::new(tilde_sub_raw);
let mut tokens = Lexer::new(lex_input).lex();
tokens.pop().unwrap_or(tilde_sub)
} else {
tilde_sub_raw
tilde_sub
}
}

View File

@@ -89,14 +89,14 @@ pub enum ShErr {
}
impl ShErr {
pub fn simple(kind: ShErrKind, message: &str) -> Self {
Self::Simple { kind, message: message.to_string() }
pub fn simple<S: Into<String>>(kind: ShErrKind, message: S) -> Self {
Self::Simple { kind, message: message.into() }
}
pub fn io() -> Self {
io::Error::last_os_error().into()
}
pub fn full(kind: ShErrKind, message: String, span: Span) -> Self {
Self::Full { kind, message, span }
pub fn full<S: Into<String>>(kind: ShErrKind, message: S, span: Span) -> Self {
Self::Full { kind, message: message.into(), span }
}
pub fn try_blame(&mut self, blame: Span) {
match self {
@@ -178,7 +178,7 @@ impl Display for ShErr {
let dist = span.end() - span.start();
let padding = " ".repeat(offset);
let line_inner = "~".repeat(dist.saturating_sub(2));
let err_kind = style_text(&self.display_kind(), Style::Red | Style::Bold);
let err_kind = &self.display_kind().styled(Style::Red | Style::Bold);
let stat_line = format!("[{}:{}] - {}{}",line_no,offset,err_kind,message);
let indicator_line = if dist == 1 {
format!("{}^",padding)

View File

@@ -5,8 +5,19 @@ use nix::libc::getpgrp;
use crate::{expand::{expand_vars::{expand_dquote, expand_var}, tilde::expand_tilde}, prelude::*};
use super::term::StyleSet;
pub trait StrOps {
fn trim_quotes(&self) -> String;
/// This function operates on anything that implements `AsRef<str>` and `Display`, which is mainly strings.
/// It takes a 'Style' which can be passed as a single Style object like `Style::Cyan` or a Bit OR of many styles,
/// For instance: `Style::Red | Style::Bold | Style::Italic`
fn styled<S: Into<StyleSet>>(self, style: S) -> String;
}
impl<T: AsRef<str> + Display> StrOps for T {
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
style_text(&self, style)
}
}
pub trait ArgVec {
@@ -16,30 +27,12 @@ pub trait ArgVec {
impl ArgVec for Vec<Token> {
/// Converts the contained tokens into strings.
/// This function also performs token expansion.
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String> {
let mut argv_iter = self.into_iter();
let mut argv_processed = vec![];
while let Some(arg) = argv_iter.next() {
match arg.rule() {
TkRule::VarSub => {
let mut tokens = expand_var(arg, shenv).into_iter();
while let Some(token) = tokens.next() {
argv_processed.push(token.to_string())
}
}
TkRule::TildeSub => {
let expanded = expand_tilde(arg);
argv_processed.push(expanded);
}
TkRule::DQuote => {
let expanded = expand_dquote(arg, shenv);
argv_processed.push(expanded)
}
_ => {
argv_processed.push(arg.to_string())
}
}
let cleaned = trim_quotes(&arg);
argv_processed.push(cleaned);
}
argv_processed
}
@@ -72,11 +65,11 @@ pub enum LogLevel {
impl Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ERROR => write!(f,"{}",style_text("ERROR", Style::Red | Style::Bold)),
WARN => write!(f,"{}",style_text("WARN", Style::Yellow | Style::Bold)),
INFO => write!(f,"{}",style_text("INFO", Style::Green | Style::Bold)),
DEBUG => write!(f,"{}",style_text("DEBUG", Style::Magenta | Style::Bold)),
TRACE => write!(f,"{}",style_text("TRACE", Style::Blue | Style::Bold)),
ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)),
WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)),
INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)),
DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)),
TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)),
NULL => write!(f,"")
}
}
@@ -89,9 +82,9 @@ macro_rules! log {
let var_name = stringify!($var);
if $level <= log_level() {
let file = file!();
let file_styled = style_text(file,Style::Cyan);
let file_styled = file.styled(Style::Cyan);
let line = line!();
let line_styled = style_text(line,Style::Cyan);
let line_styled = line.to_string().styled(Style::Cyan);
let logged = format!("[{}][{}:{}] {} = {:#?}",$level, file_styled,line_styled,var_name, &$var);
write(borrow_fd(2),format!("{}\n",logged).as_bytes()).unwrap();
@@ -102,9 +95,9 @@ macro_rules! log {
($level:expr, $lit:literal) => {{
if $level <= log_level() {
let file = file!();
let file_styled = style_text(file, Style::Cyan);
let file_styled = file.styled(Style::Cyan);
let line = line!();
let line_styled = style_text(line, Style::Cyan);
let line_styled = line.to_string().styled(Style::Cyan);
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, $lit);
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
}
@@ -114,9 +107,9 @@ macro_rules! log {
if $level <= log_level() {
let formatted = format!($($arg)*);
let file = file!();
let file_styled = style_text(file, Style::Cyan);
let file_styled = file.styled(Style::Cyan);
let line = line!();
let line_styled = style_text(line, Style::Cyan);
let line_styled = line.to_string().styled(Style::Cyan);
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, formatted);
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
}

View File

@@ -22,16 +22,21 @@ pub fn main() {
loop {
log!(TRACE, "Entered loop");
let line = match prompt::read_line(&mut shenv) {
let mut line = match prompt::read_line(&mut shenv) {
Ok(line) => line,
Err(e) => {
eprintln!("{}",e);
continue;
}
};
if let Some(line_exp) = expand_aliases(&line, &mut shenv) {
line = line_exp;
}
let input = Rc::new(line);
log!(INFO, "New input: {:?}", input);
let token_stream = Lexer::new(input).lex();
log!(DEBUG, token_stream);
log!(DEBUG, token_stream);
log!(TRACE, "Token stream: {:?}", token_stream);
match Parser::new(token_stream).parse() {
Err(e) => {

View File

@@ -1,4 +1,4 @@
use std::fmt::Display;
use std::fmt::{Debug, Display};
use crate::prelude::*;
@@ -74,7 +74,7 @@ impl Lexer {
}
}
#[derive(Debug,Clone)]
#[derive(Clone)]
pub struct Token {
rule: TkRule,
span: Span
@@ -98,6 +98,13 @@ impl Token {
}
}
impl Debug for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let info = (self.rule(),self.to_string(),self.span.start,self.span.end);
write!(f,"{:?}",info)
}
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let slice = self.span.get_slice();
@@ -462,7 +469,7 @@ tkrule_def!(DQuote, |input: &str| {
// Double quoted strings
let mut chars = input.chars();
let mut len = 0;
let mut quoted = false;
let mut quote_count = 0;
while let Some(ch) = chars.next() {
match ch {
@@ -470,21 +477,38 @@ tkrule_def!(DQuote, |input: &str| {
chars.next();
len += 2;
}
'"' if !quoted => {
'"' => {
len += 1;
quoted = true;
quote_count += 1;
}
'"' if quoted => {
len += 1;
return Some(len)
}
_ if !quoted => {
return None
' ' | '\t' | ';' | '\n' if quote_count % 2 == 0 => {
if quote_count > 0 {
if quote_count % 2 == 0 {
return Some(len)
} else {
return None
}
} else {
return None
}
}
_ => len += 1
}
}
None
match len {
0 => None,
_ => {
if quote_count > 0 {
if quote_count % 2 == 0 {
return Some(len)
} else {
return None
}
} else {
return None
}
}
}
});
tkrule_def!(ProcSub, |input: &str| {

View File

@@ -83,6 +83,7 @@ pub enum NdRule {
Main { cmd_lists: Vec<Node> },
Command { argv: Vec<Token>, redirs: Vec<Redir> },
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> },
FuncDef { name: Token, body: Token },
Subshell { body: Token, argv: Vec<Token>, redirs: Vec<Redir> },
CmdList { cmds: Vec<(Option<CmdGuard>,Node)> },
Pipeline { cmds: Vec<Node> }
@@ -255,6 +256,7 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
ndrule_def!(Expr, |tokens: &[Token]| {
try_rules!(tokens,
ShellCmd,
Pipeline,
Subshell,
Assignment,
@@ -264,12 +266,57 @@ ndrule_def!(Expr, |tokens: &[Token]| {
// Used in pipelines to avoid recursion
ndrule_def!(ExprNoPipeline, |tokens: &[Token]| {
try_rules!(tokens,
ShellCmd,
Subshell,
Assignment,
Command
);
});
ndrule_def!(ShellCmd, |tokens: &[Token]| {
try_rules!(tokens,
FuncDef
);
});
ndrule_def!(FuncDef, |tokens: &[Token]| {
let mut tokens_iter = tokens.iter();
let mut node_toks = vec![];
let name: Token;
let body: Token;
if let Some(token) = tokens_iter.next() {
if let TkRule::FuncName = token.rule() {
node_toks.push(token.clone());
name = token.clone();
} else {
return Ok(None)
}
} else {
return Ok(None)
}
if let Some(token) = tokens_iter.next() {
if let TkRule::BraceGrp = token.rule() {
node_toks.push(token.clone());
body = token.clone();
} else {
return Ok(None)
}
} else {
return Ok(None)
}
let span = get_span(&node_toks)?;
let node = Node {
node_rule: NdRule::FuncDef { name, body },
tokens: node_toks,
span,
flags: NdFlag::empty()
};
Ok(Some(node))
});
ndrule_def!(Subshell, |tokens: &[Token]| {
let mut tokens_iter = tokens.iter();
let mut node_toks = vec![];

View File

@@ -86,6 +86,7 @@ pub use crate::{
Redir,
RedirType,
RedirBldr,
StrOps,
RedirTarget,
CmdRedirs,
borrow_fd,
@@ -112,12 +113,17 @@ pub use crate::{
cd::cd,
pwd::pwd,
read::read_builtin,
alias::alias,
jobctl::{
continue_job,
jobs
},
BUILTINS
},
expand::{
expand_argv,
alias::expand_aliases
},
shellenv::{
self,
wait_fg,

View File

@@ -16,7 +16,7 @@ impl<'a> Highlighter for SynHelper<'a> {
let raw = token.to_string();
match token.rule() {
TkRule::Comment => {
let styled = style_text(&raw, Style::BrightBlack);
let styled = &raw.styled(Style::BrightBlack);
result.push_str(&styled);
}
TkRule::ErrPipeOp |
@@ -26,46 +26,70 @@ impl<'a> Highlighter for SynHelper<'a> {
TkRule::RedirOp |
TkRule::BgOp => {
is_command = true;
let styled = style_text(&raw, Style::Cyan);
let styled = &raw.styled(Style::Cyan);
result.push_str(&styled);
}
TkRule::FuncName => {
let name = raw.strip_suffix("()").unwrap_or(&raw);
let styled = name.styled(Style::Cyan);
let rebuilt = format!("{styled}()");
result.push_str(&rebuilt);
}
TkRule::Keyword => {
if &raw == "for" {
in_array = true;
}
let styled = style_text(&raw, Style::Yellow);
let styled = &raw.styled(Style::Yellow);
result.push_str(&styled);
}
TkRule::BraceGrp => {
let body = &raw[1..raw.len() - 1];
let highlighted = self.highlight(body, 0).to_string();
let styled_o_brace = "{".styled(Style::BrightBlue);
let styled_c_brace = "}".styled(Style::BrightBlue);
let rebuilt = format!("{styled_o_brace}{highlighted}{styled_c_brace}");
is_command = false;
result.push_str(&rebuilt);
}
TkRule::Subshell => {
let body = &raw[1..raw.len() - 1];
let highlighted = self.highlight(body, 0).to_string();
let styled_o_paren = style_text("(", Style::BrightBlue);
let styled_c_paren = style_text(")", Style::BrightBlue);
let styled_o_paren = "(".styled(Style::BrightBlue);
let styled_c_paren = ")".styled(Style::BrightBlue);
let rebuilt = format!("{styled_o_paren}{highlighted}{styled_c_paren}");
is_command = false;
result.push_str(&rebuilt);
}
TkRule::Ident => {
if in_array {
if &raw == "in" {
let styled = style_text(&raw, Style::Yellow);
let styled = &raw.styled(Style::Yellow);
result.push_str(&styled);
} else {
let styled = style_text(&raw, Style::Magenta);
let styled = &raw.styled(Style::Magenta);
result.push_str(&styled);
}
} else if &raw == "{" || &raw == "}" {
result.push_str(&raw);
} else if is_command {
if get_bin_path(&token.to_string(), self.shenv).is_some() ||
self.shenv.logic().get_alias(&raw).is_some() ||
self.shenv.logic().get_function(&raw).is_some() ||
BUILTINS.contains(&raw.as_str()) {
let styled = style_text(&raw, Style::Green);
let styled = &raw.styled(Style::Green);
result.push_str(&styled);
} else {
let styled = style_text(&raw, Style::Red | Style::Bold);
let styled = &raw.styled(Style::Red | Style::Bold);
result.push_str(&styled);
}
is_command = false;
} else {
result.push_str(&raw);
}

View File

@@ -1,6 +1,6 @@
use crate::prelude::*;
use readline::SynHelper;
use rustyline::{config::Configurer, history::DefaultHistory, ColorMode, CompletionType, Config, DefaultEditor, EditMode, Editor};
use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMode, CompletionType, Config, DefaultEditor, EditMode, Editor};
pub mod readline;
pub mod highlight;
@@ -27,10 +27,19 @@ fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult<String> {
log!(TRACE, "Entering prompt");
let prompt = &style_text("$ ", Style::Green | Style::Bold);
let prompt = "$ ".styled(Style::Green | Style::Bold);
let mut editor = init_rl(shenv);
match editor.readline(prompt) {
Ok(line) => Ok(line),
match editor.readline(&prompt) {
Ok(line) => {
if !line.is_empty() {
let hist_path = std::env::var("FERN_HIST").ok();
editor.history_mut().add(&line).unwrap();
if let Some(path) = hist_path {
editor.history_mut().save(&PathBuf::from(path)).unwrap();
}
}
Ok(line)
},
Err(rustyline::error::ReadlineError::Eof) => {
kill(Pid::this(), Signal::SIGQUIT)?;
Ok(String::new())

View File

@@ -54,7 +54,7 @@ pub struct SynHint {
impl SynHint {
pub fn new(text: String) -> Self {
let styled = style_text(&text, Style::BrightBlack);
let styled = (&text).styled(Style::BrightBlack);
Self { text, styled }
}
pub fn empty() -> Self {
@@ -78,7 +78,6 @@ impl Hint for SynHint {
impl<'a> Hinter for SynHelper<'a> {
type Hint = SynHint;
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
return Some(SynHint::empty());
if line.is_empty() {
return None
}

View File

@@ -99,9 +99,10 @@ impl<'a> ChildProc {
} else {
WtStat::Exited(pid, 0)
};
log!(DEBUG, command);
let mut child = Self { pgid: pid, pid, command, stat };
if let Some(pgid) = pgid {
child.set_pgid(pgid)?;
child.set_pgid(pgid).ok();
}
Ok(child)
}
@@ -345,14 +346,14 @@ impl Job {
stat_line = format!("{}{} ",pid,stat_line);
stat_line = format!("{} {}", stat_line, cmd);
stat_line = match job_stat {
WtStat::Stopped(..) | WtStat::Signaled(..) => style_text(stat_line, Style::Magenta),
WtStat::Stopped(..) | WtStat::Signaled(..) => stat_line.styled(Style::Magenta),
WtStat::Exited(_, code) => {
match code {
0 => style_text(stat_line, Style::Green),
_ => style_text(stat_line, Style::Red),
0 => stat_line.styled(Style::Green),
_ => stat_line.styled(Style::Red),
}
}
_ => style_text(stat_line, Style::Cyan)
_ => stat_line.styled(Style::Cyan)
};
if i != self.get_cmds().len() - 1 {
stat_line = format!("{} |",stat_line);

View File

@@ -16,7 +16,13 @@ impl LogTab {
pub fn get_alias(&self,name: &str) -> Option<&str> {
self.aliases.get(name).map(|a| a.as_str())
}
pub fn set_alias(&mut self, name: &str, body: &str) {
self.aliases.insert(name.to_string(),body.trim().to_string());
}
pub fn get_function(&self,name: &str) -> Option<&str> {
self.functions.get(name).map(|a| a.as_str())
}
pub fn set_function(&mut self, name: &str, body: &str) {
self.functions.insert(name.to_string(),body.trim().to_string());
}
}

View File

@@ -92,8 +92,8 @@ impl VarTab {
env::set_var("HOME", home.clone());
env_vars.insert("SHELL".into(), pathbuf_to_string(std::env::current_exe()));
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
env_vars.insert("HIST_FILE".into(),format!("{}/.fern_hist",home));
env::set_var("HIST_FILE",format!("{}/.fern_hist",home));
env_vars.insert("FERN_HIST".into(),format!("{}/.fern_hist",home));
env::set_var("FERN_HIST",format!("{}/.fern_hist",home));
env_vars
}