Merging fern line editor implementation

Implement a line editor instead of using Rustyline
This commit is contained in:
2025-05-28 20:32:43 -04:00
committed by GitHub
73 changed files with 4163 additions and 33861 deletions

2
.gitignore vendored
View File

@@ -3,7 +3,7 @@ target
.idea
*.iml
/result*
src/tests/snapshots
*snapshots*
*.log
default.nix
shell.nix

153
Cargo.lock generated
View File

@@ -47,7 +47,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
"windows-sys",
]
[[package]]
@@ -58,7 +58,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
"windows-sys",
]
[[package]]
@@ -119,15 +119,6 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "clipboard-win"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"error-code",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
@@ -143,7 +134,7 @@ dependencies = [
"encode_unicode",
"libc",
"once_cell",
"windows-sys 0.59.0",
"windows-sys",
]
[[package]]
@@ -158,39 +149,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "error-code"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "fd-lock"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "fern"
version = "0.1.0"
@@ -202,7 +160,8 @@ dependencies = [
"nix",
"pretty_assertions",
"regex",
"rustyline",
"unicode-segmentation",
"unicode-width",
]
[[package]]
@@ -217,15 +176,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "insta"
version = "1.42.2"
@@ -257,33 +207,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.29.0"
@@ -350,16 +279,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "regex"
version = "1.11.1"
@@ -389,65 +308,12 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustyline"
version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f"
dependencies = [
"bitflags",
"cfg-if",
"clipboard-win",
"fd-lock",
"home",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"rustyline-derive",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"windows-sys 0.59.0",
]
[[package]]
name = "rustyline-derive"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327e9d075f6df7e25fbf594f1be7ef55cf0d567a6cb5112eeccbbd51ceb48e0d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "strsim"
version = "0.11.1"
@@ -489,15 +355,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"

View File

@@ -17,7 +17,8 @@ insta = "1.42.2"
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl"] }
pretty_assertions = "1.4.1"
regex = "1.11.1"
rustyline = { version = "15.0.0", features = [ "derive" ] }
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"
[[bin]]
name = "fern"

View File

@@ -24,7 +24,8 @@ use crate::signal::sig_setup;
use crate::state::source_rc;
use crate::prelude::*;
use clap::Parser;
use state::{read_vars, write_vars};
use shopt::FernEditMode;
use state::{read_shopts, read_vars, write_shopts, write_vars};
#[derive(Parser,Debug)]
struct FernArgs {
@@ -98,7 +99,11 @@ fn fern_interactive() {
let mut readline_err_count: u32 = 0;
loop { // Main loop
let input = match prompt::read_line() {
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
.unwrap()
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
.unwrap();
let input = match prompt::read_line(edit_mode) {
Ok(line) => {
readline_err_count = 0;
line

View File

@@ -303,12 +303,6 @@ impl From<std::env::VarError> for ShErr {
}
}
impl From<rustyline::error::ReadlineError> for ShErr {
fn from(value: rustyline::error::ReadlineError) -> Self {
ShErr::simple(ShErrKind::ParseErr, value.to_string())
}
}
impl From<Errno> for ShErr {
fn from(value: Errno) -> Self {
ShErr::simple(ShErrKind::Errno, value.to_string())
@@ -322,6 +316,7 @@ pub enum ShErrKind {
ParseErr,
InternalErr,
ExecFail,
HistoryReadErr,
ResourceLimitExceeded,
BadPermission,
Errno,
@@ -331,27 +326,30 @@ pub enum ShErrKind {
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 {
ShErrKind::IoErr => "I/O Error",
ShErrKind::SyntaxErr => "Syntax Error",
ShErrKind::ParseErr => "Parse Error",
ShErrKind::InternalErr => "Internal Error",
ShErrKind::ExecFail => "Execution Failed",
ShErrKind::ResourceLimitExceeded => "Resource Limit Exceeded",
ShErrKind::BadPermission => "Bad Permissions",
ShErrKind::Errno => "ERRNO",
ShErrKind::FileNotFound(file) => &format!("File not found: {file}"),
ShErrKind::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
ShErrKind::CleanExit(_) => "",
ShErrKind::FuncReturn(_) => "",
ShErrKind::LoopContinue(_) => "",
ShErrKind::LoopBreak(_) => "",
ShErrKind::Null => "",
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

@@ -716,43 +716,7 @@ impl ParseStream {
}
if !from_func_def {
while self.check_redir() {
let tk = self.next_tk().unwrap();
node_tks.push(tk.clone());
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
if redir_bldr.io_mode.is_none() {
let path_tk = self.next_tk();
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Expected a filename after this redirection",
tk.span.clone()
)
)
};
let path_tk = path_tk.unwrap();
node_tks.push(path_tk.clone());
let redir_class = redir_bldr.class.unwrap();
let pathbuf = PathBuf::from(path_tk.span.as_str());
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
self.panic_mode(&mut node_tks);
return Err(parse_err_full(
"Error opening file for redirection",
&path_tk.span
)
);
};
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
let redir_bldr = redir_bldr.with_io_mode(io_mode);
let redir = redir_bldr.build();
redirs.push(redir);
}
}
self.parse_redir(&mut redirs, &mut node_tks)?;
}
let node = Node {
@@ -763,6 +727,37 @@ impl ParseStream {
};
Ok(Some(node))
}
fn parse_redir(&mut self, redirs: &mut Vec<Redir>, node_tks: &mut Vec<Tk>) -> ShResult<()> {
while self.check_redir() {
let tk = self.next_tk().unwrap();
node_tks.push(tk.clone());
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
if redir_bldr.io_mode.is_none() {
let path_tk = self.next_tk();
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Expected a filename after this redirection",
tk.span.clone()
)
)
};
let path_tk = path_tk.unwrap();
node_tks.push(path_tk.clone());
let redir_class = redir_bldr.class.unwrap();
let pathbuf = PathBuf::from(path_tk.span.as_str());
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), pathbuf, redir_class);
let redir_bldr = redir_bldr.with_io_mode(io_mode);
let redir = redir_bldr.build();
redirs.push(redir);
}
}
Ok(())
}
fn parse_case(&mut self) -> ShResult<Option<Node>> {
// Needs a pattern token
// Followed by any number of CaseNodes
@@ -938,42 +933,7 @@ impl ParseStream {
}
node_tks.push(self.next_tk().unwrap());
while self.check_redir() {
let tk = self.next_tk().unwrap();
node_tks.push(tk.clone());
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
if redir_bldr.io_mode.is_none() {
let path_tk = self.next_tk();
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Expected a filename after this redirection",
tk.span.clone()
)
)
};
let path_tk = path_tk.unwrap();
node_tks.push(path_tk.clone());
let redir_class = redir_bldr.class.unwrap();
let pathbuf = PathBuf::from(path_tk.span.as_str());
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
self.panic_mode(&mut node_tks);
return Err(parse_err_full(
"Error opening file for redirection",
&path_tk.span
));
};
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
let redir_bldr = redir_bldr.with_io_mode(io_mode);
let redir = redir_bldr.build();
redirs.push(redir);
}
}
self.parse_redir(&mut redirs, &mut node_tks)?;
self.assert_separator(&mut node_tks)?;
@@ -1040,42 +1000,7 @@ impl ParseStream {
}
node_tks.push(self.next_tk().unwrap());
while self.check_redir() {
let tk = self.next_tk().unwrap();
node_tks.push(tk.clone());
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
if redir_bldr.io_mode.is_none() {
let path_tk = self.next_tk();
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Expected a filename after this redirection",
tk.span.clone()
)
)
};
let path_tk = path_tk.unwrap();
node_tks.push(path_tk.clone());
let redir_class = redir_bldr.class.unwrap();
let pathbuf = PathBuf::from(path_tk.span.as_str());
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
self.panic_mode(&mut node_tks);
return Err(parse_err_full(
"Error opening file for redirection",
&path_tk.span
));
};
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
let redir_bldr = redir_bldr.with_io_mode(io_mode);
let redir = redir_bldr.build();
redirs.push(redir);
}
}
self.parse_redir(&mut redirs, &mut node_tks)?;
let node = Node {
class: NdRule::ForNode { vars, arr, body },
@@ -1256,15 +1181,7 @@ impl ParseStream {
let redir_class = redir_bldr.class.unwrap();
let pathbuf = PathBuf::from(path_tk.span.as_str());
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
self.panic_mode(&mut node_tks);
return Err(parse_err_full(
"Error opening file for redirection",
&path_tk.span
));
};
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), pathbuf, redir_class);
let redir_bldr = redir_bldr.with_io_mode(io_mode);
let redir = redir_bldr.build();
redirs.push(redir);
@@ -1421,7 +1338,7 @@ fn node_is_punctuated(tokens: &[Tk]) -> bool {
})
}
fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
pub fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
let result = match class {
RedirType::Input => {
OpenOptions::new()

View File

@@ -1,6 +1,6 @@
use std::{fmt::Debug, ops::{Deref, DerefMut}};
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, parse::Redir, prelude::*};
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, parse::{get_redir_file, Redir, RedirType}, prelude::*};
// Credit to fish-shell for many of the implementation ideas present in this module
// https://fishshell.com/
@@ -8,7 +8,7 @@ use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::RedirVecUtils},
#[derive(Clone,Debug)]
pub enum IoMode {
Fd { tgt_fd: RawFd, src_fd: Arc<OwnedFd> },
File { tgt_fd: RawFd, file: Arc<File> },
File { tgt_fd: RawFd, path: PathBuf, mode: RedirType },
Pipe { tgt_fd: RawFd, pipe: Arc<OwnedFd> },
Buffer { buf: String, pipe: Arc<OwnedFd> }
}
@@ -18,9 +18,8 @@ impl IoMode {
let src_fd = unsafe { OwnedFd::from_raw_fd(src_fd).into() };
Self::Fd { tgt_fd, src_fd }
}
pub fn file(tgt_fd: RawFd, file: File) -> Self {
let file = file.into();
Self::File { tgt_fd, file }
pub fn file(tgt_fd: RawFd, path: PathBuf, mode: RedirType) -> Self {
Self::File { tgt_fd, path, mode }
}
pub fn pipe(tgt_fd: RawFd, pipe: OwnedFd) -> Self {
let pipe = pipe.into();
@@ -28,20 +27,27 @@ impl IoMode {
}
pub fn tgt_fd(&self) -> RawFd {
match self {
IoMode::Fd { tgt_fd, src_fd: _ } |
IoMode::File { tgt_fd, file: _ } |
IoMode::Pipe { tgt_fd, pipe: _ } => *tgt_fd,
IoMode::Fd { tgt_fd, .. } |
IoMode::File { tgt_fd, .. } |
IoMode::Pipe { tgt_fd, .. } => *tgt_fd,
_ => panic!()
}
}
pub fn src_fd(&self) -> RawFd {
match self {
IoMode::Fd { tgt_fd: _, src_fd } => src_fd.as_raw_fd(),
IoMode::File { tgt_fd: _, file } => file.as_raw_fd(),
IoMode::File {..} => panic!("Attempted to obtain src_fd from file before opening"),
IoMode::Pipe { tgt_fd: _, pipe } => pipe.as_raw_fd(),
_ => panic!()
}
}
pub fn open_file(mut self) -> ShResult<Self> {
if let IoMode::File { tgt_fd, path, mode } = self {
let file = get_redir_file(mode, path)?;
self = IoMode::Fd { tgt_fd, src_fd: Arc::new(OwnedFd::from(file)) }
}
Ok(self)
}
pub fn get_pipes() -> (Self,Self) {
let (rpipe,wpipe) = pipe().unwrap();
(
@@ -149,6 +155,11 @@ impl<'e> IoFrame {
self.save();
for redir in &mut self.redirs {
let io_mode = &mut redir.io_mode;
flog!(DEBUG, io_mode);
if let IoMode::File {..} = io_mode {
*io_mode = io_mode.clone().open_file()?;
};
flog!(DEBUG, io_mode);
let tgt_fd = io_mode.tgt_fd();
let src_fd = io_mode.src_fd();
dup2(src_fd, tgt_fd)?;

View File

@@ -1,248 +0,0 @@
use std::{env, mem, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, sync::Arc};
use crate::builtin::BUILTINS;
use rustyline::highlight::Highlighter;
use crate::{libsh::term::{Style, StyleSet, Styled}, parse::lex::{LexFlags, LexStream, Tk, TkFlags, TkRule}, state::read_logic};
use super::readline::FernReadline;
fn is_executable(path: &Path) -> bool {
path.metadata()
.map(|m| m.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[derive(Default,Debug)]
pub struct FernHighlighter {
input: String,
}
impl FernHighlighter {
pub fn new(input: String) -> Self {
Self {
input,
}
}
pub fn highlight_subsh(&self, token: Tk) -> String {
if token.flags.contains(TkFlags::IS_SUBSH) {
let raw = token.as_str();
Self::hl_subsh_raw(raw)
} else if token.flags.contains(TkFlags::IS_CMDSUB) {
let raw = token.as_str();
Self::hl_cmdsub_raw(raw)
} else {
unreachable!()
}
}
pub fn hl_subsh_raw(raw: &str) -> String {
let mut body = &raw[1..];
let mut closed = false;
if body.ends_with(')') {
body = &body[..body.len() - 1];
closed = true;
}
let sub_hl = FernHighlighter::new(body.to_string());
let body_highlighted = sub_hl.hl_input();
let open_paren = "(".styled(Style::BrightBlue);
let close_paren = ")".styled(Style::BrightBlue);
let mut result = format!("{open_paren}{body_highlighted}");
if closed {
result.push_str(&close_paren);
}
result
}
pub fn hl_cmdsub_raw(raw: &str) -> String {
let mut body = &raw[2..];
let mut closed = false;
if body.ends_with(')') {
body = &body[..body.len() - 1];
closed = true;
}
let sub_hl = FernHighlighter::new(body.to_string());
let body_highlighted = sub_hl.hl_input();
let dollar_paren = "$(".styled(Style::BrightBlue);
let close_paren = ")".styled(Style::BrightBlue);
let mut result = format!("{dollar_paren}{body_highlighted}");
if closed {
result.push_str(&close_paren);
}
result
}
pub fn hl_command(&self, token: Tk) -> String {
let raw = token.as_str();
let paths = env::var("PATH")
.unwrap_or_default();
let mut paths = paths.split(':');
let is_in_path = {
loop {
let Some(path) = paths.next() else {
break false
};
let mut path = PathBuf::from(path);
path.push(PathBuf::from(raw));
if path.is_file() && is_executable(&path) {
break true
};
}
};
// TODO: zsh is capable of highlighting an alias red even if it exists, if the command it refers to is not found
// Implement some way to find out if the content of the alias is valid as well
let is_alias_or_function = read_logic(|l| {
l.get_func(raw).is_some() || l.get_alias(raw).is_some()
});
let is_builtin = BUILTINS.contains(&raw);
if is_alias_or_function || is_in_path || is_builtin {
raw.styled(Style::Green)
} else {
raw.styled(Style::Bold | Style::Red)
}
}
pub fn hl_dquote(&self, token: Tk) -> String {
let raw = token.as_str();
let mut chars = raw.chars().peekable();
const YELLOW: &str = "\x1b[33m";
const RESET: &str = "\x1b[0m";
let mut result = String::new();
let mut dquote_count = 0;
result.push_str(YELLOW);
while let Some(ch) = chars.next() {
match ch {
'\\' => {
result.push(ch);
if let Some(ch) = chars.next() {
result.push(ch);
}
}
'"' => {
dquote_count += 1;
result.push(ch);
if dquote_count >= 2 {
break
}
}
'$' if chars.peek() == Some(&'(') => {
let mut raw_cmd_sub = String::new();
raw_cmd_sub.push(ch);
raw_cmd_sub.push(chars.next().unwrap());
let mut cmdsub_count = 1;
while let Some(cmdsub_ch) = chars.next() {
match cmdsub_ch {
'\\' => {
raw_cmd_sub.push(cmdsub_ch);
if let Some(ch) = chars.next() {
raw_cmd_sub.push(ch);
}
}
'$' if chars.peek() == Some(&'(') => {
cmdsub_count += 1;
raw_cmd_sub.push(cmdsub_ch);
raw_cmd_sub.push(chars.next().unwrap());
}
')' => {
cmdsub_count -= 1;
raw_cmd_sub.push(cmdsub_ch);
if cmdsub_count <= 0 {
let styled = Self::hl_cmdsub_raw(&mem::take(&mut raw_cmd_sub));
result.push_str(&styled);
result.push_str(YELLOW);
break
}
}
_ => raw_cmd_sub.push(cmdsub_ch)
}
}
if !raw_cmd_sub.is_empty() {
let styled = Self::hl_cmdsub_raw(&mem::take(&mut raw_cmd_sub));
result.push_str(&styled);
result.push_str(YELLOW);
}
}
_ => result.push(ch)
}
}
result.push_str(RESET);
result
}
pub fn hl_input(&self) -> String {
let mut output = self.input.clone();
// TODO: properly implement highlighting for unfinished input
let lex_results = LexStream::new(Arc::new(output.clone()), LexFlags::LEX_UNFINISHED);
let mut tokens = vec![];
for result in lex_results {
let Ok(token) = result else {
return self.input.clone();
};
tokens.push(token)
}
// Reverse the tokens, because we want to highlight from right to left
// Doing it this way allows us to trust the spans in the tokens throughout the entire process
let tokens = tokens.into_iter()
.rev()
.collect::<Vec<Tk>>();
for token in tokens {
match token.class {
_ if token.flags.intersects(TkFlags::IS_CMDSUB | TkFlags::IS_SUBSH) => {
let styled = self.highlight_subsh(token.clone());
output.replace_range(token.span.start..token.span.end, &styled);
}
TkRule::Str => {
if token.flags.contains(TkFlags::IS_CMD) {
let styled = self.hl_command(token.clone());
output.replace_range(token.span.start..token.span.end, &styled);
} else if is_dquote(&token) {
let styled = self.hl_dquote(token.clone());
output.replace_range(token.span.start..token.span.end, &styled);
} else {
output.replace_range(token.span.start..token.span.end, &token.to_string());
}
}
TkRule::Pipe |
TkRule::ErrPipe |
TkRule::And |
TkRule::Or |
TkRule::Bg |
TkRule::Sep |
TkRule::Redir => self.style_with_token(&token,&mut output,Style::Cyan.into()),
TkRule::CasePattern => self.style_with_token(&token,&mut output,Style::Blue.into()),
TkRule::BraceGrpStart |
TkRule::BraceGrpEnd => self.style_with_token(&token,&mut output,Style::Cyan.into()),
TkRule::Comment => self.style_with_token(&token,&mut output,Style::BrightBlack.into()),
_ => { output.replace_range(token.span.start..token.span.end, &token.to_string()); }
}
}
output
}
fn style_with_token(&self, token: &Tk, highlighted: &mut String, style: StyleSet) {
let styled = token.to_string().styled(style);
highlighted.replace_range(token.span.start..token.span.end, &styled);
}
}
impl Highlighter for FernReadline {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> {
let highlighter = FernHighlighter::new(line.to_string());
std::borrow::Cow::Owned(highlighter.hl_input())
}
fn highlight_char(&self, _line: &str, _pos: usize, _kind: rustyline::highlight::CmdKind) -> bool {
true
}
}
fn is_dquote(token: &Tk) -> bool {
let raw = token.as_str();
raw.starts_with('"')
}

View File

@@ -3,89 +3,30 @@ pub mod highlight;
use std::path::Path;
use readline::FernReadline;
use rustyline::{error::ReadlineError, history::FileHistory, ColorMode, Config, Editor};
use readline::{FernVi, Readline};
use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, state::read_shopts};
use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, shopt::FernEditMode, state::read_shopts};
/// Initialize the line editor
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
let rl = FernReadline::new();
let tab_stop = read_shopts(|s| s.prompt.tab_stop);
let edit_mode = read_shopts(|s| s.prompt.edit_mode).into();
let bell_style = read_shopts(|s| s.core.bell_style).into();
let ignore_dups = read_shopts(|s| s.core.hist_ignore_dupes);
let comp_limit = read_shopts(|s| s.prompt.comp_limit);
let auto_hist = read_shopts(|s| s.core.auto_hist);
let max_hist = read_shopts(|s| s.core.max_hist);
let color_mode = match read_shopts(|s| s.prompt.prompt_highlight) {
true => ColorMode::Enabled,
false => ColorMode::Disabled,
};
let config = Config::builder()
.tab_stop(tab_stop)
.indent_size(1)
.edit_mode(edit_mode)
.bell_style(bell_style)
.color_mode(color_mode)
.history_ignore_dups(ignore_dups).unwrap()
.completion_prompt_limit(comp_limit)
.auto_add_history(auto_hist)
.max_history_size(max_hist).unwrap()
.build();
let mut editor = Editor::with_config(config).unwrap();
editor.set_helper(Some(rl));
editor.load_history(&Path::new("/home/pagedmov/.fernhist"))?;
Ok(editor)
}
fn get_prompt() -> ShResult<String> {
let Ok(prompt) = env::var("PS1") else {
// prompt expands to:
//
// username@hostname
// short/path/to/pwd/
// $
let default = "\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$ ";
return Ok(format!("\n{}",expand_prompt(default)?))
// $ _
let default = "\\n\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m ";
return expand_prompt(default)
};
Ok(format!("\n{}",expand_prompt(&prompt)?))
expand_prompt(&prompt)
}
fn get_hist_path() -> ShResult<PathBuf> {
if let Ok(path) = env::var("FERN_HIST") {
Ok(PathBuf::from(path))
} else {
let home = env::var("HOME")?;
let path = PathBuf::from(format!("{home}/.fernhist"));
Ok(path)
}
}
pub fn read_line() -> ShResult<String> {
assert!(isatty(STDIN_FILENO).unwrap());
let mut editor = init_rl()?;
pub fn read_line(edit_mode: FernEditMode) -> ShResult<String> {
let prompt = get_prompt()?;
match editor.readline(&prompt) {
Ok(line) => {
if !line.is_empty() {
let hist_path = get_hist_path()?;
editor.add_history_entry(&line)?;
editor.save_history(&hist_path)?;
}
Ok(line)
}
Err(ReadlineError::Eof) => {
kill(Pid::this(), Signal::SIGQUIT)?;
Ok(String::new())
}
Err(ReadlineError::Interrupted) => Ok(String::new()),
Err(e) => {
Err(e.into())
}
}
let mut reader: Box<dyn Readline> = match edit_mode {
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))?),
FernEditMode::Emacs => todo!()
};
reader.readline()
}

View File

@@ -1,86 +0,0 @@
use rustyline::{completion::Completer, hint::{Hint, Hinter}, history::SearchDirection, validate::{ValidationResult, Validator}, Helper};
use crate::{libsh::term::{Style, Styled}, parse::{lex::{LexFlags, LexStream}, ParseStream}};
use crate::prelude::*;
#[derive(Default,Debug)]
pub struct FernReadline;
impl FernReadline {
pub fn new() -> Self {
Self
}
pub fn search_hist(value: &str, ctx: &rustyline::Context<'_>) -> Option<String> {
let len = ctx.history().len();
for i in 0..len {
let entry = ctx.history().get(i, SearchDirection::Reverse).unwrap().unwrap();
if entry.entry.starts_with(value) {
return Some(entry.entry.into_owned())
}
}
None
}
}
impl Helper for FernReadline {}
impl Completer for FernReadline {
type Candidate = String;
}
pub struct FernHint {
raw: String,
styled: String
}
impl FernHint {
pub fn new(raw: String) -> Self {
let styled = (&raw).styled(Style::Dim | Style::BrightBlack);
Self { raw, styled }
}
}
impl Hint for FernHint {
fn display(&self) -> &str {
&self.styled
}
fn completion(&self) -> Option<&str> {
if !self.raw.is_empty() {
Some(&self.raw)
} else {
None
}
}
}
impl Hinter for FernReadline {
type Hint = FernHint;
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
if line.is_empty() {
return None
}
let ent = Self::search_hist(line,ctx)?;
let entry_raw = ent.get(pos..)?.to_string();
Some(FernHint::new(entry_raw))
}
}
impl Validator for FernReadline {
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
let mut tokens = vec![];
let tk_stream = LexStream::new(Arc::new(ctx.input().to_string()), LexFlags::empty());
for tk in tk_stream {
if tk.is_err() {
return Ok(ValidationResult::Incomplete)
}
tokens.push(tk.unwrap());
}
let nd_stream = ParseStream::new(tokens);
for nd in nd_stream {
if nd.is_err() {
return Ok(ValidationResult::Incomplete)
}
}
Ok(ValidationResult::Valid(None))
}
}

View File

@@ -0,0 +1,258 @@
use std::{env, fmt::{Write,Display}, fs::{self, OpenOptions}, io::Write as IoWrite, path::{Path, PathBuf}, str::FromStr, time::{Duration, SystemTime, UNIX_EPOCH}};
use crate::libsh::error::{ShErr, ShErrKind, ShResult};
use crate::prelude::*;
use super::vicmd::Direction; // surprisingly useful
#[derive(Debug)]
pub struct HistEntry {
id: u32,
timestamp: SystemTime,
command: String,
new: bool
}
impl HistEntry {
pub fn id(&self) -> u32 {
self.id
}
pub fn timestamp(&self) -> &SystemTime {
&self.timestamp
}
pub fn command(&self) -> &str {
&self.command
}
fn with_escaped_newlines(&self) -> String {
let mut escaped = String::new();
let mut chars = self.command.chars();
while let Some(ch) = chars.next() {
match ch {
'\\' => {
escaped.push(ch);
if let Some(ch) = chars.next() {
escaped.push(ch)
}
}
'\n' => {
escaped.push_str("\\\n");
}
_ => escaped.push(ch),
}
}
escaped
}
}
impl FromStr for HistEntry {
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = Err(
ShErr::Simple { kind: ShErrKind::HistoryReadErr, msg: format!("Bad formatting on history entry '{s}'"), notes: vec![] }
);
//: 248972349;148;echo foo; echo bar
let Some(cleaned) = s.strip_prefix(": ") else { return err };
//248972349;148;echo foo; echo bar
let Some((timestamp,id_and_command)) = cleaned.split_once(';') else { return err };
//("248972349","148;echo foo; echo bar")
let Some((id,command)) = id_and_command.split_once(';') else { return err };
//("148","echo foo; echo bar")
let Ok(ts_seconds) = timestamp.parse::<u64>() else { return err };
let Ok(id) = id.parse::<u32>() else { return err };
let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds);
let command = command.to_string();
Ok(Self { id, timestamp, command, new: false })
}
}
impl Display for HistEntry {
/// Similar to zsh's history format, but not entirely
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let command = self.with_escaped_newlines();
let HistEntry { id, timestamp, command: _, new: _ } = self;
let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
writeln!(f, ": {timestamp};{id};{command}")
}
}
pub struct HistEntries(Vec<HistEntry>);
impl FromStr for HistEntries {
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut entries = vec![];
let mut lines = s.lines().enumerate().peekable();
let mut cur_line = String::new();
while let Some((i,line)) = lines.next() {
if !line.starts_with(": ") {
return Err(
ShErr::Simple { kind: ShErrKind::HistoryReadErr, msg: format!("Bad formatting on line {i}"), notes: vec![] }
)
}
let mut chars = line.chars().peekable();
let mut feeding_lines = true;
while feeding_lines {
feeding_lines = false;
while let Some(ch) = chars.next() {
match ch {
'\\' => {
if let Some(esc_ch) = chars.next() {
cur_line.push(esc_ch);
} else {
cur_line.push('\n');
feeding_lines = true;
}
}
'\n' => {
break
}
_ => {
cur_line.push(ch);
}
}
}
if feeding_lines {
let Some((_,line)) = lines.next() else {
return Err(
ShErr::Simple { kind: ShErrKind::HistoryReadErr, msg: format!("Bad formatting on line {i}"), notes: vec![] }
)
};
chars = line.chars().peekable();
}
}
let entry = cur_line.parse::<HistEntry>()?;
entries.push(entry);
cur_line.clear();
}
Ok(Self(entries))
}
}
fn read_hist_file(path: &Path) -> ShResult<Vec<HistEntry>> {
if !path.exists() {
fs::File::create(path)?;
}
let raw = fs::read_to_string(path)?;
Ok(raw.parse::<HistEntries>()?.0)
}
pub struct History {
path: PathBuf,
entries: Vec<HistEntry>,
cursor: usize,
search_direction: Direction,
ignore_dups: bool,
max_size: Option<u32>,
}
impl History {
pub fn new() -> ShResult<Self> {
let path = PathBuf::from(env::var("FERNHIST").unwrap_or({
let home = env::var("HOME").unwrap();
format!("{home}/.fern_history")
}));
let entries = read_hist_file(&path)?;
let cursor = entries.len();
let mut new = Self {
path,
entries,
cursor,
search_direction: Direction::Backward,
ignore_dups: true,
max_size: None,
};
new.push_empty_entry(); // Current pending command
Ok(new)
}
pub fn entries(&self) -> &[HistEntry] {
&self.entries
}
pub fn push_empty_entry(&mut self) {
let id = self.get_new_id();
let timestamp = SystemTime::now();
let command = "".into();
self.entries.push(HistEntry { id, timestamp, command, new: true })
}
pub fn update_pending_cmd(&mut self, command: &str) {
flog!(DEBUG, "updating command");
let Some(ent) = self.last_mut() else {
return
};
ent.command = command.to_string()
}
pub fn last_mut(&mut self) -> Option<&mut HistEntry> {
self.entries.last_mut()
}
pub fn get_new_id(&self) -> u32 {
let Some(ent) = self.entries.last() else {
return 0
};
ent.id + 1
}
pub fn ignore_dups(&mut self, yn: bool) {
self.ignore_dups = yn
}
pub fn max_hist_size(&mut self, size: Option<u32>) {
self.max_size = size
}
pub fn scroll(&mut self, offset: isize) -> Option<&HistEntry> {
let new_idx = self.cursor
.saturating_add_signed(offset)
.clamp(0, self.entries.len());
let ent = self.entries.get(new_idx)?;
self.cursor = new_idx;
Some(ent)
}
pub fn push(&mut self, command: String) {
let timestamp = SystemTime::now();
let id = self.get_new_id();
if self.ignore_dups && self.is_dup(&command) {
return
}
self.entries.push(HistEntry { id, timestamp, command, new: true });
}
pub fn is_dup(&self, other: &str) -> bool {
let Some(ent) = self.entries.last() else {
return false
};
let ent_cmd = &ent.command;
ent_cmd == other
}
pub fn save(&mut self) -> ShResult<()> {
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
let entries = self.entries.iter_mut().filter(|ent| ent.new);
let mut data = String::new();
for ent in entries {
ent.new = false;
write!(data, "{ent}").unwrap();
}
file.write_all(data.as_bytes())?;
Ok(())
}
}

142
src/prompt/readline/keys.rs Normal file
View File

@@ -0,0 +1,142 @@
use std::sync::Arc;
use unicode_segmentation::UnicodeSegmentation;
// Credit to Rustyline for the design ideas in this module
// https://github.com/kkawakam/rustyline
#[derive(Clone,Debug)]
pub struct KeyEvent(pub KeyCode, pub ModKeys);
impl KeyEvent {
pub fn new(ch: &str, mut mods: ModKeys) -> Self {
use {KeyCode as K, KeyEvent as E, ModKeys as M};
let mut graphemes = ch.graphemes(true);
let first = match graphemes.next() {
Some(g) => g,
None => return E(K::Null, mods),
};
// If more than one grapheme, it's not a single key event
if graphemes.next().is_some() {
return E(K::Null, mods); // Or panic, or wrap in Grapheme if desired
}
let mut chars = first.chars();
let single_char = chars.next();
let is_single_char = chars.next().is_none();
match single_char {
Some(c) if is_single_char && c.is_control() => {
match c {
'\x00' => E(K::Char('@'), mods | M::CTRL),
'\x01' => E(K::Char('A'), mods | M::CTRL),
'\x02' => E(K::Char('B'), mods | M::CTRL),
'\x03' => E(K::Char('C'), mods | M::CTRL),
'\x04' => E(K::Char('D'), mods | M::CTRL),
'\x05' => E(K::Char('E'), mods | M::CTRL),
'\x06' => E(K::Char('F'), mods | M::CTRL),
'\x07' => E(K::Char('G'), mods | M::CTRL),
'\x08' => E(K::Backspace, mods),
'\x09' => {
if mods.contains(M::SHIFT) {
mods.remove(M::SHIFT);
E(K::BackTab, mods)
} else {
E(K::Tab, mods)
}
}
'\x0a' => E(K::Char('J'), mods | M::CTRL),
'\x0b' => E(K::Char('K'), mods | M::CTRL),
'\x0c' => E(K::Char('L'), mods | M::CTRL),
'\x0d' => E(K::Enter, mods),
'\x0e' => E(K::Char('N'), mods | M::CTRL),
'\x0f' => E(K::Char('O'), mods | M::CTRL),
'\x10' => E(K::Char('P'), mods | M::CTRL),
'\x11' => E(K::Char('Q'), mods | M::CTRL),
'\x12' => E(K::Char('R'), mods | M::CTRL),
'\x13' => E(K::Char('S'), mods | M::CTRL),
'\x14' => E(K::Char('T'), mods | M::CTRL),
'\x15' => E(K::Char('U'), mods | M::CTRL),
'\x16' => E(K::Char('V'), mods | M::CTRL),
'\x17' => E(K::Char('W'), mods | M::CTRL),
'\x18' => E(K::Char('X'), mods | M::CTRL),
'\x19' => E(K::Char('Y'), mods | M::CTRL),
'\x1a' => E(K::Char('Z'), mods | M::CTRL),
'\x1b' => E(K::Esc, mods),
'\x1c' => E(K::Char('\\'), mods | M::CTRL),
'\x1d' => E(K::Char(']'), mods | M::CTRL),
'\x1e' => E(K::Char('^'), mods | M::CTRL),
'\x1f' => E(K::Char('_'), mods | M::CTRL),
'\x7f' => E(K::Backspace, mods),
'\u{9b}' => E(K::Esc, mods | M::SHIFT),
_ => E(K::Null, mods),
}
}
Some(c) if is_single_char => {
if !mods.is_empty() {
mods.remove(M::SHIFT);
}
E(K::Char(c), mods)
}
_ => {
// multi-char grapheme (emoji, accented, etc)
if !mods.is_empty() {
mods.remove(M::SHIFT);
}
E(K::Grapheme(Arc::from(first)), mods)
}
}
}
}
#[derive(Clone,Debug)]
pub enum KeyCode {
UnknownEscSeq,
Backspace,
BackTab,
BracketedPasteStart,
BracketedPasteEnd,
Char(char),
Grapheme(Arc<str>),
Delete,
Down,
End,
Enter,
Esc,
F(u8),
Home,
Insert,
Left,
Null,
PageDown,
PageUp,
Right,
Tab,
Up,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ModKeys: u8 {
/// Control modifier
const CTRL = 1<<3;
/// Escape or Alt modifier
const ALT = 1<<2;
/// Shift modifier
const SHIFT = 1<<1;
/// No modifier
const NONE = 0;
/// Ctrl + Shift
const CTRL_SHIFT = Self::CTRL.bits() | Self::SHIFT.bits();
/// Alt + Shift
const ALT_SHIFT = Self::ALT.bits() | Self::SHIFT.bits();
/// Ctrl + Alt
const CTRL_ALT = Self::CTRL.bits() | Self::ALT.bits();
/// Ctrl + Alt + Shift
const CTRL_ALT_SHIFT = Self::CTRL.bits() | Self::ALT.bits() | Self::SHIFT.bits();
}
}

File diff suppressed because it is too large Load Diff

371
src/prompt/readline/mod.rs Normal file
View File

@@ -0,0 +1,371 @@
use std::time::Duration;
use history::History;
use keys::{KeyCode, KeyEvent, ModKeys};
use linebuf::{strip_ansi_codes_and_escapes, LineBuf};
use mode::{CmdReplay, ViInsert, ViMode, ViNormal, ViReplace};
use term::Terminal;
use unicode_width::UnicodeWidthStr;
use vicmd::{Motion, MotionCmd, RegisterName, To, Verb, VerbCmd, ViCmd};
use crate::libsh::{error::{ShErr, ShErrKind, ShResult}, term::{Style, Styled}};
use crate::prelude::*;
pub mod keys;
pub mod term;
pub mod linebuf;
pub mod vicmd;
pub mod mode;
pub mod register;
pub mod history;
const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore\nmagna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo\nconsequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
/// Unified interface for different line editing methods
pub trait Readline {
fn readline(&mut self) -> ShResult<String>;
}
pub struct FernVi {
term: Terminal,
line: LineBuf,
history: History,
prompt: String,
mode: Box<dyn ViMode>,
last_action: Option<CmdReplay>,
last_movement: Option<MotionCmd>,
}
impl Readline for FernVi {
fn readline(&mut self) -> ShResult<String> {
/*
self.term.writeln("This is a line!");
self.term.writeln("This is a line!");
self.term.writeln("This is a line!");
let prompt_thing = "prompt thing -> ";
self.term.write(prompt_thing);
let line = "And another!";
let mut iters: usize = 0;
let mut newlines_written = 0;
loop {
iters += 1;
for i in 0..iters {
self.term.writeln(line);
}
std::thread::sleep(Duration::from_secs(1));
self.clear_lines(iters,prompt_thing.len() + 1);
}
panic!()
*/
self.print_buf(false)?;
loop {
let key = self.term.read_key();
if let KeyEvent(KeyCode::Char('V'), ModKeys::CTRL) = key {
self.handle_verbatim()?;
continue
}
let Some(cmd) = self.mode.handle_key(key) else {
continue
};
if self.should_grab_history(&cmd) {
flog!(DEBUG, "scrolling");
self.scroll_history(cmd);
self.print_buf(true)?;
continue
}
if cmd.should_submit() {
self.term.write("\n");
let command = self.line.to_string();
if !command.is_empty() {
// We're just going to trim the command
// reduces clutter in the case of two history commands whose only difference is insignificant whitespace
self.history.push(command.trim().to_string());
self.history.save()?;
}
return Ok(command);
}
let line = self.line.to_string();
self.exec_cmd(cmd.clone())?;
let new_line = self.line.as_str();
let has_changes = line != new_line;
flog!(DEBUG, has_changes);
if cmd.verb().is_some_and(|v| v.1.is_edit()) && has_changes {
self.history.update_pending_cmd(self.line.as_str());
}
self.print_buf(true)?;
}
}
}
impl FernVi {
pub fn new(prompt: Option<String>) -> ShResult<Self> {
let prompt = prompt.unwrap_or("$ ".styled(Style::Green | Style::Bold));
let line = LineBuf::new().with_initial(LOREM_IPSUM);
let term = Terminal::new();
let history = History::new()?;
Ok(Self {
term,
line,
history,
prompt,
mode: Box::new(ViInsert::new()),
last_action: None,
last_movement: None,
})
}
/// Ctrl+V handler
pub fn handle_verbatim(&mut self) -> ShResult<()> {
let mut buf = [0u8; 8];
let mut collected = Vec::new();
loop {
let n = self.term.read_byte(&mut buf[..1]);
if n == 0 {
continue;
}
collected.push(buf[0]);
// If it starts with ESC, treat as escape sequence
if collected[0] == 0x1b {
loop {
let n = self.term.peek_byte(&mut buf[..1]);
if n == 0 {
break
}
collected.push(buf[0]);
// Ends a CSI sequence
if (0x40..=0x7e).contains(&buf[0]) {
break;
}
}
let Ok(seq) = std::str::from_utf8(&collected) else {
return Ok(())
};
let cmd = ViCmd {
register: Default::default(),
verb: Some(VerbCmd(1, Verb::Insert(seq.to_string()))),
motion: None,
raw_seq: seq.to_string(),
};
self.line.exec_cmd(cmd)?;
}
// Optional: handle other edge cases, e.g., raw control codes
if collected[0] < 0x20 || collected[0] == 0x7F {
let ctrl_seq = std::str::from_utf8(&collected).unwrap();
let cmd = ViCmd {
register: Default::default(),
verb: Some(VerbCmd(1, Verb::Insert(ctrl_seq.to_string()))),
motion: None,
raw_seq: ctrl_seq.to_string(),
};
self.line.exec_cmd(cmd)?;
break;
}
// Try to parse as UTF-8 if it's a valid Unicode sequence
if let Ok(s) = std::str::from_utf8(&collected) {
if s.chars().count() == 1 {
let ch = s.chars().next().unwrap();
// You got a literal Unicode char
eprintln!("Got char: {:?}", ch);
break;
}
}
}
Ok(())
}
pub fn scroll_history(&mut self, cmd: ViCmd) {
let count = &cmd.motion().unwrap().0;
let motion = &cmd.motion().unwrap().1;
flog!(DEBUG,count,motion);
let entry = match motion {
Motion::LineUp => {
let Some(hist_entry) = self.history.scroll(-(*count as isize)) else {
return
};
flog!(DEBUG,"found entry");
flog!(DEBUG,hist_entry.command());
hist_entry
}
Motion::LineDown => {
let Some(hist_entry) = self.history.scroll(*count as isize) else {
return
};
flog!(DEBUG,"found entry");
flog!(DEBUG,hist_entry.command());
hist_entry
}
_ => unreachable!()
};
let col = self.line.saved_col().unwrap_or(self.line.cursor_column());
let mut buf = LineBuf::new().with_initial(entry.command());
let line_end = buf.end_of_line();
if let Some(dest) = self.mode.hist_scroll_start_pos() {
match dest {
To::Start => {
/* Already at 0 */
}
To::End => {
// History entries cannot be empty
// So this subtraction is safe (maybe)
buf.cursor_fwd_to(line_end + 1);
}
}
} else {
let target = (col + 1).min(line_end + 1);
buf.cursor_fwd_to(target);
}
self.line = buf
}
pub fn should_grab_history(&self, cmd: &ViCmd) -> bool {
cmd.verb().is_none() &&
(
cmd.motion().is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineUp))) &&
self.line.start_of_line() == 0
) ||
(
cmd.motion().is_some_and(|m| matches!(m, MotionCmd(_, Motion::LineDown))) &&
self.line.end_of_line() == self.line.byte_len()
)
}
pub fn print_buf(&mut self, refresh: bool) -> ShResult<()> {
let (height,width) = self.term.get_dimensions()?;
if refresh {
self.term.unwrite()?;
}
let offset = self.calculate_prompt_offset();
self.line.set_first_line_offset(offset);
self.line.update_term_dims((height,width));
let mut line_buf = self.prompt.clone();
line_buf.push_str(self.line.as_str());
self.term.recorded_write(&line_buf, offset)?;
self.term.position_cursor(self.line.cursor_display_coords(width))?;
self.term.write(&self.mode.cursor_style());
Ok(())
}
pub fn calculate_prompt_offset(&self) -> usize {
if self.prompt.ends_with('\n') {
return 0
}
strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width() + 1 // 1 indexed
}
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
if cmd.is_mode_transition() {
let count = cmd.verb_count();
let mut mode: Box<dyn ViMode> = match cmd.verb().unwrap().1 {
Verb::InsertModeLineBreak(_) |
Verb::Change |
Verb::InsertMode => {
Box::new(ViInsert::new().with_count(count as u16))
}
Verb::NormalMode => {
Box::new(ViNormal::new())
}
Verb::ReplaceMode => {
Box::new(ViReplace::new().with_count(count as u16))
}
Verb::VisualMode => todo!(),
_ => unreachable!()
};
std::mem::swap(&mut mode, &mut self.mode);
self.line.set_cursor_clamp(self.mode.clamp_cursor());
self.line.set_move_cursor_on_undo(self.mode.move_cursor_on_undo());
self.term.write(&mode.cursor_style());
if mode.is_repeatable() {
self.last_action = mode.as_replay();
}
return self.line.exec_cmd(cmd);
} else if cmd.is_cmd_repeat() {
let Some(replay) = self.last_action.clone() else {
return Ok(())
};
let ViCmd { verb, .. } = cmd;
let VerbCmd(count,_) = verb.unwrap();
match replay {
CmdReplay::ModeReplay { cmds, mut repeat } => {
if count > 1 {
repeat = count as u16;
}
for _ in 0..repeat {
let cmds = cmds.clone();
for cmd in cmds {
self.line.exec_cmd(cmd)?
}
}
}
CmdReplay::Single(mut cmd) => {
if count > 1 {
// Override the counts with the one passed to the '.' command
if cmd.verb.is_some() {
if let Some(v_mut) = cmd.verb.as_mut() {
v_mut.0 = count
}
if let Some(m_mut) = cmd.motion.as_mut() {
m_mut.0 = 1
}
} else {
return Ok(()) // it has to have a verb to be repeatable, something weird happened
}
}
self.line.exec_cmd(cmd)?;
}
_ => unreachable!("motions should be handled in the other branch")
}
return Ok(())
} else if cmd.is_motion_repeat() {
match cmd.motion.as_ref().unwrap() {
MotionCmd(count,Motion::RepeatMotion) => {
let Some(motion) = self.last_movement.clone() else {
return Ok(())
};
let repeat_cmd = ViCmd {
register: RegisterName::default(),
verb: None,
motion: Some(motion),
raw_seq: format!("{count};")
};
return self.line.exec_cmd(repeat_cmd);
}
MotionCmd(count,Motion::RepeatMotionRev) => {
let Some(motion) = self.last_movement.clone() else {
return Ok(())
};
let mut new_motion = motion.invert_char_motion();
new_motion.0 = *count;
let repeat_cmd = ViCmd {
register: RegisterName::default(),
verb: None,
motion: Some(new_motion),
raw_seq: format!("{count},")
};
return self.line.exec_cmd(repeat_cmd);
}
_ => unreachable!()
}
}
if cmd.is_repeatable() {
self.last_action = Some(CmdReplay::Single(cmd.clone()));
}
if cmd.is_char_search() {
self.last_movement = cmd.motion.clone()
}
self.line.exec_cmd(cmd.clone())
}
}

816
src/prompt/readline/mode.rs Normal file
View File

@@ -0,0 +1,816 @@
use std::iter::Peekable;
use std::str::Chars;
use nix::NixPath;
use super::keys::{KeyEvent as E, KeyCode as K, ModKeys as M};
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, MotionBuilder, MotionCmd, RegisterName, TextObj, To, Verb, VerbBuilder, VerbCmd, ViCmd, Word};
use crate::prelude::*;
#[derive(Debug,Clone)]
pub enum CmdReplay {
ModeReplay { cmds: Vec<ViCmd>, repeat: u16 },
Single(ViCmd),
Motion(Motion)
}
impl CmdReplay {
pub fn mode(cmds: Vec<ViCmd>, repeat: u16) -> Self {
Self::ModeReplay { cmds, repeat }
}
pub fn single(cmd: ViCmd) -> Self {
Self::Single(cmd)
}
pub fn motion(motion: Motion) -> Self {
Self::Motion(motion)
}
}
pub enum CmdState {
Pending,
Complete,
Invalid
}
pub trait ViMode {
fn handle_key(&mut self, key: E) -> Option<ViCmd>;
fn is_repeatable(&self) -> bool;
fn as_replay(&self) -> Option<CmdReplay>;
fn cursor_style(&self) -> String;
fn pending_seq(&self) -> Option<String>;
fn move_cursor_on_undo(&self) -> bool;
fn clamp_cursor(&self) -> bool;
fn hist_scroll_start_pos(&self) -> Option<To>;
}
#[derive(Default,Debug)]
pub struct ViInsert {
cmds: Vec<ViCmd>,
pending_cmd: ViCmd,
repeat_count: u16
}
impl ViInsert {
pub fn new() -> Self {
Self::default()
}
pub fn with_count(mut self, repeat_count: u16) -> Self {
self.repeat_count = repeat_count;
self
}
pub fn register_and_return(&mut self) -> Option<ViCmd> {
let cmd = self.take_cmd();
self.register_cmd(&cmd);
Some(cmd)
}
pub fn ctrl_w_is_undo(&self) -> bool {
let insert_count = self.cmds.iter().filter(|cmd| {
matches!(cmd.verb(),Some(VerbCmd(1, Verb::InsertChar(_))))
}).count();
let backspace_count = self.cmds.iter().filter(|cmd| {
matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete)))
}).count();
insert_count > backspace_count
}
pub fn register_cmd(&mut self, cmd: &ViCmd) {
self.cmds.push(cmd.clone())
}
pub fn take_cmd(&mut self) -> ViCmd {
std::mem::take(&mut self.pending_cmd)
}
}
impl ViMode for ViInsert {
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
match key {
E(K::Char(ch), M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::InsertChar(ch)));
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
self.register_and_return()
}
E(K::Char('W'), M::CTRL) => {
if self.ctrl_w_is_undo() {
self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo));
self.cmds.clear();
Some(self.take_cmd())
} else {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
self.pending_cmd.set_motion(MotionCmd(1, Motion::BackwardWord(To::Start, Word::Normal)));
self.register_and_return()
}
}
E(K::Char('H'), M::CTRL) |
E(K::Backspace, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::Delete));
self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar));
self.register_and_return()
}
E(K::BackTab, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::CompleteBackward));
self.register_and_return()
}
E(K::Char('I'), M::CTRL) |
E(K::Tab, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::Complete));
self.register_and_return()
}
E(K::Esc, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::NormalMode));
self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar));
self.register_and_return()
}
_ => common_cmds(key)
}
}
fn is_repeatable(&self) -> bool {
true
}
fn as_replay(&self) -> Option<CmdReplay> {
Some(CmdReplay::mode(self.cmds.clone(), self.repeat_count))
}
fn cursor_style(&self) -> String {
"\x1b[6 q".to_string()
}
fn pending_seq(&self) -> Option<String> {
None
}
fn move_cursor_on_undo(&self) -> bool {
true
}
fn clamp_cursor(&self) -> bool {
false
}
fn hist_scroll_start_pos(&self) -> Option<To> {
Some(To::End)
}
}
#[derive(Default,Debug)]
pub struct ViReplace {
cmds: Vec<ViCmd>,
pending_cmd: ViCmd,
repeat_count: u16
}
impl ViReplace {
pub fn new() -> Self {
Self::default()
}
pub fn with_count(mut self, repeat_count: u16) -> Self {
self.repeat_count = repeat_count;
self
}
pub fn register_and_return(&mut self) -> Option<ViCmd> {
let cmd = self.take_cmd();
self.register_cmd(&cmd);
Some(cmd)
}
pub fn ctrl_w_is_undo(&self) -> bool {
let insert_count = self.cmds.iter().filter(|cmd| {
matches!(cmd.verb(),Some(VerbCmd(1, Verb::ReplaceChar(_))))
}).count();
let backspace_count = self.cmds.iter().filter(|cmd| {
matches!(cmd.verb(),Some(VerbCmd(1, Verb::Delete)))
}).count();
insert_count > backspace_count
}
pub fn register_cmd(&mut self, cmd: &ViCmd) {
self.cmds.push(cmd.clone())
}
pub fn take_cmd(&mut self) -> ViCmd {
std::mem::take(&mut self.pending_cmd)
}
}
impl ViMode for ViReplace {
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
match key {
E(K::Char(ch), M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::ReplaceChar(ch)));
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
self.register_and_return()
}
E(K::Char('W'), M::CTRL) => {
if self.ctrl_w_is_undo() {
self.pending_cmd.set_verb(VerbCmd(1,Verb::Undo));
self.cmds.clear();
Some(self.take_cmd())
} else {
self.pending_cmd.set_motion(MotionCmd(1, Motion::BackwardWord(To::Start, Word::Normal)));
self.register_and_return()
}
}
E(K::Char('H'), M::CTRL) |
E(K::Backspace, M::NONE) => {
self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar));
self.register_and_return()
}
E(K::BackTab, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::CompleteBackward));
self.register_and_return()
}
E(K::Char('I'), M::CTRL) |
E(K::Tab, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::Complete));
self.register_and_return()
}
E(K::Esc, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1,Verb::NormalMode));
self.pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar));
self.register_and_return()
}
_ => common_cmds(key)
}
}
fn is_repeatable(&self) -> bool {
true
}
fn cursor_style(&self) -> String {
"\x1b[4 q".to_string()
}
fn pending_seq(&self) -> Option<String> {
None
}
fn as_replay(&self) -> Option<CmdReplay> {
Some(CmdReplay::mode(self.cmds.clone(), self.repeat_count))
}
fn move_cursor_on_undo(&self) -> bool {
true
}
fn clamp_cursor(&self) -> bool {
true
}
fn hist_scroll_start_pos(&self) -> Option<To> {
Some(To::End)
}
}
#[derive(Default,Debug)]
pub struct ViNormal {
pending_seq: String,
}
impl ViNormal {
pub fn new() -> Self {
Self::default()
}
pub fn clear_cmd(&mut self) {
self.pending_seq = String::new();
}
pub fn take_cmd(&mut self) -> String {
std::mem::take(&mut self.pending_seq)
}
fn validate_combination(&self, verb: Option<&Verb>, motion: Option<&Motion>) -> CmdState {
if verb.is_none() {
match motion {
Some(Motion::TextObj(_,_)) => return CmdState::Invalid,
Some(_) => return CmdState::Complete,
None => return CmdState::Pending
}
}
if verb.is_some() && motion.is_none() {
match verb.unwrap() {
Verb::Put(_) |
Verb::DeleteChar(_) => CmdState::Complete,
_ => CmdState::Pending
}
} else {
CmdState::Complete
}
}
pub fn parse_count(&self, chars: &mut Peekable<Chars<'_>>) -> Option<usize> {
let mut count = String::new();
let Some(_digit @ '1'..='9') = chars.peek() else {
return None
};
count.push(chars.next().unwrap());
while let Some(_digit @ '0'..='9') = chars.peek() {
count.push(chars.next().unwrap());
}
if !count.is_empty() {
count.parse::<usize>().ok()
} else {
None
}
}
/// End the parse and clear the pending sequence
#[track_caller]
pub fn quit_parse(&mut self) -> Option<ViCmd> {
flog!(DEBUG, std::panic::Location::caller());
flog!(WARN, "exiting parse early with sequence: {}",self.pending_seq);
self.clear_cmd();
None
}
pub fn try_parse(&mut self, ch: char) -> Option<ViCmd> {
self.pending_seq.push(ch);
let mut chars = self.pending_seq.chars().peekable();
let register = 'reg_parse: {
let mut chars_clone = chars.clone();
let count = self.parse_count(&mut chars_clone);
let Some('"') = chars_clone.next() else {
break 'reg_parse RegisterName::default()
};
let Some(reg_name) = chars_clone.next() else {
return None // Pending register name
};
match reg_name {
'a'..='z' |
'A'..='Z' => { /* proceed */ }
_ => return self.quit_parse()
}
chars = chars_clone;
RegisterName::new(Some(reg_name), count)
};
let verb = 'verb_parse: {
let mut chars_clone = chars.clone();
let count = self.parse_count(&mut chars_clone).unwrap_or(1);
let Some(ch) = chars_clone.next() else {
break 'verb_parse None
};
match ch {
'.' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::RepeatLast)),
motion: None,
raw_seq: self.take_cmd(),
}
)
}
'x' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::DeleteChar(Anchor::After)));
}
'X' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::DeleteChar(Anchor::Before)));
}
'p' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Put(Anchor::After)));
}
'P' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Put(Anchor::Before)));
}
'>' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Indent));
}
'<' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Dedent));
}
'r' => {
let ch = chars_clone.next()?;
return Some(
ViCmd {
register,
verb: Some(VerbCmd(1, Verb::ReplaceChar(ch))),
motion: Some(MotionCmd(count, Motion::ForwardChar)),
raw_seq: self.take_cmd()
}
)
}
'R' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::ReplaceMode)),
motion: None,
raw_seq: self.take_cmd()
}
)
}
'~' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(1, Verb::ToggleCase)),
motion: Some(MotionCmd(count, Motion::ForwardChar)),
raw_seq: self.take_cmd()
}
)
}
'u' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::Undo)),
motion: None,
raw_seq: self.take_cmd()
}
)
}
'o' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::InsertModeLineBreak(Anchor::After))),
motion: None,
raw_seq: self.take_cmd()
}
)
}
'O' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::InsertModeLineBreak(Anchor::Before))),
motion: None,
raw_seq: self.take_cmd()
}
)
}
'a' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::InsertMode)),
motion: Some(MotionCmd(1, Motion::ForwardChar)),
raw_seq: self.take_cmd()
}
)
}
'A' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::InsertMode)),
motion: Some(MotionCmd(1, Motion::EndOfLine)),
raw_seq: self.take_cmd()
}
)
}
'i' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::InsertMode)),
motion: None,
raw_seq: self.take_cmd()
}
)
}
'I' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::InsertMode)),
motion: Some(MotionCmd(1, Motion::BeginningOfFirstWord)),
raw_seq: self.take_cmd()
}
)
}
'J' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::JoinLines)),
motion: None,
raw_seq: self.take_cmd()
}
)
}
'y' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Yank))
}
'd' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Delete))
}
'c' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Change))
}
'Y' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::Yank)),
motion: Some(MotionCmd(1, Motion::EndOfLine)),
raw_seq: self.take_cmd()
}
)
}
'D' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::Delete)),
motion: Some(MotionCmd(1, Motion::EndOfLine)),
raw_seq: self.take_cmd()
}
)
}
'C' => {
return Some(
ViCmd {
register,
verb: Some(VerbCmd(count, Verb::Change)),
motion: Some(MotionCmd(1, Motion::EndOfLine)),
raw_seq: self.take_cmd()
}
)
}
'=' => {
chars = chars_clone;
break 'verb_parse Some(VerbCmd(count, Verb::Equalize))
}
_ => break 'verb_parse None
}
};
let motion = 'motion_parse: {
let mut chars_clone = chars.clone();
let count = self.parse_count(&mut chars_clone).unwrap_or(1);
let Some(ch) = chars_clone.next() else {
break 'motion_parse None
};
match (ch, &verb) {
('d', Some(VerbCmd(_,Verb::Delete))) |
('c', Some(VerbCmd(_,Verb::Change))) |
('y', Some(VerbCmd(_,Verb::Yank))) |
('=', Some(VerbCmd(_,Verb::Equalize))) |
('>', Some(VerbCmd(_,Verb::Indent))) |
('<', Some(VerbCmd(_,Verb::Dedent))) => break 'motion_parse Some(MotionCmd(count, Motion::WholeLine)),
_ => {}
}
match ch {
'g' => {
if let Some(ch) = chars_clone.peek() {
match ch {
'g' => {
chars_clone.next();
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfBuffer))
}
'e' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::End, Word::Normal)));
}
'E' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::End, Word::Big)));
}
'k' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineUp));
}
'j' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineDown));
}
_ => return self.quit_parse()
}
} else {
break 'motion_parse None
}
}
'f' => {
let Some(ch) = chars_clone.peek() else {
break 'motion_parse None
};
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Forward, Dest::On, (*ch).into())))
}
'F' => {
let Some(ch) = chars_clone.peek() else {
break 'motion_parse None
};
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Backward, Dest::On, (*ch).into())))
}
't' => {
let Some(ch) = chars_clone.peek() else {
break 'motion_parse None
};
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Forward, Dest::Before, (*ch).into())))
}
'T' => {
let Some(ch) = chars_clone.peek() else {
break 'motion_parse None
};
break 'motion_parse Some(MotionCmd(count, Motion::CharSearch(Direction::Backward, Dest::Before, (*ch).into())))
}
';' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::RepeatMotion));
}
',' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::RepeatMotionRev));
}
'|' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(1, Motion::ToColumn(count)));
}
'0' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BeginningOfLine));
}
'$' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::EndOfLine));
}
'k' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::LineUp));
}
'j' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::LineDown));
}
'h' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BackwardChar));
}
'l' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ForwardChar));
}
'w' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::Start, Word::Normal)));
}
'W' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::Start, Word::Big)));
}
'e' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::End, Word::Normal)));
}
'E' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::ForwardWord(To::End, Word::Big)));
}
'b' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::Start, Word::Normal)));
}
'B' => {
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::Start, Word::Big)));
}
ch if ch == 'i' || ch == 'a' => {
let bound = match ch {
'i' => Bound::Inside,
'a' => Bound::Around,
_ => unreachable!()
};
if chars_clone.peek().is_none() {
break 'motion_parse None
}
let obj = match chars_clone.next().unwrap() {
'w' => TextObj::Word(Word::Normal),
'W' => TextObj::Word(Word::Big),
'"' => TextObj::DoubleQuote,
'\'' => TextObj::SingleQuote,
'(' | ')' | 'b' => TextObj::Paren,
'{' | '}' | 'B' => TextObj::Brace,
'[' | ']' => TextObj::Bracket,
'<' | '>' => TextObj::Angle,
_ => return self.quit_parse()
};
chars = chars_clone;
break 'motion_parse Some(MotionCmd(count, Motion::TextObj(obj, bound)))
}
_ => return self.quit_parse(),
}
};
if chars.peek().is_some() {
flog!(WARN, "Unused characters in Vi command parse!");
flog!(WARN, "{:?}",chars)
}
let verb_ref = verb.as_ref().map(|v| &v.1);
let motion_ref = motion.as_ref().map(|m| &m.1);
match self.validate_combination(verb_ref, motion_ref) {
CmdState::Complete => {
let cmd = Some(
ViCmd {
register,
verb,
motion,
raw_seq: std::mem::take(&mut self.pending_seq)
}
);
cmd
}
CmdState::Pending => {
None
}
CmdState::Invalid => {
self.pending_seq.clear();
None
}
}
}
}
impl ViMode for ViNormal {
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
match key {
E(K::Char(ch), M::NONE) => self.try_parse(ch),
E(K::Backspace, M::NONE) => {
Some(ViCmd {
register: Default::default(),
verb: None,
motion: Some(MotionCmd(1, Motion::BackwardChar)),
raw_seq: "".into(),
})
}
E(K::Char('R'), M::CTRL) => {
let mut chars = self.pending_seq.chars().peekable();
let count = self.parse_count(&mut chars).unwrap_or(1);
Some(
ViCmd {
register: RegisterName::default(),
verb: Some(VerbCmd(count,Verb::Redo)),
motion: None,
raw_seq: self.take_cmd()
}
)
}
E(K::Esc, M::NONE) => {
self.clear_cmd();
None
}
_ => {
if let Some(cmd) = common_cmds(key) {
self.clear_cmd();
Some(cmd)
} else {
None
}
}
}
}
fn is_repeatable(&self) -> bool {
false
}
fn as_replay(&self) -> Option<CmdReplay> {
None
}
fn cursor_style(&self) -> String {
"\x1b[2 q".to_string()
}
fn pending_seq(&self) -> Option<String> {
Some(self.pending_seq.clone())
}
fn move_cursor_on_undo(&self) -> bool {
false
}
fn clamp_cursor(&self) -> bool {
true
}
fn hist_scroll_start_pos(&self) -> Option<To> {
None
}
}
pub fn common_cmds(key: E) -> Option<ViCmd> {
let mut pending_cmd = ViCmd::new();
match key {
E(K::Home, M::NONE) => pending_cmd.set_motion(MotionCmd(1,Motion::BeginningOfLine)),
E(K::End, M::NONE) => pending_cmd.set_motion(MotionCmd(1,Motion::EndOfLine)),
E(K::Left, M::NONE) => pending_cmd.set_motion(MotionCmd(1,Motion::BackwardChar)),
E(K::Right, M::NONE) => pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar)),
E(K::Up, M::NONE) => pending_cmd.set_motion(MotionCmd(1,Motion::LineUp)),
E(K::Down, M::NONE) => pending_cmd.set_motion(MotionCmd(1,Motion::LineDown)),
E(K::Enter, M::NONE) => pending_cmd.set_verb(VerbCmd(1,Verb::AcceptLine)),
E(K::Char('D'), M::CTRL) => pending_cmd.set_verb(VerbCmd(1,Verb::EndOfFile)),
E(K::Delete, M::NONE) => pending_cmd.set_verb(VerbCmd(1,Verb::DeleteChar(Anchor::After))),
E(K::Backspace, M::NONE) |
E(K::Char('H'), M::CTRL) => pending_cmd.set_verb(VerbCmd(1,Verb::DeleteChar(Anchor::Before))),
_ => return None
}
Some(pending_cmd)
}

View File

@@ -0,0 +1,168 @@
use std::sync::Mutex;
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
pub fn read_register(ch: Option<char>) -> Option<String> {
let lock = REGISTERS.lock().unwrap();
lock.get_reg(ch).map(|r| r.buf().clone())
}
pub fn write_register(ch: Option<char>, buf: String) {
let mut lock = REGISTERS.lock().unwrap();
if let Some(r) = lock.get_reg_mut(ch) { r.write(buf) }
}
pub fn append_register(ch: Option<char>, buf: String) {
let mut lock = REGISTERS.lock().unwrap();
if let Some(r) = lock.get_reg_mut(ch) { r.append(buf) }
}
#[derive(Default,Debug)]
pub struct Registers {
default: Register,
a: Register,
b: Register,
c: Register,
d: Register,
e: Register,
f: Register,
g: Register,
h: Register,
i: Register,
j: Register,
k: Register,
l: Register,
m: Register,
n: Register,
o: Register,
p: Register,
q: Register,
r: Register,
s: Register,
t: Register,
u: Register,
v: Register,
w: Register,
x: Register,
y: Register,
z: Register,
}
impl Registers {
pub const fn new() -> Self {
Self {
default: Register(String::new()),
a: Register(String::new()),
b: Register(String::new()),
c: Register(String::new()),
d: Register(String::new()),
e: Register(String::new()),
f: Register(String::new()),
g: Register(String::new()),
h: Register(String::new()),
i: Register(String::new()),
j: Register(String::new()),
k: Register(String::new()),
l: Register(String::new()),
m: Register(String::new()),
n: Register(String::new()),
o: Register(String::new()),
p: Register(String::new()),
q: Register(String::new()),
r: Register(String::new()),
s: Register(String::new()),
t: Register(String::new()),
u: Register(String::new()),
v: Register(String::new()),
w: Register(String::new()),
x: Register(String::new()),
y: Register(String::new()),
z: Register(String::new()),
}
}
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
let Some(ch) = ch else {
return Some(&self.default)
};
match ch {
'a' => Some(&self.a),
'b' => Some(&self.b),
'c' => Some(&self.c),
'd' => Some(&self.d),
'e' => Some(&self.e),
'f' => Some(&self.f),
'g' => Some(&self.g),
'h' => Some(&self.h),
'i' => Some(&self.i),
'j' => Some(&self.j),
'k' => Some(&self.k),
'l' => Some(&self.l),
'm' => Some(&self.m),
'n' => Some(&self.n),
'o' => Some(&self.o),
'p' => Some(&self.p),
'q' => Some(&self.q),
'r' => Some(&self.r),
's' => Some(&self.s),
't' => Some(&self.t),
'u' => Some(&self.u),
'v' => Some(&self.v),
'w' => Some(&self.w),
'x' => Some(&self.x),
'y' => Some(&self.y),
'z' => Some(&self.z),
_ => None
}
}
pub fn get_reg_mut(&mut self, ch: Option<char>) -> Option<&mut Register> {
let Some(ch) = ch else {
return Some(&mut self.default)
};
match ch {
'a' => Some(&mut self.a),
'b' => Some(&mut self.b),
'c' => Some(&mut self.c),
'd' => Some(&mut self.d),
'e' => Some(&mut self.e),
'f' => Some(&mut self.f),
'g' => Some(&mut self.g),
'h' => Some(&mut self.h),
'i' => Some(&mut self.i),
'j' => Some(&mut self.j),
'k' => Some(&mut self.k),
'l' => Some(&mut self.l),
'm' => Some(&mut self.m),
'n' => Some(&mut self.n),
'o' => Some(&mut self.o),
'p' => Some(&mut self.p),
'q' => Some(&mut self.q),
'r' => Some(&mut self.r),
's' => Some(&mut self.s),
't' => Some(&mut self.t),
'u' => Some(&mut self.u),
'v' => Some(&mut self.v),
'w' => Some(&mut self.w),
'x' => Some(&mut self.x),
'y' => Some(&mut self.y),
'z' => Some(&mut self.z),
_ => None
}
}
}
#[derive(Clone,Default,Debug)]
pub struct Register(String);
impl Register {
pub fn buf(&self) -> &String {
&self.0
}
pub fn write(&mut self, buf: String) {
self.0 = buf
}
pub fn append(&mut self, buf: String) {
self.0.push_str(&buf)
}
pub fn clear(&mut self) {
self.0.clear()
}
}

369
src/prompt/readline/term.rs Normal file
View File

@@ -0,0 +1,369 @@
use std::{os::fd::{BorrowedFd, RawFd}, thread::sleep, time::{Duration, Instant}};
use nix::{errno::Errno, fcntl::{fcntl, FcntlArg, OFlag}, libc::{self, STDIN_FILENO}, sys::termios, unistd::{isatty, read, write}};
use nix::libc::{winsize, TIOCGWINSZ};
use unicode_width::UnicodeWidthChar;
use std::mem::zeroed;
use std::io;
use crate::libsh::error::ShResult;
use crate::prelude::*;
use super::keys::{KeyCode, KeyEvent, ModKeys};
#[derive(Default,Debug)]
struct WriteMap {
lines: usize,
cols: usize,
offset: usize
}
#[derive(Debug)]
pub struct Terminal {
stdin: RawFd,
stdout: RawFd,
recording: bool,
write_records: WriteMap,
cursor_records: WriteMap
}
impl Terminal {
pub fn new() -> Self {
assert!(isatty(STDIN_FILENO).unwrap());
Self {
stdin: STDIN_FILENO,
stdout: 1,
recording: false,
// Records for buffer writes
// Used to find the start of the buffer
write_records: WriteMap::default(),
// Records for cursor movements after writes
// Used to find the end of the buffer
cursor_records: WriteMap::default(),
}
}
fn raw_mode() -> termios::Termios {
let orig = termios::tcgetattr(unsafe{BorrowedFd::borrow_raw(STDIN_FILENO)}).expect("Failed to get terminal attributes");
let mut raw = orig.clone();
termios::cfmakeraw(&mut raw);
termios::tcsetattr(unsafe{BorrowedFd::borrow_raw(STDIN_FILENO)}, termios::SetArg::TCSANOW, &raw)
.expect("Failed to set terminal to raw mode");
orig
}
pub fn restore_termios(termios: termios::Termios) {
termios::tcsetattr(unsafe{BorrowedFd::borrow_raw(STDIN_FILENO)}, termios::SetArg::TCSANOW, &termios)
.expect("Failed to restore terminal settings");
}
pub fn get_dimensions(&self) -> ShResult<(usize, usize)> {
if !isatty(self.stdin).unwrap_or(false) {
return Err(io::Error::new(io::ErrorKind::Other, "Not a TTY"))?;
}
let mut ws: winsize = unsafe { zeroed() };
let res = unsafe { libc::ioctl(self.stdin, TIOCGWINSZ, &mut ws) };
if res == -1 {
return Err(io::Error::last_os_error())?;
}
Ok((ws.ws_row as usize, ws.ws_col as usize))
}
pub fn start_recording(&mut self, offset: usize) {
self.recording = true;
self.write_records.offset = offset;
}
pub fn stop_recording(&mut self) {
self.recording = false;
}
pub fn save_cursor_pos(&mut self) {
self.write("\x1b[s")
}
pub fn restore_cursor_pos(&mut self) {
self.write("\x1b[u")
}
pub fn move_cursor_to(&mut self, (row,col): (usize,usize)) {
self.write(&format!("\x1b[{row};{col}H",))
}
pub fn with_raw_mode<F: FnOnce() -> R, R>(func: F) -> R {
let saved = Self::raw_mode();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(func));
Self::restore_termios(saved);
match result {
Ok(r) => r,
Err(e) => std::panic::resume_unwind(e),
}
}
pub fn read_byte(&self, buf: &mut [u8]) -> usize {
Self::with_raw_mode(|| {
read(self.stdin, buf).expect("Failed to read from stdin")
})
}
fn read_blocks_then_read(&self, buf: &mut [u8], timeout: Duration) -> Option<usize> {
Self::with_raw_mode(|| {
self.read_blocks(false);
let start = Instant::now();
loop {
match read(self.stdin, buf) {
Ok(n) if n > 0 => {
self.read_blocks(true);
return Some(n);
}
Ok(_) => {}
Err(e) if e == Errno::EAGAIN => {}
Err(_) => return None,
}
if start.elapsed() > timeout {
self.read_blocks(true);
return None;
}
sleep(Duration::from_millis(1));
}
})
}
/// Same as read_byte(), only non-blocking with a very short timeout
pub fn peek_byte(&self, buf: &mut [u8]) -> usize {
const TIMEOUT_DUR: Duration = Duration::from_millis(50);
Self::with_raw_mode(|| {
self.read_blocks(false);
let start = Instant::now();
loop {
match read(self.stdin, buf) {
Ok(n) if n > 0 => {
self.read_blocks(true);
return n
}
Ok(_) => {}
Err(Errno::EAGAIN) => {}
Err(e) => panic!("nonblocking read failed: {e}")
}
if start.elapsed() >= TIMEOUT_DUR {
self.read_blocks(true);
return 0
}
sleep(Duration::from_millis(1));
}
})
}
pub fn read_blocks(&self, yn: bool) {
let flags = OFlag::from_bits_truncate(fcntl(self.stdin, FcntlArg::F_GETFL).unwrap());
let new_flags = if !yn {
flags | OFlag::O_NONBLOCK
} else {
flags & !OFlag::O_NONBLOCK
};
fcntl(self.stdin, FcntlArg::F_SETFL(new_flags)).unwrap();
}
pub fn reset_records(&mut self) {
self.write_records = Default::default();
self.cursor_records = Default::default();
}
pub fn recorded_write(&mut self, buf: &str, offset: usize) -> ShResult<()> {
self.start_recording(offset);
self.write(buf);
self.stop_recording();
Ok(())
}
/// Rewinds terminal writing, clears lines and lands on the anchor point of the prompt
pub fn unwrite(&mut self) -> ShResult<()> {
self.unposition_cursor()?;
let WriteMap { lines, cols, offset } = self.write_records;
for _ in 0..lines {
self.write("\x1b[2K\x1b[A")
}
let col = offset;
self.write(&format!("\x1b[{col}G\x1b[0K"));
self.reset_records();
Ok(())
}
pub fn position_cursor(&mut self, (lines,col): (usize,usize)) -> ShResult<()> {
flog!(DEBUG,lines);
self.cursor_records.lines = lines;
self.cursor_records.cols = col;
self.cursor_records.offset = self.cursor_pos().1;
for _ in 0..lines {
self.write("\x1b[A")
}
self.write(&format!("\x1b[{col}G"));
Ok(())
}
/// Rewinds cursor positioning, lands on the end of the buffer
pub fn unposition_cursor(&mut self) ->ShResult<()> {
let WriteMap { lines, cols, offset } = self.cursor_records;
for _ in 0..lines {
self.write("\x1b[B")
}
self.write(&format!("\x1b[{offset}G"));
Ok(())
}
pub fn write_bytes(&mut self, buf: &[u8]) {
if self.recording {
let (_, width) = self.get_dimensions().unwrap();
let mut bytes = buf.iter().map(|&b| b as char).peekable();
while let Some(ch) = bytes.next() {
match ch {
'\n' => {
self.write_records.lines += 1;
self.write_records.cols = 0;
}
'\r' => {
self.write_records.cols = 0;
}
// Consume escape sequences
'\x1b' if bytes.peek() == Some(&'[') => {
bytes.next();
while let Some(&ch) = bytes.peek() {
if ch.is_ascii_alphabetic() {
bytes.next();
break
} else {
bytes.next();
}
}
}
'\t' => {
let tab_size = 8;
let next_tab = tab_size - (self.write_records.cols % tab_size);
self.write_records.cols += next_tab;
if self.write_records.cols > width {
self.write_records.lines += 1;
self.write_records.cols = 0;
}
}
_ if ch.is_control() => {
// ignore control characters for visual width
}
_ => {
let ch_width = ch.width().unwrap_or(0);
if self.write_records.cols + ch_width > width {
self.write_records.lines += 1;
self.write_records.cols = 0;
}
self.write_records.cols += ch_width;
}
}
}
}
write(unsafe { BorrowedFd::borrow_raw(self.stdout) }, buf).expect("Failed to write to stdout");
}
pub fn write(&mut self, s: &str) {
self.write_bytes(s.as_bytes());
}
pub fn writeln(&mut self, s: &str) {
self.write(s);
self.write_bytes(b"\n");
}
pub fn clear(&mut self) {
self.write_bytes(b"\x1b[2J\x1b[H");
}
pub fn read_key(&self) -> KeyEvent {
use core::str;
let mut buf = [0u8; 8];
let mut collected = Vec::with_capacity(5);
loop {
let n = self.read_byte(&mut buf[..1]); // Read one byte at a time
if n == 0 {
continue;
}
collected.push(buf[0]);
// ESC sequences
if collected[0] == 0x1b && collected.len() == 1 {
// Peek next byte if any
let n = self.peek_byte(&mut buf[..1]);
if n == 0 {
return KeyEvent(KeyCode::Esc, ModKeys::empty());
}
collected.push(buf[0]);
if buf[0] == b'[' {
// Read third byte
let _ = self.read_byte(&mut buf[..1]);
collected.push(buf[0]);
return match buf[0] {
b'A' => KeyEvent(KeyCode::Up, ModKeys::empty()),
b'B' => KeyEvent(KeyCode::Down, ModKeys::empty()),
b'C' => KeyEvent(KeyCode::Right, ModKeys::empty()),
b'D' => KeyEvent(KeyCode::Left, ModKeys::empty()),
_ => KeyEvent(KeyCode::Esc, ModKeys::empty()),
};
}
return KeyEvent(KeyCode::Esc, ModKeys::empty());
}
// Try parse valid UTF-8 from collected bytes
if let Ok(s) = str::from_utf8(&collected) {
return KeyEvent::new(s, ModKeys::empty());
}
// If it's not valid UTF-8 yet, loop to collect more bytes
if collected.len() >= 4 {
// UTF-8 max char length is 4; if it's still invalid, give up
break;
}
}
KeyEvent(KeyCode::Null, ModKeys::empty())
}
pub fn cursor_pos(&mut self) -> (usize, usize) {
self.write("\x1b[6n");
let mut buf = [0u8;32];
let n = self.read_byte(&mut buf);
let response = std::str::from_utf8(&buf[..n]).unwrap_or("");
let mut row = 0;
let mut col = 0;
if let Some(caps) = response.strip_prefix("\x1b[").and_then(|s| s.strip_suffix("R")) {
let mut parts = caps.split(';');
if let (Some(rowstr), Some(colstr)) = (parts.next(), parts.next()) {
row = rowstr.parse().unwrap_or(1);
col = colstr.parse().unwrap_or(1);
}
}
(row,col)
}
}
impl Default for Terminal {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,358 @@
use super::register::{append_register, read_register, write_register};
#[derive(Clone,Copy,Debug)]
pub struct RegisterName {
name: Option<char>,
count: usize,
append: bool
}
impl RegisterName {
pub fn new(name: Option<char>, count: Option<usize>) -> Self {
let Some(ch) = name else {
return Self::default()
};
let append = ch.is_uppercase();
let name = ch.to_ascii_lowercase();
Self {
name: Some(name),
count: count.unwrap_or(1),
append
}
}
pub fn name(&self) -> Option<char> {
self.name
}
pub fn is_append(&self) -> bool {
self.append
}
pub fn count(&self) -> usize {
self.count
}
pub fn write_to_register(&self, buf: String) {
if self.append {
append_register(self.name, buf);
} else {
write_register(self.name, buf);
}
}
pub fn read_from_register(&self) -> Option<String> {
read_register(self.name)
}
}
impl Default for RegisterName {
fn default() -> Self {
Self {
name: None,
count: 1,
append: false
}
}
}
#[derive(Clone,Default,Debug)]
pub struct ViCmd {
pub register: RegisterName,
pub verb: Option<VerbCmd>,
pub motion: Option<MotionCmd>,
pub raw_seq: String,
}
impl ViCmd {
pub fn new() -> Self {
Self::default()
}
pub fn set_motion(&mut self, motion: MotionCmd) {
self.motion = Some(motion)
}
pub fn set_verb(&mut self, verb: VerbCmd) {
self.verb = Some(verb)
}
pub fn verb(&self) -> Option<&VerbCmd> {
self.verb.as_ref()
}
pub fn motion(&self) -> Option<&MotionCmd> {
self.motion.as_ref()
}
pub fn verb_count(&self) -> usize {
self.verb.as_ref().map(|v| v.0).unwrap_or(1)
}
pub fn motion_count(&self) -> usize {
self.motion.as_ref().map(|m| m.0).unwrap_or(1)
}
pub fn is_repeatable(&self) -> bool {
self.verb.as_ref().is_some_and(|v| v.1.is_repeatable())
}
pub fn is_cmd_repeat(&self) -> bool {
self.verb.as_ref().is_some_and(|v| matches!(v.1,Verb::RepeatLast))
}
pub fn is_motion_repeat(&self) -> bool {
self.motion.as_ref().is_some_and(|m| matches!(m.1,Motion::RepeatMotion | Motion::RepeatMotionRev))
}
pub fn is_char_search(&self) -> bool {
self.motion.as_ref().is_some_and(|m| matches!(m.1, Motion::CharSearch(..)))
}
pub fn should_submit(&self) -> bool {
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::AcceptLine))
}
pub fn is_undo_op(&self) -> bool {
self.verb.as_ref().is_some_and(|v| matches!(v.1, Verb::Undo | Verb::Redo))
}
pub fn is_line_motion(&self) -> bool {
self.motion.as_ref().is_some_and(|m| matches!(m.1, Motion::LineUp | Motion::LineDown))
}
pub fn is_mode_transition(&self) -> bool {
self.verb.as_ref().is_some_and(|v| {
matches!(v.1,
Verb::Change |
Verb::InsertMode |
Verb::InsertModeLineBreak(_) |
Verb::NormalMode |
Verb::VisualMode |
Verb::ReplaceMode
)
})
}
}
#[derive(Clone,Debug)]
pub struct VerbCmd(pub usize,pub Verb);
#[derive(Clone,Debug)]
pub struct MotionCmd(pub usize,pub Motion);
impl MotionCmd {
pub fn invert_char_motion(self) -> Self {
let MotionCmd(count,Motion::CharSearch(dir, dest, ch)) = self else {
unreachable!()
};
let new_dir = match dir {
Direction::Forward => Direction::Backward,
Direction::Backward => Direction::Forward,
};
MotionCmd(count,Motion::CharSearch(new_dir, dest, ch))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum Verb {
Delete,
DeleteChar(Anchor),
Change,
Yank,
ReplaceChar(char),
Substitute,
ToggleCase,
Complete,
CompleteBackward,
Undo,
Redo,
RepeatLast,
Put(Anchor),
ReplaceMode,
InsertMode,
InsertModeLineBreak(Anchor),
NormalMode,
VisualMode,
JoinLines,
InsertChar(char),
Insert(String),
Breakline(Anchor),
Indent,
Dedent,
Equalize,
AcceptLine,
Builder(VerbBuilder),
EndOfFile
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum VerbBuilder {
}
impl Verb {
pub fn needs_motion(&self) -> bool {
matches!(self,
Self::Indent |
Self::Dedent |
Self::Delete |
Self::Change |
Self::Yank
)
}
pub fn is_repeatable(&self) -> bool {
matches!(self,
Self::Delete |
Self::DeleteChar(_) |
Self::Change |
Self::ReplaceChar(_) |
Self::Substitute |
Self::ToggleCase |
Self::Put(_) |
Self::ReplaceMode |
Self::InsertModeLineBreak(_) |
Self::JoinLines |
Self::InsertChar(_) |
Self::Insert(_) |
Self::Breakline(_) |
Self::Indent |
Self::Dedent |
Self::Equalize
)
}
pub fn is_edit(&self) -> bool {
matches!(self,
Self::Delete |
Self::DeleteChar(_) |
Self::Change |
Self::ReplaceChar(_) |
Self::Substitute |
Self::ToggleCase |
Self::RepeatLast |
Self::Put(_) |
Self::ReplaceMode |
Self::InsertModeLineBreak(_) |
Self::JoinLines |
Self::InsertChar(_) |
Self::Insert(_) |
Self::Breakline(_) |
Self::EndOfFile
)
}
pub fn is_char_insert(&self) -> bool {
matches!(self,
Self::Change |
Self::InsertChar(_) |
Self::ReplaceChar(_)
)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Motion {
/// Whole current line (not really a movement but a range)
WholeLine,
TextObj(TextObj, Bound),
BeginningOfFirstWord,
/// beginning-of-line
BeginningOfLine,
/// end-of-line
EndOfLine,
/// backward-word, vi-prev-word
BackwardWord(To, Word), // Backward until start of word
/// forward-word, vi-end-word, vi-next-word
ForwardWord(To, Word), // Forward until start/end of word
/// character-search, character-search-backward, vi-char-search
CharSearch(Direction,Dest,char),
/// backward-char
BackwardChar,
/// forward-char
ForwardChar,
/// move to the same column on the previous line
LineUp,
/// move to the same column on the previous visual line
ScreenLineUp,
/// move to the same column on the next line
LineDown,
/// move to the same column on the next visual line
ScreenLineDown,
/// Whole user input (not really a movement but a range)
WholeBuffer,
/// beginning-of-register
BeginningOfBuffer,
/// end-of-register
EndOfBuffer,
ToColumn(usize),
Range(usize,usize),
Builder(MotionBuilder),
RepeatMotion,
RepeatMotionRev,
Null
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MotionBuilder {
CharSearch(Option<Direction>,Option<Dest>,Option<char>),
TextObj(Option<TextObj>,Option<Bound>)
}
impl Motion {
pub fn needs_verb(&self) -> bool {
matches!(self, Self::TextObj(_, _))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Anchor {
After,
Before
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TextObj {
/// `iw`, `aw` — inner word, around word
Word(Word),
/// for stuff like 'dd'
Line,
/// `is`, `as` — inner sentence, around sentence
Sentence,
/// `ip`, `ap` — inner paragraph, around paragraph
Paragraph,
/// `i"`, `a"` — inner/around double quotes
DoubleQuote,
/// `i'`, `a'`
SingleQuote,
/// `i\``, `a\``
BacktickQuote,
/// `i)`, `a)` — round parens
Paren,
/// `i]`, `a]`
Bracket,
/// `i}`, `a}`
Brace,
/// `i<`, `a<`
Angle,
/// `it`, `at` — HTML/XML tags (if you support it)
Tag,
/// Custom user-defined objects maybe?
Custom(char),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Word {
Big,
Normal
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Bound {
Inside,
Around
}
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
pub enum Direction {
#[default]
Forward,
Backward
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Dest {
On,
Before,
After
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum To {
Start,
End
}

View File

@@ -1,6 +1,5 @@
use std::{collections::HashMap, fmt::Display, str::FromStr};
use rustyline::{config::BellStyle, EditMode};
use crate::{libsh::error::{Note, ShErr, ShErrKind, ShResult}, state::ShFunc};
@@ -29,17 +28,6 @@ impl FromStr for FernBellStyle {
}
}
impl From<FernBellStyle> for BellStyle {
fn from(val: FernBellStyle) -> Self {
match val {
FernBellStyle::Audible => BellStyle::Audible,
FernBellStyle::Visible => BellStyle::Visible,
FernBellStyle::Disable => BellStyle::None
}
}
}
impl Display for FernBellStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -50,21 +38,13 @@ impl Display for FernBellStyle {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Default, Clone, Copy, Debug)]
pub enum FernEditMode {
#[default]
Vi,
Emacs
}
impl From<FernEditMode> for EditMode {
fn from(val: FernEditMode) -> Self {
match val {
FernEditMode::Vi => EditMode::Vi,
FernEditMode::Emacs => EditMode::Emacs
}
}
}
impl FromStr for FernEditMode {
type Err = ShErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {

View File

@@ -1,27 +1 @@
use insta::assert_snapshot;
use crate::prompt::highlight::FernHighlighter;
use super::super::*;
#[test]
fn highlight_simple() {
let line = "echo foo bar";
let styled = FernHighlighter::new(line.to_string()).hl_input();
assert_snapshot!(styled)
}
#[test]
fn highlight_cmd_sub() {
let line = "echo foo $(echo bar)";
let styled = FernHighlighter::new(line.to_string()).hl_input();
assert_snapshot!(styled)
}
#[test]
fn highlight_cmd_sub_in_dquotes() {
let line = "echo \"foo $(echo bar) biz\"";
let styled = FernHighlighter::new(line.to_string()).hl_input();
assert_snapshot!(styled)
}

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected 'esac' after case block
-> [1;1]
 |
1 | case foo in foo) bar;; bar) foo;;
 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected 'in' after case variable name
-> [1;1]
 |
1 | case foo foo) bar;; bar) foo;; esac
 | ^^^^^^^^^^^^^^^^^^^
 |

View File

@@ -1,9 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Command not found: foo -
-> [1;1]
 |
1 | foo
 |

View File

@@ -1,8 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Execution failed
note: Execution failed for this reason
note: Here is how to fix it: blah blah blah

View File

@@ -1,11 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Execution failed
note: Execution failed for this reason
note: Here is how to fix it:
- blah
- blah
- blah

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected 'fi' after if statement
-> [1;1]
 |
1 | if foo; then bar;
 | ^^^^^^^^^^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected 'then' after 'if' condition
-> [1;1]
 |
1 | if foo; bar; fi
 | ^^^^^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected 'do' after loop condition
-> [1;1]
 |
1 | while true; echo foo; done
 | ^^^^^^^^^^^^^^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected 'done' after loop body
-> [1;1]
 |
1 | while true; do echo foo;
 | ^^^^^^^^^^^^^^^^^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Expected a closing brace for this brace group
-> [1;1]
 |
1 | { foo bar
 | ^^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Unterminated quote
-> [1;1]
 |
1 | "foo bar
 | ^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Unterminated quote
-> [1;1]
 |
1 | 'foo bar
 | ^^^^^^^^
 |

View File

@@ -1,10 +0,0 @@
---
source: src/tests/error.rs
expression: err_fmt
---
Parse Error - Unclosed subshell
-> [1;2]
 |
1 | (foo
 | ^
 |

View File

@@ -1,13 +0,0 @@
---
source: src/tests/expand.rs
expression: exp_tk.get_words()
---
[
"this",
"is",
"the",
"value",
"of",
"the",
"variable",
]

View File

@@ -1,5 +0,0 @@
---
source: src/tests/expand.rs
expression: unescaped
---
echo ﷐foo $bar

View File

@@ -1,12 +0,0 @@
---
source: src/tests/getopt.rs
expression: opts
---
[
Short(
'n',
),
Short(
'e',
),
]

View File

@@ -1,26 +0,0 @@
---
source: src/tests/getopt.rs
expression: words
---
[
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo -n -e foo",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo -n -e foo",
},
flags: TkFlags(
0x0,
),
},
]

View File

@@ -1,15 +0,0 @@
---
source: src/tests/getopt.rs
expression: opts
---
[
Short(
'n',
),
Short(
'r',
),
Short(
'e',
),
]

View File

@@ -1,8 +0,0 @@
---
source: src/tests/getopt.rs
expression: words
---
[
"echo",
"foo",
]

View File

@@ -1,9 +0,0 @@
---
source: src/tests/getopt.rs
expression: opts
---
[
Short(
'n',
),
]

View File

@@ -1,8 +0,0 @@
---
source: src/tests/getopt.rs
expression: words
---
[
"echo",
"foo",
]

View File

@@ -1,186 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..4,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 5..9,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 10..12,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
),
Ok(
Tk {
class: CasePattern,
span: Span {
range: 13..17,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 18..21,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 21..24,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: CasePattern,
span: Span {
range: 24..28,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 29..32,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 32..35,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: CasePattern,
span: Span {
range: 35..39,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 40..43,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 43..46,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 46..50,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 50..50,
source: "case $foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,162 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 16..17,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 17..21,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 22..25,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 26..29,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 29..30,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 30..34,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 35..38,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 39..42,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 42..42,
source: "echo hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,78 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "echo \"foo bar\" biz baz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo \"foo bar\" biz baz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 5..14,
source: "echo \"foo bar\" biz baz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 15..18,
source: "echo \"foo bar\" biz baz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 19..22,
source: "echo \"foo bar\" biz baz",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 22..22,
source: "echo \"foo bar\" biz baz",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,78 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "echo foo > bar.txt",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo > bar.txt",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo > bar.txt",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Redir,
span: Span {
range: 9..10,
source: "echo foo > bar.txt",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 11..18,
source: "echo foo > bar.txt",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 18..18,
source: "echo foo > bar.txt",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,66 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "echo foo 1>&2",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo 1>&2",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo 1>&2",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Redir,
span: Span {
range: 9..13,
source: "echo foo 1>&2",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 13..13,
source: "echo foo 1>&2",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,66 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 16..16,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,126 +0,0 @@
---
source: src/tests/lexer.rs
expression: tokens
---
[
Ok(
Tk {
class: SOI,
span: Span {
range: 0..0,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 3..7,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 7..9,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 9..13,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 14..18,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 19..22,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Sep,
span: Span {
range: 22..24,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
),
Ok(
Tk {
class: Str,
span: Span {
range: 24..26,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
),
Ok(
Tk {
class: EOI,
span: Span {
range: 26..26,
source: "if true; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
),
]

View File

@@ -1,162 +0,0 @@
---
source: src/tests/parser.rs
expression: check_nodes
---
[
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 16..18,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 18..22,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 23..26,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 27..30,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 18..22,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 23..26,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 27..30,
source: "echo hello world; echo foo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
]

View File

@@ -1,595 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: CaseNode {
pattern: Tk {
class: Str,
span: Span {
range: 5..8,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
case_blocks: [
CaseNode {
pattern: Tk {
class: CasePattern,
span: Span {
range: 13..17,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 18..21,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 18..21,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 21..27,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 18..21,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 21..27,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
CaseNode {
pattern: Tk {
class: CasePattern,
span: Span {
range: 27..31,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 32..35,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 32..35,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 35..41,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 32..35,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 35..41,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
CaseNode {
pattern: Tk {
class: CasePattern,
span: Span {
range: 41..45,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 46..49,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 46..49,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 49..54,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 46..49,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 49..54,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 9..11,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Sep,
span: Span {
range: 11..13,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 13..17,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 21..27,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 27..31,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 32..35,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 35..41,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 41..45,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 46..49,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 49..54,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 54..58,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 9..11,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Sep,
span: Span {
range: 11..13,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 13..17,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 21..27,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 27..31,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 32..35,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 35..41,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 41..45,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 46..49,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 49..54,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 54..58,
source: "case foo in\n\tfoo) bar\n\t;;\n\tbar) foo\n\t;;\n\tbiz) baz\n\t;;\nesac",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,575 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: CaseNode {
pattern: Tk {
class: Str,
span: Span {
range: 5..8,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
case_blocks: [
CaseNode {
pattern: Tk {
class: CasePattern,
span: Span {
range: 12..16,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 17..20,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 17..20,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 20..23,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 17..20,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 20..23,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
CaseNode {
pattern: Tk {
class: CasePattern,
span: Span {
range: 23..27,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 28..31,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 28..31,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..34,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 28..31,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..34,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
CaseNode {
pattern: Tk {
class: CasePattern,
span: Span {
range: 34..38,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 39..42,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 39..42,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 42..45,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 39..42,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 42..45,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 9..11,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: CasePattern,
span: Span {
range: 12..16,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 17..20,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 20..23,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 23..27,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 28..31,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..34,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 34..38,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 39..42,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 42..45,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 45..49,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 9..11,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: CasePattern,
span: Span {
range: 12..16,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 17..20,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 20..23,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 23..27,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 28..31,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..34,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: CasePattern,
span: Span {
range: 34..38,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 39..42,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 42..45,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 45..49,
source: "case foo in foo) bar;; bar) foo;; biz) baz;; esac",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

View File

@@ -1,248 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: And,
},
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 12..16,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 17..20,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 12..16,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 17..20,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 12..16,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 17..20,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: And,
span: Span {
range: 9..11,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..16,
source: "echo foo && echo bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 17..20,
source: "echo foo && echo bar",
},
flags: TkFlags(
0x0,
),
},
],
},
),
]

View File

@@ -1,826 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 9..10,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: And,
},
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 29..33,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 29..33,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 40..43,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 40..43,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 29..33,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 38..39,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 40..43,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Or,
},
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 58..62,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 58..62,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 73..76,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 73..76,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 58..62,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 71..72,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 73..76,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 9..10,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: And,
span: Span {
range: 26..28,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 29..33,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 38..39,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 40..43,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Or,
span: Span {
range: 55..57,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 58..62,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 71..72,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 73..76,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
),
},
],
},
),
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,435 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: IfNode {
cond_nodes: [
CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
],
else_block: [],
},
flags: NdFlags(
0x0,
),
redirs: [
Redir {
io_mode: File {
tgt_fd: 1,
file: File {
fd: 3,
path: "/home/pagedmov/Coding/projects/rust/fern/file.txt",
read: false,
write: true,
},
},
class: Output,
},
],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 8..12,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 23..25,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Redir,
span: Span {
range: 26..27,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 28..36,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 8..12,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 23..25,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Redir,
span: Span {
range: 26..27,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 28..36,
source: "if foo; then echo bar; fi > file.txt",
},
flags: TkFlags(
0x0,
),
},
],
},
),
]

View File

@@ -1,382 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: IfNode {
cond_nodes: [
CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
],
else_block: [],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 8..12,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 23..25,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 8..12,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 23..25,
source: "if foo; then echo bar; fi",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

View File

@@ -1,708 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: IfNode {
cond_nodes: [
CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 28..31,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 28..31,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..33,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 28..31,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..33,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 38..42,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 43..46,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 38..42,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 43..46,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 46..48,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 38..42,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 43..46,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 46..48,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
],
else_block: [],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 8..12,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 23..27,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 28..31,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..33,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 33..37,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 38..42,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 43..46,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 46..48,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 48..50,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..2,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 3..6,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 6..8,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 8..12,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 13..17,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 18..21,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 21..23,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 23..27,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 28..31,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 31..33,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 33..37,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 38..42,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 43..46,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 46..48,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 48..50,
source: "if foo; then echo bar; elif bar; then echo foo; fi",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

View File

@@ -1,350 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: LoopNode {
kind: Until,
cond_node: CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 7..10,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 7..10,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 10..12,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 7..10,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 10..12,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 16..19,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 16..19,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 19..20,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 16..19,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 19..20,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 1..6,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 7..10,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 10..12,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..14,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Sep,
span: Span {
range: 14..16,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 16..19,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 19..20,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 20..24,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 1..6,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 7..10,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 10..12,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..14,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Sep,
span: Span {
range: 14..16,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 16..19,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 19..20,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 20..24,
source: "\nuntil foo; do\n\tbar\ndone",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

View File

@@ -1,330 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: LoopNode {
kind: While,
cond_node: CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 6..9,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 6..9,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 6..9,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 14..17,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 14..17,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 14..17,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..5,
source: "while foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 6..9,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..13,
source: "while foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 14..17,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 19..23,
source: "while foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..5,
source: "while foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 6..9,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..13,
source: "while foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 14..17,
source: "while foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "while foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 19..23,
source: "while foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

View File

@@ -1,330 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: LoopNode {
kind: Until,
cond_node: CondNode {
cond: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 6..9,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 6..9,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 6..9,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
body: [
Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 14..17,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 14..17,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 14..17,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
],
},
],
},
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..5,
source: "until foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 6..9,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..13,
source: "until foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 14..17,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 19..23,
source: "until foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..5,
source: "until foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 6..9,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 9..11,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..13,
source: "until foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
Tk {
class: Str,
span: Span {
range: 14..17,
source: "until foo; do bar; done",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Sep,
span: Span {
range: 17..19,
source: "until foo; do bar; done",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 19..23,
source: "until foo; do bar; done",
},
flags: TkFlags(
KEYWORD,
),
},
],
},
),
]

View File

@@ -1,555 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 1..5,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 6..11,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..17,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 1..5,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 6..11,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..17,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 17..18,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 1..5,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 6..11,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..17,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 17..18,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 1..5,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 6..11,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 12..17,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 17..18,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
),
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 18..22,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 23..26,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 27..30,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 18..22,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 23..26,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 27..30,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 30..31,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 18..22,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 23..26,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 27..30,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 30..31,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 18..22,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 23..26,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 27..30,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Sep,
span: Span {
range: 30..31,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
),
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 31..35,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 36..39,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 40..43,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 31..35,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 36..39,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 40..43,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 31..35,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 36..39,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 40..43,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 31..35,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 36..39,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 40..43,
source: "\necho hello world\necho foo bar\necho boo biz",
},
flags: TkFlags(
0x0,
),
},
],
},
),
]

View File

@@ -1,242 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
],
},
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 9..10,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..8,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Pipe,
span: Span {
range: 9..10,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..14,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
IS_CMD,
),
},
Tk {
class: Str,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar",
},
flags: TkFlags(
0x0,
),
},
],
},
),
]

View File

@@ -1,169 +0,0 @@
---
source: src/tests/parser.rs
expression: nodes
---
[
Ok(
Node {
class: Conjunction {
elements: [
ConjunctNode {
cmd: Node {
class: Pipeline {
cmds: [
Node {
class: Command {
assignments: [],
argv: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
],
},
],
pipe_err: false,
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
],
},
operator: Null,
},
],
},
flags: NdFlags(
0x0,
),
redirs: [],
tokens: [
Tk {
class: Str,
span: Span {
range: 0..4,
source: "echo hello world",
},
flags: TkFlags(
IS_CMD | BUILTIN,
),
},
Tk {
class: Str,
span: Span {
range: 5..10,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
Tk {
class: Str,
span: Span {
range: 11..16,
source: "echo hello world",
},
flags: TkFlags(
0x0,
),
},
],
},
),
]

View File

@@ -1,5 +0,0 @@
---
source: src/tests/term.rs
expression: styled
---
text with background

View File

@@ -1,5 +0,0 @@
---
source: src/tests/term.rs
expression: styled
---
styled text

View File

@@ -1,5 +0,0 @@
---
source: src/tests/term.rs
expression: styled
---
reset test

View File

@@ -1,5 +0,0 @@
---
source: src/tests/term.rs
expression: styled
---
RGB styled text

View File

@@ -1,5 +0,0 @@
---
source: src/tests/term.rs
expression: styled
---
multi-style text

View File

@@ -1,5 +0,0 @@
---
source: src/tests/term.rs
expression: styled
---
hello world