From 8a7211d42ea62d272514601b728acbb6385945e8 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Fri, 6 Mar 2026 02:01:45 -0500 Subject: [PATCH] Expose completion/history metadata to autocmd hooks and add broader Var conversion impls --- src/readline/complete.rs | 32 +++++++++++++++++++++++-- src/readline/mod.rs | 52 ++++++++++++++++++++++++++++++++-------- src/state.rs | 52 ++++++++++++++++++++++++++++------------ 3 files changed, 109 insertions(+), 27 deletions(-) diff --git a/src/readline/complete.rs b/src/readline/complete.rs index 42d2362..b622400 100644 --- a/src/readline/complete.rs +++ b/src/readline/complete.rs @@ -544,9 +544,15 @@ pub trait Completer { fn reset(&mut self); fn reset_stay_active(&mut self); fn is_active(&self) -> bool; + fn all_candidates(&self) -> Vec { vec![] } fn selected_candidate(&self) -> Option; fn token_span(&self) -> (usize, usize); fn original_input(&self) -> &str; + fn token(&self) -> &str { + let orig = self.original_input(); + let (s,e) = self.token_span(); + orig.get(s..e).unwrap_or(orig) + } fn draw(&mut self, writer: &mut TermWriter) -> ShResult<()>; fn clear(&mut self, _writer: &mut TermWriter) -> ShResult<()> { Ok(()) @@ -558,8 +564,8 @@ pub trait Completer { #[derive(Default, Debug, Clone)] pub struct ScoredCandidate { - content: String, - score: Option, + pub content: String, + pub score: Option, } impl ScoredCandidate { @@ -766,6 +772,22 @@ impl FuzzySelector { } } + pub fn candidates(&self) -> &[String] { + &self.candidates + } + + pub fn filtered(&self) -> &[ScoredCandidate] { + &self.filtered + } + + pub fn filtered_len(&self) -> usize { + self.filtered.len() + } + + pub fn candidates_len(&self) -> usize { + self.candidates.len() + } + pub fn activate(&mut self, candidates: Vec) { self.active = true; self.candidates = candidates; @@ -1119,6 +1141,9 @@ impl Default for FuzzyCompleter { } impl Completer for FuzzyCompleter { + fn all_candidates(&self) -> Vec { + self.selector.candidates.clone() + } fn set_prompt_line_context(&mut self, line_width: u16, cursor_col: u16) { self .selector @@ -1209,6 +1234,9 @@ pub struct SimpleCompleter { } impl Completer for SimpleCompleter { + fn all_candidates(&self) -> Vec { + self.candidates.clone() + } fn reset_stay_active(&mut self) { let active = self.is_active(); self.reset(); diff --git a/src/readline/mod.rs b/src/readline/mod.rs index 0fb15a1..111e94b 100644 --- a/src/readline/mod.rs +++ b/src/readline/mod.rs @@ -16,8 +16,7 @@ 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, Var, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars }; use crate::{ libsh::error::ShResult, @@ -319,6 +318,10 @@ impl ShedVi { self.completer.reset_stay_active(); self.needs_redraw = true; Ok(()) + } else if self.history.fuzzy_finder.is_active() { + self.history.fuzzy_finder.reset_stay_active(); + self.needs_redraw = true; + Ok(()) } else { self.reset(full_redraw) } @@ -646,10 +649,16 @@ impl ShedVi { } Ok(None) => { let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart)); + let candidates = self.completer.all_candidates(); + let num_candidates = candidates.len(); + with_vars([ + ("_NUM_MATCHES".into(), Into::::into(num_candidates)), + ("_MATCHES".into(), Into::::into(candidates)), + ("_SEARCH_STR".into(), Into::::into(self.completer.token())), + ], || { + post_cmds.exec(); + }); - post_cmds.exec(); - - self.writer.send_bell().ok(); if self.completer.is_active() { write_vars(|v| { v.set_var( @@ -662,7 +671,9 @@ impl ShedVi { self.prompt.refresh(); self.needs_redraw = true; self.editor.set_hint(None); - } + } else { + self.writer.send_bell().ok(); + } } } @@ -673,7 +684,9 @@ impl ShedVi { 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())], || { + with_vars([ + ("_HIST_ENTRY".into(), entry.clone()), + ], || { post_cmds.exec_with(&entry); }); @@ -686,9 +699,26 @@ impl ShedVi { } None => { let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen)); - post_cmds.exec(); + let entries = self.history.fuzzy_finder.candidates(); + let matches = self.history.fuzzy_finder + .filtered() + .iter() + .cloned() + .map(|sc| sc.content) + .collect::>(); + + let num_entries = entries.len(); + let num_matches = matches.len(); + with_vars([ + ("_ENTRIES".into(),Into::::into(entries)), + ("_NUM_ENTRIES".into(),Into::::into(num_entries)), + ("_MATCHES".into(),Into::::into(matches)), + ("_NUM_MATCHES".into(),Into::::into(num_matches)), + ("_SEARCH_STR".into(), Into::::into(initial)), + ], || { + post_cmds.exec(); + }); - self.writer.send_bell().ok(); if self.history.fuzzy_finder.is_active() { write_vars(|v| { v.set_var( @@ -701,7 +731,9 @@ impl ShedVi { self.prompt.refresh(); self.needs_redraw = true; self.editor.set_hint(None); - } + } else { + self.writer.send_bell().ok(); + } } } } diff --git a/src/state.rs b/src/state.rs index af46741..6cbecef 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,11 +1,5 @@ use std::{ - cell::RefCell, - collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, - fmt::Display, - ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}, - os::unix::fs::PermissionsExt, - str::FromStr, - time::Duration, + cell::RefCell, collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, fmt::Display, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign}, os::unix::fs::PermissionsExt, str::FromStr, time::Duration }; use nix::unistd::{User, gethostname, getppid}; @@ -920,18 +914,46 @@ impl Display for Var { } } -impl From for Var { - fn from(value: String) -> Self { - Self::new(VarKind::Str(value), VarFlags::NONE) - } +impl From> for Var { + fn from(value: Vec) -> Self { + Self::new(VarKind::Arr(value.into()), VarFlags::NONE) + } } -impl From<&str> for Var { - fn from(value: &str) -> Self { - Self::new(VarKind::Str(value.into()), VarFlags::NONE) - } +impl From<&[String]> for Var { + fn from(value: &[String]) -> Self { + let mut new = VecDeque::new(); + new.extend(value.iter().cloned()); + Self::new(VarKind::Arr(new), VarFlags::NONE) + } } +macro_rules! impl_var_from { + ($($t:ty),*) => { + $(impl From<$t> for Var { + fn from(value: $t) -> Self { + Self::new(VarKind::Str(value.to_string()), VarFlags::NONE) + } + })* + }; +} + +impl_var_from!( + i8, + i16, + i32, + i64, + isize, + u8, + u16, + u32, + u64, + usize, + String, + &str, + bool +); + #[derive(Default, Clone, Debug)] pub struct VarTab { vars: HashMap,