mirror of
https://github.com/nushell/reedline.git
synced 2024-07-14 19:00:30 +03:00
Add list style completions (#162)
This commit is contained in:
parent
de0f399650
commit
88bded3417
@ -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),
|
||||
));
|
||||
```
|
||||
|
||||
|
@ -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 {
|
@ -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
194
src/completion/list.rs
Normal 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"));
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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>(())
|
||||
/// ```
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user