[API] Allow the Completer impl to be stateful (#379)

Changes the completer trait to take a mutable reference of itself on
calling `Completer::complete`

This enables the `Completer` to do caching or other smart things on the fly.
This commit is contained in:
Stefan Holderbach 2022-04-04 23:21:13 +02:00 committed by GitHub
parent 698190c534
commit 6332747730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 58 additions and 54 deletions

View File

@ -30,13 +30,13 @@ impl Span {
pub trait Completer: Send {
/// the action that will take the line and position and convert it to a vector of completions, which include the
/// span to replace and the contents of that replacement
fn complete(&self, line: &str, pos: usize) -> Vec<Suggestion>;
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion>;
/// action that will return a partial section of available completions
/// this command comes handy when trying to avoid to pull all the data at once
/// from the completer
fn partial_complete(
&self,
&mut self,
line: &str,
pos: usize,
start: usize,
@ -50,7 +50,7 @@ pub trait Completer: Send {
}
/// number of available completions
fn total_completions(&self, line: &str, pos: usize) -> usize {
fn total_completions(&mut self, line: &str, pos: usize) -> usize {
self.complete(line, pos).len()
}
}

View File

@ -28,7 +28,11 @@ impl CircularCompletionHandler {
// in the line_buffer only the specified range of characters.
// If internal index is 0 it means that is the first tab event pressed.
// If internal index is greater than completions vector, we bring it back to 0.
pub(crate) fn handle(&mut self, completer: &dyn Completer, present_buffer: &mut LineBuffer) {
pub(crate) fn handle(
&mut self,
completer: &mut dyn Completer,
present_buffer: &mut LineBuffer,
) {
if let Some(last_buffer) = &self.last_buffer {
if last_buffer != present_buffer {
self.reset_index();
@ -93,58 +97,58 @@ mod test {
#[test]
fn repetitive_calls_to_handle_works() {
let mut tab = CircularCompletionHandler::default();
let comp = get_completer(vec!["login", "logout"]);
let mut comp = get_completer(vec!["login", "logout"]);
let mut buf = buffer_with("lo");
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("login"));
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("logout"));
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("lo"));
}
#[test]
fn behaviour_with_hyphens_and_underscores() {
let mut tab = CircularCompletionHandler::default();
let comp = get_completer(vec!["test-hyphen", "test_underscore"]);
let mut comp = get_completer(vec!["test-hyphen", "test_underscore"]);
let mut buf = buffer_with("te");
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("test"));
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("te"));
}
#[test]
fn auto_resets_on_new_query() {
let mut tab = CircularCompletionHandler::default();
let comp = get_completer(vec!["login", "logout", "exit"]);
let mut comp = get_completer(vec!["login", "logout", "exit"]);
let mut buf = buffer_with("log");
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("login"));
let mut new_buf = buffer_with("ex");
tab.handle(&comp, &mut new_buf);
tab.handle(&mut comp, &mut new_buf);
assert_eq!(new_buf, buffer_with("exit"));
}
#[test]
fn same_string_different_places() {
let mut tab = CircularCompletionHandler::default();
let comp = get_completer(vec!["that", "this"]);
let mut comp = get_completer(vec!["that", "this"]);
let mut buf = buffer_with("th is my test th");
// Hitting tab after `th` fills the first completion `that`
buf.set_insertion_point(2);
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
let mut expected_buffer = buffer_with("that is my test th");
expected_buffer.set_insertion_point(4);
assert_eq!(buf, expected_buffer);
// updating the cursor to end should reset the completions
buf.set_insertion_point(18);
tab.handle(&comp, &mut buf);
tab.handle(&mut comp, &mut buf);
assert_eq!(buf, buffer_with("that is my test that"));
}
}

View File

@ -68,7 +68,7 @@ impl Completer for DefaultCompleter {
/// Suggestion {value: "batmobile".into(), description: None, extra: None, span: Span { start: 7, end: 10 }},
/// ]);
/// ```
fn complete(&self, line: &str, pos: usize) -> Vec<Suggestion> {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut span_line_whitespaces = 0;
let mut completions = vec![];
if !line.is_empty() {

View File

@ -13,7 +13,7 @@ pub(crate) struct HistoryCompleter<'menu>(&'menu dyn History);
unsafe impl<'menu> Send for HistoryCompleter<'menu> {}
impl<'menu> Completer for HistoryCompleter<'menu> {
fn complete(&self, line: &str, pos: usize) -> Vec<Suggestion> {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let parsed = parse_selection_char(line, SELECTION_CHAR);
let values = self.0.query_entries(parsed.remainder);
@ -24,7 +24,7 @@ impl<'menu> Completer for HistoryCompleter<'menu> {
}
fn partial_complete(
&self,
&mut self,
line: &str,
pos: usize,
start: usize,
@ -39,7 +39,7 @@ impl<'menu> Completer for HistoryCompleter<'menu> {
.collect()
}
fn total_completions(&self, _line: &str, _pos: usize) -> usize {
fn total_completions(&mut self, _line: &str, _pos: usize) -> usize {
self.0.max_values()
}
}

View File

@ -382,7 +382,7 @@ mod test {
}
fn str_to_edit_commands(s: &str) -> Vec<EditCommand> {
s.chars().map(|c| EditCommand::InsertChar(c)).collect()
s.chars().map(EditCommand::InsertChar).collect()
}
#[test]

View File

@ -582,7 +582,7 @@ impl Reedline {
if self.quick_completions && menu.can_quick_complete() {
menu.update_values(
self.editor.line_buffer(),
self.completer.as_ref(),
self.completer.as_mut(),
self.history.as_ref(),
);
@ -595,7 +595,7 @@ impl Reedline {
&& menu.can_partially_complete(
self.quick_completions,
self.editor.line_buffer(),
self.completer.as_ref(),
self.completer.as_mut(),
self.history.as_ref(),
)
{
@ -692,7 +692,7 @@ impl Reedline {
ReedlineEvent::ActionHandler => {
let line_buffer = self.editor.line_buffer();
self.circular_completion_handler
.handle(self.completer.as_ref(), line_buffer);
.handle(self.completer.as_mut(), line_buffer);
Ok(EventStatus::Handled)
}
ReedlineEvent::Esc => {
@ -770,7 +770,7 @@ impl Reedline {
menu.menu_event(MenuEvent::Edit(self.quick_completions));
menu.update_values(
self.editor.line_buffer(),
self.completer.as_ref(),
self.completer.as_mut(),
self.history.as_ref(),
);
@ -1183,7 +1183,7 @@ impl Reedline {
if menu.is_active() {
menu.update_working_details(
self.editor.line_buffer(),
self.completer.as_ref(),
self.completer.as_mut(),
self.history.as_ref(),
&self.painter,
);

View File

@ -449,7 +449,7 @@ impl Menu for ColumnarMenu {
&mut self,
values_updated: bool,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
) -> bool {
// If the values were already updated (e.g. quick completions are true)
// there is no need to update the values from the menu
@ -503,7 +503,7 @@ impl Menu for ColumnarMenu {
}
/// Updates menu values
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &dyn Completer) {
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
if self.only_buffer_difference {
if let Some(old_string) = &self.input {
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
@ -530,7 +530,7 @@ impl Menu for ColumnarMenu {
fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
painter: &Painter,
) {
if let Some(event) = self.event.take() {

View File

@ -365,7 +365,7 @@ impl Menu for ListMenu {
&mut self,
_values_updated: bool,
_line_buffer: &mut LineBuffer,
_completer: &dyn Completer,
_completer: &mut dyn Completer,
) -> bool {
false
}
@ -385,7 +385,7 @@ impl Menu for ListMenu {
}
/// Collecting the value from the completer to be shown in the menu
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &dyn Completer) {
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
let (start, input) = if self.only_buffer_difference {
match &self.input {
Some(old_string) => {
@ -471,7 +471,7 @@ impl Menu for ListMenu {
fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
painter: &Painter,
) {
if let Some(event) = self.event.clone() {

View File

@ -83,7 +83,7 @@ pub trait Menu: Send {
&mut self,
values_updated: bool,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
) -> bool;
/// Updates the values presented in the menu
@ -91,7 +91,7 @@ pub trait Menu: Send {
/// activated or the `quick_completion` option is true, the len of the values
/// is calculated to know if there is only one value so it can be selected
/// immediately
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &dyn Completer);
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer);
/// The working details of a menu are values that could change based on
/// the menu conditions before it being printed, such as the number or size
@ -101,7 +101,7 @@ pub trait Menu: Send {
fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
painter: &Painter,
);
@ -158,7 +158,7 @@ impl ReedlineMenu {
&mut self,
values_updated: bool,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
history: &dyn History,
) -> bool {
match self {
@ -166,39 +166,39 @@ impl ReedlineMenu {
menu.can_partially_complete(values_updated, line_buffer, completer)
}
Self::HistoryMenu(menu) => {
let history_completer = HistoryCompleter::new(history);
menu.can_partially_complete(values_updated, line_buffer, &history_completer)
let mut history_completer = HistoryCompleter::new(history);
menu.can_partially_complete(values_updated, line_buffer, &mut history_completer)
}
Self::WithCompleter {
menu,
completer: own_completer,
} => menu.can_partially_complete(values_updated, line_buffer, own_completer.as_ref()),
} => menu.can_partially_complete(values_updated, line_buffer, own_completer.as_mut()),
}
}
pub(crate) fn update_values(
&mut self,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
history: &dyn History,
) {
match self {
Self::EngineCompleter(menu) => menu.update_values(line_buffer, completer),
Self::HistoryMenu(menu) => {
let history_completer = HistoryCompleter::new(history);
menu.update_values(line_buffer, &history_completer)
let mut history_completer = HistoryCompleter::new(history);
menu.update_values(line_buffer, &mut history_completer)
}
Self::WithCompleter {
menu,
completer: own_completer,
} => menu.update_values(line_buffer, own_completer.as_ref()),
} => menu.update_values(line_buffer, own_completer.as_mut()),
}
}
pub(crate) fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
history: &dyn History,
painter: &Painter,
) {
@ -207,13 +207,13 @@ impl ReedlineMenu {
menu.update_working_details(line_buffer, completer, painter)
}
Self::HistoryMenu(menu) => {
let history_completer = HistoryCompleter::new(history);
menu.update_working_details(line_buffer, &history_completer, painter)
let mut history_completer = HistoryCompleter::new(history);
menu.update_working_details(line_buffer, &mut history_completer, painter)
}
Self::WithCompleter {
menu,
completer: own_completer,
} => menu.update_working_details(line_buffer, own_completer.as_ref(), painter),
} => menu.update_working_details(line_buffer, own_completer.as_mut(), painter),
}
}
}
@ -243,7 +243,7 @@ impl Menu for ReedlineMenu {
&mut self,
values_updated: bool,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
) -> bool {
match self {
Self::EngineCompleter(menu) => {
@ -255,25 +255,25 @@ impl Menu for ReedlineMenu {
Self::WithCompleter {
menu,
completer: own_completer,
} => menu.can_partially_complete(values_updated, line_buffer, own_completer.as_ref()),
} => menu.can_partially_complete(values_updated, line_buffer, own_completer.as_mut()),
}
}
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &dyn Completer) {
fn update_values(&mut self, line_buffer: &mut LineBuffer, completer: &mut dyn Completer) {
match self {
Self::EngineCompleter(menu) => menu.update_values(line_buffer, completer),
Self::HistoryMenu(menu) => menu.update_values(line_buffer, completer),
Self::WithCompleter {
menu,
completer: own_completer,
} => menu.update_values(line_buffer, own_completer.as_ref()),
} => menu.update_values(line_buffer, own_completer.as_mut()),
}
}
fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
completer: &dyn Completer,
completer: &mut dyn Completer,
painter: &Painter,
) {
match self {
@ -284,7 +284,7 @@ impl Menu for ReedlineMenu {
Self::WithCompleter {
menu,
completer: own_completer,
} => menu.update_working_details(line_buffer, own_completer.as_ref(), painter),
} => menu.update_working_details(line_buffer, own_completer.as_mut(), painter),
}
}