Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/completion/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,7 @@ pub struct Suggestion {
/// Whether to append a space after selecting this suggestion.
/// This helps to avoid that a completer repeats the complete suggestion.
pub append_whitespace: bool,
/// Indices of the graphemes in the suggestion that matched the typed text.
/// Useful if using fuzzy matching.
pub match_indices: Option<Vec<usize>>,
}
25 changes: 16 additions & 9 deletions src/completion/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ impl Completer for DefaultCompleter {
/// assert_eq!(
/// completions.complete("bat",3),
/// vec![
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false},
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 3 }, append_whitespace: false, ..Default::default()},
/// ]);
///
/// assert_eq!(
/// completions.complete("to the\r\nbat",11),
/// vec![
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false},
/// Suggestion {value: "batcave".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batman".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "batmobile".into(), description: None, style: None, extra: None, span: Span { start: 8, end: 11 }, append_whitespace: false, ..Default::default()},
/// ]);
/// ```
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
Expand Down Expand Up @@ -110,6 +110,7 @@ impl Completer for DefaultCompleter {
extra: None,
span,
append_whitespace: false,
..Default::default()
}
})
.filter(|t| t.value.len() > (t.span.end - t.span.start))
Expand Down Expand Up @@ -182,15 +183,15 @@ impl DefaultCompleter {
/// completions.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
/// assert_eq!(
/// completions.complete("te",2),
/// vec![Suggestion {value: "test".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false}]);
/// vec![Suggestion {value: "test".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false, ..Default::default()}]);
///
/// let mut completions = DefaultCompleter::with_inclusions(&['-', '_']);
/// completions.insert(vec!["test-hyphen","test_underscore"].iter().map(|s| s.to_string()).collect());
/// assert_eq!(
/// completions.complete("te",2),
/// vec![
/// Suggestion {value: "test-hyphen".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false},
/// Suggestion {value: "test_underscore".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false},
/// Suggestion {value: "test-hyphen".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false, ..Default::default()},
/// Suggestion {value: "test_underscore".into(), description: None, style: None, extra: None, span: Span { start: 0, end: 2 }, append_whitespace: false, ..Default::default()},
/// ]);
/// ```
pub fn with_inclusions(incl: &[char]) -> Self {
Expand Down Expand Up @@ -384,6 +385,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 3 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "number".into(),
Expand All @@ -392,6 +394,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 3 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "nushell".into(),
Expand All @@ -400,6 +403,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 3 },
append_whitespace: false,
..Default::default()
},
]
);
Expand Down Expand Up @@ -428,6 +432,7 @@ mod tests {
extra: None,
span: Span { start: 8, end: 9 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "this is the reedline crate".into(),
Expand All @@ -436,6 +441,7 @@ mod tests {
extra: None,
span: Span { start: 8, end: 9 },
append_whitespace: false,
..Default::default()
},
Suggestion {
value: "this is the reedline crate".into(),
Expand All @@ -444,6 +450,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: 9 },
append_whitespace: false,
..Default::default()
},
]
);
Expand Down
1 change: 1 addition & 0 deletions src/completion/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl<'menu> HistoryCompleter<'menu> {
extra: None,
span,
append_whitespace: false,
..Default::default()
}
}
}
Expand Down
167 changes: 44 additions & 123 deletions src/menu/columnar_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use super::{Menu, MenuBuilder, MenuEvent, MenuSettings};
use crate::{
core_editor::Editor,
menu_functions::{
can_partially_complete, completer_input, floor_char_boundary, replace_in_buffer,
can_partially_complete, completer_input, floor_char_boundary, get_match_indices,
replace_in_buffer, style_suggestion,
},
painting::Painter,
Completer, Suggestion,
Expand Down Expand Up @@ -377,135 +378,68 @@ impl ColumnarMenu {
&self,
suggestion: &Suggestion,
index: usize,
empty_space: usize,
use_ansi_coloring: bool,
) -> String {
let selected = index == self.index();
let empty_space = self.get_width().saturating_sub(suggestion.value.width());

if use_ansi_coloring {
// strip quotes
// TODO(ysthakur): let the user strip quotes, rather than doing it here
let is_quote = |c: char| "`'\"".contains(c);
let shortest_base = &self.working_details.shortest_base_string;
let shortest_base = shortest_base
.strip_prefix(is_quote)
.unwrap_or(shortest_base);
let match_len = shortest_base.chars().count();

// Find match position - look for the base string in the suggestion (case-insensitive)
let match_position = suggestion
.value
.to_lowercase()
.find(&shortest_base.to_lowercase())
.unwrap_or(0);

// The match is just the part that matches the shortest_base
let match_str = {
let match_str = &suggestion.value[match_position..];
let match_len_bytes = match_str
.char_indices()
.nth(match_len)
.map(|(i, _)| i)
.unwrap_or_else(|| match_str.len());
&suggestion.value[match_position..match_position + match_len_bytes]
};

// Prefix is everything before the match
let prefix = &suggestion.value[..match_position];

// Remaining is everything after the match
let remaining_str = &suggestion.value[match_position + match_str.len()..];

let suggestion_style_prefix = suggestion
.style
.unwrap_or(self.settings.color.text_style)
.prefix();
let match_indices =
get_match_indices(&suggestion.value, &suggestion.match_indices, shortest_base);

let left_text_size = self.longest_suggestion + self.default_details.col_padding;
let right_text_size = self.get_width().saturating_sub(left_text_size);
let description_size = self.get_width().saturating_sub(left_text_size);
let padding = left_text_size.saturating_sub(suggestion.value.len());

let max_remaining = left_text_size.saturating_sub(match_str.width() + prefix.width());
let max_match = max_remaining.saturating_sub(remaining_str.width());

if index == self.index() {
if let Some(description) = &suggestion.description {
let value_style = if selected {
&self.settings.color.selected_text_style
} else {
&suggestion.style.unwrap_or(self.settings.color.text_style)
};
let styled_value = style_suggestion(
&value_style.paint(&suggestion.value).to_string(),
match_indices.as_ref(),
&self.settings.color.match_style,
);

if let Some(description) = &suggestion.description {
let desc_trunc = description
.chars()
.take(description_size)
.collect::<String>()
.replace('\n', " ");
if selected {
format!(
"{}{}{}{}{}{}{}{}{}{:max_match$}{:max_remaining$}{}{}{}{}{}",
suggestion_style_prefix,
self.settings.color.selected_text_style.prefix(),
prefix,
RESET,
suggestion_style_prefix,
self.settings.color.selected_match_style.prefix(),
match_str,
RESET,
suggestion_style_prefix,
self.settings.color.selected_text_style.prefix(),
remaining_str,
RESET,
self.settings.color.description_style.prefix(),
self.settings.color.selected_text_style.prefix(),
description
.chars()
.take(right_text_size)
.collect::<String>()
.replace('\n', " "),
"{}{}{}{}{}",
styled_value,
value_style.prefix(),
" ".repeat(padding),
desc_trunc,
RESET,
)
} else {
format!(
"{}{}{}{}{}{}{}{}{}{}{}{}{:>empty$}",
suggestion_style_prefix,
self.settings.color.selected_text_style.prefix(),
prefix,
"{}{}{}{}",
styled_value,
" ".repeat(padding),
self.settings.color.description_style.paint(desc_trunc),
RESET,
suggestion_style_prefix,
self.settings.color.selected_match_style.prefix(),
match_str,
RESET,
suggestion_style_prefix,
self.settings.color.selected_text_style.prefix(),
remaining_str,
RESET,
"",
empty = empty_space,
)
}
} else if let Some(description) = &suggestion.description {
format!(
"{}{}{}{}{}{}{}{:max_match$}{:max_remaining$}{}{}{}{}",
suggestion_style_prefix,
prefix,
RESET,
suggestion_style_prefix,
self.settings.color.match_style.prefix(),
match_str,
RESET,
suggestion_style_prefix,
remaining_str,
RESET,
self.settings.color.description_style.prefix(),
description
.chars()
.take(right_text_size)
.collect::<String>()
.replace('\n', " "),
RESET,
)
} else {
format!(
"{}{}{}{}{}{}{}{}{}{}{}{:>empty$}{}",
suggestion_style_prefix,
prefix,
RESET,
suggestion_style_prefix,
self.settings.color.match_style.prefix(),
match_str,
RESET,
suggestion_style_prefix,
remaining_str,
"{}{}{:>empty$}",
styled_value,
RESET,
self.settings.color.description_style.prefix(),
"",
RESET,
empty = empty_space,
empty = empty_space
)
}
} else {
Expand Down Expand Up @@ -538,7 +472,7 @@ impl ColumnarMenu {
)
};

if index == self.index() {
if selected {
line.to_uppercase()
} else {
line
Expand Down Expand Up @@ -789,14 +723,7 @@ impl Menu for ColumnarMenu {
.step_by(num_rows)
.take(self.get_cols().into())
.map(|(index, suggestion)| {
let empty_space =
self.get_width().saturating_sub(suggestion.value.width());
self.create_string(
suggestion,
index,
empty_space,
use_ansi_coloring,
)
self.create_string(suggestion, index, use_ansi_coloring)
})
.collect();
menu_string.push_str(&row_string);
Expand All @@ -817,8 +744,6 @@ impl Menu for ColumnarMenu {
// Correcting the enumerate index based on the number of skipped values
let index = index + skip_values;
let column = index % self.get_cols() as usize;
let empty_space =
self.get_width().saturating_sub(suggestion.value.width());

let end_of_line =
if column == self.get_cols().saturating_sub(1) as usize {
Expand All @@ -828,12 +753,7 @@ impl Menu for ColumnarMenu {
};
format!(
"{}{}",
self.create_string(
suggestion,
index,
empty_space,
use_ansi_coloring
),
self.create_string(suggestion, index, use_ansi_coloring),
end_of_line
)
})
Expand Down Expand Up @@ -931,6 +851,7 @@ mod tests {
extra: None,
span: Span { start: 0, end: pos },
append_whitespace: false,
..Default::default()
}
}

Expand Down
Loading