Compare commits
3 Commits
07d7015dd4
...
85e5fc2875
| Author | SHA1 | Date | |
|---|---|---|---|
| 85e5fc2875 | |||
| ac429cbdf4 | |||
| a464540fbe |
@@ -250,7 +250,7 @@ mod tests {
|
|||||||
"pre-mode-change", "post-mode-change",
|
"pre-mode-change", "post-mode-change",
|
||||||
"on-history-open", "on-history-close", "on-history-select",
|
"on-history-open", "on-history-close", "on-history-select",
|
||||||
"on-completion-start", "on-completion-cancel", "on-completion-select",
|
"on-completion-start", "on-completion-cancel", "on-completion-select",
|
||||||
"on-exit",
|
"on-exit"
|
||||||
];
|
];
|
||||||
for kind in kinds {
|
for kind in kinds {
|
||||||
test_input(format!("autocmd {kind} 'true'")).unwrap();
|
test_input(format!("autocmd {kind} 'true'")).unwrap();
|
||||||
|
|||||||
@@ -130,6 +130,15 @@ fn main() -> ExitCode {
|
|||||||
return ExitCode::SUCCESS;
|
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 {
|
if let Err(e) = if let Some(path) = args.script {
|
||||||
run_script(path, args.script_args)
|
run_script(path, args.script_args)
|
||||||
} else if let Some(cmd) = args.command {
|
} else if let Some(cmd) = args.command {
|
||||||
|
|||||||
@@ -258,6 +258,9 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn dispatch_cmd(&mut self, node: Node) -> ShResult<()> {
|
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 {
|
let Some(cmd) = node.get_command() else {
|
||||||
return self.exec_cmd(node); // Argv is empty, probably an assignment
|
return self.exec_cmd(node); // Argv is empty, probably an assignment
|
||||||
};
|
};
|
||||||
@@ -764,8 +767,16 @@ impl Dispatcher {
|
|||||||
self.job_stack.new_job();
|
self.job_stack.new_job();
|
||||||
if cmds.len() == 1 {
|
if cmds.len() == 1 {
|
||||||
self.fg_job = !is_bg && self.interactive;
|
self.fg_job = !is_bg && self.interactive;
|
||||||
let cmd = cmds.into_iter().next().unwrap();
|
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)?;
|
self.dispatch_node(cmd)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Give the pipeline terminal control as soon as the first child
|
// Give the pipeline terminal control as soon as the first child
|
||||||
// establishes the PGID, so later children (e.g. nvim) don't get
|
// establishes the PGID, so later children (e.g. nvim) don't get
|
||||||
@@ -1100,6 +1111,7 @@ impl Dispatcher {
|
|||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
let _ = setpgid(Pid::from_raw(0), existing_pgid.unwrap_or(Pid::from_raw(0)));
|
let _ = setpgid(Pid::from_raw(0), existing_pgid.unwrap_or(Pid::from_raw(0)));
|
||||||
|
self.interactive = false;
|
||||||
f(self);
|
f(self);
|
||||||
exit(state::get_status())
|
exit(state::get_status())
|
||||||
}
|
}
|
||||||
@@ -1398,4 +1410,15 @@ mod tests {
|
|||||||
assert_eq!(state::get_status(), 0);
|
assert_eq!(state::get_status(), 0);
|
||||||
assert_eq!(g.read_output(), "yes\n");
|
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 {
|
impl Display for LineBuf {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut full_buf = self.buffer.clone();
|
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() {
|
if let Some((start, end)) = self.select_range() {
|
||||||
let mode = self.select_mode.unwrap();
|
let mode = self.select_mode.unwrap();
|
||||||
let start_byte = self.read_idx_byte_pos(start);
|
let start_byte = self.read_idx_byte_pos(start);
|
||||||
@@ -3497,7 +3500,7 @@ impl Display for LineBuf {
|
|||||||
match mode.anchor() {
|
match mode.anchor() {
|
||||||
SelectAnchor::Start => {
|
SelectAnchor::Start => {
|
||||||
let mut inclusive = start_byte..=end_byte;
|
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);
|
inclusive = start_byte..=end_byte.saturating_sub(1);
|
||||||
}
|
}
|
||||||
let selected = format!(
|
let selected = format!(
|
||||||
|
|||||||
@@ -225,5 +225,6 @@ vi_test! {
|
|||||||
vi_dollar_single : "h" => "$" => "h", 0;
|
vi_dollar_single : "h" => "$" => "h", 0;
|
||||||
vi_caret_no_ws : "hello" => "$^" => "hello", 0;
|
vi_caret_no_ws : "hello" => "$^" => "hello", 0;
|
||||||
vi_f_last_char : "hello" => "fo" => "hello", 4;
|
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)
|
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>> {
|
pub fn get_arr_elems(&self, var_name: &str) -> ShResult<Vec<String>> {
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
if scope.var_exists(var_name)
|
if scope.var_exists(var_name)
|
||||||
@@ -440,7 +468,9 @@ impl ScopeStack {
|
|||||||
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
pub fn try_get_var(&self, var_name: &str) -> Option<String> {
|
||||||
// This version of get_var() is mainly used internally
|
// This version of get_var() is mainly used internally
|
||||||
// so that we have access to Option methods
|
// 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);
|
let val = self.get_param(param);
|
||||||
if !val.is_empty() {
|
if !val.is_empty() {
|
||||||
return Some(val);
|
return Some(val);
|
||||||
@@ -463,6 +493,9 @@ impl ScopeStack {
|
|||||||
var
|
var
|
||||||
}
|
}
|
||||||
pub fn get_var(&self, var_name: &str) -> String {
|
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>() {
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
return self.get_param(param);
|
return self.get_param(param);
|
||||||
}
|
}
|
||||||
@@ -1305,8 +1338,11 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A table of metadata for the shell
|
/// A table of metadata for the shell
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MetaTab {
|
pub struct MetaTab {
|
||||||
|
// Time when the shell was started, used for calculating shell uptime
|
||||||
|
shell_time: Instant,
|
||||||
|
|
||||||
// command running duration
|
// command running duration
|
||||||
runtime_start: Option<Instant>,
|
runtime_start: Option<Instant>,
|
||||||
runtime_stop: Option<Instant>,
|
runtime_stop: Option<Instant>,
|
||||||
@@ -1331,6 +1367,25 @@ pub struct MetaTab {
|
|||||||
pending_widget_keys: Vec<KeyEvent>,
|
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 {
|
impl MetaTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -1338,6 +1393,9 @@ impl MetaTab {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn shell_time(&self) -> Instant {
|
||||||
|
self.shell_time
|
||||||
|
}
|
||||||
pub fn set_pending_widget_keys(&mut self, keys: &str) {
|
pub fn set_pending_widget_keys(&mut self, keys: &str) {
|
||||||
let exp = expand_keymap(keys);
|
let exp = expand_keymap(keys);
|
||||||
self.pending_widget_keys = exp;
|
self.pending_widget_keys = exp;
|
||||||
|
|||||||
Reference in New Issue
Block a user