feat(shuma): ghosting predictivo en el prompt
shuma-line: ghost_suggestion(line, corpus) — el resto de la línea que el shell predice, a partir de un corpus priorizado. shuma-infer: predict_next(recent, patterns) — si los últimos comandos coinciden con el prefijo de un patrón, devuelve los pasos que faltan. shuma-shell: mientras se escribe, el prompt pinta en gris tenue la continuación predicha — historial reciente o, con prioridad, la secuencia que el motor de inferencia anticipa (cd a un proyecto → fantasma «git pull && cargo build»). La flecha → al final de la línea, o Ctrl+Space, aceptan el fantasma. 13 tests shuma-infer, 37 shuma-line. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
//! Sugerencia fantasma — el "ghosting" predictivo del prompt.
|
||||
//!
|
||||
//! Mientras se escribe, el shell predice el resto de la línea y lo pinta
|
||||
//! en gris tenue. Esta función es el cerebro de esa predicción: dada la
|
||||
//! línea parcial y un corpus de líneas conocidas (historial, secuencias
|
||||
//! inferidas), devuelve el sufijo que falta.
|
||||
//!
|
||||
//! El orden del corpus es la prioridad: el caller pone primero lo más
|
||||
//! relevante (la secuencia predicha por `shuma-infer`), luego el
|
||||
//! historial de lo más reciente a lo más viejo.
|
||||
|
||||
/// Devuelve el sufijo fantasma: lo que falta para completar la primera
|
||||
/// entrada del `corpus` que empieza con `line` y es estrictamente más
|
||||
/// larga. `None` si nada coincide.
|
||||
pub fn ghost_suggestion(line: &str, corpus: &[String]) -> Option<String> {
|
||||
if line.is_empty() {
|
||||
return None;
|
||||
}
|
||||
corpus
|
||||
.iter()
|
||||
.find(|c| c.len() > line.len() && c.starts_with(line))
|
||||
.map(|c| c[line.len()..].to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn suggests_the_remainder_of_a_known_line() {
|
||||
let corpus = vec!["git pull".to_string(), "cargo build".to_string()];
|
||||
assert_eq!(ghost_suggestion("git pu", &corpus), Some("ll".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corpus_order_is_priority() {
|
||||
// Dos coinciden; gana la primera del corpus.
|
||||
let corpus = vec!["cargo build --release".to_string(), "cargo build".to_string()];
|
||||
assert_eq!(
|
||||
ghost_suggestion("cargo b", &corpus),
|
||||
Some("uild --release".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_match_yields_none() {
|
||||
let corpus = vec!["ls -la".to_string()];
|
||||
assert_eq!(ghost_suggestion("git", &corpus), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_line_is_not_a_suggestion() {
|
||||
// El corpus contiene exactamente la línea: nada que sugerir.
|
||||
let corpus = vec!["git pull".to_string()];
|
||||
assert_eq!(ghost_suggestion("git pull", &corpus), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_line_yields_none() {
|
||||
let corpus = vec!["git pull".to_string()];
|
||||
assert_eq!(ghost_suggestion("", &corpus), None);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
pub mod complete;
|
||||
pub mod dialect;
|
||||
pub mod editor;
|
||||
pub mod ghost;
|
||||
pub mod lexer;
|
||||
pub mod pipeline;
|
||||
pub mod token;
|
||||
@@ -30,6 +31,7 @@ pub mod token;
|
||||
pub use complete::{complete, Completion, CompletionKind, CompletionSource, StaticSource};
|
||||
pub use dialect::Dialect;
|
||||
pub use editor::LineState;
|
||||
pub use ghost::ghost_suggestion;
|
||||
pub use lexer::tokenize;
|
||||
pub use pipeline::{split_pipeline, Pipeline, Stage};
|
||||
pub use token::{Token, TokenKind};
|
||||
|
||||
Reference in New Issue
Block a user