Expose completion/history metadata to autocmd hooks and add broader Var conversion impls

This commit is contained in:
2026-03-06 02:01:45 -05:00
parent dc0ff23903
commit 330afd0141
3 changed files with 108 additions and 25 deletions

View File

@@ -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<String> { vec![] }
fn selected_candidate(&self) -> Option<String>;
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<i32>,
pub content: String,
pub score: Option<i32>,
}
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<String>) {
self.active = true;
self.candidates = candidates;
@@ -1119,6 +1141,9 @@ impl Default for FuzzyCompleter {
}
impl Completer for FuzzyCompleter {
fn all_candidates(&self) -> Vec<String> {
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<String> {
self.candidates.clone()
}
fn reset_stay_active(&mut self) {
let active = self.is_active();
self.reset();

View File

@@ -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,17 @@ 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::<Var>::into(num_candidates)),
("_MATCHES".into(), Into::<Var>::into(candidates)),
("_SEARCH_STR".into(), Into::<Var>::into(self.completer.token())),
], || {
});
post_cmds.exec();
self.writer.send_bell().ok();
if self.completer.is_active() {
write_vars(|v| {
v.set_var(
@@ -662,7 +672,9 @@ impl ShedVi {
self.prompt.refresh();
self.needs_redraw = true;
self.editor.set_hint(None);
}
} else {
self.writer.send_bell().ok();
}
}
}
@@ -673,7 +685,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 +700,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::<Vec<_>>();
let num_entries = entries.len();
let num_matches = matches.len();
with_vars([
("_ENTRIES".into(),Into::<Var>::into(entries)),
("_NUM_ENTRIES".into(),Into::<Var>::into(num_entries)),
("_MATCHES".into(),Into::<Var>::into(matches)),
("_NUM_MATCHES".into(),Into::<Var>::into(num_matches)),
("_SEARCH_STR".into(), Into::<Var>::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 +732,9 @@ impl ShedVi {
self.prompt.refresh();
self.needs_redraw = true;
self.editor.set_hint(None);
}
} else {
self.writer.send_bell().ok();
}
}
}
}

View File

@@ -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<String> for Var {
fn from(value: String) -> Self {
Self::new(VarKind::Str(value), VarFlags::NONE)
}
impl From<Vec<String>> for Var {
fn from(value: Vec<String>) -> 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<String, Var>,