Add list style completions (#162)

This commit is contained in:
JT 2021-10-05 10:32:21 +13:00 committed by GitHub
parent de0f399650
commit 88bded3417
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 226 additions and 33 deletions

View File

@ -104,7 +104,7 @@ Reedline::create()?.with_highlighter(Box::new(DefaultHighlighter::new(commands))
```rust,no_run
// Create a reedline object with tab completions support
use reedline::{DefaultCompleter, DefaultCompletionActionHandler, Reedline};
use reedline::{DefaultCompleter, CircularCompletionHandler, Reedline};
let commands = vec![
"test".into(),
@ -115,7 +115,7 @@ let commands = vec![
let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
let mut line_editor = Reedline::create()?.with_completion_action_handler(Box::new(
DefaultCompletionActionHandler::default().with_completer(completer),
CircularCompletionHandler::default().with_completer(completer),
));
```

View File

@ -1,7 +1,7 @@
use crate::{core_editor::LineBuffer, Completer, CompletionActionHandler, DefaultCompleter};
/// A simple handler that will do a cycle-based rotation through the options given by the Completer
pub struct DefaultCompletionActionHandler {
pub struct CircularCompletionHandler {
completer: Box<dyn Completer>,
initial_line: LineBuffer,
index: usize,
@ -9,8 +9,8 @@ pub struct DefaultCompletionActionHandler {
last_buffer: Option<LineBuffer>,
}
impl DefaultCompletionActionHandler {
/// Build a `DefaultCompletionActionHandler` configured to use a specific completer
impl CircularCompletionHandler {
/// Build a `CircularCompletionHandler` configured to use a specific completer
///
/// # Arguments
///
@ -18,7 +18,7 @@ impl DefaultCompletionActionHandler {
///
/// # Example
/// ```
/// use reedline::{DefaultCompletionActionHandler, DefaultCompleter, Completer, Span};
/// use reedline::{CircularCompletionHandler, DefaultCompleter, Completer, Span};
///
/// let mut completer = DefaultCompleter::default();
/// completer.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
@ -26,19 +26,16 @@ impl DefaultCompletionActionHandler {
/// completer.complete("te",2),
/// vec![(Span { start: 0, end: 2 }, "test".into())]);
///
/// let mut completions = DefaultCompletionActionHandler::default().with_completer(Box::new(completer));
/// let mut completions = CircularCompletionHandler::default().with_completer(Box::new(completer));
/// ```
pub fn with_completer(
mut self,
completer: Box<dyn Completer>,
) -> DefaultCompletionActionHandler {
pub fn with_completer(mut self, completer: Box<dyn Completer>) -> CircularCompletionHandler {
self.completer = completer;
self
}
}
impl Default for DefaultCompletionActionHandler {
impl Default for CircularCompletionHandler {
fn default() -> Self {
DefaultCompletionActionHandler {
CircularCompletionHandler {
completer: Box::new(DefaultCompleter::default()),
initial_line: LineBuffer::new(),
index: 0,
@ -47,13 +44,13 @@ impl Default for DefaultCompletionActionHandler {
}
}
impl DefaultCompletionActionHandler {
impl CircularCompletionHandler {
fn reset_index(&mut self) {
self.index = 0;
}
}
impl CompletionActionHandler for DefaultCompletionActionHandler {
impl CompletionActionHandler for CircularCompletionHandler {
// With this function we handle the tab events.
//
// If completions vector is not empty we proceed to replace
@ -106,11 +103,11 @@ mod test {
use super::*;
use pretty_assertions::assert_eq;
fn get_tab_handler_with(values: Vec<&'_ str>) -> DefaultCompletionActionHandler {
fn get_tab_handler_with(values: Vec<&'_ str>) -> CircularCompletionHandler {
let mut completer = DefaultCompleter::default();
completer.insert(values.iter().map(|s| s.to_string()).collect());
DefaultCompletionActionHandler::default().with_completer(Box::new(completer))
CircularCompletionHandler::default().with_completer(Box::new(completer))
}
fn buffer_with(content: &str) -> LineBuffer {

View File

@ -11,7 +11,7 @@ use crate::{Completer, Span};
/// # Example
///
/// ```rust, no_run
/// use reedline::{DefaultCompleter, DefaultCompletionActionHandler, Reedline};
/// use reedline::{DefaultCompleter, CircularCompletionHandler, Reedline};
///
/// let commands = vec![
/// "test".into(),
@ -23,7 +23,7 @@ use crate::{Completer, Span};
///
/// let mut line_editor = Reedline::create().unwrap()
/// .with_completion_action_handler(Box::new(
/// DefaultCompletionActionHandler::default().with_completer(completer),
/// CircularCompletionHandler::default().with_completer(completer),
/// ));
/// ```
#[derive(Debug, Clone)]

194
src/completion/list.rs Normal file
View File

@ -0,0 +1,194 @@
use crate::{core_editor::LineBuffer, Completer, CompletionActionHandler, DefaultCompleter, Span};
/// A simple handler that will do a cycle-based rotation through the options given by the Completer
pub struct ListCompletionHandler {
completer: Box<dyn Completer>,
complete: bool,
}
impl ListCompletionHandler {
/// Build a `ListCompletionHandler` configured to use a specific completer
///
/// # Arguments
///
/// * `completer` The completion logic to use
///
/// # Example
/// ```
/// use reedline::{ListCompletionHandler, DefaultCompleter, Completer, Span};
///
/// let mut completer = DefaultCompleter::default();
/// completer.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
/// assert_eq!(
/// completer.complete("te",2),
/// vec![(Span { start: 0, end: 2 }, "test".into())]);
///
/// let mut completions = ListCompletionHandler::default().with_completer(Box::new(completer));
/// ```
pub fn with_completer(mut self, completer: Box<dyn Completer>) -> ListCompletionHandler {
self.completer = completer;
self
}
}
impl Default for ListCompletionHandler {
fn default() -> Self {
ListCompletionHandler {
completer: Box::new(DefaultCompleter::default()),
complete: true,
}
}
}
impl CompletionActionHandler for ListCompletionHandler {
// With this function we handle the tab events.
//
// If completions vector is not empty we proceed to replace
// 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.
fn handle(&mut self, present_buffer: &mut LineBuffer) {
// if let Some(last_buffer) = &self.last_buffer {
// if last_buffer != present_buffer {
// self.reset_index();
// }
// }
// // NOTE: This is required to cycle through the tabs for what is presently present in the
// // buffer. Without this `repetitive_calls_to_handle_works` will not work
// if self.index == 0 {
// self.initial_line = present_buffer.clone();
// } else {
// *present_buffer = self.initial_line.clone();
// }
let completions = self
.completer
.complete(present_buffer.get_buffer(), present_buffer.offset());
if completions.is_empty() {
// do nothing
} else if completions.len() == 1 {
let span = completions[0].0;
let mut offset = present_buffer.offset();
offset += completions[0].1.len() - (span.end - span.start);
// TODO improve the support for multiline replace
present_buffer.replace(span.start..span.end, &completions[0].1);
present_buffer.set_insertion_point(offset);
self.complete = true;
} else {
let prefix = calculate_prefix(&completions);
let span = completions[0].0;
let mut offset = present_buffer.offset();
offset += prefix.len() - (span.end - span.start);
present_buffer.replace(span.start..span.end, &prefix);
present_buffer.set_insertion_point(offset);
print!("\r\n");
for completion in completions {
// TODO: make this list pretty
print!("{}\r\n", completion.1);
}
print!("\r\n");
}
}
}
fn calculate_prefix(inputs: &[(Span, String)]) -> String {
let mut iter = inputs.iter();
if let Some(first) = iter.next() {
let prefix = first.1.clone();
let prefix_bytes = prefix.as_bytes();
let mut longest_match = prefix.len();
for i in iter {
longest_match = std::cmp::min(
longest_match,
i.1.as_bytes()
.iter()
.zip(prefix_bytes)
.take_while(|(x, y)| x == y)
.count(),
);
}
prefix[0..longest_match].to_string()
} else {
String::new()
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
fn get_tab_handler_with(values: Vec<&'_ str>) -> ListCompletionHandler {
let mut completer = DefaultCompleter::default();
completer.insert(values.iter().map(|s| s.to_string()).collect());
ListCompletionHandler::default().with_completer(Box::new(completer))
}
fn buffer_with(content: &str) -> LineBuffer {
let mut line_buffer = LineBuffer::new();
line_buffer.insert_str(content);
line_buffer
}
#[test]
fn repetitive_calls_to_handle_works() {
let mut tab = get_tab_handler_with(vec!["login", "logout"]);
let mut buf = buffer_with("lo");
tab.handle(&mut buf);
assert_eq!(buf, buffer_with("log"));
tab.handle(&mut buf);
assert_eq!(buf, buffer_with("log"));
}
#[test]
fn behaviour_with_hyphens_and_underscores() {
let mut tab = get_tab_handler_with(vec!["test-hyphen", "test_underscore"]);
let mut buf = buffer_with("te");
tab.handle(&mut buf);
assert_eq!(buf, buffer_with("test"));
}
#[test]
fn auto_resets_on_new_query() {
let mut tab = get_tab_handler_with(vec!["login", "exit"]);
let mut buf = buffer_with("log");
tab.handle(&mut buf);
assert_eq!(buf, buffer_with("login"));
let mut new_buf = buffer_with("ex");
tab.handle(&mut new_buf);
assert_eq!(new_buf, buffer_with("exit"));
}
#[test]
fn same_string_different_places() {
let mut tab = get_tab_handler_with(vec!["that", "another"]);
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(&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(&mut buf);
assert_eq!(buf, buffer_with("that is my test that"));
}
}

View File

@ -1,7 +1,9 @@
mod base;
mod circular;
mod default;
mod tab_handler;
mod list;
pub use base::{Completer, CompletionActionHandler, Span};
pub use circular::CircularCompletionHandler;
pub use default::DefaultCompleter;
pub use tab_handler::DefaultCompletionActionHandler;
pub use list::ListCompletionHandler;

View File

@ -1,6 +1,6 @@
use {
crate::{
completion::{CompletionActionHandler, DefaultCompletionActionHandler},
completion::{CircularCompletionHandler, CompletionActionHandler},
core_editor::Editor,
edit_mode::{EditMode, Emacs},
enums::ReedlineEvent,
@ -128,7 +128,7 @@ impl Reedline {
input_mode: InputMode::Regular,
painter,
edit_mode,
tab_handler: Box::new(DefaultCompletionActionHandler::default()),
tab_handler: Box::new(CircularCompletionHandler::default()),
terminal_size,
prompt_widget,
highlighter: buffer_highlighter,
@ -178,7 +178,7 @@ impl Reedline {
/// // Create a reedline object with tab completions support
///
/// use std::io;
/// use reedline::{DefaultCompleter, DefaultCompletionActionHandler, Reedline};
/// use reedline::{DefaultCompleter, CircularCompletionHandler, Reedline};
///
/// let commands = vec![
/// "test".into(),
@ -189,7 +189,7 @@ impl Reedline {
/// let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
///
/// let mut line_editor = Reedline::create()?.with_completion_action_handler(Box::new(
/// DefaultCompletionActionHandler::default().with_completer(completer),
/// CircularCompletionHandler::default().with_completer(completer),
/// ));
/// # Ok::<(), io::Error>(())
/// ```

View File

@ -107,7 +107,7 @@
//! // Create a reedline object with tab completions support
//!
//! use std::io;
//! use reedline::{DefaultCompleter, DefaultCompletionActionHandler, Reedline};
//! use reedline::{DefaultCompleter, CircularCompletionHandler, Reedline};
//!
//! let commands = vec![
//! "test".into(),
@ -118,7 +118,7 @@
//! let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
//!
//! let mut line_editor = Reedline::create()?.with_completion_action_handler(Box::new(
//! DefaultCompletionActionHandler::default().with_completer(completer),
//! CircularCompletionHandler::default().with_completer(completer),
//! ));
//! # Ok::<(), io::Error>(())
//! ```
@ -218,7 +218,8 @@ pub use styled_text::StyledText;
mod completion;
pub use completion::{
Completer, CompletionActionHandler, DefaultCompleter, DefaultCompletionActionHandler, Span,
CircularCompletionHandler, Completer, CompletionActionHandler, DefaultCompleter,
ListCompletionHandler, Span,
};
mod hinter;

View File

@ -1,4 +1,4 @@
use reedline::{EditMode, Emacs, Vi};
use reedline::{EditMode, Emacs, ListCompletionHandler, Vi};
use {
crossterm::{
@ -7,9 +7,8 @@ use {
},
nu_ansi_term::{Color, Style},
reedline::{
default_emacs_keybindings, DefaultCompleter, DefaultCompletionActionHandler,
DefaultHighlighter, DefaultHinter, DefaultPrompt, EditCommand, FileBackedHistory, Reedline,
ReedlineEvent, Signal,
default_emacs_keybindings, DefaultCompleter, DefaultHighlighter, DefaultHinter,
DefaultPrompt, EditCommand, FileBackedHistory, Reedline, ReedlineEvent, Signal,
},
std::{
io::{stdout, Write},
@ -60,7 +59,7 @@ fn main() -> Result<()> {
.with_edit_mode(edit_mode)
.with_highlighter(Box::new(DefaultHighlighter::new(commands)))
.with_completion_action_handler(Box::new(
DefaultCompletionActionHandler::default().with_completer(completer.clone()),
ListCompletionHandler::default().with_completer(completer.clone()),
))
.with_hinter(Box::new(
DefaultHinter::default()