Skip to content
Open
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
6 changes: 4 additions & 2 deletions examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use {

#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
use reedline::FileBackedHistory;
use reedline::{CursorConfig, MenuBuilder};
use reedline::{CursorConfig, MenuBuilder, OutputMode};

fn main() -> reedline::Result<()> {
println!("Ctrl-D to quit");
Expand Down Expand Up @@ -102,7 +102,9 @@ fn main() -> reedline::Result<()> {
ColumnarMenu::default().with_name("completion_menu"),
)))
.with_menu(ReedlineMenu::HistoryMenu(Box::new(
ListMenu::default().with_name("history_menu"),
ListMenu::default()
.with_name("history_menu")
.with_output_mode(OutputMode::FullBuffer),
)));

let edit_mode: Box<dyn EditMode> = if vi_mode {
Expand Down
3 changes: 3 additions & 0 deletions src/completion/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ impl<'menu> HistoryCompleter<'menu> {
Self(history)
}

/// Assumes `line.len() <= pos` (i.e. `line` is the cursor-prefix slice).
/// Update this span calculation before HistoryMenu opts into `InputMode::FullBuffer`,
/// where `line` would be the entire buffer and `pos - line.len()` would underflow.
fn create_suggestion(&self, line: &str, pos: usize, value: &str) -> Suggestion {
let span = Span {
start: pos - line.len(),
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,9 @@ pub use validator::{DefaultValidator, ValidationResult, Validator};

mod menu;
pub use menu::{
menu_functions, ColumnarMenu, DescriptionMenu, DescriptionMode, IdeMenu, ListMenu, Menu,
MenuBuilder, MenuEvent, MenuSettings, MenuTextStyle, ReedlineMenu, TraversalDirection,
menu_functions, ColumnarMenu, DescriptionMenu, DescriptionMode, IdeMenu, InputMode, ListMenu,
Menu, MenuBuilder, MenuEvent, MenuSettings, MenuTextStyle, OutputMode, ReedlineMenu,
TraversalDirection,
};

mod terminal_extensions;
Expand Down
17 changes: 4 additions & 13 deletions src/menu/columnar_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use super::{Menu, MenuBuilder, MenuEvent, MenuSettings};
use crate::{
core_editor::Editor,
menu_functions::{
can_partially_complete, completer_input, floor_char_boundary, get_match_indices,
replace_in_buffer, style_suggestion, truncate_with_ansi,
can_partially_complete, floor_char_boundary, get_match_indices, replace_in_buffer,
resolve_completer_input, style_suggestion, truncate_with_ansi,
},
painting::Painter,
Completer, Suggestion,
Expand Down Expand Up @@ -551,16 +551,7 @@ impl Menu for ColumnarMenu {

/// Updates menu values
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
if self.settings.only_buffer_difference && self.input.is_none() {
self.input = Some(editor.get_buffer().to_string());
}

let (input, pos) = completer_input(
editor.get_buffer(),
editor.insertion_point(),
self.input.as_deref(),
self.settings.only_buffer_difference,
);
let (input, pos) = resolve_completer_input(editor, &mut self.input, &self.settings);

let (values, base_ranges) = completer.complete_with_base_ranges(&input, pos);

Expand Down Expand Up @@ -681,7 +672,7 @@ impl Menu for ColumnarMenu {

/// The buffer gets replaced in the Span location
fn replace_in_buffer(&self, editor: &mut Editor) {
replace_in_buffer(self.get_value(), editor);
replace_in_buffer(self.get_value(), editor, self.settings.output_mode);
}

/// Minimum rows that should be displayed by the menu
Expand Down
15 changes: 3 additions & 12 deletions src/menu/description_menu.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
super::MenuSettings,
crate::{
menu_functions::{completer_input, replace_in_buffer},
menu_functions::{replace_in_buffer, resolve_completer_input},
Completer, Editor, Menu, MenuBuilder, MenuEvent, Painter, Suggestion,
},
nu_ansi_term::ansi::RESET,
Expand Down Expand Up @@ -443,16 +443,7 @@ impl Menu for DescriptionMenu {

/// Updates menu values
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
if self.settings.only_buffer_difference && self.input.is_none() {
self.input = Some(editor.get_buffer().to_string());
}

let (input, pos) = completer_input(
editor.get_buffer(),
editor.insertion_point(),
self.input.as_deref(),
self.settings.only_buffer_difference,
);
let (input, pos) = resolve_completer_input(editor, &mut self.input, &self.settings);
self.values = completer.complete(&input, pos);

self.reset_position();
Expand Down Expand Up @@ -593,7 +584,7 @@ impl Menu for DescriptionMenu {
.expect("the example index is always checked");
suggestion.value.clone_from(example);
}
replace_in_buffer(Some(suggestion), editor);
replace_in_buffer(Some(suggestion), editor, self.settings.output_mode);
}
}

Expand Down
17 changes: 4 additions & 13 deletions src/menu/ide_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use super::{Menu, MenuBuilder, MenuEvent, MenuSettings};
use crate::{
core_editor::Editor,
menu_functions::{
can_partially_complete, completer_input, floor_char_boundary, get_match_indices,
replace_in_buffer, style_suggestion, truncate_with_ansi,
can_partially_complete, floor_char_boundary, get_match_indices, replace_in_buffer,
resolve_completer_input, style_suggestion, truncate_with_ansi,
},
painting::Painter,
Completer, Suggestion,
Expand Down Expand Up @@ -620,16 +620,7 @@ impl Menu for IdeMenu {

/// Update menu values
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
if self.settings.only_buffer_difference && self.input.is_none() {
self.input = Some(editor.get_buffer().to_string());
}

let (input, pos) = completer_input(
editor.get_buffer(),
editor.insertion_point(),
self.input.as_deref(),
self.settings.only_buffer_difference,
);
let (input, pos) = resolve_completer_input(editor, &mut self.input, &self.settings);
let (values, base_ranges) = completer.complete_with_base_ranges(&input, pos);

self.values = values;
Expand Down Expand Up @@ -817,7 +808,7 @@ impl Menu for IdeMenu {

/// The buffer gets replaced in the Span location
fn replace_in_buffer(&self, editor: &mut Editor) {
replace_in_buffer(self.get_value(), editor);
replace_in_buffer(self.get_value(), editor, self.settings.output_mode);
}

/// Minimum rows that should be displayed by the menu
Expand Down
15 changes: 3 additions & 12 deletions src/menu/list_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
super::{menu_functions::parse_selection_char, Menu, MenuBuilder, MenuEvent, MenuSettings},
crate::{
core_editor::Editor,
menu_functions::{completer_input, replace_in_buffer},
menu_functions::{replace_in_buffer, resolve_completer_input},
painting::{estimate_single_line_wraps, Painter},
Completer, Suggestion,
},
Expand Down Expand Up @@ -346,16 +346,7 @@ impl Menu for ListMenu {

/// Collecting the value from the completer to be shown in the menu
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
if self.settings.only_buffer_difference && self.input.is_none() {
self.input = Some(editor.get_buffer().to_string());
}

let (input, pos) = completer_input(
editor.get_buffer(),
editor.insertion_point(),
self.input.as_deref(),
self.settings.only_buffer_difference,
);
let (input, pos) = resolve_completer_input(editor, &mut self.input, &self.settings);

let parsed = parse_selection_char(&input, SELECTION_CHAR);
self.update_row_pos(parsed.index);
Expand Down Expand Up @@ -412,7 +403,7 @@ impl Menu for ListMenu {

/// The buffer gets cleared with the actual value
fn replace_in_buffer(&self, editor: &mut Editor) {
replace_in_buffer(self.get_value(), editor);
replace_in_buffer(self.get_value(), editor, self.settings.output_mode);
}

fn update_working_details(
Expand Down
142 changes: 116 additions & 26 deletions src/menu/menu_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use nu_ansi_term::{ansi::RESET, Style};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

use crate::{Editor, Suggestion, UndoBehavior};
use crate::{
menu::{InputMode, MenuSettings, OutputMode},
Editor, Suggestion, UndoBehavior,
};

/// Index result obtained from parsing a string with an index marker
/// For example, the next string:
Expand Down Expand Up @@ -272,31 +275,56 @@ pub fn string_difference<'a>(new_string: &'a str, old_string: &str) -> (usize, &
/// Get the part of the line that should be given as input to the completer, as well
/// as the index of the end of that piece of text
///
/// `prev_input` is the text in the buffer when the menu was activated. Needed if only_buffer_difference is true
/// `prev_input` is the text in the buffer when the menu was activated. Needed for `InputMode::Diff`.
pub fn completer_input(
buffer: &str,
insertion_point: usize,
prev_input: Option<&str>,
only_buffer_difference: bool,
input_mode: InputMode,
) -> (String, usize) {
if only_buffer_difference {
if let Some(old_string) = prev_input {
let (start, input) = string_difference(buffer, old_string);
if !input.is_empty() {
(input.to_owned(), start + input.len())
match input_mode {
InputMode::FullBuffer => (buffer.to_owned(), insertion_point),
InputMode::CursorPrefix => {
// TODO previously, all but the list menu replaced newlines with spaces here
// The completers should be adapted to account for this, and tests need to be added
(buffer[..insertion_point].to_owned(), insertion_point)
}
InputMode::Diff => {
if let Some(old_string) = prev_input {
let (start, input) = string_difference(buffer, old_string);
if !input.is_empty() {
(input.to_owned(), start + input.len())
} else {
(String::new(), insertion_point)
}
} else {
(String::new(), insertion_point)
}
} else {
(String::new(), insertion_point)
}
} else {
// TODO previously, all but the list menu replaced newlines with spaces here
// The completers should be adapted to account for this, and tests need to be added
(buffer[..insertion_point].to_owned(), insertion_point)
}
}

/// Stashes the buffer on first call when in `InputMode::Diff` (so later calls can diff
/// against the original), then resolves the completer input via [`completer_input`].
///
/// Centralises the input-resolution boilerplate shared by all menu `update_values` impls.
pub fn resolve_completer_input(
editor: &Editor,
saved_input: &mut Option<String>,
settings: &MenuSettings,
) -> (String, usize) {
let mode = settings.effective_input_mode();
if mode == InputMode::Diff && saved_input.is_none() {
*saved_input = Some(editor.get_buffer().to_string());
}
completer_input(
editor.get_buffer(),
editor.insertion_point(),
saved_input.as_deref(),
mode,
)
}

/// Find the closest index less than or equal to the current index that's a
/// character boundary
///
Expand All @@ -314,16 +342,26 @@ pub fn floor_char_boundary(s: &str, index: usize) -> usize {
}

/// Helper to accept a completion suggestion and edit the buffer
pub fn replace_in_buffer(value: Option<Suggestion>, editor: &mut Editor) {
pub fn replace_in_buffer(
value: Option<Suggestion>,
editor: &mut Editor,
output_mode: Option<OutputMode>,
) {
if let Some(Suggestion {
mut value,
span,
append_whitespace,
..
}) = value
{
let end = floor_char_boundary(editor.get_buffer(), span.end);
let start = floor_char_boundary(editor.get_buffer(), span.start).min(end);
let buffer_len = editor.get_buffer().len();
let (raw_start, raw_end) = match output_mode {
Some(OutputMode::FullBuffer) => (0, buffer_len),
Some(OutputMode::ExtendToEnd) => (span.start, buffer_len),
Some(OutputMode::SuggestedSpan) | None => (span.start, span.end),
};
let end = floor_char_boundary(editor.get_buffer(), raw_end);
let start = floor_char_boundary(editor.get_buffer(), raw_start).min(end);
if append_whitespace {
value.push(' ');
}
Expand Down Expand Up @@ -948,24 +986,25 @@ mod tests {
}

#[rstest]
#[case("foobar", 6, None, false, "foobar", 6)]
#[case("foo\r\nbar", 5, None, false, "foo\r\n", 5)]
#[case("foo\nbar", 4, None, false, "foo\n", 4)]
#[case("foobar", 6, None, true, "", 6)]
#[case("foobar", 3, Some("foobar"), true, "", 3)]
#[case("foobar", 6, Some("foo"), true, "bar", 6)]
#[case("foobar", 6, Some("for"), true, "oba", 5)]
#[case("foobar", 6, None, InputMode::CursorPrefix, "foobar", 6)]
#[case("foo\r\nbar", 5, None, InputMode::CursorPrefix, "foo\r\n", 5)]
#[case("foo\nbar", 4, None, InputMode::CursorPrefix, "foo\n", 4)]
#[case("foobar", 6, None, InputMode::Diff, "", 6)]
#[case("foobar", 3, Some("foobar"), InputMode::Diff, "", 3)]
#[case("foobar", 6, Some("foo"), InputMode::Diff, "bar", 6)]
#[case("foobar", 6, Some("for"), InputMode::Diff, "oba", 5)]
#[case("foobar baz", 3, None, InputMode::FullBuffer, "foobar baz", 3)]
fn test_completer_input(
#[case] buffer: String,
#[case] insertion_point: usize,
#[case] prev_input: Option<&str>,
#[case] only_buffer_difference: bool,
#[case] input_mode: InputMode,
#[case] output: String,
#[case] pos: usize,
) {
assert_eq!(
(output, pos),
completer_input(&buffer, insertion_point, prev_input, only_buffer_difference)
completer_input(&buffer, insertion_point, prev_input, input_mode)
)
}

Expand Down Expand Up @@ -994,6 +1033,57 @@ mod tests {
..Default::default()
}),
&mut editor,
None,
);
assert_eq!(new_buffer, editor.get_buffer());
assert_eq!(new_insertion_point, editor.insertion_point());

editor.run_edit_command(&EditCommand::Undo);
assert_eq!(orig_buffer, editor.get_buffer());
assert_eq!(orig_insertion_point, editor.insertion_point());
}

#[rstest]
#[case::full_buffer(
"old content",
11,
"new",
3,
"new",
Span::new(0, 0),
OutputMode::FullBuffer
)]
#[case::extend_to_end(
"hello world",
11,
"hello rust",
10,
"rust",
Span::new(6, 8),
OutputMode::ExtendToEnd
)]
fn test_replace_in_buffer_with_output_mode(
#[case] orig_buffer: &str,
#[case] orig_insertion_point: usize,
#[case] new_buffer: &str,
#[case] new_insertion_point: usize,
#[case] value: String,
#[case] span: Span,
#[case] output_mode: OutputMode,
) {
let mut editor = Editor::default();
let mut line_buffer = LineBuffer::new();
line_buffer.set_buffer(orig_buffer.to_owned());
line_buffer.set_insertion_point(orig_insertion_point);
editor.set_line_buffer(line_buffer, UndoBehavior::CreateUndoPoint);
replace_in_buffer(
Some(Suggestion {
value,
span,
..Default::default()
}),
&mut editor,
Some(output_mode),
);
assert_eq!(new_buffer, editor.get_buffer());
assert_eq!(new_insertion_point, editor.insertion_point());
Expand Down
Loading
Loading