Early implementation of fuzzy completion menu

This commit is contained in:
2026-03-02 01:54:23 -05:00
parent 6d2d94b6a7
commit a2b8fc203f
9 changed files with 469 additions and 72 deletions

View File

@@ -10,12 +10,13 @@ use crate::expand::expand_prompt;
use crate::libsh::sys::TTY_FILENO;
use crate::parse::lex::{LexStream, QuoteState};
use crate::prelude::*;
use crate::readline::complete::FuzzyCompleter;
use crate::readline::term::{Pos, calc_str_width};
use crate::state::{ShellParam, read_shopts};
use crate::{
libsh::error::ShResult,
parse::lex::{self, LexFlags, Tk, TkFlags, TkRule},
readline::{complete::Completer, highlight::Highlighter},
readline::{complete::{CompResponse, Completer}, highlight::Highlighter},
};
pub mod complete;
@@ -206,7 +207,7 @@ pub struct ShedVi {
pub prompt: Prompt,
pub highlighter: Highlighter,
pub completer: Completer,
pub completer: Box<dyn Completer>,
pub mode: Box<dyn ViMode>,
pub repeat_action: Option<CmdReplay>,
@@ -225,7 +226,7 @@ impl ShedVi {
reader: PollReader::new(),
writer: TermWriter::new(tty),
prompt,
completer: Completer::new(),
completer: Box::new(FuzzyCompleter::default()),
highlighter: Highlighter::new(),
mode: Box::new(ViInsert::new()),
old_layout: None,
@@ -319,6 +320,44 @@ impl ShedVi {
// Process all available keys
while let Some(key) = self.reader.read_key()? {
// If completer is active, delegate input to it
if self.completer.is_active() {
match self.completer.handle_key(key.clone())? {
CompResponse::Accept(candidate) => {
let span_start = self.completer.token_span().0;
let new_cursor = span_start + candidate.len();
let line = self.completer.get_completed_line(&candidate);
self.editor.set_buffer(line);
self.editor.cursor.set(new_cursor);
// Don't reset yet — clear() needs old_layout to erase the selector.
if !self.history.at_pending() {
self.history.reset_to_pending();
}
self
.history
.update_pending_cmd((self.editor.as_str(), self.editor.cursor.get()));
let hint = self.history.get_hint();
self.editor.set_hint(hint);
self.completer.clear(&mut self.writer)?;
self.needs_redraw = true;
continue;
}
CompResponse::Dismiss => {
self.completer.clear(&mut self.writer)?;
// Don't reset yet — clear() needs old_layout to erase the selector.
// The next print_line() will call clear(), then we can reset.
continue;
}
CompResponse::Consumed => {
/* just redraw */
self.needs_redraw = true;
continue;
}
CompResponse::Passthrough => { /* fall through to normal handling below */ }
}
}
if self.should_accept_hint(&key) {
self.editor.accept_hint();
if !self.history.at_pending() {
@@ -347,7 +386,7 @@ impl ShedVi {
self.old_layout = None;
}
Ok(Some(line)) => {
let span_start = self.completer.token_span.0;
let span_start = self.completer.token_span().0;
let new_cursor = span_start
+ self
.completer
@@ -556,6 +595,8 @@ impl ShedVi {
.unwrap_or_default() as usize;
let one_line = new_layout.end.row == 0;
self.completer.clear(&mut self.writer)?;
if let Some(layout) = self.old_layout.as_ref() {
self.writer.clear_rows(layout)?;
}
@@ -610,6 +651,8 @@ impl ShedVi {
self.writer.flush_write(&self.mode.cursor_style())?;
self.completer.draw(&mut self.writer)?;
self.old_layout = Some(new_layout);
self.needs_redraw = false;
Ok(())