Added rustfmt.toml, formatted codebase

This commit is contained in:
2025-08-12 13:58:25 -04:00
parent 23fb67aba8
commit 8ad53f09b3
52 changed files with 15188 additions and 14451 deletions

View File

@@ -1,356 +1,439 @@
use std::fmt::Display;
use crate::{
libsh::term::{Style, Styled},
parse::lex::Span,
prelude::*
libsh::term::{Style, Styled},
parse::lex::Span,
prelude::*,
};
pub type ShResult<T> = Result<T,ShErr>;
pub type ShResult<T> = Result<T, ShErr>;
pub trait ShResultExt {
fn blame(self, span: Span) -> Self;
fn try_blame(self, span: Span) -> Self;
fn blame(self, span: Span) -> Self;
fn try_blame(self, span: Span) -> Self;
}
impl<T> ShResultExt for Result<T,ShErr> {
/// Blame a span for an error
fn blame(self, new_span: Span) -> Self {
let Err(e) = self else {
return self
};
match e {
ShErr::Simple { kind, msg, notes } |
ShErr::Full { kind, msg, notes, span: _ } => Err(ShErr::Full { kind: kind.clone(), msg: msg.clone(), notes: notes.clone(), span: new_span }),
}
}
/// Blame a span if no blame has been assigned yet
fn try_blame(self, new_span: Span) -> Self {
let Err(e) = &self else {
return self
};
match e {
ShErr::Simple { kind, msg, notes } => Err(ShErr::Full { kind: kind.clone(), msg: msg.clone(), notes: notes.clone(), span: new_span }),
ShErr::Full { kind: _, msg: _, span: _, notes: _ } => self
}
}
impl<T> ShResultExt for Result<T, ShErr> {
/// Blame a span for an error
fn blame(self, new_span: Span) -> Self {
let Err(e) = self else { return self };
match e {
ShErr::Simple { kind, msg, notes }
| ShErr::Full {
kind,
msg,
notes,
span: _,
} => Err(ShErr::Full {
kind: kind.clone(),
msg: msg.clone(),
notes: notes.clone(),
span: new_span,
}),
}
}
/// Blame a span if no blame has been assigned yet
fn try_blame(self, new_span: Span) -> Self {
let Err(e) = &self else { return self };
match e {
ShErr::Simple { kind, msg, notes } => Err(ShErr::Full {
kind: kind.clone(),
msg: msg.clone(),
notes: notes.clone(),
span: new_span,
}),
ShErr::Full {
kind: _,
msg: _,
span: _,
notes: _,
} => self,
}
}
}
#[derive(Clone,Debug)]
#[derive(Clone, Debug)]
pub struct Note {
main: String,
sub_notes: Vec<Note>,
depth: usize
main: String,
sub_notes: Vec<Note>,
depth: usize,
}
impl Note {
pub fn new(main: impl Into<String>) -> Self {
Self {
main: main.into(),
sub_notes: vec![],
depth: 0
}
}
pub fn new(main: impl Into<String>) -> Self {
Self {
main: main.into(),
sub_notes: vec![],
depth: 0,
}
}
pub fn with_sub_notes(self, new_sub_notes: Vec<impl Into<String>>) -> Self {
let Self { main, mut sub_notes, depth } = self;
for raw_note in new_sub_notes {
let mut note = Note::new(raw_note);
note.depth = self.depth + 1;
sub_notes.push(note);
}
Self { main, sub_notes, depth }
}
pub fn with_sub_notes(self, new_sub_notes: Vec<impl Into<String>>) -> Self {
let Self {
main,
mut sub_notes,
depth,
} = self;
for raw_note in new_sub_notes {
let mut note = Note::new(raw_note);
note.depth = self.depth + 1;
sub_notes.push(note);
}
Self {
main,
sub_notes,
depth,
}
}
}
impl Display for Note {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let note = "note".styled(Style::Green);
let main = &self.main;
if self.depth == 0 {
writeln!(f, "{note}: {main}")?;
} else {
let bar_break = "-".styled(Style::Cyan | Style::Bold);
let indent = " ".repeat(self.depth);
writeln!(f, " {indent}{bar_break} {main}")?;
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let note = "note".styled(Style::Green);
let main = &self.main;
if self.depth == 0 {
writeln!(f, "{note}: {main}")?;
} else {
let bar_break = "-".styled(Style::Cyan | Style::Bold);
let indent = " ".repeat(self.depth);
writeln!(f, " {indent}{bar_break} {main}")?;
}
for sub_note in &self.sub_notes {
write!(f, "{sub_note}")?;
}
Ok(())
}
for sub_note in &self.sub_notes {
write!(f, "{sub_note}")?;
}
Ok(())
}
}
#[derive(Debug)]
pub enum ShErr {
Simple { kind: ShErrKind, msg: String, notes: Vec<Note> },
Full { kind: ShErrKind, msg: String, notes: Vec<Note>, span: Span }
Simple {
kind: ShErrKind,
msg: String,
notes: Vec<Note>,
},
Full {
kind: ShErrKind,
msg: String,
notes: Vec<Note>,
span: Span,
},
}
impl ShErr {
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
let msg = msg.into();
Self::Simple { kind, msg, notes: vec![] }
}
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
let msg = msg.into();
Self::Full { kind, msg, span, notes: vec![] }
}
pub fn unpack(self) -> (ShErrKind,String,Vec<Note>,Option<Span>) {
match self {
ShErr::Simple { kind, msg, notes } => (kind,msg,notes,None),
ShErr::Full { kind, msg, notes, span } => (kind,msg,notes,Some(span))
}
}
pub fn with_note(self, note: Note) -> Self {
let (kind,msg,mut notes,span) = self.unpack();
notes.push(note);
if let Some(span) = span {
Self::Full { kind, msg, notes, span }
} else {
Self::Simple { kind, msg, notes }
}
}
pub fn with_span(sherr: ShErr, span: Span) -> Self {
let (kind,msg,notes,_) = sherr.unpack();
Self::Full { kind, msg, notes, span }
}
pub fn kind(&self) -> &ShErrKind {
match self {
ShErr::Simple { kind, msg: _, notes: _ } |
ShErr::Full { kind, msg: _, notes: _, span: _ } => kind
}
}
pub fn get_window(&self) -> Vec<(usize,String)> {
let ShErr::Full { kind: _, msg: _, notes: _, span } = self else {
unreachable!()
};
let mut total_len: usize = 0;
let mut total_lines: usize = 1;
let mut lines = vec![];
let mut cur_line = String::new();
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
let msg = msg.into();
Self::Simple {
kind,
msg,
notes: vec![],
}
}
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
let msg = msg.into();
Self::Full {
kind,
msg,
span,
notes: vec![],
}
}
pub fn unpack(self) -> (ShErrKind, String, Vec<Note>, Option<Span>) {
match self {
ShErr::Simple { kind, msg, notes } => (kind, msg, notes, None),
ShErr::Full {
kind,
msg,
notes,
span,
} => (kind, msg, notes, Some(span)),
}
}
pub fn with_note(self, note: Note) -> Self {
let (kind, msg, mut notes, span) = self.unpack();
notes.push(note);
if let Some(span) = span {
Self::Full {
kind,
msg,
notes,
span,
}
} else {
Self::Simple { kind, msg, notes }
}
}
pub fn with_span(sherr: ShErr, span: Span) -> Self {
let (kind, msg, notes, _) = sherr.unpack();
Self::Full {
kind,
msg,
notes,
span,
}
}
pub fn kind(&self) -> &ShErrKind {
match self {
ShErr::Simple {
kind,
msg: _,
notes: _,
}
| ShErr::Full {
kind,
msg: _,
notes: _,
span: _,
} => kind,
}
}
pub fn get_window(&self) -> Vec<(usize, String)> {
let ShErr::Full {
kind: _,
msg: _,
notes: _,
span,
} = self
else {
unreachable!()
};
let mut total_len: usize = 0;
let mut total_lines: usize = 1;
let mut lines = vec![];
let mut cur_line = String::new();
let src = span.get_source();
let mut chars = src.chars();
let src = span.get_source();
let mut chars = src.chars();
while let Some(ch) = chars.next() {
total_len += ch.len_utf8();
cur_line.push(ch);
if ch == '\n' {
if total_len > span.start {
let line = (
total_lines,
mem::take(&mut cur_line)
);
lines.push(line);
}
if total_len >= span.end {
break
}
total_lines += 1;
while let Some(ch) = chars.next() {
total_len += ch.len_utf8();
cur_line.push(ch);
if ch == '\n' {
if total_len > span.start {
let line = (total_lines, mem::take(&mut cur_line));
lines.push(line);
}
if total_len >= span.end {
break;
}
total_lines += 1;
cur_line.clear();
}
}
cur_line.clear();
}
}
if !cur_line.is_empty() {
let line = (
total_lines,
mem::take(&mut cur_line)
);
lines.push(line);
}
if !cur_line.is_empty() {
let line = (total_lines, mem::take(&mut cur_line));
lines.push(line);
}
lines
}
pub fn get_line_col(&self) -> (usize,usize) {
let ShErr::Full { kind: _, msg: _, notes: _, span } = self else {
unreachable!()
};
lines
}
pub fn get_line_col(&self) -> (usize, usize) {
let ShErr::Full {
kind: _,
msg: _,
notes: _,
span,
} = self
else {
unreachable!()
};
let mut lineno = 1;
let mut colno = 1;
let src = span.get_source();
let mut chars = src.chars().enumerate();
while let Some((pos,ch)) = chars.next() {
if pos >= span.start {
break
}
if ch == '\n' {
lineno += 1;
colno = 1;
} else {
colno += 1;
}
}
(lineno,colno)
}
pub fn get_indicator_lines(&self) -> Option<Vec<String>> {
match self {
ShErr::Simple { kind: _, msg: _, notes: _ } => None,
ShErr::Full { kind: _, msg: _, notes: _, span } => {
let text = span.as_str();
let lines = text.lines();
let mut indicator_lines = vec![];
let mut lineno = 1;
let mut colno = 1;
let src = span.get_source();
let mut chars = src.chars().enumerate();
while let Some((pos, ch)) = chars.next() {
if pos >= span.start {
break;
}
if ch == '\n' {
lineno += 1;
colno = 1;
} else {
colno += 1;
}
}
(lineno, colno)
}
pub fn get_indicator_lines(&self) -> Option<Vec<String>> {
match self {
ShErr::Simple {
kind: _,
msg: _,
notes: _,
} => None,
ShErr::Full {
kind: _,
msg: _,
notes: _,
span,
} => {
let text = span.as_str();
let lines = text.lines();
let mut indicator_lines = vec![];
for line in lines {
let indicator_line = "^".repeat(line.trim().len()).styled(Style::Red | Style::Bold);
indicator_lines.push(indicator_line);
}
for line in lines {
let indicator_line = "^"
.repeat(line.trim().len())
.styled(Style::Red | Style::Bold);
indicator_lines.push(indicator_line);
}
Some(indicator_lines)
}
}
}
Some(indicator_lines)
}
}
}
}
impl Display for ShErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Simple { msg, kind: _, notes } => {
let mut all_strings = vec![msg.to_string()];
let mut notes_fmt = vec![];
for note in notes {
let fmt = format!("{note}");
notes_fmt.push(fmt);
}
all_strings.append(&mut notes_fmt);
let mut output = all_strings.join("\n");
output.push('\n');
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Simple {
msg,
kind: _,
notes,
} => {
let mut all_strings = vec![msg.to_string()];
let mut notes_fmt = vec![];
for note in notes {
let fmt = format!("{note}");
notes_fmt.push(fmt);
}
all_strings.append(&mut notes_fmt);
let mut output = all_strings.join("\n");
output.push('\n');
writeln!(f, "{}", output)
}
writeln!(f, "{}", output)
}
Self::Full { msg, kind, notes, span: _ } => {
let window = self.get_window();
let mut indicator_lines = self.get_indicator_lines().unwrap().into_iter();
let mut lineno_pad_count = 0;
for (lineno,_) in window.clone() {
if lineno.to_string().len() > lineno_pad_count {
lineno_pad_count = lineno.to_string().len() + 1
}
}
let padding = " ".repeat(lineno_pad_count);
writeln!(f)?;
Self::Full {
msg,
kind,
notes,
span: _,
} => {
let window = self.get_window();
let mut indicator_lines = self.get_indicator_lines().unwrap().into_iter();
let mut lineno_pad_count = 0;
for (lineno, _) in window.clone() {
if lineno.to_string().len() > lineno_pad_count {
lineno_pad_count = lineno.to_string().len() + 1
}
}
let padding = " ".repeat(lineno_pad_count);
writeln!(f)?;
let (line, col) = self.get_line_col();
let line_fmt = line.styled(Style::Cyan | Style::Bold);
let col_fmt = col.styled(Style::Cyan | Style::Bold);
let kind = kind.styled(Style::Red | Style::Bold);
let arrow = "->".styled(Style::Cyan | Style::Bold);
writeln!(f, "{kind} - {msg}",)?;
writeln!(f, "{padding}{arrow} [{line_fmt};{col_fmt}]",)?;
let (line,col) = self.get_line_col();
let line_fmt = line.styled(Style::Cyan | Style::Bold);
let col_fmt = col.styled(Style::Cyan | Style::Bold);
let kind = kind.styled(Style::Red | Style::Bold);
let arrow = "->".styled(Style::Cyan | Style::Bold);
writeln!(f,
"{kind} - {msg}",
)?;
writeln!(f,
"{padding}{arrow} [{line_fmt};{col_fmt}]",
)?;
let bar = format!("{padding}|").styled(Style::Cyan | Style::Bold);
writeln!(f, "{bar}")?;
let bar = format!("{padding}|").styled(Style::Cyan | Style::Bold);
writeln!(f,"{bar}")?;
let mut first_ind_ln = true;
for (lineno, line) in window {
let lineno = lineno.to_string();
let line = line.trim();
let mut prefix = format!("{padding}|");
prefix.replace_range(0..lineno.len(), &lineno);
prefix = prefix.styled(Style::Cyan | Style::Bold);
writeln!(f, "{prefix} {line}")?;
let mut first_ind_ln = true;
for (lineno,line) in window {
let lineno = lineno.to_string();
let line = line.trim();
let mut prefix = format!("{padding}|");
prefix.replace_range(0..lineno.len(), &lineno);
prefix = prefix.styled(Style::Cyan | Style::Bold);
writeln!(f,"{prefix} {line}")?;
if let Some(ind_ln) = indicator_lines.next() {
if first_ind_ln {
let ind_ln_padding = " ".repeat(col);
let ind_ln = format!("{ind_ln_padding}{ind_ln}");
writeln!(f, "{bar}{ind_ln}")?;
first_ind_ln = false;
} else {
writeln!(f, "{bar} {ind_ln}")?;
}
}
}
if let Some(ind_ln) = indicator_lines.next() {
if first_ind_ln {
let ind_ln_padding = " ".repeat(col);
let ind_ln = format!("{ind_ln_padding}{ind_ln}");
writeln!(f, "{bar}{ind_ln}")?;
first_ind_ln = false;
} else {
writeln!(f, "{bar} {ind_ln}")?;
}
}
}
write!(f, "{bar}")?;
write!(f,"{bar}")?;
let bar_break = "-".styled(Style::Cyan | Style::Bold);
if !notes.is_empty() {
writeln!(f)?;
}
for note in notes {
write!(f,
"{padding}{bar_break} {note}"
)?;
}
Ok(())
}
}
}
let bar_break = "-".styled(Style::Cyan | Style::Bold);
if !notes.is_empty() {
writeln!(f)?;
}
for note in notes {
write!(f, "{padding}{bar_break} {note}")?;
}
Ok(())
}
}
}
}
impl From<std::io::Error> for ShErr {
fn from(_: std::io::Error) -> Self {
let msg = std::io::Error::last_os_error();
ShErr::simple(ShErrKind::IoErr, msg.to_string())
}
fn from(_: std::io::Error) -> Self {
let msg = std::io::Error::last_os_error();
ShErr::simple(ShErrKind::IoErr, msg.to_string())
}
}
impl From<std::env::VarError> for ShErr {
fn from(value: std::env::VarError) -> Self {
ShErr::simple(ShErrKind::InternalErr, value.to_string())
}
fn from(value: std::env::VarError) -> Self {
ShErr::simple(ShErrKind::InternalErr, value.to_string())
}
}
impl From<Errno> for ShErr {
fn from(value: Errno) -> Self {
ShErr::simple(ShErrKind::Errno, value.to_string())
}
fn from(value: Errno) -> Self {
ShErr::simple(ShErrKind::Errno, value.to_string())
}
}
#[derive(Debug,Clone)]
#[derive(Debug, Clone)]
pub enum ShErrKind {
IoErr,
SyntaxErr,
ParseErr,
InternalErr,
ExecFail,
HistoryReadErr,
ResourceLimitExceeded,
BadPermission,
Errno,
FileNotFound(String),
CmdNotFound(String),
CleanExit(i32),
FuncReturn(i32),
LoopContinue(i32),
LoopBreak(i32),
ReadlineErr,
Null
IoErr,
SyntaxErr,
ParseErr,
InternalErr,
ExecFail,
HistoryReadErr,
ResourceLimitExceeded,
BadPermission,
Errno,
FileNotFound(String),
CmdNotFound(String),
CleanExit(i32),
FuncReturn(i32),
LoopContinue(i32),
LoopBreak(i32),
ReadlineErr,
Null,
}
impl Display for ShErrKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let output = match self {
Self::IoErr => "I/O Error",
Self::SyntaxErr => "Syntax Error",
Self::ParseErr => "Parse Error",
Self::InternalErr => "Internal Error",
Self::HistoryReadErr => "History Parse Error",
Self::ExecFail => "Execution Failed",
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
Self::BadPermission => "Bad Permissions",
Self::Errno => "ERRNO",
Self::FileNotFound(file) => &format!("File not found: {file}"),
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
Self::CleanExit(_) => "",
Self::FuncReturn(_) => "",
Self::LoopContinue(_) => "",
Self::LoopBreak(_) => "",
Self::ReadlineErr => "Line Read Error",
Self::Null => "",
};
write!(f,"{output}")
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let output = match self {
Self::IoErr => "I/O Error",
Self::SyntaxErr => "Syntax Error",
Self::ParseErr => "Parse Error",
Self::InternalErr => "Internal Error",
Self::HistoryReadErr => "History Parse Error",
Self::ExecFail => "Execution Failed",
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
Self::BadPermission => "Bad Permissions",
Self::Errno => "ERRNO",
Self::FileNotFound(file) => &format!("File not found: {file}"),
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
Self::CleanExit(_) => "",
Self::FuncReturn(_) => "",
Self::LoopContinue(_) => "",
Self::LoopBreak(_) => "",
Self::ReadlineErr => "Line Read Error",
Self::Null => "",
};
write!(f, "{output}")
}
}

View File

@@ -2,55 +2,57 @@ use std::fmt::Display;
use super::term::{Style, Styled};
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Debug)]
#[repr(u8)]
pub enum FernLogLevel {
NONE = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5
NONE = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5,
}
impl Display for FernLogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use FernLogLevel::*;
match self {
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)),
NONE => write!(f,"")
}
}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use FernLogLevel::*;
match self {
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)),
NONE => write!(f, ""),
}
}
}
pub fn log_level() -> FernLogLevel {
use FernLogLevel::*;
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
match level.to_lowercase().as_str() {
"error" => ERROR,
"warn" => WARN,
"info" => INFO,
"debug" => DEBUG,
"trace" => TRACE,
_ => NONE
}
use FernLogLevel::*;
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
match level.to_lowercase().as_str() {
"error" => ERROR,
"warn" => WARN,
"info" => INFO,
"debug" => DEBUG,
"trace" => TRACE,
_ => NONE,
}
}
/// A structured logging macro designed for `fern`.
///
/// `flog!` was implemented because `rustyline` uses `env_logger`, which clutters the debug output.
/// This macro prints log messages in a structured format, including the log level, filename, and line number.
/// `flog!` was implemented because `rustyline` uses `env_logger`, which
/// clutters the debug output. This macro prints log messages in a structured
/// format, including the log level, filename, and line number.
///
/// # Usage
///
/// The macro supports three types of arguments:
///
/// ## 1. **Formatted Messages**
/// Similar to `println!` or `format!`, allows embedding values inside a formatted string.
/// Similar to `println!` or `format!`, allows embedding values inside a
/// formatted string.
///
/// ```rust
/// flog!(ERROR, "foo is {}", foo);
@@ -73,7 +75,8 @@ pub fn log_level() -> FernLogLevel {
/// ```
///
/// ## 3. **Expressions**
/// Logs the evaluated result of each given expression, displaying both the expression and its value.
/// Logs the evaluated result of each given expression, displaying both the
/// expression and its value.
///
/// ```rust
/// flog!(INFO, 1.min(2));
@@ -84,8 +87,10 @@ pub fn log_level() -> FernLogLevel {
/// ```
///
/// # Considerations
/// - This macro uses `eprintln!()` internally, so its formatting rules must be followed.
/// - **Literals and formatted messages** require arguments that implement [`std::fmt::Display`].
/// - This macro uses `eprintln!()` internally, so its formatting rules must be
/// followed.
/// - **Literals and formatted messages** require arguments that implement
/// [`std::fmt::Display`].
/// - **Expressions** require arguments that implement [`std::fmt::Debug`].
#[macro_export]
macro_rules! flog {

View File

@@ -1,5 +1,5 @@
pub mod error;
pub mod term;
pub mod flog;
pub mod sys;
pub mod term;
pub mod utils;

View File

@@ -4,70 +4,91 @@ use crate::{prelude::*, state::write_jobs};
///
/// The previous state of the terminal options.
///
/// This variable stores the terminal settings at the start of the program and restores them when the program exits.
/// It is initialized exactly once at the start of the program and accessed exactly once at the end of the program.
/// It will not be mutated or accessed under any other circumstances.
/// This variable stores the terminal settings at the start of the program and
/// restores them when the program exits. It is initialized exactly once at the
/// start of the program and accessed exactly once at the end of the program. It
/// will not be mutated or accessed under any other circumstances.
///
/// This ended up being necessary because wrapping Termios in a thread-safe way was unreasonably tricky.
/// This ended up being necessary because wrapping Termios in a thread-safe way
/// was unreasonably tricky.
///
/// The possible states of this variable are:
/// - `None`: The terminal options have not been set yet (before initialization).
/// - `Some(None)`: There were no terminal options to save (i.e., no terminal input detected).
/// - `Some(Some(Termios))`: The terminal options (as `Termios`) have been saved.
/// - `None`: The terminal options have not been set yet (before
/// initialization).
/// - `Some(None)`: There were no terminal options to save (i.e., no terminal
/// input detected).
/// - `Some(Some(Termios))`: The terminal options (as `Termios`) have been
/// saved.
///
/// **Important:** This static variable is mutable and accessed via unsafe code. It is only safe to use because:
/// - It is set once during program startup and accessed once during program exit.
/// **Important:** This static variable is mutable and accessed via unsafe code.
/// It is only safe to use because:
/// - It is set once during program startup and accessed once during program
/// exit.
/// - It is not mutated or accessed after the initial setup and final read.
///
/// **Caution:** Future changes to this code should respect these constraints to ensure safety. Modifying or accessing this variable outside the defined lifecycle could lead to undefined behavior.
/// **Caution:** Future changes to this code should respect these constraints to
/// ensure safety. Modifying or accessing this variable outside the defined
/// lifecycle could lead to undefined behavior.
pub(crate) static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
pub fn save_termios() {
unsafe {
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
termios.local_flags &= !LocalFlags::ECHOCTL;
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
Some(termios)
} else {
None
});
}
unsafe {
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
termios.local_flags &= !LocalFlags::ECHOCTL;
termios::tcsetattr(
std::io::stdin(),
nix::sys::termios::SetArg::TCSANOW,
&termios,
)
.unwrap();
Some(termios)
} else {
None
});
}
}
#[allow(static_mut_refs)]
///Access the saved termios
///
///# Safety
///This function is unsafe because it accesses a public mutable static value. This function should only ever be called after save_termios() has already been called.
///This function is unsafe because it accesses a public mutable static value.
/// This function should only ever be called after save_termios() has already
/// been called.
pub unsafe fn get_saved_termios() -> Option<Termios> {
// SAVED_TERMIOS should *only ever* be set once and accessed once
// Set at the start of the program, and accessed during the exit of the program to reset the termios.
// Do not use this variable anywhere else
SAVED_TERMIOS.clone().flatten()
// SAVED_TERMIOS should *only ever* be set once and accessed once
// Set at the start of the program, and accessed during the exit of the program
// to reset the termios. Do not use this variable anywhere else
SAVED_TERMIOS.clone().flatten()
}
/// Set termios to not echo control characters, like ^Z for instance
pub fn set_termios() {
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
termios.local_flags &= !LocalFlags::ECHOCTL;
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
}
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
termios.local_flags &= !LocalFlags::ECHOCTL;
termios::tcsetattr(
std::io::stdin(),
nix::sys::termios::SetArg::TCSANOW,
&termios,
)
.unwrap();
}
}
pub fn sh_quit(code: i32) -> ! {
write_jobs(|j| {
for job in j.jobs_mut().iter_mut().flatten() {
job.killpg(Signal::SIGTERM).ok();
}
});
if let Some(termios) = unsafe { get_saved_termios() } {
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
}
if code == 0 {
eprintln!("exit");
} else {
eprintln!("exit {code}");
}
exit(code);
write_jobs(|j| {
for job in j.jobs_mut().iter_mut().flatten() {
job.killpg(Signal::SIGTERM).ok();
}
});
if let Some(termios) = unsafe { get_saved_termios() } {
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
}
if code == 0 {
eprintln!("exit");
} else {
eprintln!("exit {code}");
}
exit(code);
}

View File

@@ -1,11 +1,11 @@
use std::{fmt::Display, ops::BitOr};
pub trait Styled: Sized + Display {
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
let styles: StyleSet = style.into();
let reset = Style::Reset;
format!("{styles}{self}{reset}")
}
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
let styles: StyleSet = style.into();
let reset = Style::Reset;
format!("{styles}{self}{reset}")
}
}
impl<T: Display> Styled for T {}
@@ -13,157 +13,157 @@ impl<T: Display> Styled for T {}
/// Enum representing a single ANSI style
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Style {
// Undoes all styles
Reset,
// Foreground Colors
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
RGB(u8, u8, u8), // Custom foreground color
// Undoes all styles
Reset,
// Foreground Colors
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
RGB(u8, u8, u8), // Custom foreground color
// Background Colors
BgBlack,
BgRed,
BgGreen,
BgYellow,
BgBlue,
BgMagenta,
BgCyan,
BgWhite,
BgBrightBlack,
BgBrightRed,
BgBrightGreen,
BgBrightYellow,
BgBrightBlue,
BgBrightMagenta,
BgBrightCyan,
BgBrightWhite,
BgRGB(u8, u8, u8), // Custom background color
// Background Colors
BgBlack,
BgRed,
BgGreen,
BgYellow,
BgBlue,
BgMagenta,
BgCyan,
BgWhite,
BgBrightBlack,
BgBrightRed,
BgBrightGreen,
BgBrightYellow,
BgBrightBlue,
BgBrightMagenta,
BgBrightCyan,
BgBrightWhite,
BgRGB(u8, u8, u8), // Custom background color
// Text Attributes
Bold,
Dim,
Italic,
Underline,
Strikethrough,
Reversed,
// Text Attributes
Bold,
Dim,
Italic,
Underline,
Strikethrough,
Reversed,
}
impl Display for Style {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Style::Reset => write!(f, "\x1b[0m"),
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Style::Reset => write!(f, "\x1b[0m"),
// Foreground colors
Style::Black => write!(f, "\x1b[30m"),
Style::Red => write!(f, "\x1b[31m"),
Style::Green => write!(f, "\x1b[32m"),
Style::Yellow => write!(f, "\x1b[33m"),
Style::Blue => write!(f, "\x1b[34m"),
Style::Magenta => write!(f, "\x1b[35m"),
Style::Cyan => write!(f, "\x1b[36m"),
Style::White => write!(f, "\x1b[37m"),
Style::BrightBlack => write!(f, "\x1b[90m"),
Style::BrightRed => write!(f, "\x1b[91m"),
Style::BrightGreen => write!(f, "\x1b[92m"),
Style::BrightYellow => write!(f, "\x1b[93m"),
Style::BrightBlue => write!(f, "\x1b[94m"),
Style::BrightMagenta => write!(f, "\x1b[95m"),
Style::BrightCyan => write!(f, "\x1b[96m"),
Style::BrightWhite => write!(f, "\x1b[97m"),
Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"),
// Foreground colors
Style::Black => write!(f, "\x1b[30m"),
Style::Red => write!(f, "\x1b[31m"),
Style::Green => write!(f, "\x1b[32m"),
Style::Yellow => write!(f, "\x1b[33m"),
Style::Blue => write!(f, "\x1b[34m"),
Style::Magenta => write!(f, "\x1b[35m"),
Style::Cyan => write!(f, "\x1b[36m"),
Style::White => write!(f, "\x1b[37m"),
Style::BrightBlack => write!(f, "\x1b[90m"),
Style::BrightRed => write!(f, "\x1b[91m"),
Style::BrightGreen => write!(f, "\x1b[92m"),
Style::BrightYellow => write!(f, "\x1b[93m"),
Style::BrightBlue => write!(f, "\x1b[94m"),
Style::BrightMagenta => write!(f, "\x1b[95m"),
Style::BrightCyan => write!(f, "\x1b[96m"),
Style::BrightWhite => write!(f, "\x1b[97m"),
Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"),
// Background colors
Style::BgBlack => write!(f, "\x1b[40m"),
Style::BgRed => write!(f, "\x1b[41m"),
Style::BgGreen => write!(f, "\x1b[42m"),
Style::BgYellow => write!(f, "\x1b[43m"),
Style::BgBlue => write!(f, "\x1b[44m"),
Style::BgMagenta => write!(f, "\x1b[45m"),
Style::BgCyan => write!(f, "\x1b[46m"),
Style::BgWhite => write!(f, "\x1b[47m"),
Style::BgBrightBlack => write!(f, "\x1b[100m"),
Style::BgBrightRed => write!(f, "\x1b[101m"),
Style::BgBrightGreen => write!(f, "\x1b[102m"),
Style::BgBrightYellow => write!(f, "\x1b[103m"),
Style::BgBrightBlue => write!(f, "\x1b[104m"),
Style::BgBrightMagenta => write!(f, "\x1b[105m"),
Style::BgBrightCyan => write!(f, "\x1b[106m"),
Style::BgBrightWhite => write!(f, "\x1b[107m"),
Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"),
// Background colors
Style::BgBlack => write!(f, "\x1b[40m"),
Style::BgRed => write!(f, "\x1b[41m"),
Style::BgGreen => write!(f, "\x1b[42m"),
Style::BgYellow => write!(f, "\x1b[43m"),
Style::BgBlue => write!(f, "\x1b[44m"),
Style::BgMagenta => write!(f, "\x1b[45m"),
Style::BgCyan => write!(f, "\x1b[46m"),
Style::BgWhite => write!(f, "\x1b[47m"),
Style::BgBrightBlack => write!(f, "\x1b[100m"),
Style::BgBrightRed => write!(f, "\x1b[101m"),
Style::BgBrightGreen => write!(f, "\x1b[102m"),
Style::BgBrightYellow => write!(f, "\x1b[103m"),
Style::BgBrightBlue => write!(f, "\x1b[104m"),
Style::BgBrightMagenta => write!(f, "\x1b[105m"),
Style::BgBrightCyan => write!(f, "\x1b[106m"),
Style::BgBrightWhite => write!(f, "\x1b[107m"),
Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"),
// Text attributes
Style::Bold => write!(f, "\x1b[1m"),
Style::Dim => write!(f, "\x1b[2m"), // New
Style::Italic => write!(f, "\x1b[3m"),
Style::Underline => write!(f, "\x1b[4m"),
Style::Strikethrough => write!(f, "\x1b[9m"), // New
Style::Reversed => write!(f, "\x1b[7m"),
}
}
// Text attributes
Style::Bold => write!(f, "\x1b[1m"),
Style::Dim => write!(f, "\x1b[2m"), // New
Style::Italic => write!(f, "\x1b[3m"),
Style::Underline => write!(f, "\x1b[4m"),
Style::Strikethrough => write!(f, "\x1b[9m"), // New
Style::Reversed => write!(f, "\x1b[7m"),
}
}
}
/// Struct representing a **set** of styles
#[derive(Debug, Default, Clone)]
pub struct StyleSet {
styles: Vec<Style>,
styles: Vec<Style>,
}
impl StyleSet {
pub fn new() -> Self {
Self { styles: vec![] }
}
pub fn new() -> Self {
Self { styles: vec![] }
}
pub fn add_style(mut self, style: Style) -> Self {
if !self.styles.contains(&style) {
self.styles.push(style);
}
self
}
pub fn add_style(mut self, style: Style) -> Self {
if !self.styles.contains(&style) {
self.styles.push(style);
}
self
}
}
impl Display for StyleSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for style in &self.styles {
style.fmt(f)?
}
Ok(())
}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for style in &self.styles {
style.fmt(f)?
}
Ok(())
}
}
/// Allow OR (`|`) operator to combine multiple `Style` values into a `StyleSet`
impl BitOr for Style {
type Output = StyleSet;
type Output = StyleSet;
fn bitor(self, rhs: Self) -> Self::Output {
StyleSet::new().add_style(self).add_style(rhs)
}
fn bitor(self, rhs: Self) -> Self::Output {
StyleSet::new().add_style(self).add_style(rhs)
}
}
/// Allow OR (`|`) operator to combine `StyleSet` with `Style`
impl BitOr<Style> for StyleSet {
type Output = StyleSet;
type Output = StyleSet;
fn bitor(self, rhs: Style) -> Self::Output {
self.add_style(rhs)
}
fn bitor(self, rhs: Style) -> Self::Output {
self.add_style(rhs)
}
}
impl From<Style> for StyleSet {
fn from(style: Style) -> Self {
StyleSet::new().add_style(style)
}
fn from(style: Style) -> Self {
StyleSet::new().add_style(style)
}
}

View File

@@ -5,107 +5,105 @@ use crate::parse::{Redir, RedirType};
use crate::prelude::*;
pub trait VecDequeExt<T> {
fn to_vec(self) -> Vec<T>;
fn to_vec(self) -> Vec<T>;
}
pub trait CharDequeUtils {
fn to_string(self) -> String;
fn ends_with(&self, pat: &str) -> bool;
fn starts_with(&self, pat: &str) -> bool;
fn to_string(self) -> String;
fn ends_with(&self, pat: &str) -> bool;
fn starts_with(&self, pat: &str) -> bool;
}
pub trait TkVecUtils<Tk> {
fn get_span(&self) -> Option<Span>;
fn debug_tokens(&self);
fn get_span(&self) -> Option<Span>;
fn debug_tokens(&self);
}
pub trait RedirVecUtils<Redir> {
/// Splits the vector of redirections into two vectors
///
/// One vector contains input redirs, the other contains output redirs
fn split_by_channel(self) -> (Vec<Redir>,Vec<Redir>);
/// Splits the vector of redirections into two vectors
///
/// One vector contains input redirs, the other contains output redirs
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>);
}
impl<T> VecDequeExt<T> for VecDeque<T> {
fn to_vec(self) -> Vec<T> {
self.into_iter().collect::<Vec<T>>()
}
fn to_vec(self) -> Vec<T> {
self.into_iter().collect::<Vec<T>>()
}
}
impl CharDequeUtils for VecDeque<char> {
fn to_string(mut self) -> String {
let mut result = String::with_capacity(self.len());
while let Some(ch) = self.pop_front() {
result.push(ch);
}
result
}
fn to_string(mut self) -> String {
let mut result = String::with_capacity(self.len());
while let Some(ch) = self.pop_front() {
result.push(ch);
}
result
}
fn ends_with(&self, pat: &str) -> bool {
let pat_chars = pat.chars();
let self_len = self.len();
fn ends_with(&self, pat: &str) -> bool {
let pat_chars = pat.chars();
let self_len = self.len();
// If pattern is longer than self, return false
if pat_chars.clone().count() > self_len {
return false;
}
// If pattern is longer than self, return false
if pat_chars.clone().count() > self_len {
return false;
}
// Compare from the back
self.iter().rev().zip(pat_chars.rev()).all(|(c1, c2)| c1 == &c2)
}
// Compare from the back
self
.iter()
.rev()
.zip(pat_chars.rev())
.all(|(c1, c2)| c1 == &c2)
}
fn starts_with(&self, pat: &str) -> bool {
let pat_chars = pat.chars();
let self_len = self.len();
fn starts_with(&self, pat: &str) -> bool {
let pat_chars = pat.chars();
let self_len = self.len();
// If pattern is longer than self, return false
if pat_chars.clone().count() > self_len {
return false;
}
// If pattern is longer than self, return false
if pat_chars.clone().count() > self_len {
return false;
}
// Compare from the front
self.iter().zip(pat_chars).all(|(c1, c2)| c1 == &c2)
}
// Compare from the front
self.iter().zip(pat_chars).all(|(c1, c2)| c1 == &c2)
}
}
impl TkVecUtils<Tk> for Vec<Tk> {
fn get_span(&self) -> Option<Span> {
if let Some(first_tk) = self.first() {
self.last().map(|last_tk| {
Span::new(
first_tk.span.start..last_tk.span.end,
first_tk.source()
)
})
} else {
None
}
}
fn debug_tokens(&self) {
for token in self {
flog!(DEBUG, "token: {}",token)
}
}
fn get_span(&self) -> Option<Span> {
if let Some(first_tk) = self.first() {
self
.last()
.map(|last_tk| Span::new(first_tk.span.start..last_tk.span.end, first_tk.source()))
} else {
None
}
}
fn debug_tokens(&self) {
for token in self {
flog!(DEBUG, "token: {}", token)
}
}
}
impl RedirVecUtils<Redir> for Vec<Redir> {
fn split_by_channel(self) -> (Vec<Redir>,Vec<Redir>) {
let mut input = vec![];
let mut output = vec![];
for redir in self {
match redir.class {
RedirType::Input => input.push(redir),
RedirType::Pipe => {
match redir.io_mode.tgt_fd() {
STDIN_FILENO => input.push(redir),
STDOUT_FILENO |
STDERR_FILENO => output.push(redir),
_ => unreachable!()
}
}
_ => output.push(redir)
}
}
(input,output)
}
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>) {
let mut input = vec![];
let mut output = vec![];
for redir in self {
match redir.class {
RedirType::Input => input.push(redir),
RedirType::Pipe => match redir.io_mode.tgt_fd() {
STDIN_FILENO => input.push(redir),
STDOUT_FILENO | STDERR_FILENO => output.push(redir),
_ => unreachable!(),
},
_ => output.push(redir),
}
}
(input, output)
}
}