Compare commits
2 Commits
62d651eb8d
...
cab7a0fea7
| Author | SHA1 | Date | |
|---|---|---|---|
| cab7a0fea7 | |||
| 367218d3e8 |
@@ -3,7 +3,7 @@ use crate::{
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::{self, read_logic, write_logic},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ use std::sync::LazyLock;
|
||||
use crate::{
|
||||
builtin::setup_builtin,
|
||||
expand::expand_prompt,
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
getopt::{get_opts_from_tokens, Opt, OptSpec},
|
||||
jobs::JobBldr,
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
builtin::setup_builtin,
|
||||
jobs::JobBldr,
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node, execute::exec_input},
|
||||
parse::{execute::exec_input, NdRule, Node},
|
||||
procio::IoStack,
|
||||
state,
|
||||
};
|
||||
@@ -25,7 +25,8 @@ pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let joined_argv = expanded_argv.into_iter()
|
||||
let joined_argv = expanded_argv
|
||||
.into_iter()
|
||||
.map(|(s, _)| s)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
builtin::setup_builtin,
|
||||
jobs::JobBldr,
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node, execute::ExecArgs},
|
||||
parse::{execute::ExecArgs, NdRule, Node},
|
||||
procio::IoStack,
|
||||
state,
|
||||
};
|
||||
@@ -40,11 +40,7 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
||||
// execvpe only returns on error
|
||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||
match e {
|
||||
Errno::ENOENT => {
|
||||
Err(ShErr::full(ShErrKind::CmdNotFound(cmd_str), "", span))
|
||||
}
|
||||
_ => {
|
||||
Err(ShErr::full(ShErrKind::Errno(e), format!("{e}"), span))
|
||||
}
|
||||
Errno::ENOENT => Err(ShErr::full(ShErrKind::CmdNotFound(cmd_str), "", span)),
|
||||
_ => Err(ShErr::full(ShErrKind::Errno(e), format!("{e}"), span)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::{
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
state::{self, VarFlags, read_vars, write_vars},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::{self, read_vars, write_vars, VarFlags},
|
||||
};
|
||||
|
||||
use super::setup_builtin;
|
||||
@@ -60,7 +60,8 @@ pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
let vars_output = read_vars(|v| {
|
||||
let mut vars = v.flatten_vars()
|
||||
let mut vars = v
|
||||
.flatten_vars()
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node, execute::prepare_argv},
|
||||
parse::{execute::prepare_argv, NdRule, Node},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
jobs::{JobBldr, JobCmdFlags, JobID},
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node, lex::Span},
|
||||
parse::{lex::Span, NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::{self, read_jobs, write_jobs},
|
||||
};
|
||||
|
||||
@@ -196,7 +196,11 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::ExecFail, "disown: No jobs to disown", blame));
|
||||
return Err(ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"disown: No jobs to disown",
|
||||
blame,
|
||||
));
|
||||
};
|
||||
|
||||
let mut tabid = curr_job_id;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ use nix::{
|
||||
|
||||
use crate::{
|
||||
builtin::setup_builtin,
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
getopt::{get_opts_from_tokens, Opt, OptSpec},
|
||||
jobs::JobBldr,
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
parse::{NdRule, Node},
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
prompt::readline::term::RawModeGuard,
|
||||
state::{self, VarFlags, read_vars, write_vars},
|
||||
state::{self, read_vars, write_vars, VarFlags},
|
||||
};
|
||||
|
||||
pub const READ_OPTS: [OptSpec; 7] = [
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
libsh::error::{ShResult, ShResultExt},
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::write_shopts,
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{ConjunctOp, NdRule, Node, TEST_UNARY_OPS, TestCase},
|
||||
parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
jobs::JobBldr,
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node},
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::{self, read_logic, write_logic},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
||||
|
||||
use crate::{
|
||||
getopt::{Opt, OptSet, OptSpec, get_opts_from_tokens},
|
||||
getopt::{get_opts_from_tokens, Opt, OptSet, OptSpec},
|
||||
jobs::JobBldr,
|
||||
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{IoStack, borrow_fd},
|
||||
procio::{borrow_fd, IoStack},
|
||||
};
|
||||
|
||||
use super::setup_builtin;
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
term::{Style, Styled},
|
||||
},
|
||||
prelude::*,
|
||||
procio::{IoMode, borrow_fd},
|
||||
procio::{borrow_fd, IoMode},
|
||||
signal::{disable_reaping, enable_reaping},
|
||||
state::{self, read_jobs, set_status, write_jobs},
|
||||
};
|
||||
|
||||
@@ -34,8 +34,7 @@ use crate::prelude::*;
|
||||
pub(crate) static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||
|
||||
pub static TTY_FILENO: LazyLock<RawFd> = LazyLock::new(|| {
|
||||
open("/dev/tty", OFlag::O_RDWR, Mode::empty())
|
||||
.expect("Failed to open /dev/tty")
|
||||
open("/dev/tty", OFlag::O_RDWR, Mode::empty()).expect("Failed to open /dev/tty")
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@@ -23,7 +23,6 @@ use std::process::ExitCode;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use nix::libc::STDIN_FILENO;
|
||||
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
|
||||
use nix::unistd::read;
|
||||
|
||||
@@ -33,7 +32,7 @@ use crate::libsh::sys::TTY_FILENO;
|
||||
use crate::parse::execute::exec_input;
|
||||
use crate::prelude::*;
|
||||
use crate::prompt::get_prompt;
|
||||
use crate::prompt::readline::term::raw_mode;
|
||||
use crate::prompt::readline::term::{RawModeGuard, raw_mode};
|
||||
use crate::prompt::readline::{FernVi, ReadlineEvent};
|
||||
use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending};
|
||||
use crate::state::{read_logic, source_rc, write_jobs, write_meta};
|
||||
@@ -115,8 +114,7 @@ fn main() -> ExitCode {
|
||||
};
|
||||
|
||||
if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit))
|
||||
&& let Err(e) = exec_input(trap, None, false)
|
||||
{
|
||||
&& let Err(e) = exec_input(trap, None, false) {
|
||||
eprintln!("fern: error running EXIT trap: {e}");
|
||||
}
|
||||
|
||||
@@ -215,10 +213,7 @@ fn fern_interactive() -> ShResult<()> {
|
||||
}
|
||||
|
||||
// Check if stdin has data
|
||||
if fds[0]
|
||||
.revents()
|
||||
.is_some_and(|r| r.contains(PollFlags::POLLIN))
|
||||
{
|
||||
if fds[0].revents().is_some_and(|r| r.contains(PollFlags::POLLIN)) {
|
||||
let mut buffer = [0u8; 1024];
|
||||
match read(*TTY_FILENO, &mut buffer) {
|
||||
Ok(0) => {
|
||||
@@ -244,7 +239,7 @@ fn fern_interactive() -> ShResult<()> {
|
||||
Ok(ReadlineEvent::Line(input)) => {
|
||||
let start = Instant::now();
|
||||
write_meta(|m| m.start_timer());
|
||||
if let Err(e) = exec_input(input, None, true) {
|
||||
if let Err(e) = RawModeGuard::with_cooked_mode(|| exec_input(input, None, true)) {
|
||||
match e.kind() {
|
||||
ShErrKind::CleanExit(code) => {
|
||||
QUIT_CODE.store(*code, Ordering::SeqCst);
|
||||
|
||||
@@ -155,7 +155,9 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool) -
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut dispatcher = Dispatcher::new(parser.extract_nodes(), interactive);
|
||||
let nodes = parser.extract_nodes();
|
||||
|
||||
let mut dispatcher = Dispatcher::new(nodes, interactive);
|
||||
if let Some(mut stack) = io_stack {
|
||||
dispatcher.io_stack.extend(stack.drain(..));
|
||||
}
|
||||
|
||||
@@ -659,6 +659,9 @@ impl LexStream {
|
||||
}
|
||||
_ if is_cmd_sub(text) => {
|
||||
new_tk.mark(TkFlags::IS_CMDSUB);
|
||||
if self.next_is_cmd() {
|
||||
new_tk.mark(TkFlags::IS_CMD);
|
||||
}
|
||||
self.set_next_is_cmd(false);
|
||||
}
|
||||
_ => {
|
||||
@@ -846,11 +849,26 @@ pub fn is_field_sep(ch: char) -> bool {
|
||||
}
|
||||
|
||||
pub fn is_keyword(slice: &str) -> bool {
|
||||
KEYWORDS.contains(&slice) || (slice.ends_with("()") && !slice.ends_with("\\()"))
|
||||
KEYWORDS.contains(&slice) || ends_with_unescaped(slice, "()")
|
||||
}
|
||||
|
||||
pub fn is_cmd_sub(slice: &str) -> bool {
|
||||
(slice.starts_with("$(") && slice.ends_with(')')) && !slice.ends_with("\\)")
|
||||
slice.starts_with("$(") && ends_with_unescaped(slice,")")
|
||||
}
|
||||
|
||||
pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
|
||||
slice.ends_with(pat) && !pos_is_escaped(slice, slice.len() - pat.len())
|
||||
}
|
||||
|
||||
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
|
||||
let bytes = slice.as_bytes();
|
||||
let mut escaped = false;
|
||||
let mut i = pos;
|
||||
while i > 0 && bytes[i - 1] == b'\\' {
|
||||
escaped = !escaped;
|
||||
i -= 1;
|
||||
}
|
||||
escaped
|
||||
}
|
||||
|
||||
pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
|
||||
|
||||
@@ -19,17 +19,17 @@ pub use std::os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd,
|
||||
pub use bitflags::bitflags;
|
||||
pub use nix::{
|
||||
errno::Errno,
|
||||
fcntl::{OFlag, open},
|
||||
fcntl::{open, OFlag},
|
||||
libc::{self, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO},
|
||||
sys::{
|
||||
signal::{self, SigHandler, SigSet, SigmaskHow, Signal, kill, killpg, pthread_sigmask, signal},
|
||||
signal::{self, kill, killpg, pthread_sigmask, signal, SigHandler, SigSet, SigmaskHow, Signal},
|
||||
stat::Mode,
|
||||
termios::{self},
|
||||
wait::{WaitPidFlag as WtFlag, WaitStatus as WtStat, waitpid},
|
||||
wait::{waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat},
|
||||
},
|
||||
unistd::{
|
||||
ForkResult, Pid, close, dup, dup2, execvpe, fork, getpgid, getpgrp, isatty, pipe, read,
|
||||
setpgid, tcgetpgrp, tcsetpgrp, write,
|
||||
close, dup, dup2, execvpe, fork, getpgid, getpgrp, isatty, pipe, read, setpgid, tcgetpgrp,
|
||||
tcsetpgrp, write, ForkResult, Pid,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
error::{ShErr, ShErrKind, ShResult},
|
||||
utils::RedirVecUtils,
|
||||
},
|
||||
parse::{Redir, RedirType, get_redir_file},
|
||||
parse::{get_redir_file, Redir, RedirType},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ pub enum IoMode {
|
||||
},
|
||||
Close {
|
||||
tgt_fd: RawFd,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
impl IoMode {
|
||||
|
||||
@@ -184,14 +184,16 @@ impl Highlighter {
|
||||
input_chars.next();
|
||||
}
|
||||
|
||||
let inner_clean = Self::strip_markers(&inner);
|
||||
|
||||
// Determine prefix from content (handles both <( and >( for proc subs)
|
||||
let prefix = match ch {
|
||||
markers::CMD_SUB => "$(",
|
||||
markers::SUBSH => "(",
|
||||
markers::PROC_SUB => {
|
||||
if inner.starts_with("<(") {
|
||||
if inner_clean.starts_with("<(") {
|
||||
"<("
|
||||
} else if inner.starts_with(">(") {
|
||||
} else if inner_clean.starts_with(">(") {
|
||||
">("
|
||||
} else {
|
||||
"<("
|
||||
@@ -199,14 +201,13 @@ impl Highlighter {
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let inner_content = if incomplete {
|
||||
inner.strip_prefix(prefix).unwrap_or(&inner)
|
||||
inner_clean.strip_prefix(prefix).unwrap_or(&inner_clean)
|
||||
} else {
|
||||
inner
|
||||
inner_clean
|
||||
.strip_prefix(prefix)
|
||||
.and_then(|s| s.strip_suffix(")"))
|
||||
.unwrap_or(&inner)
|
||||
.unwrap_or(&inner_clean)
|
||||
};
|
||||
|
||||
let mut recursive_highlighter = Self::new();
|
||||
|
||||
@@ -9,8 +9,11 @@ use std::{
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, prompt::readline::linebuf::LineBuf};
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
prompt::readline::linebuf::LineBuf,
|
||||
};
|
||||
|
||||
use super::vicmd::Direction; // surprisingly useful
|
||||
|
||||
|
||||
@@ -18,7 +18,13 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
use vte::{Parser, Perform};
|
||||
|
||||
use crate::{
|
||||
libsh::{error::{ShErr, ShErrKind, ShResult}, sys::TTY_FILENO}, prompt::readline::keys::{KeyCode, ModKeys}, shopt::FernBellStyle, state::read_shopts
|
||||
libsh::{
|
||||
error::{ShErr, ShErrKind, ShResult},
|
||||
sys::TTY_FILENO,
|
||||
},
|
||||
prompt::readline::keys::{KeyCode, ModKeys},
|
||||
shopt::FernBellStyle,
|
||||
state::read_shopts,
|
||||
};
|
||||
use crate::{
|
||||
prelude::*,
|
||||
@@ -107,7 +113,8 @@ fn write_all(fd: RawFd, buf: &str) -> nix::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if a string ends with a newline, ignoring any trailing ANSI escape sequences.
|
||||
/// Check if a string ends with a newline, ignoring any trailing ANSI escape
|
||||
/// sequences.
|
||||
fn ends_with_newline(s: &str) -> bool {
|
||||
let bytes = s.as_bytes();
|
||||
let mut i = bytes.len();
|
||||
@@ -757,12 +764,7 @@ impl Layout {
|
||||
end: Pos::default(),
|
||||
}
|
||||
}
|
||||
pub fn from_parts(
|
||||
term_width: u16,
|
||||
prompt: &str,
|
||||
to_cursor: &str,
|
||||
to_end: &str,
|
||||
) -> Self {
|
||||
pub fn from_parts(term_width: u16, prompt: &str, to_cursor: &str, to_end: &str) -> Self {
|
||||
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 });
|
||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end);
|
||||
let end = Self::calc_pos(term_width, to_end, prompt_end);
|
||||
@@ -1004,5 +1006,4 @@ impl LineWriter for TermWriter {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
13
src/shopt.rs
13
src/shopt.rs
@@ -212,12 +212,10 @@ impl ShOptCore {
|
||||
}
|
||||
"bell_enabled" => {
|
||||
let Ok(val) = val.parse::<bool>() else {
|
||||
return Err(
|
||||
ShErr::simple(
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::SyntaxErr,
|
||||
"shopt: expected 'true' or 'false' for bell_enabled value",
|
||||
),
|
||||
);
|
||||
));
|
||||
};
|
||||
self.bell_enabled = val;
|
||||
}
|
||||
@@ -371,7 +369,7 @@ pub struct ShOptPrompt {
|
||||
pub edit_mode: FernEditMode,
|
||||
pub comp_limit: usize,
|
||||
pub highlight: bool,
|
||||
pub auto_indent: bool
|
||||
pub auto_indent: bool,
|
||||
}
|
||||
|
||||
impl ShOptPrompt {
|
||||
@@ -481,7 +479,8 @@ impl ShOptPrompt {
|
||||
Ok(Some(output))
|
||||
}
|
||||
"auto_indent" => {
|
||||
let mut output = String::from("Whether to automatically indent new lines in multiline commands\n");
|
||||
let mut output =
|
||||
String::from("Whether to automatically indent new lines in multiline commands\n");
|
||||
output.push_str(&format!("{}", self.auto_indent));
|
||||
Ok(Some(output))
|
||||
}
|
||||
@@ -530,7 +529,7 @@ impl Default for ShOptPrompt {
|
||||
edit_mode: FernEditMode::Vi,
|
||||
comp_limit: 100,
|
||||
highlight: true,
|
||||
auto_indent: true
|
||||
auto_indent: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use tempfile::TempDir;
|
||||
|
||||
use crate::prompt::readline::complete::Completer;
|
||||
use crate::prompt::readline::markers;
|
||||
use crate::state::{VarFlags, write_logic, write_vars};
|
||||
use crate::state::{write_logic, write_vars, VarFlags};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -192,12 +192,10 @@ fn complete_filename_with_slash() {
|
||||
|
||||
// Should complete files in subdir/
|
||||
if result.is_some() {
|
||||
assert!(
|
||||
completer
|
||||
assert!(completer
|
||||
.candidates
|
||||
.iter()
|
||||
.any(|c| c.contains("nested.txt"))
|
||||
);
|
||||
.any(|c| c.contains("nested.txt")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,12 +702,10 @@ fn complete_special_characters_in_filename() {
|
||||
|
||||
if result.is_some() {
|
||||
// Should handle special chars in filenames
|
||||
assert!(
|
||||
completer
|
||||
assert!(completer
|
||||
.candidates
|
||||
.iter()
|
||||
.any(|c| c.contains("file-with-dash") || c.contains("file_with_underscore"))
|
||||
);
|
||||
.any(|c| c.contains("file-with-dash") || c.contains("file_with_underscore")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::expand::{DUB_QUOTE, VAR_SUB, perform_param_expansion};
|
||||
use crate::expand::{perform_param_expansion, DUB_QUOTE, VAR_SUB};
|
||||
use crate::state::VarFlags;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -4,9 +4,8 @@ use super::*;
|
||||
use crate::expand::{expand_aliases, unescape_str};
|
||||
use crate::libsh::error::{Note, ShErr, ShErrKind};
|
||||
use crate::parse::{
|
||||
NdRule, Node, ParseStream,
|
||||
lex::{LexFlags, LexStream, Tk, TkRule},
|
||||
node_operation,
|
||||
node_operation, NdRule, Node, ParseStream,
|
||||
};
|
||||
use crate::state::{write_logic, write_vars};
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ use crate::{
|
||||
term::{Style, Styled},
|
||||
},
|
||||
prompt::readline::{
|
||||
FernVi,
|
||||
history::History,
|
||||
keys::{KeyCode, KeyEvent, ModKeys},
|
||||
linebuf::LineBuf,
|
||||
term::{KeyReader, LineWriter, raw_mode},
|
||||
term::{raw_mode, KeyReader, LineWriter},
|
||||
vimode::{ViInsert, ViMode, ViNormal},
|
||||
FernVi,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -605,31 +605,27 @@ fn editor_delete_line_up() {
|
||||
#[test]
|
||||
fn editor_insert_at_line_start() {
|
||||
// I should move cursor to position 0 when line starts with non-whitespace
|
||||
assert_eq!(
|
||||
normal_cmd("I", "hello world", 5),
|
||||
("hello world".into(), 0)
|
||||
);
|
||||
assert_eq!(normal_cmd("I", "hello world", 5), ("hello world".into(), 0));
|
||||
// I should skip leading whitespace
|
||||
assert_eq!(
|
||||
normal_cmd("I", " hello world", 8),
|
||||
(" hello world".into(), 2)
|
||||
);
|
||||
// I should move to the first non-whitespace on the current line in a multiline buffer
|
||||
// I should move to the first non-whitespace on the current line in a multiline
|
||||
// buffer
|
||||
assert_eq!(
|
||||
normal_cmd("I", "first line\nsecond line", 14),
|
||||
("first line\nsecond line".into(), 11)
|
||||
);
|
||||
// I should land on position 0 when cursor is already at 0
|
||||
assert_eq!(
|
||||
normal_cmd("I", "hello", 0),
|
||||
("hello".into(), 0)
|
||||
);
|
||||
assert_eq!(normal_cmd("I", "hello", 0), ("hello".into(), 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_f_char_from_position_zero() {
|
||||
// f<char> at position 0 should skip the cursor and find the next occurrence
|
||||
// Regression: previously at pos 0, f would match the char under the cursor itself
|
||||
// Regression: previously at pos 0, f would match the char under the cursor
|
||||
// itself
|
||||
assert_eq!(
|
||||
normal_cmd("fa", "abcaef", 0),
|
||||
("abcaef".into(), 3) // should find second 'a', not the 'a' at position 0
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::parse::{
|
||||
NdRule, Node, ParseStream, Redir, RedirType,
|
||||
lex::{LexFlags, LexStream},
|
||||
NdRule, Node, ParseStream, Redir, RedirType,
|
||||
};
|
||||
use crate::procio::{IoFrame, IoMode, IoStack};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
use crate::state::{LogTab, MetaTab, ScopeStack, ShellParam, VarFlags, VarTab};
|
||||
use std::path::PathBuf;
|
||||
|
||||
// ============================================================================
|
||||
// ScopeStack Tests - Variable Scoping
|
||||
@@ -296,13 +296,21 @@ fn scopestack_local_var_mutation() {
|
||||
|
||||
// `foo="bar"` — reassign without LOCAL flag (plain assignment)
|
||||
stack.set_var("foo", "bar", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("foo"), "bar", "Local var should be mutated in place");
|
||||
assert_eq!(
|
||||
stack.get_var("foo"),
|
||||
"bar",
|
||||
"Local var should be mutated in place"
|
||||
);
|
||||
|
||||
// Ascend back to global
|
||||
stack.ascend();
|
||||
|
||||
// foo should not exist in global scope
|
||||
assert_eq!(stack.get_var("foo"), "", "Local var should not leak to global scope");
|
||||
assert_eq!(
|
||||
stack.get_var("foo"),
|
||||
"",
|
||||
"Local var should not leak to global scope"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -318,13 +326,21 @@ fn scopestack_local_var_uninitialized() {
|
||||
|
||||
// `foo="bar"` — assign a value later
|
||||
stack.set_var("foo", "bar", VarFlags::NONE);
|
||||
assert_eq!(stack.get_var("foo"), "bar", "Uninitialized local should be assignable");
|
||||
assert_eq!(
|
||||
stack.get_var("foo"),
|
||||
"bar",
|
||||
"Uninitialized local should be assignable"
|
||||
);
|
||||
|
||||
// Ascend back to global
|
||||
stack.ascend();
|
||||
|
||||
// foo should not exist in global scope
|
||||
assert_eq!(stack.get_var("foo"), "", "Local var should not leak to global scope");
|
||||
assert_eq!(
|
||||
stack.get_var("foo"),
|
||||
"",
|
||||
"Local var should not leak to global scope"
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -740,11 +756,14 @@ fn dirstack_pushd_rotation_with_cwd() {
|
||||
|
||||
assert_eq!(new_cwd, Some(PathBuf::from("/var")));
|
||||
let remaining: Vec<_> = meta.dirs().iter().collect();
|
||||
assert_eq!(remaining, vec![
|
||||
assert_eq!(
|
||||
remaining,
|
||||
vec![
|
||||
&PathBuf::from("/etc"),
|
||||
&PathBuf::from("/home"),
|
||||
&PathBuf::from("/tmp"),
|
||||
]);
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -821,10 +840,10 @@ fn dirstack_popd_plus_n_offset() {
|
||||
assert_eq!(removed, Some(PathBuf::from("/var")));
|
||||
|
||||
let remaining: Vec<_> = meta.dirs().iter().collect();
|
||||
assert_eq!(remaining, vec![
|
||||
&PathBuf::from("/tmp"),
|
||||
&PathBuf::from("/etc"),
|
||||
]);
|
||||
assert_eq!(
|
||||
remaining,
|
||||
vec![&PathBuf::from("/tmp"), &PathBuf::from("/etc"),]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user