From e07a85307488e4ba2ca33a7220f7114e91df339c Mon Sep 17 00:00:00 2001 From: pagedmov Date: Thu, 5 Mar 2026 10:29:54 -0500 Subject: [PATCH] the second field of history entries in the hist file now contain command runtime in seconds, instead of an id updated rustfmt.toml and formatted codebase --- rustfmt.toml | 4 +- src/expand.rs | 3 +- src/main.rs | 186 ++++++++++-------------- src/parse/lex.rs | 2 +- src/parse/mod.rs | 2 +- src/procio.rs | 8 +- src/readline/complete.rs | 136 ++++++++++-------- src/readline/highlight.rs | 6 +- src/readline/history.rs | 135 ++++++++++-------- src/readline/keys.rs | 12 +- src/readline/linebuf.rs | 148 ++++++++++--------- src/readline/mod.rs | 253 +++++++++++++++++++-------------- src/readline/vimode/insert.rs | 16 +-- src/readline/vimode/normal.rs | 18 +-- src/readline/vimode/replace.rs | 16 +-- src/readline/vimode/visual.rs | 18 +-- src/state.rs | 36 ++--- 17 files changed, 522 insertions(+), 477 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index d243aa0..6ddc6e3 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,7 +1,5 @@ max_width = 100 tab_spaces = 2 -edition = "2021" +edition = "2024" newline_style = "Unix" - -wrap_comments = true diff --git a/src/expand.rs b/src/expand.rs index 7e76a29..b2a793a 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1404,7 +1404,6 @@ impl FromStr for ParamExp { )) }; - // Handle indirect var expansion: ${!var} if let Some(var) = s.strip_prefix('!') { if var.ends_with('*') || var.ends_with('@') { @@ -2374,7 +2373,7 @@ pub fn parse_key_alias(alias: &str) -> Option { "RIGHT" => KeyCode::Right, "HOME" => KeyCode::Home, "END" => KeyCode::End, - "CMD" => KeyCode::ExMode, + "CMD" => KeyCode::ExMode, "PGUP" | "PAGEUP" => KeyCode::PageUp, "PGDN" | "PAGEDOWN" => KeyCode::PageDown, k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()), diff --git a/src/main.rs b/src/main.rs index 0b8b206..22dfd4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ use crate::prelude::*; use crate::readline::term::{LineWriter, RawModeGuard, raw_mode}; use crate::readline::{Prompt, ReadlineEvent, ShedVi}; use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending}; -use crate::state::{AutoCmdKind, read_logic, source_rc, write_jobs, write_meta}; +use crate::state::{AutoCmdKind, read_logic, read_shopts, source_rc, write_jobs, write_meta}; use clap::Parser; use state::{read_vars, write_vars}; @@ -292,36 +292,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> { let action = km.action_expanded(); readline.pending_keymap.clear(); for key in action { - if let Some(event) = readline.handle_key(key)? { - match event { - ReadlineEvent::Line(input) => { - let start = Instant::now(); - write_meta(|m| m.start_timer()); - if let Err(e) = RawModeGuard::with_cooked_mode(|| { - exec_input(input, None, true, Some("".into())) - }) { - match e.kind() { - ShErrKind::CleanExit(code) => { - QUIT_CODE.store(*code, Ordering::SeqCst); - return Ok(()); - } - _ => e.print_error(), - } - } - let command_run_time = start.elapsed(); - log::info!("Command executed in {:.2?}", command_run_time); - write_meta(|m| m.stop_timer()); - readline.fix_column()?; - readline.writer.flush_write("\n\r")?; - readline.reset(true)?; - break; - } - ReadlineEvent::Eof => { - QUIT_CODE.store(0, Ordering::SeqCst); - return Ok(()); - } - ReadlineEvent::Pending => {} - } + let event = readline.handle_key(key).transpose(); + if let Some(event) = event { + handle_readline_event(&mut readline, event)?; } } } else { @@ -331,36 +304,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> { ); let buffered = std::mem::take(&mut readline.pending_keymap); for key in buffered { - if let Some(event) = readline.handle_key(key)? { - match event { - ReadlineEvent::Line(input) => { - let start = Instant::now(); - write_meta(|m| m.start_timer()); - if let Err(e) = RawModeGuard::with_cooked_mode(|| { - exec_input(input, None, true, Some("".into())) - }) { - match e.kind() { - ShErrKind::CleanExit(code) => { - QUIT_CODE.store(*code, Ordering::SeqCst); - return Ok(()); - } - _ => e.print_error(), - } - } - let command_run_time = start.elapsed(); - log::info!("Command executed in {:.2?}", command_run_time); - write_meta(|m| m.stop_timer()); - readline.fix_column()?; - readline.writer.flush_write("\n\r")?; - readline.reset(true)?; - break; - } - ReadlineEvent::Eof => { - QUIT_CODE.store(0, Ordering::SeqCst); - return Ok(()); - } - ReadlineEvent::Pending => {} - } + let event = readline.handle_key(key).transpose(); + if let Some(event) = event { + handle_readline_event(&mut readline, event)?; } } } @@ -394,58 +340,76 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> { } // Process any available input - match readline.process_input() { - Ok(ReadlineEvent::Line(input)) => { - let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd)); - let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd)); - - pre_exec.exec_with(&input); - - let start = Instant::now(); - write_meta(|m| m.start_timer()); - if let Err(e) = RawModeGuard::with_cooked_mode(|| { - exec_input(input.clone(), None, true, Some("".into())) - }) { - match e.kind() { - ShErrKind::CleanExit(code) => { - QUIT_CODE.store(*code, Ordering::SeqCst); - return Ok(()); - } - _ => e.print_error(), - } - } - let command_run_time = start.elapsed(); - log::info!("Command executed in {:.2?}", command_run_time); - write_meta(|m| m.stop_timer()); - - post_exec.exec_with(&input); - - readline.fix_column()?; - readline.writer.flush_write("\n\r")?; - - // Reset for next command with fresh prompt - readline.reset(true)?; - - let real_end = start.elapsed(); - log::info!("Total round trip time: {:.2?}", real_end); - } - Ok(ReadlineEvent::Eof) => { - // Ctrl+D on empty line - QUIT_CODE.store(0, Ordering::SeqCst); - return Ok(()); - } - Ok(ReadlineEvent::Pending) => { - // No complete input yet, keep polling - } - Err(e) => match e.kind() { - ShErrKind::CleanExit(code) => { - QUIT_CODE.store(*code, Ordering::SeqCst); - return Ok(()); - } - _ => e.print_error(), - }, + let event = readline.process_input(); + match handle_readline_event(&mut readline, event)? { + true => return Ok(()), + false => { /* continue looping */ } } } Ok(()) } + +fn handle_readline_event(readline: &mut ShedVi, event: ShResult) -> ShResult { + match event { + Ok(ReadlineEvent::Line(input)) => { + let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd)); + let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd)); + + pre_exec.exec_with(&input); + + let start = Instant::now(); + write_meta(|m| m.start_timer()); + if let Err(e) = RawModeGuard::with_cooked_mode(|| { + exec_input(input.clone(), None, true, Some("".into())) + }) { + match e.kind() { + ShErrKind::CleanExit(code) => { + QUIT_CODE.store(*code, Ordering::SeqCst); + return Ok(true); + } + _ => e.print_error(), + } + } + let command_run_time = start.elapsed(); + log::info!("Command executed in {:.2?}", command_run_time); + write_meta(|m| m.stop_timer()); + + post_exec.exec_with(&input); + + if read_shopts(|s| s.core.auto_hist) && !input.is_empty() { + readline.history.push(input.clone()); + readline.history.save()?; + } + + readline.fix_column()?; + readline.writer.flush_write("\n\r")?; + + // Reset for next command with fresh prompt + readline.reset(true)?; + + let real_end = start.elapsed(); + log::info!("Total round trip time: {:.2?}", real_end); + Ok(false) + } + Ok(ReadlineEvent::Eof) => { + // Ctrl+D on empty line + QUIT_CODE.store(0, Ordering::SeqCst); + Ok(true) + } + Ok(ReadlineEvent::Pending) => { + // No complete input yet, keep polling + Ok(false) + } + Err(e) => match e.kind() { + ShErrKind::CleanExit(code) => { + QUIT_CODE.store(*code, Ordering::SeqCst); + Ok(true) + } + _ => { + e.print_error(); + Ok(false) + } + }, + } +} diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 262b938..3fbb0cd 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -877,7 +877,7 @@ impl Iterator for LexStream { if self.flags.contains(LexFlags::LEX_UNFINISHED) { self.get_token(ch_idx..self.cursor, TkRule::Comment) } else { - // After consuming the comment, we call next() recursively. This effectively filters out comment tokens. + // After consuming the comment, we call next() recursively. This effectively filters out comment tokens. return self.next(); } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 797181e..cb10327 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -87,7 +87,7 @@ impl ParsedSrc { Err(error) => return Err(vec![error]), } } - log::debug!("Tokens: {:#?}", tokens); + log::debug!("Tokens: {:#?}", tokens); let mut errors = vec![]; let mut nodes = vec![]; diff --git a/src/procio.rs b/src/procio.rs index b526e44..6915bf4 100644 --- a/src/procio.rs +++ b/src/procio.rs @@ -92,7 +92,7 @@ impl IoMode { Ok(self) } pub fn get_pipes() -> (Self, Self) { - let (rpipe, wpipe) = pipe().unwrap(); + let (rpipe, wpipe) = nix::unistd::pipe2(OFlag::O_CLOEXEC).unwrap(); ( Self::Pipe { tgt_fd: STDIN_FILENO, @@ -220,6 +220,12 @@ impl<'e> IoFrame { let tgt_fd = io_mode.tgt_fd(); let src_fd = io_mode.src_fd(); dup2(src_fd, tgt_fd)?; + // Close the original pipe fd after dup2 — it's been duplicated to + // tgt_fd and keeping it open prevents SIGPIPE delivery in pipelines. + // We replace the IoMode to drop the Arc, which closes the fd. + if matches!(io_mode, IoMode::Pipe { .. }) { + *io_mode = IoMode::Close { tgt_fd }; + } } Ok(RedirGuard::new(self)) } diff --git a/src/readline/complete.rs b/src/readline/complete.rs index 780f2e0..42d2362 100644 --- a/src/readline/complete.rs +++ b/src/readline/complete.rs @@ -713,7 +713,7 @@ pub struct FuzzySelector { filtered: Vec, candidates: Vec, cursor: ClampedUsize, - number_candidates: bool, + number_candidates: bool, old_layout: Option, max_height: usize, scroll_offset: usize, @@ -749,7 +749,7 @@ impl FuzzySelector { filtered: vec![], candidates: vec![], cursor: ClampedUsize::new(0, 0, true), - number_candidates: false, + number_candidates: false, old_layout: None, scroll_offset: 0, active: false, @@ -759,12 +759,12 @@ impl FuzzySelector { } } - pub fn number_candidates(self, enable: bool) -> Self { - Self { - number_candidates: enable, - ..self - } - } + pub fn number_candidates(self, enable: bool) -> Self { + Self { + number_candidates: enable, + ..self + } + } pub fn activate(&mut self, candidates: Vec) { self.active = true; @@ -772,11 +772,11 @@ impl FuzzySelector { self.score_candidates(); } - pub fn set_query(&mut self, query: String) { - self.query.linebuf = LineBuf::new().with_initial(&query, query.len()); - self.query.update_scroll_offset(); - self.score_candidates(); - } + pub fn set_query(&mut self, query: String) { + self.query.linebuf = LineBuf::new().with_initial(&query, query.len()); + self.query.update_scroll_offset(); + self.score_candidates(); + } pub fn reset(&mut self) { self.query.clear(); @@ -812,7 +812,9 @@ impl FuzzySelector { } fn candidate_height(&self, idx: usize) -> usize { - self.filtered.get(idx) + self + .filtered + .get(idx) .map(|c| c.content.trim_end().lines().count().max(1)) .unwrap_or(1) } @@ -929,9 +931,15 @@ impl FuzzySelector { let num_candidates = format!("\x1b[33m{}\x1b[0m", self.candidates.len()); let title = self.title.clone(); let title_width = title.len() as u16; - let number_candidates = self.number_candidates; - let min_pad = self.candidates.len().to_string().len().saturating_add(1).max(6); - let max_height = self.max_height; + let number_candidates = self.number_candidates; + let min_pad = self + .candidates + .len() + .to_string() + .len() + .saturating_add(1) + .max(6); + let max_height = self.max_height; let visible = self.get_window(); let mut rows: u16 = 0; let top_bar = format!( @@ -966,51 +974,63 @@ impl FuzzySelector { buf.push_str(&sep_line_final); rows += 1; - let mut lines_drawn = 0; + let mut lines_drawn = 0; for (i, candidate) in visible.iter().enumerate() { - if lines_drawn >= max_height { - break; - } + if lines_drawn >= max_height { + break; + } let selector = if i + offset == cursor_pos { Self::SELECTOR_HL } else { Self::SELECTOR_GRAY }; - let mut drew_number = false; - for line in candidate.content.trim_end().lines() { - if lines_drawn >= max_height { - break; - } - let mut line = line.trim_end().replace('\t', " "); - let col_lim = if number_candidates{ - cols.saturating_sub(3 + min_pad as u16) - } else { - cols.saturating_sub(3) - }; - if calc_str_width(&line) > col_lim { - line.truncate(col_lim.saturating_sub(6) as usize); - line.push_str("..."); - } - let left = if number_candidates { - if !drew_number { - let this_num = i + offset + 1; - let right_pad = " ".repeat(min_pad.saturating_sub(this_num.to_string().len())); - format!("{} {}\x1b[33m{}\x1b[39m{right_pad}{}\x1b[0m", Self::VERT_LINE, &selector,i + offset + 1, &line) - } else { - let right_pad = " ".repeat(min_pad); - format!("{} {}{}{}\x1b[0m", Self::VERT_LINE, &selector,right_pad, &line) - } - } else { - format!("{} {}{}\x1b[0m", Self::VERT_LINE, &selector, &line) - }; - let cols_used = calc_str_width(&left); - let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize); - let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE); - buf.push_str(&hl_cand_line); - rows += 1; - drew_number = true; - lines_drawn += 1; - } + let mut drew_number = false; + for line in candidate.content.trim_end().lines() { + if lines_drawn >= max_height { + break; + } + let mut line = line.trim_end().replace('\t', " "); + let col_lim = if number_candidates { + cols.saturating_sub(3 + min_pad as u16) + } else { + cols.saturating_sub(3) + }; + if calc_str_width(&line) > col_lim { + line.truncate(col_lim.saturating_sub(6) as usize); + line.push_str("..."); + } + let left = if number_candidates { + if !drew_number { + let this_num = i + offset + 1; + let right_pad = " ".repeat(min_pad.saturating_sub(this_num.to_string().len())); + format!( + "{} {}\x1b[33m{}\x1b[39m{right_pad}{}\x1b[0m", + Self::VERT_LINE, + &selector, + i + offset + 1, + &line + ) + } else { + let right_pad = " ".repeat(min_pad); + format!( + "{} {}{}{}\x1b[0m", + Self::VERT_LINE, + &selector, + right_pad, + &line + ) + } + } else { + format!("{} {}{}\x1b[0m", Self::VERT_LINE, &selector, &line) + }; + let cols_used = calc_str_width(&left); + let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize); + let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE); + buf.push_str(&hl_cand_line); + rows += 1; + drew_number = true; + lines_drawn += 1; + } } let bot_bar = format!( @@ -1100,7 +1120,9 @@ impl Default for FuzzyCompleter { impl Completer for FuzzyCompleter { fn set_prompt_line_context(&mut self, line_width: u16, cursor_col: u16) { - self.selector.set_prompt_line_context(line_width, cursor_col); + self + .selector + .set_prompt_line_context(line_width, cursor_col); } fn reset_stay_active(&mut self) { self.selector.reset_stay_active(); diff --git a/src/readline/highlight.rs b/src/readline/highlight.rs index 7801213..a720e54 100644 --- a/src/readline/highlight.rs +++ b/src/readline/highlight.rs @@ -105,9 +105,9 @@ impl Highlighter { self.in_selection = false; } _ if self.only_hl_visual => { - if !is_marker(ch) { - self.output.push(ch); - } + if !is_marker(ch) { + self.output.push(ch); + } } markers::STRING_DQ_END | markers::STRING_SQ_END diff --git a/src/readline/history.rs b/src/readline/history.rs index 75d6369..034d9ae 100644 --- a/src/readline/history.rs +++ b/src/readline/history.rs @@ -1,10 +1,19 @@ use std::{ - cmp::Ordering, collections::HashSet, env, fmt::{Display, Write}, fs::{self, OpenOptions}, io::Write as IoWrite, path::{Path, PathBuf}, str::FromStr, time::{Duration, SystemTime, UNIX_EPOCH} + cmp::Ordering, + collections::HashSet, + env, + fmt::{Display, Write}, + fs::{self, OpenOptions}, + io::Write as IoWrite, + path::{Path, PathBuf}, + str::FromStr, + time::{Duration, SystemTime, UNIX_EPOCH}, }; use crate::{ libsh::error::{ShErr, ShErrKind, ShResult}, readline::{complete::FuzzySelector, linebuf::LineBuf}, + state::read_meta, }; #[derive(Default, Clone, Copy, Debug)] @@ -28,16 +37,13 @@ impl SearchConstraint { #[derive(Debug, Clone)] pub struct HistEntry { - id: u32, + runtime: Duration, timestamp: SystemTime, command: String, new: bool, } impl HistEntry { - pub fn id(&self) -> u32 { - self.id - } pub fn timestamp(&self) -> &SystemTime { &self.timestamp } @@ -73,24 +79,25 @@ impl FromStr for HistEntry { return err; }; //248972349;148;echo foo; echo bar - let Some((timestamp, id_and_command)) = cleaned.split_once(';') else { + let Some((timestamp, runtime_and_cmd)) = cleaned.split_once(';') else { return err; }; //("248972349","148;echo foo; echo bar") - let Some((id, command)) = id_and_command.split_once(';') else { + let Some((runtime, command)) = runtime_and_cmd.split_once(';') else { return err; }; //("148","echo foo; echo bar") let Ok(ts_seconds) = timestamp.parse::() else { return err; }; - let Ok(id) = id.parse::() else { + let Ok(runtime) = runtime.parse::() else { return err; }; + let runtime = Duration::from_secs(runtime); let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds); let command = command.to_string(); Ok(Self { - id, + runtime, timestamp, command, new: false, @@ -103,13 +110,14 @@ impl Display for HistEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let command = self.with_escaped_newlines(); let HistEntry { - id, + runtime, timestamp, command: _, new: _, } = self; let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs(); - writeln!(f, ": {timestamp};{id};{command}") + let runtime = runtime.as_secs(); + writeln!(f, ": {timestamp};{runtime};{command}") } } @@ -200,7 +208,7 @@ pub struct History { pub pending: Option, // command, cursor_pos entries: Vec, search_mask: Vec, - pub fuzzy_finder: FuzzySelector, + pub fuzzy_finder: FuzzySelector, no_matches: bool, pub cursor: usize, //search_direction: Direction, @@ -223,11 +231,15 @@ impl History { } let search_mask = dedupe_entries(&entries); let cursor = search_mask.len(); - let max_size = if max_hist < 0 { None } else { Some(max_hist as u32) }; + let max_size = if max_hist < 0 { + None + } else { + Some(max_hist as u32) + }; Ok(Self { path, entries, - fuzzy_finder: FuzzySelector::new("History").number_candidates(true), + fuzzy_finder: FuzzySelector::new("History").number_candidates(true), pending: None, search_mask, no_matches: false, @@ -238,19 +250,22 @@ impl History { }) } - pub fn start_search(&mut self, initial: &str) -> Option { - if self.search_mask.is_empty() { - None - } else if self.search_mask.len() == 1 { - Some(self.search_mask[0].command().to_string()) - } else { - self.fuzzy_finder.set_query(initial.to_string()); - let raw_entries = self.search_mask.clone().into_iter() - .map(|ent| ent.command().to_string()); - self.fuzzy_finder.activate(raw_entries.collect()); - None - } - } + pub fn start_search(&mut self, initial: &str) -> Option { + if self.search_mask.is_empty() { + None + } else if self.search_mask.len() == 1 { + Some(self.search_mask[0].command().to_string()) + } else { + self.fuzzy_finder.set_query(initial.to_string()); + let raw_entries = self + .search_mask + .clone() + .into_iter() + .map(|ent| ent.command().to_string()); + self.fuzzy_finder.activate(raw_entries.collect()); + None + } + } pub fn reset(&mut self) { self.search_mask = dedupe_entries(&self.entries); @@ -301,42 +316,36 @@ impl History { pub fn last_mut(&mut self) -> Option<&mut HistEntry> { self.entries.last_mut() } - pub fn last(&self) -> Option<&HistEntry> { - self.entries.last() - } + pub fn last(&self) -> Option<&HistEntry> { + self.entries.last() + } - pub fn resolve_hist_token(&self, token: &str) -> Option { - let token = token.strip_prefix('!').unwrap_or(token).to_string(); - if let Ok(num) = token.parse::() && num != 0 { - match num.cmp(&0) { - Ordering::Less => { - if num.unsigned_abs() > self.entries.len() as u32 { - return None; - } + pub fn resolve_hist_token(&self, token: &str) -> Option { + let token = token.strip_prefix('!').unwrap_or(token).to_string(); + if let Ok(num) = token.parse::() + && num != 0 + { + match num.cmp(&0) { + Ordering::Less => { + if num.unsigned_abs() > self.entries.len() as u32 { + return None; + } - let rev_idx = self.entries.len() - num.unsigned_abs() as usize; - self.entries.get(rev_idx) - .map(|e| e.command().to_string()) - } - Ordering::Greater => { - self.entries.get(num as usize) - .map(|e| e.command().to_string()) - } - _ => unreachable!() - } - } else { - let mut rev_search = self.entries.iter(); - rev_search - .rfind(|e| e.command().starts_with(&token)) - .map(|e| e.command().to_string()) - } - } - - pub fn get_new_id(&self) -> u32 { - let Some(ent) = self.entries.last() else { - return 0; - }; - ent.id + 1 + let rev_idx = self.entries.len() - num.unsigned_abs() as usize; + self.entries.get(rev_idx).map(|e| e.command().to_string()) + } + Ordering::Greater => self + .entries + .get(num as usize) + .map(|e| e.command().to_string()), + _ => unreachable!(), + } + } else { + let mut rev_search = self.entries.iter(); + rev_search + .rfind(|e| e.command().starts_with(&token)) + .map(|e| e.command().to_string()) + } } pub fn ignore_dups(&mut self, yn: bool) { @@ -401,12 +410,12 @@ impl History { pub fn push(&mut self, command: String) { let timestamp = SystemTime::now(); - let id = self.get_new_id(); + let runtime = read_meta(|m| m.get_time()).unwrap_or_default(); if self.ignore_dups && self.is_dup(&command) { return; } self.entries.push(HistEntry { - id, + runtime, timestamp, command, new: true, diff --git a/src/readline/keys.rs b/src/readline/keys.rs index 6b88a9d..d9fc7f2 100644 --- a/src/readline/keys.rs +++ b/src/readline/keys.rs @@ -114,10 +114,10 @@ impl KeyEvent { "Cannot convert unknown escape sequence to Vim key sequence".to_string(), )); } - KeyCode::ExMode => { - seq.push_str("CMD"); - needs_angle_bracket = true; - } + KeyCode::ExMode => { + seq.push_str("CMD"); + needs_angle_bracket = true; + } KeyCode::Backspace => { seq.push_str("BS"); needs_angle_bracket = true; @@ -227,8 +227,8 @@ pub enum KeyCode { Tab, Up, - // weird stuff - ExMode, // keycode emitted by the byte alias in vim keymaps + // weird stuff + ExMode, // keycode emitted by the byte alias in vim keymaps } bitflags::bitflags! { diff --git a/src/readline/linebuf.rs b/src/readline/linebuf.rs index a2b88e3..bc2ba90 100644 --- a/src/readline/linebuf.rs +++ b/src/readline/linebuf.rs @@ -19,7 +19,10 @@ use crate::{ }, prelude::*, readline::{ - history::History, markers, register::{RegisterContent, write_register}, term::RawModeGuard + history::History, + markers, + register::{RegisterContent, write_register}, + term::RawModeGuard, }, state::{VarFlags, VarKind, read_shopts, write_meta, write_vars}, }; @@ -297,13 +300,13 @@ impl ClampedUsize { let max = self.upper_bound(); self.value = (self.value + value).clamp(0, max) } - pub fn add_signed(&mut self, value: isize) { - if value.is_negative() { - self.sub(value.wrapping_abs() as usize); - } else { - self.add(value as usize); - } - } + pub fn add_signed(&mut self, value: isize) { + if value.is_negative() { + self.sub(value.wrapping_abs() as usize); + } else { + self.add(value as usize); + } + } pub fn sub(&mut self, value: usize) { self.value = self.value.saturating_sub(value) } @@ -650,10 +653,10 @@ impl LineBuf { self.buffer.push_str(slice); self.update_graphemes(); } - pub fn insert_str_at_cursor(&mut self, slice: &str) { - let pos = self.index_byte_pos(self.cursor.get()); - self.insert_str_at(pos, slice); - } + pub fn insert_str_at_cursor(&mut self, slice: &str) { + let pos = self.index_byte_pos(self.cursor.get()); + self.insert_str_at(pos, slice); + } pub fn insert_at_cursor(&mut self, ch: char) { self.insert_at(self.cursor.get(), ch); } @@ -3327,71 +3330,74 @@ impl LineBuf { Ok(()) } - pub fn attempt_history_expansion(&mut self, hist: &History) -> bool { - self.update_graphemes(); - let mut changes: Vec<(Range,String)> = vec![]; - let mut graphemes = self.buffer.grapheme_indices(true); - let mut qt_state = QuoteState::default(); + pub fn attempt_history_expansion(&mut self, hist: &History) -> bool { + self.update_graphemes(); + let mut changes: Vec<(Range, String)> = vec![]; + let mut graphemes = self.buffer.grapheme_indices(true); + let mut qt_state = QuoteState::default(); - while let Some((i,gr)) = graphemes.next() { - match gr { - "\\" => { - graphemes.next(); - } - "'" => qt_state.toggle_single(), - "\"" => qt_state.toggle_double(), - "!" if !qt_state.in_single() => { - let start = i; - match graphemes.next() { - Some((_,"!")) => { - // we have "!!", which expands to the previous command - if let Some(prev) = hist.last() { - let raw = prev.command(); - changes.push((start..start+2, raw.to_string())); - } - } - Some((_,"$")) => { - // we have "!$", which expands to the last word of the previous command - if let Some(prev) = hist.last() { - let raw = prev.command(); - if let Some(last_word) = raw.split_whitespace().last() { - changes.push((start..start+2, last_word.to_string())); - } - } - } - Some((j,gr)) if !is_whitespace(gr) => { - let mut end = j + gr.len(); - while let Some((k, gr2)) = graphemes.next() { - if is_whitespace(gr2) { break; } - end = k + gr2.len(); - } - let token = &self.buffer[j..end]; - let cmd = hist.resolve_hist_token(token).unwrap_or(token.into()); - changes.push((start..end, cmd)); - } - _ => { /* not a hist expansion */ } - } - } - _ => { /* carry on */ } - } - } + while let Some((i, gr)) = graphemes.next() { + match gr { + "\\" | "$" => { + // skip on dollars because '$!' is a shell parameter + graphemes.next(); + } + "'" => qt_state.toggle_single(), + "\"" => qt_state.toggle_double(), + "!" if !qt_state.in_single() => { + let start = i; + match graphemes.next() { + Some((_, "!")) => { + // we have "!!", which expands to the previous command + if let Some(prev) = hist.last() { + let raw = prev.command(); + changes.push((start..start + 2, raw.to_string())); + } + } + Some((_, "$")) => { + // we have "!$", which expands to the last word of the previous command + if let Some(prev) = hist.last() { + let raw = prev.command(); + if let Some(last_word) = raw.split_whitespace().last() { + changes.push((start..start + 2, last_word.to_string())); + } + } + } + Some((j, gr)) if !is_whitespace(gr) => { + let mut end = j + gr.len(); + while let Some((k, gr2)) = graphemes.next() { + if is_whitespace(gr2) { + break; + } + end = k + gr2.len(); + } + let token = &self.buffer[j..end]; + let cmd = hist.resolve_hist_token(token).unwrap_or(token.into()); + changes.push((start..end, cmd)); + } + _ => { /* not a hist expansion */ } + } + } + _ => { /* carry on */ } + } + } - let ret = !changes.is_empty(); + let ret = !changes.is_empty(); - let buf_len = self.grapheme_indices().len(); + let buf_len = self.grapheme_indices().len(); - for (range,change) in changes.into_iter().rev() { - self.buffer.replace_range(range, &change); - } - self.update_graphemes(); + for (range, change) in changes.into_iter().rev() { + self.buffer.replace_range(range, &change); + } + self.update_graphemes(); - let new_len = self.grapheme_indices().len(); - let delta = new_len as isize - buf_len as isize; + let new_len = self.grapheme_indices().len(); + let delta = new_len as isize - buf_len as isize; - self.cursor.set_max(new_len); - self.cursor.add_signed(delta); - ret - } + self.cursor.set_max(new_len); + self.cursor.add_signed(delta); + ret + } pub fn as_str(&self) -> &str { &self.buffer // FIXME: this will have to be fixed up later diff --git a/src/readline/mod.rs b/src/readline/mod.rs index f9b5b48..77c1768 100644 --- a/src/readline/mod.rs +++ b/src/readline/mod.rs @@ -16,7 +16,8 @@ use crate::readline::complete::{FuzzyCompleter, SelectorResponse}; use crate::readline::term::{Pos, TermReader, calc_str_width}; use crate::readline::vimode::{ViEx, ViVerbatim}; use crate::state::{ - AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars + AutoCmdKind, ShellParam, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, + write_vars, }; use crate::{ libsh::error::ShResult, @@ -212,7 +213,7 @@ impl Prompt { fn refresh_now(&mut self) { let saved_status = state::get_status(); - *self = Self::new(); + *self = Self::new(); state::set_status(saved_status); self.dirty = false; } @@ -405,49 +406,65 @@ impl ShedVi { // Process all available keys while let Some(key) = self.reader.read_key()? { // If completer or history search are active, delegate input to it - if self.history.fuzzy_finder.is_active() { - self.print_line(false)?; - match self.history.fuzzy_finder.handle_key(key)? { - SelectorResponse::Accept(cmd) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); + if self.history.fuzzy_finder.is_active() { + self.print_line(false)?; + match self.history.fuzzy_finder.handle_key(key)? { + SelectorResponse::Accept(cmd) => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); - self.editor.set_buffer(cmd.to_string()); - self.editor.move_cursor_to_end(); - self.history.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); - self.editor.set_hint(None); - self.history.fuzzy_finder.clear(&mut self.writer)?; - self.history.fuzzy_finder.reset(); + self.editor.set_buffer(cmd.to_string()); + self.editor.move_cursor_to_end(); + self + .history + .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); + self.editor.set_hint(None); + self.history.fuzzy_finder.clear(&mut self.writer)?; + self.history.fuzzy_finder.reset(); - with_vars([("_HIST_ENTRY".into(), cmd.clone())], || { - post_cmds.exec_with(&cmd); - }); + with_vars([("_HIST_ENTRY".into(), cmd.clone())], || { + post_cmds.exec_with(&cmd); + }); - write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok(); - self.prompt.refresh(); - self.needs_redraw = true; - continue; - } - SelectorResponse::Dismiss => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose)); - post_cmds.exec(); + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + self.needs_redraw = true; + continue; + } + SelectorResponse::Dismiss => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose)); + post_cmds.exec(); - self.editor.set_hint(None); - self.history.fuzzy_finder.clear(&mut self.writer)?; - write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok(); - self.prompt.refresh(); - self.needs_redraw = true; - continue; - } - SelectorResponse::Consumed => { - self.needs_redraw = true; - continue; - } - } - } else if self.completer.is_active() { + self.editor.set_hint(None); + self.history.fuzzy_finder.clear(&mut self.writer)?; + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + self.needs_redraw = true; + continue; + } + SelectorResponse::Consumed => { + self.needs_redraw = true; + continue; + } + } + } else if self.completer.is_active() { self.print_line(false)?; match self.completer.handle_key(key.clone())? { CompResponse::Accept(candidate) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); let span_start = self.completer.token_span().0; let new_cursor = span_start + candidate.len(); @@ -468,21 +485,28 @@ impl ShedVi { self.needs_redraw = true; self.completer.reset(); - with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || { - post_cmds.exec_with(&candidate); - }); + with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || { + post_cmds.exec_with(&candidate); + }); continue; } CompResponse::Dismiss => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel)); - post_cmds.exec(); + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel)); + post_cmds.exec(); let hint = self.history.get_hint(); self.editor.set_hint(hint); self.completer.clear(&mut self.writer)?; - write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok(); - self.prompt.refresh(); + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); self.completer.reset(); continue; } @@ -531,9 +555,16 @@ impl ShedVi { return Ok(event); } } - if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() { - write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str(self.mode.report_mode().to_string()), VarFlags::NONE)).ok(); - } + if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() { + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }) + .ok(); + } // Redraw if we processed any input if self.needs_redraw { @@ -546,7 +577,10 @@ impl ShedVi { pub fn handle_key(&mut self, key: KeyEvent) -> ShResult> { if self.should_accept_hint(&key) { - log::debug!("Accepting hint on key {key:?} in mode {:?}", self.mode.report_mode()); + log::debug!( + "Accepting hint on key {key:?} in mode {:?}", + self.mode.report_mode() + ); self.editor.accept_hint(); if !self.history.at_pending() { self.history.reset_to_pending(); @@ -559,11 +593,11 @@ impl ShedVi { } if let KeyEvent(KeyCode::Tab, mod_keys) = key { - if self.editor.attempt_history_expansion(&self.history) { - // If history expansion occurred, don't attempt completion yet - // allow the user to see the expanded command and accept or edit it before completing - return Ok(None); - } + if self.editor.attempt_history_expansion(&self.history) { + // If history expansion occurred, don't attempt completion yet + // allow the user to see the expanded command and accept or edit it before completing + return Ok(None); + } let direction = match mod_keys { ModKeys::SHIFT => -1, @@ -579,11 +613,11 @@ impl ShedVi { self.old_layout = None; } Ok(Some(line)) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); - let cand = self.completer.selected_candidate().unwrap_or_default(); - with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || { - post_cmds.exec_with(&cand); - }); + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); + let cand = self.completer.selected_candidate().unwrap_or_default(); + with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || { + post_cmds.exec_with(&cand); + }); let span_start = self.completer.token_span().0; @@ -605,19 +639,24 @@ impl ShedVi { .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); let hint = self.history.get_hint(); self.editor.set_hint(hint); - - } Ok(None) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart)); + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart)); - post_cmds.exec(); + post_cmds.exec(); self.writer.send_bell().ok(); if self.completer.is_active() { - write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str("COMPLETE".to_string()), VarFlags::NONE)).ok(); - self.prompt.refresh(); - self.needs_redraw = true; + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str("COMPLETE".to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + self.needs_redraw = true; self.editor.set_hint(None); } } @@ -626,33 +665,42 @@ impl ShedVi { self.needs_redraw = true; return Ok(None); } else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key { - let initial = self.editor.as_str(); - match self.history.start_search(initial) { - Some(entry) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); - with_vars([("_HIST_ENTRY".into(), entry.clone())], || { - post_cmds.exec_with(&entry); - }); + let initial = self.editor.as_str(); + match self.history.start_search(initial) { + Some(entry) => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); + with_vars([("_HIST_ENTRY".into(), entry.clone())], || { + post_cmds.exec_with(&entry); + }); - self.editor.set_buffer(entry); - self.editor.move_cursor_to_end(); - self.history.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); - self.editor.set_hint(None); - } - None => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen)); - post_cmds.exec(); + self.editor.set_buffer(entry); + self.editor.move_cursor_to_end(); + self + .history + .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); + self.editor.set_hint(None); + } + None => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen)); + post_cmds.exec(); - self.writer.send_bell().ok(); - if self.history.fuzzy_finder.is_active() { - write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str("SEARCH".to_string()), VarFlags::NONE)).ok(); - self.prompt.refresh(); - self.needs_redraw = true; - self.editor.set_hint(None); - } - } - } - } + self.writer.send_bell().ok(); + if self.history.fuzzy_finder.is_active() { + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str("SEARCH".to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + self.needs_redraw = true; + self.editor.set_hint(None); + } + } + } + } if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key && !self.next_is_escaped @@ -684,23 +732,17 @@ impl ShedVi { && !self.editor.buffer.ends_with('\\') && (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete)) { - if self.editor.attempt_history_expansion(&self.history) { - // If history expansion occurred, don't submit yet - // allow the user to see the expanded command and accept or edit it before submitting - return Ok(None); - } + if self.editor.attempt_history_expansion(&self.history) { + // If history expansion occurred, don't submit yet + // allow the user to see the expanded command and accept or edit it before submitting + return Ok(None); + } self.editor.set_hint(None); self.editor.cursor.set(self.editor.cursor_max()); self.print_line(true)?; self.writer.flush_write("\n")?; let buf = self.editor.take_buf(); - if read_shopts(|s| s.core.auto_hist) && !buf.is_empty() { - self.history.push(buf.clone()); - if let Err(e) = self.history.save() { - eprintln!("Failed to save history: {e}"); - } - } self.history.reset(); return Ok(Some(ReadlineEvent::Line(buf))); } @@ -942,8 +984,11 @@ impl ShedVi { .set_prompt_line_context(preceding_width, new_layout.cursor.col); self.completer.draw(&mut self.writer)?; - self.history.fuzzy_finder.set_prompt_line_context(preceding_width, new_layout.cursor.col); - self.history.fuzzy_finder.draw(&mut self.writer)?; + self + .history + .fuzzy_finder + .set_prompt_line_context(preceding_width, new_layout.cursor.col); + self.history.fuzzy_finder.draw(&mut self.writer)?; self.old_layout = Some(new_layout); self.needs_redraw = false; diff --git a/src/readline/vimode/insert.rs b/src/readline/vimode/insert.rs index 3df06a2..c7c74ca 100644 --- a/src/readline/vimode/insert.rs +++ b/src/readline/vimode/insert.rs @@ -54,15 +54,13 @@ impl ViMode for ViInsert { .set_motion(MotionCmd(1, Motion::ForwardChar)); self.register_and_return() } - E(K::ExMode, _) => { - Some(ViCmd { - register: Default::default(), - verb: Some(VerbCmd(1, Verb::ExMode)), - motion: None, - raw_seq: String::new(), - flags: Default::default(), - }) - } + E(K::ExMode, _) => Some(ViCmd { + register: Default::default(), + verb: Some(VerbCmd(1, Verb::ExMode)), + motion: None, + raw_seq: String::new(), + flags: Default::default(), + }), E(K::Char('W'), M::CTRL) => { self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); self.pending_cmd.set_motion(MotionCmd( diff --git a/src/readline/vimode/normal.rs b/src/readline/vimode/normal.rs index 49418a3..469ac83 100644 --- a/src/readline/vimode/normal.rs +++ b/src/readline/vimode/normal.rs @@ -756,15 +756,15 @@ impl ViMode for ViNormal { raw_seq: "".into(), flags: self.flags(), }), - E(K::ExMode, _) => { - return Some(ViCmd { - register: Default::default(), - verb: Some(VerbCmd(1, Verb::ExMode)), - motion: None, - raw_seq: self.take_cmd(), - flags: self.flags(), - }); - } + E(K::ExMode, _) => { + return Some(ViCmd { + register: Default::default(), + verb: Some(VerbCmd(1, Verb::ExMode)), + motion: None, + raw_seq: self.take_cmd(), + flags: self.flags(), + }); + } E(K::Char('A'), M::CTRL) => { let count = self .parse_count(&mut self.pending_seq.chars().peekable()) diff --git a/src/readline/vimode/replace.rs b/src/readline/vimode/replace.rs index 2bfb697..5f97d45 100644 --- a/src/readline/vimode/replace.rs +++ b/src/readline/vimode/replace.rs @@ -41,15 +41,13 @@ impl ViMode for ViReplace { .set_motion(MotionCmd(1, Motion::ForwardChar)); self.register_and_return() } - E(K::ExMode, _) => { - Some(ViCmd { - register: Default::default(), - verb: Some(VerbCmd(1, Verb::ExMode)), - motion: None, - raw_seq: String::new(), - flags: Default::default(), - }) - } + E(K::ExMode, _) => Some(ViCmd { + register: Default::default(), + verb: Some(VerbCmd(1, Verb::ExMode)), + motion: None, + raw_seq: String::new(), + flags: Default::default(), + }), E(K::Char('W'), M::CTRL) => { self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); self.pending_cmd.set_motion(MotionCmd( diff --git a/src/readline/vimode/visual.rs b/src/readline/vimode/visual.rs index ededaac..37b9f01 100644 --- a/src/readline/vimode/visual.rs +++ b/src/readline/vimode/visual.rs @@ -614,15 +614,15 @@ impl ViMode for ViVisual { raw_seq: "".into(), flags: CmdFlags::empty(), }), - E(K::ExMode, _) => { - return Some(ViCmd { - register: Default::default(), - verb: Some(VerbCmd(1, Verb::ExMode)), - motion: None, - raw_seq: String::new(), - flags: Default::default(), - }); - } + E(K::ExMode, _) => { + return Some(ViCmd { + register: Default::default(), + verb: Some(VerbCmd(1, Verb::ExMode)), + motion: None, + raw_seq: String::new(), + flags: Default::default(), + }); + } E(K::Char('A'), M::CTRL) => { let count = self .parse_count(&mut self.pending_seq.chars().peekable()) diff --git a/src/state.rs b/src/state.rs index aefee3b..af46741 100644 --- a/src/state.rs +++ b/src/state.rs @@ -550,12 +550,12 @@ pub enum AutoCmdKind { PostPrompt, PreModeChange, PostModeChange, - OnHistoryOpen, - OnHistoryClose, - OnHistorySelect, - OnCompletionStart, - OnCompletionCancel, - OnCompletionSelect, + OnHistoryOpen, + OnHistoryClose, + OnHistorySelect, + OnCompletionStart, + OnCompletionCancel, + OnCompletionSelect, OnExit, } @@ -571,12 +571,12 @@ impl Display for AutoCmdKind { Self::PostPrompt => write!(f, "post-prompt"), Self::PreModeChange => write!(f, "pre-mode-change"), Self::PostModeChange => write!(f, "post-mode-change"), - Self::OnHistoryOpen => write!(f, "on-history-open"), - Self::OnHistoryClose => write!(f, "on-history-close"), - Self::OnHistorySelect => write!(f, "on-history-select"), - Self::OnCompletionStart => write!(f, "on-completion-start"), - Self::OnCompletionCancel => write!(f, "on-completion-cancel"), - Self::OnCompletionSelect => write!(f, "on-completion-select"), + Self::OnHistoryOpen => write!(f, "on-history-open"), + Self::OnHistoryClose => write!(f, "on-history-close"), + Self::OnHistorySelect => write!(f, "on-history-select"), + Self::OnCompletionStart => write!(f, "on-completion-start"), + Self::OnCompletionCancel => write!(f, "on-completion-cancel"), + Self::OnCompletionSelect => write!(f, "on-completion-select"), Self::OnExit => write!(f, "on-exit"), } } @@ -595,12 +595,12 @@ impl FromStr for AutoCmdKind { "post-prompt" => Ok(Self::PostPrompt), "pre-mode-change" => Ok(Self::PreModeChange), "post-mode-change" => Ok(Self::PostModeChange), - "on-history-open" => Ok(Self::OnHistoryOpen), - "on-history-close" => Ok(Self::OnHistoryClose), - "on-history-select" => Ok(Self::OnHistorySelect), - "on-completion-start" => Ok(Self::OnCompletionStart), - "on-completion-cancel" => Ok(Self::OnCompletionCancel), - "on-completion-select" => Ok(Self::OnCompletionSelect), + "on-history-open" => Ok(Self::OnHistoryOpen), + "on-history-close" => Ok(Self::OnHistoryClose), + "on-history-select" => Ok(Self::OnHistorySelect), + "on-completion-start" => Ok(Self::OnCompletionStart), + "on-completion-cancel" => Ok(Self::OnCompletionCancel), + "on-completion-select" => Ok(Self::OnCompletionSelect), "on-exit" => Ok(Self::OnExit), _ => Err(ShErr::simple( ShErrKind::ParseErr,