Skip to content
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

get back the ranges of the strings from the completer used for generating completions #713

Merged
merged 11 commits into from
Jan 22, 2024
18 changes: 18 additions & 0 deletions src/completion/base.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ops::Range;

/// A span of source code, with positions in bytes
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct Span {
Expand Down Expand Up @@ -31,6 +33,22 @@ pub trait Completer: Send {
/// span to replace and the contents of that replacement
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion>;

/// same as [`Completer::complete`] but it will return a vector of ranges of the strings
/// the suggestions are based on
fn complete_with_base_ranges(
&mut self,
line: &str,
pos: usize,
) -> (Vec<Suggestion>, Vec<Range<usize>>) {
let mut ranges = vec![];
let suggestions = self.complete(line, pos);
for suggestion in &suggestions {
ranges.push(suggestion.span.start..suggestion.span.end);
}
ranges.dedup();
(suggestions, ranges)
}

/// 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
Expand Down
51 changes: 49 additions & 2 deletions src/completion/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,10 @@ impl CompletionNode {

#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn default_completer_with_non_ansi() {
use super::*;

let mut completions = DefaultCompleter::default();
completions.insert(
["nushell", "null", "number"]
Expand Down Expand Up @@ -395,4 +395,51 @@ mod tests {
]
);
}

#[test]
fn default_completer_with_start_strings() {
let mut completions = DefaultCompleter::default();
completions.insert(
["this is the reedline crate", "test"]
.iter()
.map(|s| s.to_string())
.collect(),
);

let buffer = "this is t";

let (suggestions, ranges) = completions.complete_with_base_ranges(buffer, 9);
assert_eq!(
suggestions,
[
Suggestion {
value: "test".into(),
description: None,
extra: None,
span: Span { start: 8, end: 9 },
append_whitespace: false,
},
Suggestion {
value: "this is the reedline crate".into(),
description: None,
extra: None,
span: Span { start: 8, end: 9 },
append_whitespace: false,
},
Suggestion {
value: "this is the reedline crate".into(),
description: None,
extra: None,
span: Span { start: 0, end: 9 },
append_whitespace: false,
},
]
);

assert_eq!(ranges, [8..9, 0..9]);
assert_eq!(
["t", "this is t"],
[&buffer[ranges[0].clone()], &buffer[ranges[1].clone()]]
);
}
}
4 changes: 0 additions & 4 deletions src/menu/columnar_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,10 +728,6 @@ impl Menu for ColumnarMenu {
.collect()
}
}

fn set_cursor_pos(&mut self, _pos: (u16, u16)) {
// The columnar menu does not need the cursor position
}
}

#[cfg(test)]
Expand Down
48 changes: 42 additions & 6 deletions src/menu/ide_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ struct DefaultIdeMenuDetails {
pub max_description_height: u16,
/// Offset from the suggestion box to the description box
pub description_offset: u16,
/// If true, the cursor pos will be corrected, so the suggestions match up with the typed text
/// ```text
/// C:\> str
/// str join
/// str trim
/// str split
/// ```
pub correct_cursor_pos: bool,
}

impl Default for DefaultIdeMenuDetails {
Expand All @@ -91,6 +99,7 @@ impl Default for DefaultIdeMenuDetails {
max_description_width: 50,
max_description_height: 10,
description_offset: 1,
correct_cursor_pos: false,
}
}
}
Expand All @@ -114,6 +123,9 @@ struct IdeMenuDetails {
pub space_right: u16,
/// Corrected description offset, based on the available space
pub description_offset: u16,
/// The ranges of the strings, the suggestions are based on (ranges in [`Editor::get_buffer`])
/// This is required to adjust the suggestion boxes position, when `correct_cursor_pos` in [`DefaultIdeMenuDetails`] is true
pub base_strings: Vec<String>,
}

/// Menu to present suggestions like similar to Ide completion menus
Expand Down Expand Up @@ -306,6 +318,13 @@ impl IdeMenu {
self.default_details.description_offset = description_offset;
self
}

/// Menu builder with new correct cursor pos
#[must_use]
pub fn with_correct_cursor_pos(mut self, correct_cursor_pos: bool) -> Self {
self.default_details.correct_cursor_pos = correct_cursor_pos;
self
}
}

// Menu functionality
Expand Down Expand Up @@ -662,16 +681,16 @@ impl Menu for IdeMenu {

/// Update menu values
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
self.values = if self.only_buffer_difference {
let (values, base_ranges) = if self.only_buffer_difference {
if let Some(old_string) = &self.input {
let (start, input) = string_difference(editor.get_buffer(), old_string);
if !input.is_empty() {
completer.complete(input, start + input.len())
completer.complete_with_base_ranges(input, start + input.len())
} else {
completer.complete("", editor.insertion_point())
completer.complete_with_base_ranges("", editor.insertion_point())
}
} else {
completer.complete("", editor.insertion_point())
completer.complete_with_base_ranges("", editor.insertion_point())
}
} else {
// If there is a new line character in the line buffer, the completer
Expand All @@ -680,12 +699,18 @@ impl Menu for IdeMenu {
// Also, by replacing the new line character with a space, the insert
// position is maintain in the line buffer.
let trimmed_buffer = editor.get_buffer().replace("\r\n", " ").replace('\n', " ");
completer.complete(
completer.complete_with_base_ranges(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expanded example with the added ability to throw some new lines in there will test this part of the code better

&trimmed_buffer[..editor.insertion_point()],
editor.insertion_point(),
)
};

self.values = values;
self.working_details.base_strings = base_ranges
.iter()
.map(|range| editor.get_buffer()[range.clone()].to_string())
.collect::<Vec<String>>();

self.reset_position();
}

Expand Down Expand Up @@ -739,7 +764,18 @@ impl Menu for IdeMenu {
});

let terminal_width = painter.screen_width();
let cursor_pos = self.working_details.cursor_col;
let mut cursor_pos = self.working_details.cursor_col;

if self.default_details.correct_cursor_pos {
let base_string = self
.working_details
.base_strings
.iter()
.min_by_key(|s| s.len())
.cloned()
.unwrap_or_default();
cursor_pos = cursor_pos.saturating_sub(base_string.width() as u16);
}

let border_width = if self.default_details.border.is_some() {
2
Expand Down
4 changes: 0 additions & 4 deletions src/menu/list_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,10 +670,6 @@ impl Menu for ListMenu {
fn min_rows(&self) -> u16 {
self.max_lines + 1
}

fn set_cursor_pos(&mut self, _pos: (u16, u16)) {
// The list menu does not need the cursor position
}
}

fn number_of_lines(entry: &str, max_lines: usize, terminal_columns: u16) -> u16 {
Expand Down
4 changes: 3 additions & 1 deletion src/menu/mod.rs
Copy link
Contributor

@stormasm stormasm Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition for simplicity and consolidation as it cleans up the columnar and list menu 👍

Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ pub trait Menu: Send {
/// Gets cached values from menu that will be displayed
fn get_values(&self) -> &[Suggestion];
/// Sets the position of the cursor (currently only required by the IDE menu)
fn set_cursor_pos(&mut self, pos: (u16, u16));
fn set_cursor_pos(&mut self, _pos: (u16, u16)) {
// empty implementation to make it optional
}
}

/// Allowed menus in Reedline
Expand Down