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
This commit is contained in:
2026-03-05 10:29:54 -05:00
parent 1efaf0e516
commit e07a853074
17 changed files with 522 additions and 477 deletions

View File

@@ -1,7 +1,5 @@
max_width = 100 max_width = 100
tab_spaces = 2 tab_spaces = 2
edition = "2021" edition = "2024"
newline_style = "Unix" newline_style = "Unix"
wrap_comments = true

View File

@@ -1404,7 +1404,6 @@ impl FromStr for ParamExp {
)) ))
}; };
// Handle indirect var expansion: ${!var} // Handle indirect var expansion: ${!var}
if let Some(var) = s.strip_prefix('!') { if let Some(var) = s.strip_prefix('!') {
if var.ends_with('*') || var.ends_with('@') { if var.ends_with('*') || var.ends_with('@') {
@@ -2374,7 +2373,7 @@ pub fn parse_key_alias(alias: &str) -> Option<KeyEvent> {
"RIGHT" => KeyCode::Right, "RIGHT" => KeyCode::Right,
"HOME" => KeyCode::Home, "HOME" => KeyCode::Home,
"END" => KeyCode::End, "END" => KeyCode::End,
"CMD" => KeyCode::ExMode, "CMD" => KeyCode::ExMode,
"PGUP" | "PAGEUP" => KeyCode::PageUp, "PGUP" | "PAGEUP" => KeyCode::PageUp,
"PGDN" | "PAGEDOWN" => KeyCode::PageDown, "PGDN" | "PAGEDOWN" => KeyCode::PageDown,
k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()), k if k.len() == 1 => KeyCode::Char(k.chars().next().unwrap()),

View File

@@ -37,7 +37,7 @@ use crate::prelude::*;
use crate::readline::term::{LineWriter, RawModeGuard, raw_mode}; use crate::readline::term::{LineWriter, RawModeGuard, raw_mode};
use crate::readline::{Prompt, ReadlineEvent, ShedVi}; use crate::readline::{Prompt, ReadlineEvent, ShedVi};
use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending}; 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 clap::Parser;
use state::{read_vars, write_vars}; use state::{read_vars, write_vars};
@@ -292,36 +292,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
let action = km.action_expanded(); let action = km.action_expanded();
readline.pending_keymap.clear(); readline.pending_keymap.clear();
for key in action { for key in action {
if let Some(event) = readline.handle_key(key)? { let event = readline.handle_key(key).transpose();
match event { if let Some(event) = event {
ReadlineEvent::Line(input) => { handle_readline_event(&mut readline, event)?;
let start = Instant::now();
write_meta(|m| m.start_timer());
if let Err(e) = RawModeGuard::with_cooked_mode(|| {
exec_input(input, None, true, Some("<stdin>".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 => {}
}
} }
} }
} else { } else {
@@ -331,36 +304,9 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
); );
let buffered = std::mem::take(&mut readline.pending_keymap); let buffered = std::mem::take(&mut readline.pending_keymap);
for key in buffered { for key in buffered {
if let Some(event) = readline.handle_key(key)? { let event = readline.handle_key(key).transpose();
match event { if let Some(event) = event {
ReadlineEvent::Line(input) => { handle_readline_event(&mut readline, event)?;
let start = Instant::now();
write_meta(|m| m.start_timer());
if let Err(e) = RawModeGuard::with_cooked_mode(|| {
exec_input(input, None, true, Some("<stdin>".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 => {}
}
} }
} }
} }
@@ -394,58 +340,76 @@ fn shed_interactive(args: ShedArgs) -> ShResult<()> {
} }
// Process any available input // Process any available input
match readline.process_input() { let event = readline.process_input();
Ok(ReadlineEvent::Line(input)) => { match handle_readline_event(&mut readline, event)? {
let pre_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PreCmd)); true => return Ok(()),
let post_exec = read_logic(|l| l.get_autocmds(AutoCmdKind::PostCmd)); false => { /* continue looping */ }
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("<stdin>".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(),
},
} }
} }
Ok(()) Ok(())
} }
fn handle_readline_event(readline: &mut ShedVi, event: ShResult<ReadlineEvent>) -> ShResult<bool> {
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("<stdin>".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)
}
},
}
}

View File

@@ -877,7 +877,7 @@ impl Iterator for LexStream {
if self.flags.contains(LexFlags::LEX_UNFINISHED) { if self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.get_token(ch_idx..self.cursor, TkRule::Comment) self.get_token(ch_idx..self.cursor, TkRule::Comment)
} else { } 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(); return self.next();
} }
} }

View File

@@ -87,7 +87,7 @@ impl ParsedSrc {
Err(error) => return Err(vec![error]), Err(error) => return Err(vec![error]),
} }
} }
log::debug!("Tokens: {:#?}", tokens); log::debug!("Tokens: {:#?}", tokens);
let mut errors = vec![]; let mut errors = vec![];
let mut nodes = vec![]; let mut nodes = vec![];

View File

@@ -92,7 +92,7 @@ impl IoMode {
Ok(self) Ok(self)
} }
pub fn get_pipes() -> (Self, Self) { pub fn get_pipes() -> (Self, Self) {
let (rpipe, wpipe) = pipe().unwrap(); let (rpipe, wpipe) = nix::unistd::pipe2(OFlag::O_CLOEXEC).unwrap();
( (
Self::Pipe { Self::Pipe {
tgt_fd: STDIN_FILENO, tgt_fd: STDIN_FILENO,
@@ -220,6 +220,12 @@ impl<'e> IoFrame {
let tgt_fd = io_mode.tgt_fd(); let tgt_fd = io_mode.tgt_fd();
let src_fd = io_mode.src_fd(); let src_fd = io_mode.src_fd();
dup2(src_fd, tgt_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<OwnedFd>, which closes the fd.
if matches!(io_mode, IoMode::Pipe { .. }) {
*io_mode = IoMode::Close { tgt_fd };
}
} }
Ok(RedirGuard::new(self)) Ok(RedirGuard::new(self))
} }

View File

@@ -713,7 +713,7 @@ pub struct FuzzySelector {
filtered: Vec<ScoredCandidate>, filtered: Vec<ScoredCandidate>,
candidates: Vec<String>, candidates: Vec<String>,
cursor: ClampedUsize, cursor: ClampedUsize,
number_candidates: bool, number_candidates: bool,
old_layout: Option<FuzzyLayout>, old_layout: Option<FuzzyLayout>,
max_height: usize, max_height: usize,
scroll_offset: usize, scroll_offset: usize,
@@ -749,7 +749,7 @@ impl FuzzySelector {
filtered: vec![], filtered: vec![],
candidates: vec![], candidates: vec![],
cursor: ClampedUsize::new(0, 0, true), cursor: ClampedUsize::new(0, 0, true),
number_candidates: false, number_candidates: false,
old_layout: None, old_layout: None,
scroll_offset: 0, scroll_offset: 0,
active: false, active: false,
@@ -759,12 +759,12 @@ impl FuzzySelector {
} }
} }
pub fn number_candidates(self, enable: bool) -> Self { pub fn number_candidates(self, enable: bool) -> Self {
Self { Self {
number_candidates: enable, number_candidates: enable,
..self ..self
} }
} }
pub fn activate(&mut self, candidates: Vec<String>) { pub fn activate(&mut self, candidates: Vec<String>) {
self.active = true; self.active = true;
@@ -772,11 +772,11 @@ impl FuzzySelector {
self.score_candidates(); self.score_candidates();
} }
pub fn set_query(&mut self, query: String) { pub fn set_query(&mut self, query: String) {
self.query.linebuf = LineBuf::new().with_initial(&query, query.len()); self.query.linebuf = LineBuf::new().with_initial(&query, query.len());
self.query.update_scroll_offset(); self.query.update_scroll_offset();
self.score_candidates(); self.score_candidates();
} }
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.query.clear(); self.query.clear();
@@ -812,7 +812,9 @@ impl FuzzySelector {
} }
fn candidate_height(&self, idx: usize) -> usize { 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)) .map(|c| c.content.trim_end().lines().count().max(1))
.unwrap_or(1) .unwrap_or(1)
} }
@@ -929,9 +931,15 @@ impl FuzzySelector {
let num_candidates = format!("\x1b[33m{}\x1b[0m", self.candidates.len()); let num_candidates = format!("\x1b[33m{}\x1b[0m", self.candidates.len());
let title = self.title.clone(); let title = self.title.clone();
let title_width = title.len() as u16; let title_width = title.len() as u16;
let number_candidates = self.number_candidates; let number_candidates = self.number_candidates;
let min_pad = self.candidates.len().to_string().len().saturating_add(1).max(6); let min_pad = self
let max_height = self.max_height; .candidates
.len()
.to_string()
.len()
.saturating_add(1)
.max(6);
let max_height = self.max_height;
let visible = self.get_window(); let visible = self.get_window();
let mut rows: u16 = 0; let mut rows: u16 = 0;
let top_bar = format!( let top_bar = format!(
@@ -966,51 +974,63 @@ impl FuzzySelector {
buf.push_str(&sep_line_final); buf.push_str(&sep_line_final);
rows += 1; rows += 1;
let mut lines_drawn = 0; let mut lines_drawn = 0;
for (i, candidate) in visible.iter().enumerate() { for (i, candidate) in visible.iter().enumerate() {
if lines_drawn >= max_height { if lines_drawn >= max_height {
break; break;
} }
let selector = if i + offset == cursor_pos { let selector = if i + offset == cursor_pos {
Self::SELECTOR_HL Self::SELECTOR_HL
} else { } else {
Self::SELECTOR_GRAY Self::SELECTOR_GRAY
}; };
let mut drew_number = false; let mut drew_number = false;
for line in candidate.content.trim_end().lines() { for line in candidate.content.trim_end().lines() {
if lines_drawn >= max_height { if lines_drawn >= max_height {
break; break;
} }
let mut line = line.trim_end().replace('\t', " "); let mut line = line.trim_end().replace('\t', " ");
let col_lim = if number_candidates{ let col_lim = if number_candidates {
cols.saturating_sub(3 + min_pad as u16) cols.saturating_sub(3 + min_pad as u16)
} else { } else {
cols.saturating_sub(3) cols.saturating_sub(3)
}; };
if calc_str_width(&line) > col_lim { if calc_str_width(&line) > col_lim {
line.truncate(col_lim.saturating_sub(6) as usize); line.truncate(col_lim.saturating_sub(6) as usize);
line.push_str("..."); line.push_str("...");
} }
let left = if number_candidates { let left = if number_candidates {
if !drew_number { if !drew_number {
let this_num = i + offset + 1; let this_num = i + offset + 1;
let right_pad = " ".repeat(min_pad.saturating_sub(this_num.to_string().len())); 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) format!(
} else { "{} {}\x1b[33m{}\x1b[39m{right_pad}{}\x1b[0m",
let right_pad = " ".repeat(min_pad); Self::VERT_LINE,
format!("{} {}{}{}\x1b[0m", Self::VERT_LINE, &selector,right_pad, &line) &selector,
} i + offset + 1,
} else { &line
format!("{} {}{}\x1b[0m", Self::VERT_LINE, &selector, &line) )
}; } else {
let cols_used = calc_str_width(&left); let right_pad = " ".repeat(min_pad);
let right_pad = " ".repeat(cols.saturating_sub(cols_used + 1) as usize); format!(
let hl_cand_line = format!("{}{}{}", left, right_pad, Self::VERT_LINE); "{} {}{}{}\x1b[0m",
buf.push_str(&hl_cand_line); Self::VERT_LINE,
rows += 1; &selector,
drew_number = true; right_pad,
lines_drawn += 1; &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!( let bot_bar = format!(
@@ -1100,7 +1120,9 @@ impl Default for FuzzyCompleter {
impl Completer for FuzzyCompleter { impl Completer for FuzzyCompleter {
fn set_prompt_line_context(&mut self, line_width: u16, cursor_col: u16) { 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) { fn reset_stay_active(&mut self) {
self.selector.reset_stay_active(); self.selector.reset_stay_active();

View File

@@ -105,9 +105,9 @@ impl Highlighter {
self.in_selection = false; self.in_selection = false;
} }
_ if self.only_hl_visual => { _ if self.only_hl_visual => {
if !is_marker(ch) { if !is_marker(ch) {
self.output.push(ch); self.output.push(ch);
} }
} }
markers::STRING_DQ_END markers::STRING_DQ_END
| markers::STRING_SQ_END | markers::STRING_SQ_END

View File

@@ -1,10 +1,19 @@
use std::{ 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::{ use crate::{
libsh::error::{ShErr, ShErrKind, ShResult}, libsh::error::{ShErr, ShErrKind, ShResult},
readline::{complete::FuzzySelector, linebuf::LineBuf}, readline::{complete::FuzzySelector, linebuf::LineBuf},
state::read_meta,
}; };
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
@@ -28,16 +37,13 @@ impl SearchConstraint {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct HistEntry { pub struct HistEntry {
id: u32, runtime: Duration,
timestamp: SystemTime, timestamp: SystemTime,
command: String, command: String,
new: bool, new: bool,
} }
impl HistEntry { impl HistEntry {
pub fn id(&self) -> u32 {
self.id
}
pub fn timestamp(&self) -> &SystemTime { pub fn timestamp(&self) -> &SystemTime {
&self.timestamp &self.timestamp
} }
@@ -73,24 +79,25 @@ impl FromStr for HistEntry {
return err; return err;
}; };
//248972349;148;echo foo; echo bar //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; return err;
}; };
//("248972349","148;echo foo; echo bar") //("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; return err;
}; };
//("148","echo foo; echo bar") //("148","echo foo; echo bar")
let Ok(ts_seconds) = timestamp.parse::<u64>() else { let Ok(ts_seconds) = timestamp.parse::<u64>() else {
return err; return err;
}; };
let Ok(id) = id.parse::<u32>() else { let Ok(runtime) = runtime.parse::<u64>() else {
return err; return err;
}; };
let runtime = Duration::from_secs(runtime);
let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds); let timestamp = UNIX_EPOCH + Duration::from_secs(ts_seconds);
let command = command.to_string(); let command = command.to_string();
Ok(Self { Ok(Self {
id, runtime,
timestamp, timestamp,
command, command,
new: false, new: false,
@@ -103,13 +110,14 @@ impl Display for HistEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let command = self.with_escaped_newlines(); let command = self.with_escaped_newlines();
let HistEntry { let HistEntry {
id, runtime,
timestamp, timestamp,
command: _, command: _,
new: _, new: _,
} = self; } = self;
let timestamp = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs(); 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<LineBuf>, // command, cursor_pos pub pending: Option<LineBuf>, // command, cursor_pos
entries: Vec<HistEntry>, entries: Vec<HistEntry>,
search_mask: Vec<HistEntry>, search_mask: Vec<HistEntry>,
pub fuzzy_finder: FuzzySelector, pub fuzzy_finder: FuzzySelector,
no_matches: bool, no_matches: bool,
pub cursor: usize, pub cursor: usize,
//search_direction: Direction, //search_direction: Direction,
@@ -223,11 +231,15 @@ impl History {
} }
let search_mask = dedupe_entries(&entries); let search_mask = dedupe_entries(&entries);
let cursor = search_mask.len(); 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 { Ok(Self {
path, path,
entries, entries,
fuzzy_finder: FuzzySelector::new("History").number_candidates(true), fuzzy_finder: FuzzySelector::new("History").number_candidates(true),
pending: None, pending: None,
search_mask, search_mask,
no_matches: false, no_matches: false,
@@ -238,19 +250,22 @@ impl History {
}) })
} }
pub fn start_search(&mut self, initial: &str) -> Option<String> { pub fn start_search(&mut self, initial: &str) -> Option<String> {
if self.search_mask.is_empty() { if self.search_mask.is_empty() {
None None
} else if self.search_mask.len() == 1 { } else if self.search_mask.len() == 1 {
Some(self.search_mask[0].command().to_string()) Some(self.search_mask[0].command().to_string())
} else { } else {
self.fuzzy_finder.set_query(initial.to_string()); self.fuzzy_finder.set_query(initial.to_string());
let raw_entries = self.search_mask.clone().into_iter() let raw_entries = self
.map(|ent| ent.command().to_string()); .search_mask
self.fuzzy_finder.activate(raw_entries.collect()); .clone()
None .into_iter()
} .map(|ent| ent.command().to_string());
} self.fuzzy_finder.activate(raw_entries.collect());
None
}
}
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.search_mask = dedupe_entries(&self.entries); self.search_mask = dedupe_entries(&self.entries);
@@ -301,42 +316,36 @@ impl History {
pub fn last_mut(&mut self) -> Option<&mut HistEntry> { pub fn last_mut(&mut self) -> Option<&mut HistEntry> {
self.entries.last_mut() self.entries.last_mut()
} }
pub fn last(&self) -> Option<&HistEntry> { pub fn last(&self) -> Option<&HistEntry> {
self.entries.last() self.entries.last()
} }
pub fn resolve_hist_token(&self, token: &str) -> Option<String> { pub fn resolve_hist_token(&self, token: &str) -> Option<String> {
let token = token.strip_prefix('!').unwrap_or(token).to_string(); let token = token.strip_prefix('!').unwrap_or(token).to_string();
if let Ok(num) = token.parse::<i32>() && num != 0 { if let Ok(num) = token.parse::<i32>()
match num.cmp(&0) { && num != 0
Ordering::Less => { {
if num.unsigned_abs() > self.entries.len() as u32 { match num.cmp(&0) {
return None; Ordering::Less => {
} if num.unsigned_abs() > self.entries.len() as u32 {
return None;
}
let rev_idx = self.entries.len() - num.unsigned_abs() as usize; let rev_idx = self.entries.len() - num.unsigned_abs() as usize;
self.entries.get(rev_idx) self.entries.get(rev_idx).map(|e| e.command().to_string())
.map(|e| e.command().to_string()) }
} Ordering::Greater => self
Ordering::Greater => { .entries
self.entries.get(num as usize) .get(num as usize)
.map(|e| e.command().to_string()) .map(|e| e.command().to_string()),
} _ => unreachable!(),
_ => unreachable!() }
} } else {
} else { let mut rev_search = self.entries.iter();
let mut rev_search = self.entries.iter(); rev_search
rev_search .rfind(|e| e.command().starts_with(&token))
.rfind(|e| e.command().starts_with(&token)) .map(|e| e.command().to_string())
.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
} }
pub fn ignore_dups(&mut self, yn: bool) { pub fn ignore_dups(&mut self, yn: bool) {
@@ -401,12 +410,12 @@ impl History {
pub fn push(&mut self, command: String) { pub fn push(&mut self, command: String) {
let timestamp = SystemTime::now(); 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) { if self.ignore_dups && self.is_dup(&command) {
return; return;
} }
self.entries.push(HistEntry { self.entries.push(HistEntry {
id, runtime,
timestamp, timestamp,
command, command,
new: true, new: true,

View File

@@ -114,10 +114,10 @@ impl KeyEvent {
"Cannot convert unknown escape sequence to Vim key sequence".to_string(), "Cannot convert unknown escape sequence to Vim key sequence".to_string(),
)); ));
} }
KeyCode::ExMode => { KeyCode::ExMode => {
seq.push_str("CMD"); seq.push_str("CMD");
needs_angle_bracket = true; needs_angle_bracket = true;
} }
KeyCode::Backspace => { KeyCode::Backspace => {
seq.push_str("BS"); seq.push_str("BS");
needs_angle_bracket = true; needs_angle_bracket = true;
@@ -227,8 +227,8 @@ pub enum KeyCode {
Tab, Tab,
Up, Up,
// weird stuff // weird stuff
ExMode, // keycode emitted by the <cmd> byte alias in vim keymaps ExMode, // keycode emitted by the <cmd> byte alias in vim keymaps
} }
bitflags::bitflags! { bitflags::bitflags! {

View File

@@ -19,7 +19,10 @@ use crate::{
}, },
prelude::*, prelude::*,
readline::{ 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}, state::{VarFlags, VarKind, read_shopts, write_meta, write_vars},
}; };
@@ -297,13 +300,13 @@ impl ClampedUsize {
let max = self.upper_bound(); let max = self.upper_bound();
self.value = (self.value + value).clamp(0, max) self.value = (self.value + value).clamp(0, max)
} }
pub fn add_signed(&mut self, value: isize) { pub fn add_signed(&mut self, value: isize) {
if value.is_negative() { if value.is_negative() {
self.sub(value.wrapping_abs() as usize); self.sub(value.wrapping_abs() as usize);
} else { } else {
self.add(value as usize); self.add(value as usize);
} }
} }
pub fn sub(&mut self, value: usize) { pub fn sub(&mut self, value: usize) {
self.value = self.value.saturating_sub(value) self.value = self.value.saturating_sub(value)
} }
@@ -650,10 +653,10 @@ impl LineBuf {
self.buffer.push_str(slice); self.buffer.push_str(slice);
self.update_graphemes(); self.update_graphemes();
} }
pub fn insert_str_at_cursor(&mut self, slice: &str) { pub fn insert_str_at_cursor(&mut self, slice: &str) {
let pos = self.index_byte_pos(self.cursor.get()); let pos = self.index_byte_pos(self.cursor.get());
self.insert_str_at(pos, slice); self.insert_str_at(pos, slice);
} }
pub fn insert_at_cursor(&mut self, ch: char) { pub fn insert_at_cursor(&mut self, ch: char) {
self.insert_at(self.cursor.get(), ch); self.insert_at(self.cursor.get(), ch);
} }
@@ -3327,71 +3330,74 @@ impl LineBuf {
Ok(()) Ok(())
} }
pub fn attempt_history_expansion(&mut self, hist: &History) -> bool { pub fn attempt_history_expansion(&mut self, hist: &History) -> bool {
self.update_graphemes(); self.update_graphemes();
let mut changes: Vec<(Range<usize>,String)> = vec![]; let mut changes: Vec<(Range<usize>, String)> = vec![];
let mut graphemes = self.buffer.grapheme_indices(true); let mut graphemes = self.buffer.grapheme_indices(true);
let mut qt_state = QuoteState::default(); let mut qt_state = QuoteState::default();
while let Some((i,gr)) = graphemes.next() { while let Some((i, gr)) = graphemes.next() {
match gr { match gr {
"\\" => { "\\" | "$" => {
graphemes.next(); // skip on dollars because '$!' is a shell parameter
} graphemes.next();
"'" => qt_state.toggle_single(), }
"\"" => qt_state.toggle_double(), "'" => qt_state.toggle_single(),
"!" if !qt_state.in_single() => { "\"" => qt_state.toggle_double(),
let start = i; "!" if !qt_state.in_single() => {
match graphemes.next() { let start = i;
Some((_,"!")) => { match graphemes.next() {
// we have "!!", which expands to the previous command Some((_, "!")) => {
if let Some(prev) = hist.last() { // we have "!!", which expands to the previous command
let raw = prev.command(); if let Some(prev) = hist.last() {
changes.push((start..start+2, raw.to_string())); 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 Some((_, "$")) => {
if let Some(prev) = hist.last() { // we have "!$", which expands to the last word of the previous command
let raw = prev.command(); if let Some(prev) = hist.last() {
if let Some(last_word) = raw.split_whitespace().last() { let raw = prev.command();
changes.push((start..start+2, last_word.to_string())); 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(); Some((j, gr)) if !is_whitespace(gr) => {
while let Some((k, gr2)) = graphemes.next() { let mut end = j + gr.len();
if is_whitespace(gr2) { break; } while let Some((k, gr2)) = graphemes.next() {
end = k + gr2.len(); if is_whitespace(gr2) {
} break;
let token = &self.buffer[j..end]; }
let cmd = hist.resolve_hist_token(token).unwrap_or(token.into()); end = k + gr2.len();
changes.push((start..end, cmd)); }
} let token = &self.buffer[j..end];
_ => { /* not a hist expansion */ } let cmd = hist.resolve_hist_token(token).unwrap_or(token.into());
} changes.push((start..end, cmd));
} }
_ => { /* carry on */ } _ => { /* 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() { for (range, change) in changes.into_iter().rev() {
self.buffer.replace_range(range, &change); self.buffer.replace_range(range, &change);
} }
self.update_graphemes(); self.update_graphemes();
let new_len = self.grapheme_indices().len(); let new_len = self.grapheme_indices().len();
let delta = new_len as isize - buf_len as isize; let delta = new_len as isize - buf_len as isize;
self.cursor.set_max(new_len); self.cursor.set_max(new_len);
self.cursor.add_signed(delta); self.cursor.add_signed(delta);
ret ret
} }
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&self.buffer // FIXME: this will have to be fixed up later &self.buffer // FIXME: this will have to be fixed up later

View File

@@ -16,7 +16,8 @@ use crate::readline::complete::{FuzzyCompleter, SelectorResponse};
use crate::readline::term::{Pos, TermReader, calc_str_width}; use crate::readline::term::{Pos, TermReader, calc_str_width};
use crate::readline::vimode::{ViEx, ViVerbatim}; use crate::readline::vimode::{ViEx, ViVerbatim};
use crate::state::{ 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::{ use crate::{
libsh::error::ShResult, libsh::error::ShResult,
@@ -212,7 +213,7 @@ impl Prompt {
fn refresh_now(&mut self) { fn refresh_now(&mut self) {
let saved_status = state::get_status(); let saved_status = state::get_status();
*self = Self::new(); *self = Self::new();
state::set_status(saved_status); state::set_status(saved_status);
self.dirty = false; self.dirty = false;
} }
@@ -405,49 +406,65 @@ impl ShedVi {
// Process all available keys // Process all available keys
while let Some(key) = self.reader.read_key()? { while let Some(key) = self.reader.read_key()? {
// If completer or history search are active, delegate input to it // If completer or history search are active, delegate input to it
if self.history.fuzzy_finder.is_active() { if self.history.fuzzy_finder.is_active() {
self.print_line(false)?; self.print_line(false)?;
match self.history.fuzzy_finder.handle_key(key)? { match self.history.fuzzy_finder.handle_key(key)? {
SelectorResponse::Accept(cmd) => { SelectorResponse::Accept(cmd) => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
self.editor.set_buffer(cmd.to_string()); self.editor.set_buffer(cmd.to_string());
self.editor.move_cursor_to_end(); self.editor.move_cursor_to_end();
self.history.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); self
self.editor.set_hint(None); .history
self.history.fuzzy_finder.clear(&mut self.writer)?; .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
self.history.fuzzy_finder.reset(); 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())], || { with_vars([("_HIST_ENTRY".into(), cmd.clone())], || {
post_cmds.exec_with(&cmd); 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(); write_vars(|v| {
self.prompt.refresh(); v.set_var(
self.needs_redraw = true; "SHED_VI_MODE",
continue; VarKind::Str(self.mode.report_mode().to_string()),
} VarFlags::NONE,
SelectorResponse::Dismiss => { )
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryClose)); })
post_cmds.exec(); .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.editor.set_hint(None);
self.history.fuzzy_finder.clear(&mut self.writer)?; 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(); write_vars(|v| {
self.prompt.refresh(); v.set_var(
self.needs_redraw = true; "SHED_VI_MODE",
continue; VarKind::Str(self.mode.report_mode().to_string()),
} VarFlags::NONE,
SelectorResponse::Consumed => { )
self.needs_redraw = true; })
continue; .ok();
} self.prompt.refresh();
} self.needs_redraw = true;
} else if self.completer.is_active() { continue;
}
SelectorResponse::Consumed => {
self.needs_redraw = true;
continue;
}
}
} else if self.completer.is_active() {
self.print_line(false)?; self.print_line(false)?;
match self.completer.handle_key(key.clone())? { match self.completer.handle_key(key.clone())? {
CompResponse::Accept(candidate) => { 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 span_start = self.completer.token_span().0;
let new_cursor = span_start + candidate.len(); let new_cursor = span_start + candidate.len();
@@ -468,21 +485,28 @@ impl ShedVi {
self.needs_redraw = true; self.needs_redraw = true;
self.completer.reset(); self.completer.reset();
with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || { with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || {
post_cmds.exec_with(&candidate); post_cmds.exec_with(&candidate);
}); });
continue; continue;
} }
CompResponse::Dismiss => { CompResponse::Dismiss => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel)); let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionCancel));
post_cmds.exec(); post_cmds.exec();
let hint = self.history.get_hint(); let hint = self.history.get_hint();
self.editor.set_hint(hint); self.editor.set_hint(hint);
self.completer.clear(&mut self.writer)?; 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(); write_vars(|v| {
self.prompt.refresh(); v.set_var(
"SHED_VI_MODE",
VarKind::Str(self.mode.report_mode().to_string()),
VarFlags::NONE,
)
})
.ok();
self.prompt.refresh();
self.completer.reset(); self.completer.reset();
continue; continue;
} }
@@ -531,9 +555,16 @@ impl ShedVi {
return Ok(event); return Ok(event);
} }
} }
if !self.completer.is_active() && !self.history.fuzzy_finder.is_active() { 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(); 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 // Redraw if we processed any input
if self.needs_redraw { if self.needs_redraw {
@@ -546,7 +577,10 @@ impl ShedVi {
pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<Option<ReadlineEvent>> { pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<Option<ReadlineEvent>> {
if self.should_accept_hint(&key) { 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(); self.editor.accept_hint();
if !self.history.at_pending() { if !self.history.at_pending() {
self.history.reset_to_pending(); self.history.reset_to_pending();
@@ -559,11 +593,11 @@ impl ShedVi {
} }
if let KeyEvent(KeyCode::Tab, mod_keys) = key { if let KeyEvent(KeyCode::Tab, mod_keys) = key {
if self.editor.attempt_history_expansion(&self.history) { if self.editor.attempt_history_expansion(&self.history) {
// If history expansion occurred, don't attempt completion yet // If history expansion occurred, don't attempt completion yet
// allow the user to see the expanded command and accept or edit it before completing // allow the user to see the expanded command and accept or edit it before completing
return Ok(None); return Ok(None);
} }
let direction = match mod_keys { let direction = match mod_keys {
ModKeys::SHIFT => -1, ModKeys::SHIFT => -1,
@@ -579,11 +613,11 @@ impl ShedVi {
self.old_layout = None; self.old_layout = None;
} }
Ok(Some(line)) => { Ok(Some(line)) => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect));
let cand = self.completer.selected_candidate().unwrap_or_default(); let cand = self.completer.selected_candidate().unwrap_or_default();
with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || { with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || {
post_cmds.exec_with(&cand); post_cmds.exec_with(&cand);
}); });
let span_start = self.completer.token_span().0; 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())); .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
let hint = self.history.get_hint(); let hint = self.history.get_hint();
self.editor.set_hint(hint); self.editor.set_hint(hint);
} }
Ok(None) => { 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(); self.writer.send_bell().ok();
if self.completer.is_active() { if self.completer.is_active() {
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str("COMPLETE".to_string()), VarFlags::NONE)).ok(); write_vars(|v| {
self.prompt.refresh(); v.set_var(
self.needs_redraw = true; "SHED_VI_MODE",
VarKind::Str("COMPLETE".to_string()),
VarFlags::NONE,
)
})
.ok();
self.prompt.refresh();
self.needs_redraw = true;
self.editor.set_hint(None); self.editor.set_hint(None);
} }
} }
@@ -626,33 +665,42 @@ impl ShedVi {
self.needs_redraw = true; self.needs_redraw = true;
return Ok(None); return Ok(None);
} else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key { } else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key {
let initial = self.editor.as_str(); let initial = self.editor.as_str();
match self.history.start_search(initial) { match self.history.start_search(initial) {
Some(entry) => { Some(entry) => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect));
with_vars([("_HIST_ENTRY".into(), entry.clone())], || { with_vars([("_HIST_ENTRY".into(), entry.clone())], || {
post_cmds.exec_with(&entry); post_cmds.exec_with(&entry);
}); });
self.editor.set_buffer(entry); self.editor.set_buffer(entry);
self.editor.move_cursor_to_end(); self.editor.move_cursor_to_end();
self.history.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); self
self.editor.set_hint(None); .history
} .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
None => { self.editor.set_hint(None);
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen)); }
post_cmds.exec(); None => {
let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen));
post_cmds.exec();
self.writer.send_bell().ok(); self.writer.send_bell().ok();
if self.history.fuzzy_finder.is_active() { if self.history.fuzzy_finder.is_active() {
write_vars(|v| v.set_var("SHED_VI_MODE", VarKind::Str("SEARCH".to_string()), VarFlags::NONE)).ok(); write_vars(|v| {
self.prompt.refresh(); v.set_var(
self.needs_redraw = true; "SHED_VI_MODE",
self.editor.set_hint(None); 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 if let KeyEvent(KeyCode::Char('\\'), ModKeys::NONE) = key
&& !self.next_is_escaped && !self.next_is_escaped
@@ -684,23 +732,17 @@ impl ShedVi {
&& !self.editor.buffer.ends_with('\\') && !self.editor.buffer.ends_with('\\')
&& (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete)) && (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete))
{ {
if self.editor.attempt_history_expansion(&self.history) { if self.editor.attempt_history_expansion(&self.history) {
// If history expansion occurred, don't submit yet // If history expansion occurred, don't submit yet
// allow the user to see the expanded command and accept or edit it before submitting // allow the user to see the expanded command and accept or edit it before submitting
return Ok(None); return Ok(None);
} }
self.editor.set_hint(None); self.editor.set_hint(None);
self.editor.cursor.set(self.editor.cursor_max()); self.editor.cursor.set(self.editor.cursor_max());
self.print_line(true)?; self.print_line(true)?;
self.writer.flush_write("\n")?; self.writer.flush_write("\n")?;
let buf = self.editor.take_buf(); 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(); self.history.reset();
return Ok(Some(ReadlineEvent::Line(buf))); return Ok(Some(ReadlineEvent::Line(buf)));
} }
@@ -942,8 +984,11 @@ impl ShedVi {
.set_prompt_line_context(preceding_width, new_layout.cursor.col); .set_prompt_line_context(preceding_width, new_layout.cursor.col);
self.completer.draw(&mut self.writer)?; self.completer.draw(&mut self.writer)?;
self.history.fuzzy_finder.set_prompt_line_context(preceding_width, new_layout.cursor.col); self
self.history.fuzzy_finder.draw(&mut self.writer)?; .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.old_layout = Some(new_layout);
self.needs_redraw = false; self.needs_redraw = false;

View File

@@ -54,15 +54,13 @@ impl ViMode for ViInsert {
.set_motion(MotionCmd(1, Motion::ForwardChar)); .set_motion(MotionCmd(1, Motion::ForwardChar));
self.register_and_return() self.register_and_return()
} }
E(K::ExMode, _) => { E(K::ExMode, _) => Some(ViCmd {
Some(ViCmd { register: Default::default(),
register: Default::default(), verb: Some(VerbCmd(1, Verb::ExMode)),
verb: Some(VerbCmd(1, Verb::ExMode)), motion: None,
motion: None, raw_seq: String::new(),
raw_seq: String::new(), flags: Default::default(),
flags: Default::default(), }),
})
}
E(K::Char('W'), M::CTRL) => { E(K::Char('W'), M::CTRL) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
self.pending_cmd.set_motion(MotionCmd( self.pending_cmd.set_motion(MotionCmd(

View File

@@ -756,15 +756,15 @@ impl ViMode for ViNormal {
raw_seq: "".into(), raw_seq: "".into(),
flags: self.flags(), flags: self.flags(),
}), }),
E(K::ExMode, _) => { E(K::ExMode, _) => {
return Some(ViCmd { return Some(ViCmd {
register: Default::default(), register: Default::default(),
verb: Some(VerbCmd(1, Verb::ExMode)), verb: Some(VerbCmd(1, Verb::ExMode)),
motion: None, motion: None,
raw_seq: self.take_cmd(), raw_seq: self.take_cmd(),
flags: self.flags(), flags: self.flags(),
}); });
} }
E(K::Char('A'), M::CTRL) => { E(K::Char('A'), M::CTRL) => {
let count = self let count = self
.parse_count(&mut self.pending_seq.chars().peekable()) .parse_count(&mut self.pending_seq.chars().peekable())

View File

@@ -41,15 +41,13 @@ impl ViMode for ViReplace {
.set_motion(MotionCmd(1, Motion::ForwardChar)); .set_motion(MotionCmd(1, Motion::ForwardChar));
self.register_and_return() self.register_and_return()
} }
E(K::ExMode, _) => { E(K::ExMode, _) => Some(ViCmd {
Some(ViCmd { register: Default::default(),
register: Default::default(), verb: Some(VerbCmd(1, Verb::ExMode)),
verb: Some(VerbCmd(1, Verb::ExMode)), motion: None,
motion: None, raw_seq: String::new(),
raw_seq: String::new(), flags: Default::default(),
flags: Default::default(), }),
})
}
E(K::Char('W'), M::CTRL) => { E(K::Char('W'), M::CTRL) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete)); self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
self.pending_cmd.set_motion(MotionCmd( self.pending_cmd.set_motion(MotionCmd(

View File

@@ -614,15 +614,15 @@ impl ViMode for ViVisual {
raw_seq: "".into(), raw_seq: "".into(),
flags: CmdFlags::empty(), flags: CmdFlags::empty(),
}), }),
E(K::ExMode, _) => { E(K::ExMode, _) => {
return Some(ViCmd { return Some(ViCmd {
register: Default::default(), register: Default::default(),
verb: Some(VerbCmd(1, Verb::ExMode)), verb: Some(VerbCmd(1, Verb::ExMode)),
motion: None, motion: None,
raw_seq: String::new(), raw_seq: String::new(),
flags: Default::default(), flags: Default::default(),
}); });
} }
E(K::Char('A'), M::CTRL) => { E(K::Char('A'), M::CTRL) => {
let count = self let count = self
.parse_count(&mut self.pending_seq.chars().peekable()) .parse_count(&mut self.pending_seq.chars().peekable())

View File

@@ -550,12 +550,12 @@ pub enum AutoCmdKind {
PostPrompt, PostPrompt,
PreModeChange, PreModeChange,
PostModeChange, PostModeChange,
OnHistoryOpen, OnHistoryOpen,
OnHistoryClose, OnHistoryClose,
OnHistorySelect, OnHistorySelect,
OnCompletionStart, OnCompletionStart,
OnCompletionCancel, OnCompletionCancel,
OnCompletionSelect, OnCompletionSelect,
OnExit, OnExit,
} }
@@ -571,12 +571,12 @@ impl Display for AutoCmdKind {
Self::PostPrompt => write!(f, "post-prompt"), Self::PostPrompt => write!(f, "post-prompt"),
Self::PreModeChange => write!(f, "pre-mode-change"), Self::PreModeChange => write!(f, "pre-mode-change"),
Self::PostModeChange => write!(f, "post-mode-change"), Self::PostModeChange => write!(f, "post-mode-change"),
Self::OnHistoryOpen => write!(f, "on-history-open"), Self::OnHistoryOpen => write!(f, "on-history-open"),
Self::OnHistoryClose => write!(f, "on-history-close"), Self::OnHistoryClose => write!(f, "on-history-close"),
Self::OnHistorySelect => write!(f, "on-history-select"), Self::OnHistorySelect => write!(f, "on-history-select"),
Self::OnCompletionStart => write!(f, "on-completion-start"), Self::OnCompletionStart => write!(f, "on-completion-start"),
Self::OnCompletionCancel => write!(f, "on-completion-cancel"), Self::OnCompletionCancel => write!(f, "on-completion-cancel"),
Self::OnCompletionSelect => write!(f, "on-completion-select"), Self::OnCompletionSelect => write!(f, "on-completion-select"),
Self::OnExit => write!(f, "on-exit"), Self::OnExit => write!(f, "on-exit"),
} }
} }
@@ -595,12 +595,12 @@ impl FromStr for AutoCmdKind {
"post-prompt" => Ok(Self::PostPrompt), "post-prompt" => Ok(Self::PostPrompt),
"pre-mode-change" => Ok(Self::PreModeChange), "pre-mode-change" => Ok(Self::PreModeChange),
"post-mode-change" => Ok(Self::PostModeChange), "post-mode-change" => Ok(Self::PostModeChange),
"on-history-open" => Ok(Self::OnHistoryOpen), "on-history-open" => Ok(Self::OnHistoryOpen),
"on-history-close" => Ok(Self::OnHistoryClose), "on-history-close" => Ok(Self::OnHistoryClose),
"on-history-select" => Ok(Self::OnHistorySelect), "on-history-select" => Ok(Self::OnHistorySelect),
"on-completion-start" => Ok(Self::OnCompletionStart), "on-completion-start" => Ok(Self::OnCompletionStart),
"on-completion-cancel" => Ok(Self::OnCompletionCancel), "on-completion-cancel" => Ok(Self::OnCompletionCancel),
"on-completion-select" => Ok(Self::OnCompletionSelect), "on-completion-select" => Ok(Self::OnCompletionSelect),
"on-exit" => Ok(Self::OnExit), "on-exit" => Ok(Self::OnExit),
_ => Err(ShErr::simple( _ => Err(ShErr::simple(
ShErrKind::ParseErr, ShErrKind::ParseErr,