Compare commits
3 Commits
07d7015dd4
...
85e5fc2875
| Author | SHA1 | Date | |
|---|---|---|---|
| 85e5fc2875 | |||
| ac429cbdf4 | |||
| a464540fbe |
@@ -250,7 +250,7 @@ mod tests {
|
||||
"pre-mode-change", "post-mode-change",
|
||||
"on-history-open", "on-history-close", "on-history-select",
|
||||
"on-completion-start", "on-completion-cancel", "on-completion-select",
|
||||
"on-exit",
|
||||
"on-exit"
|
||||
];
|
||||
for kind in kinds {
|
||||
test_input(format!("autocmd {kind} 'true'")).unwrap();
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -130,6 +130,15 @@ fn main() -> ExitCode {
|
||||
return ExitCode::SUCCESS;
|
||||
}
|
||||
|
||||
// Increment SHLVL, or set to 1 if not present or invalid.
|
||||
// This var represents how many nested shell instances we're in
|
||||
if let Ok(var) = env::var("SHLVL")
|
||||
&& let Ok(lvl) = var.parse::<u32>() {
|
||||
unsafe { env::set_var("SHLVL", (lvl + 1).to_string()) };
|
||||
} else {
|
||||
unsafe { env::set_var("SHLVL", "1") };
|
||||
}
|
||||
|
||||
if let Err(e) = if let Some(path) = args.script {
|
||||
run_script(path, args.script_args)
|
||||
} else if let Some(cmd) = args.command {
|
||||
@@ -240,7 +249,7 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
|
||||
// may have moved it during resize/rewrap
|
||||
readline.writer.update_t_cols();
|
||||
readline.mark_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
if JOB_DONE.swap(false, Ordering::SeqCst) {
|
||||
// update the prompt so any job count escape sequences update dynamically
|
||||
|
||||
@@ -258,6 +258,9 @@ impl Dispatcher {
|
||||
Ok(())
|
||||
}
|
||||
pub fn dispatch_cmd(&mut self, node: Node) -> ShResult<()> {
|
||||
let (line, _) = node.get_span().clone().line_and_col();
|
||||
write_vars(|v| v.set_var("LINENO", VarKind::Str((line + 1).to_string()), VarFlags::NONE))?;
|
||||
|
||||
let Some(cmd) = node.get_command() else {
|
||||
return self.exec_cmd(node); // Argv is empty, probably an assignment
|
||||
};
|
||||
@@ -764,8 +767,16 @@ impl Dispatcher {
|
||||
self.job_stack.new_job();
|
||||
if cmds.len() == 1 {
|
||||
self.fg_job = !is_bg && self.interactive;
|
||||
let cmd = cmds.into_iter().next().unwrap();
|
||||
self.dispatch_node(cmd)?;
|
||||
let mut cmd = cmds.into_iter().next().unwrap();
|
||||
if is_bg && !matches!(cmd.class, NdRule::Command { .. }) {
|
||||
self.run_fork(&cmd.get_command().map(|t| t.to_string()).unwrap_or_default(), |s| {
|
||||
if let Err(e) = s.dispatch_node(cmd) {
|
||||
e.print_error();
|
||||
}
|
||||
})?;
|
||||
} else {
|
||||
self.dispatch_node(cmd)?;
|
||||
}
|
||||
|
||||
// Give the pipeline terminal control as soon as the first child
|
||||
// establishes the PGID, so later children (e.g. nvim) don't get
|
||||
@@ -1100,6 +1111,7 @@ impl Dispatcher {
|
||||
match unsafe { fork()? } {
|
||||
ForkResult::Child => {
|
||||
let _ = setpgid(Pid::from_raw(0), existing_pgid.unwrap_or(Pid::from_raw(0)));
|
||||
self.interactive = false;
|
||||
f(self);
|
||||
exit(state::get_status())
|
||||
}
|
||||
@@ -1398,4 +1410,15 @@ mod tests {
|
||||
assert_eq!(state::get_status(), 0);
|
||||
assert_eq!(g.read_output(), "yes\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_var_in_test() {
|
||||
let _g = TestGuard::new();
|
||||
// POSIX specifies that a quoted unset variable expands to an empty string, so the shell actually sees `[ -n "" ]`, which returns false
|
||||
test_input("[ -n \"$EMPTYVAR_PROBABLY_NOT_SET_TO_ANYTHING\" ]").unwrap();
|
||||
assert_eq!(state::get_status(), 1);
|
||||
// Without quotes, word splitting causes an empty var to be removed entirely, so the shell actually sees `[ -n ]`, testing the value of ']', which returns true
|
||||
test_input("[ -n $EMPTYVAR_PROBABLY_NOT_SET_TO_ANYTHING ]").unwrap();
|
||||
assert_eq!(state::get_status(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3489,6 +3489,9 @@ impl LineBuf {
|
||||
impl Display for LineBuf {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut full_buf = self.buffer.clone();
|
||||
if full_buf.is_empty() {
|
||||
return write!(f, "{}", full_buf);
|
||||
}
|
||||
if let Some((start, end)) = self.select_range() {
|
||||
let mode = self.select_mode.unwrap();
|
||||
let start_byte = self.read_idx_byte_pos(start);
|
||||
@@ -3497,7 +3500,7 @@ impl Display for LineBuf {
|
||||
match mode.anchor() {
|
||||
SelectAnchor::Start => {
|
||||
let mut inclusive = start_byte..=end_byte;
|
||||
if *inclusive.end() == full_buf.len() {
|
||||
if *inclusive.end() >= full_buf.len() {
|
||||
inclusive = start_byte..=end_byte.saturating_sub(1);
|
||||
}
|
||||
let selected = format!(
|
||||
|
||||
@@ -225,5 +225,6 @@ vi_test! {
|
||||
vi_dollar_single : "h" => "$" => "h", 0;
|
||||
vi_caret_no_ws : "hello" => "$^" => "hello", 0;
|
||||
vi_f_last_char : "hello" => "fo" => "hello", 4;
|
||||
vi_r_on_space : "hello world" => "5|r-" => "hell- world", 4
|
||||
vi_r_on_space : "hello world" => "5|r-" => "hell- world", 4;
|
||||
vi_vw_doesnt_crash : "" => "vw" => "", 0
|
||||
}
|
||||
|
||||
62
src/state.rs
62
src/state.rs
@@ -315,6 +315,34 @@ impl ScopeStack {
|
||||
};
|
||||
scope.set_var(var_name, val, flags)
|
||||
}
|
||||
pub fn get_magic_var(&self, var_name: &str) -> Option<String> {
|
||||
match var_name {
|
||||
"SECONDS" => {
|
||||
let shell_time = read_meta(|m| m.shell_time());
|
||||
let secs = Instant::now().duration_since(shell_time).as_secs();
|
||||
Some(secs.to_string())
|
||||
}
|
||||
"EPOCHREALTIME" => {
|
||||
let epoch = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or(Duration::from_secs(0))
|
||||
.as_secs_f64();
|
||||
Some(epoch.to_string())
|
||||
}
|
||||
"EPOCHSECONDS" => {
|
||||
let epoch = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or(Duration::from_secs(0))
|
||||
.as_secs();
|
||||
Some(epoch.to_string())
|
||||
}
|
||||
"RANDOM" => {
|
||||
let random = rand::random_range(0..32768);
|
||||
Some(random.to_string())
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
pub fn get_arr_elems(&self, var_name: &str) -> ShResult<Vec<String>> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
if scope.var_exists(var_name)
|
||||
@@ -440,7 +468,9 @@ impl ScopeStack {
|
||||
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
||||
// This version of get_var() is mainly used internally
|
||||
// so that we have access to Option methods
|
||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||
if let Some(magic) = self.get_magic_var(var_name) {
|
||||
return Some(magic);
|
||||
} else if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||
let val = self.get_param(param);
|
||||
if !val.is_empty() {
|
||||
return Some(val);
|
||||
@@ -463,6 +493,9 @@ impl ScopeStack {
|
||||
var
|
||||
}
|
||||
pub fn get_var(&self, var_name: &str) -> String {
|
||||
if let Some(magic) = self.get_magic_var(var_name) {
|
||||
return magic;
|
||||
}
|
||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||
return self.get_param(param);
|
||||
}
|
||||
@@ -1305,8 +1338,11 @@ impl VarTab {
|
||||
}
|
||||
|
||||
/// A table of metadata for the shell
|
||||
#[derive(Clone, Default, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MetaTab {
|
||||
// Time when the shell was started, used for calculating shell uptime
|
||||
shell_time: Instant,
|
||||
|
||||
// command running duration
|
||||
runtime_start: Option<Instant>,
|
||||
runtime_stop: Option<Instant>,
|
||||
@@ -1331,6 +1367,25 @@ pub struct MetaTab {
|
||||
pending_widget_keys: Vec<KeyEvent>,
|
||||
}
|
||||
|
||||
impl Default for MetaTab {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
shell_time: Instant::now(),
|
||||
runtime_start: None,
|
||||
runtime_stop: None,
|
||||
system_msg: vec![],
|
||||
dir_stack: VecDeque::new(),
|
||||
getopts_offset: 0,
|
||||
old_path: None,
|
||||
old_pwd: None,
|
||||
path_cache: HashSet::new(),
|
||||
cwd_cache: HashSet::new(),
|
||||
comp_specs: HashMap::new(),
|
||||
pending_widget_keys: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaTab {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -1338,6 +1393,9 @@ impl MetaTab {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn shell_time(&self) -> Instant {
|
||||
self.shell_time
|
||||
}
|
||||
pub fn set_pending_widget_keys(&mut self, keys: &str) {
|
||||
let exp = expand_keymap(keys);
|
||||
self.pending_widget_keys = exp;
|
||||
|
||||
Reference in New Issue
Block a user