Skip to content

Add match_indices field to Suggestion #798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ crossbeam = { version = "0.8.2", optional = true }
crossterm = { version = "0.28.1", features = ["serde"] }
fd-lock = "4.0.2"
itertools = "0.13.0"
lazy_static = "1.5.0"
nu-ansi-term = "0.50.0"
regex = "1.11"
rusqlite = { version = "0.31.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0.79", optional = true }
Expand Down
138 changes: 138 additions & 0 deletions examples/fuzzy_completions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Modifies the completions example to demonstrate highlighting of fuzzy completions
// cargo run --example fuzzy_completions
//
// One of the suggestions is "multiple 汉 by̆tes字👩🏾". Try typing in "y" or "👩" and note how
// the entire grapheme "y̆" or "👩🏾" is highlighted (might not look right in your terminal).

use nu_ansi_term::{Color, Style};
use reedline::{
default_emacs_keybindings, ColumnarMenu, Completer, DefaultPrompt, EditCommand, Emacs, KeyCode,
KeyModifiers, Keybindings, MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu, Signal, Span,
Suggestion,
};
use std::io;
use unicode_segmentation::UnicodeSegmentation;

struct HomegrownFuzzyCompleter(Vec<String>);

impl Completer for HomegrownFuzzyCompleter {
fn complete(&mut self, line: &str, pos: usize) -> Vec<reedline::Suggestion> {
// Grandma's fuzzy matching recipe. She swears it's better than that crates.io-bought stuff
self.0
.iter()
.filter_map(|command_str| {
let command = command_str.graphemes(true).collect::<Vec<_>>();
let mut ind = 0;
let mut match_indices = Vec::new();
for g in line[..pos].graphemes(true) {
while ind < command.len() && command[ind] != g {
ind += 1;
}
if ind == command.len() {
return None;
}
match_indices.push(ind);
ind += 1;
}

Some(Suggestion {
value: command_str.to_string(),
description: None,
style: None,
extra: None,
span: Span::new(0, pos),
append_whitespace: false,
match_indices: Some(match_indices),
})
})
.collect()
}
}

fn add_menu_keybindings(keybindings: &mut Keybindings) {
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
KeyModifiers::ALT,
KeyCode::Enter,
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
);
}

fn main() -> io::Result<()> {
// Number of columns
let columns: u16 = 4;
// Column width
let col_width: Option<usize> = None;
// Column padding
let col_padding: usize = 2;

let commands = vec![
"test".into(),
"clear".into(),
"exit".into(),
"history 1".into(),
"history 2".into(),
"logout".into(),
"login".into(),
"hello world".into(),
"hello world reedline".into(),
"hello world something".into(),
"hello world another".into(),
"hello world 1".into(),
"hello world 2".into(),
"hello another very large option for hello word that will force one column".into(),
"this is the reedline crate".into(),
"abaaabas".into(),
"abaaacas".into(),
"ababac".into(),
"abacaxyc".into(),
"abadarabc".into(),
"multiple 汉 by̆tes字👩🏾".into(),
"ab汉 by̆tes👩🏾".into(),
];

let completer = Box::new(HomegrownFuzzyCompleter(commands));

// Use the interactive menu to select options from the completer
let columnar_menu = ColumnarMenu::default()
.with_name("completion_menu")
.with_columns(columns)
.with_column_width(col_width)
.with_column_padding(col_padding)
.with_text_style(Style::new().italic().on(Color::LightGreen))
.with_match_text_style(Style::new().on(Color::LightBlue));

let completion_menu = Box::new(columnar_menu);

let mut keybindings = default_emacs_keybindings();
add_menu_keybindings(&mut keybindings);

let edit_mode = Box::new(Emacs::new(keybindings));

let mut line_editor = Reedline::create()
.with_completer(completer)
.with_menu(ReedlineMenu::EngineCompleter(completion_menu))
.with_edit_mode(edit_mode);

let prompt = DefaultPrompt::default();

loop {
let sig = line_editor.read_line(&prompt)?;
match sig {
Signal::Success(buffer) => {
println!("We processed: {buffer}");
}
Signal::CtrlD | Signal::CtrlC => {
println!("\nAborted!");
break Ok(());
}
}
}
}
3 changes: 3 additions & 0 deletions src/completion/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,7 @@ pub struct Suggestion {
/// Whether to append a space after selecting this suggestion.
/// This helps to avoid that a completer repeats the complete suggestion.
pub append_whitespace: bool,
/// Indices of the graphemes in the suggestion that matched the typed text.
/// Useful if using fuzzy matching.
pub match_indices: Option<Vec<usize>>,
}
25 changes: 16 additions & 9 deletions src/completion/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ impl Completer for DefaultCompleter {
/// assert_eq!(
/// completions.complete("bat",3),
/// vec![
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false, ..Default::default()},
/// ]);
///
/// assert_eq!(
/// completions.complete("to the\r\nbat",11),
/// vec![
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false},
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false, ..Default::default()},
/// ]);
/// ```
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
Expand Down Expand Up @@ -110,6 +110,7 @@ impl Completer for DefaultCompleter {
extra: None,
span,
append_whitespace: false,
..Default::default()
}
})
.filter(|t| t.value.len() > (t.span.end - t.span.start))
Expand Down Expand Up @@ -182,15 +183,15 @@ impl DefaultCompleter {
/// completions.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
/// assert_eq!(
/// completions.complete("te",2),
/// vec![Suggestion {value: "test".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false}]);
/// vec![Suggestion {value: "test".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false, ..Default::default()}]);
///
/// let mut completions = DefaultCompleter::with_inclusions(&['-', '_']);
/// completions.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
/// assert_eq!(
/// completions.complete("te",2),
/// vec![
/// Suggestion {value: "test-hyphen".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false},
/// Suggestion {value: "test_underscore".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false},
/// Suggestion {value: "test-hyphen".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "test_underscore".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false, ..Default::default()},
/// ]);
/// ```
pub fn with_inclusions(incl: &[char]) -> Self {
Expand Down Expand Up @@ -384,6 +385,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 3 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "number".into(),
Expand All @@ -392,6 +394,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 3 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "nushell".into(),
Expand All @@ -400,6 +403,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 3 },
append_whitespace: false,
..Default::default()
},
]
);
Expand Down Expand Up @@ -428,6 +432,7 @@ mod tests {
extra: None,
span: Span { start: 8, end: 9 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "this is the reedline crate".into(),
Expand All @@ -436,6 +441,7 @@ mod tests {
extra: None,
span: Span { start: 8, end: 9 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "this is the reedline crate".into(),
Expand All @@ -444,6 +450,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 9 },
append_whitespace: false,
..Default::default()
},
]
);
Expand Down
1 change: 1 addition & 0 deletions src/completion/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl<'menu> HistoryCompleter<'menu> {
extra: None,
span,
append_whitespace: false,
..Default::default()
}
}
}
Expand Down
Loading
Loading