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 correct cursor pos when menu indicator contains newline #708

Merged
merged 7 commits into from
Jan 20, 2024
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: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ pub use validator::{DefaultValidator, ValidationResult, Validator};

mod menu;
pub use menu::{
menu_functions, ColumnarMenu, IdeMenu, ListMenu, Menu, MenuEvent, MenuTextStyle, ReedlineMenu,
menu_functions, ColumnarMenu, DescriptionMode, IdeMenu, ListMenu, Menu, MenuEvent,
MenuTextStyle, ReedlineMenu,
};

mod terminal_extensions;
Expand Down
1 change: 1 addition & 0 deletions src/menu/ide_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

/// The direction of the description box
pub enum DescriptionMode {
/// Description is always shown on the left
Left,
Expand All @@ -32,16 +33,16 @@
}

impl Default for BorderSymbols {
fn default() -> Self {
Self {
top_left: '╭',
top_right: '╮',
bottom_left: '╰',
bottom_right: '╯',
horizontal: '─',
vertical: '│',
}
}

Check warning on line 45 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L36-L45

Added lines #L36 - L45 were not covered by tests
}

/// Default values used as reference for the menu. These values are set during
Expand Down Expand Up @@ -174,156 +175,156 @@

/// Menu builder with new value for text style
#[must_use]
pub fn with_text_style(mut self, text_style: Style) -> Self {
self.color.text_style = text_style;
self
}

Check warning on line 181 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L178-L181

Added lines #L178 - L181 were not covered by tests

/// Menu builder with new value for text style
#[must_use]
pub fn with_selected_text_style(mut self, selected_text_style: Style) -> Self {
self.color.selected_text_style = selected_text_style;
self
}

Check warning on line 188 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L185-L188

Added lines #L185 - L188 were not covered by tests

/// Menu builder with new value for text style
#[must_use]
pub fn with_description_text_style(mut self, description_text_style: Style) -> Self {
self.color.description_style = description_text_style;
self
}

Check warning on line 195 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L192-L195

Added lines #L192 - L195 were not covered by tests

/// Menu builder with new value for min completion width value
#[must_use]
pub fn with_min_completion_width(mut self, width: u16) -> Self {
self.default_details.min_completion_width = width;
self
}

Check warning on line 202 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L199-L202

Added lines #L199 - L202 were not covered by tests

/// Menu builder with new value for max completion width value
#[must_use]
pub fn with_max_completion_width(mut self, width: u16) -> Self {
self.default_details.max_completion_width = width;
self
}

Check warning on line 209 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L206-L209

Added lines #L206 - L209 were not covered by tests

/// Menu builder with new value for max completion height value
#[must_use]
pub fn with_max_completion_height(mut self, height: u16) -> Self {
self.default_details.max_completion_height = height;
self
}

Check warning on line 216 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L213-L216

Added lines #L213 - L216 were not covered by tests

/// Menu builder with new value for padding value
#[must_use]
pub fn with_padding(mut self, padding: u16) -> Self {
self.default_details.padding = padding;
self
}

Check warning on line 223 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L220-L223

Added lines #L220 - L223 were not covered by tests

/// Menu builder with the default border value
#[must_use]
pub fn with_default_border(mut self) -> Self {
self.default_details.border = Some(BorderSymbols::default());
self
}

Check warning on line 230 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L227-L230

Added lines #L227 - L230 were not covered by tests

/// Menu builder with new value for border value
#[must_use]
pub fn with_border(
mut self,
top_right: char,
top_left: char,
bottom_right: char,
bottom_left: char,
horizontal: char,
vertical: char,
) -> Self {
self.default_details.border = Some(BorderSymbols {
top_right,
top_left,
bottom_right,
bottom_left,
horizontal,
vertical,
});
self
}

Check warning on line 252 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L234-L252

Added lines #L234 - L252 were not covered by tests

/// Menu builder with new value for cursor offset value
#[must_use]
pub fn with_cursor_offset(mut self, cursor_offset: i16) -> Self {
self.default_details.cursor_offset = cursor_offset;
self
}

Check warning on line 259 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L256-L259

Added lines #L256 - L259 were not covered by tests

/// Menu builder with marker
#[must_use]
pub fn with_marker(mut self, marker: String) -> Self {
self.marker = marker;
self
}

Check warning on line 266 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L263-L266

Added lines #L263 - L266 were not covered by tests

/// Menu builder with new only buffer difference
#[must_use]
pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
self.only_buffer_difference = only_buffer_difference;
self
}

Check warning on line 273 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L270-L273

Added lines #L270 - L273 were not covered by tests

/// Menu builder with new description mode
#[must_use]
pub fn with_description_mode(mut self, description_mode: DescriptionMode) -> Self {
self.default_details.description_mode = description_mode;
self
}

Check warning on line 280 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L277-L280

Added lines #L277 - L280 were not covered by tests

/// Menu builder with new min description width
#[must_use]
pub fn with_min_description_width(mut self, min_description_width: u16) -> Self {
self.default_details.min_description_width = min_description_width;
self
}

Check warning on line 287 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L284-L287

Added lines #L284 - L287 were not covered by tests

/// Menu builder with new max description width
#[must_use]
pub fn with_max_description_width(mut self, max_description_width: u16) -> Self {
self.default_details.max_description_width = max_description_width;
self
}

Check warning on line 294 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L291-L294

Added lines #L291 - L294 were not covered by tests

/// Menu builder with new max description height
#[must_use]
pub fn with_max_description_height(mut self, max_description_height: u16) -> Self {
self.default_details.max_description_height = max_description_height;
self
}

Check warning on line 301 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L298-L301

Added lines #L298 - L301 were not covered by tests

/// Menu builder with new description offset
#[must_use]
pub fn with_description_offset(mut self, description_offset: u16) -> Self {
self.default_details.description_offset = description_offset;
self
}

Check warning on line 308 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L305-L308

Added lines #L305 - L308 were not covered by tests
}

// Menu functionality
impl IdeMenu {
fn move_next(&mut self) {
if self.selected < (self.values.len() as u16).saturating_sub(1) {
self.selected += 1;
} else {
self.selected = 0;
}
}

Check warning on line 319 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L313-L319

Added lines #L313 - L319 were not covered by tests

fn move_previous(&mut self) {
if self.selected > 0 {
self.selected -= 1;
} else {
self.selected = self.values.len().saturating_sub(1) as u16;
}
}

Check warning on line 327 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L321-L327

Added lines #L321 - L327 were not covered by tests

fn index(&self) -> usize {
self.selected as usize
Expand All @@ -334,343 +335,343 @@
}

/// Calculates how many rows the Menu will try to use (if available)
fn get_rows(&self) -> u16 {
let mut values = self.get_values().len() as u16;

if values == 0 {

Check warning on line 341 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L338-L341

Added lines #L338 - L341 were not covered by tests
// When the values are empty the no_records_msg is shown, taking 1 line
return 1;
}

if self.default_details.border.is_some() {
// top and bottom border take 1 line each
values += 2;
}

Check warning on line 349 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L343-L349

Added lines #L343 - L349 were not covered by tests

let description_height = self
.get_value()
.and_then(|value| value.description)
.map(|description| {
self.description_dims(
description,
self.working_details.description_width,
self.default_details.max_description_height,
0,
)
.1
})
.unwrap_or(0)
.min(self.default_details.max_description_height);

values.max(description_height)
}

Check warning on line 367 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L351-L367

Added lines #L351 - L367 were not covered by tests

/// Returns working details width
fn get_width(&self) -> u16 {
self.working_details.menu_width
}

Check warning on line 372 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L370-L372

Added lines #L370 - L372 were not covered by tests

fn reset_position(&mut self) {
self.selected = 0;
}

fn no_records_msg(&self, use_ansi_coloring: bool) -> String {
let msg = "NO RECORDS FOUND";
if use_ansi_coloring {
format!(
"{}{}{}",
self.color.selected_text_style.prefix(),
msg,
RESET
)

Check warning on line 386 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L378-L386

Added lines #L378 - L386 were not covered by tests
} else {
msg.to_string()

Check warning on line 388 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L388

Added line #L388 was not covered by tests
}
}

Check warning on line 390 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L390

Added line #L390 was not covered by tests

fn create_description(
&self,
description: String,
use_ansi_coloring: bool,
available_width: u16,
available_height: u16,
min_width: u16,
) -> Vec<String> {
if description.is_empty() || available_width == 0 || available_height == 0 {
return Vec::new();
}

Check warning on line 402 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L392-L402

Added lines #L392 - L402 were not covered by tests

let border_width = if self.default_details.border.is_some() {
2

Check warning on line 405 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L404-L405

Added lines #L404 - L405 were not covered by tests
} else {
0

Check warning on line 407 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L407

Added line #L407 was not covered by tests
};

let content_width = available_width.saturating_sub(border_width);
let content_height = available_height.saturating_sub(border_width);

let mut description_lines = split_string(&description, content_width as usize);

// panic!("{:?}", description_lines);

if description_lines.len() > content_height as usize {
description_lines.truncate(content_height as usize);
truncate_string_list(&mut description_lines, "...");
}

Check warning on line 420 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L410-L420

Added lines #L410 - L420 were not covered by tests

let content_width = description_lines
.iter()
.map(|s| s.width())
.max()
.unwrap_or_default()
.max(min_width.saturating_sub(border_width) as usize);

Check warning on line 427 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L422-L427

Added lines #L422 - L427 were not covered by tests

// let needs_padding = description_lines.len() > 1

if let Some(border) = &self.default_details.border {
let horizontal_border = border.horizontal.to_string().repeat(content_width);

Check warning on line 432 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L431-L432

Added lines #L431 - L432 were not covered by tests

for line in &mut description_lines {
let padding = " ".repeat(content_width.saturating_sub(line.width()));

if use_ansi_coloring {
*line = format!(
"{}{}{}{}{}{}",
border.vertical,
self.color.description_style.prefix(),
line,
padding,
RESET,
border.vertical
);
} else {
*line = format!("{}{}{}{}", border.vertical, line, padding, border.vertical);
}

Check warning on line 449 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L434-L449

Added lines #L434 - L449 were not covered by tests
}

description_lines.insert(
0,
format!(
"{}{}{}",
border.top_left, horizontal_border, border.top_right
),
);
description_lines.push(format!(
"{}{}{}",
border.bottom_left, horizontal_border, border.bottom_right
));

Check warning on line 462 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L452-L462

Added lines #L452 - L462 were not covered by tests
} else {
for line in &mut description_lines {
let padding = " ".repeat(content_width.saturating_sub(line.width()));

if use_ansi_coloring {
*line = format!(
"{}{}{}{}",
self.color.description_style.prefix(),
line,
padding,
RESET
);
} else {
*line = format!("{}{}", line, padding);
}

Check warning on line 477 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L464-L477

Added lines #L464 - L477 were not covered by tests
}
}

description_lines
}

Check warning on line 482 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L481-L482

Added lines #L481 - L482 were not covered by tests

/// Returns width and height of the description, including the border
fn description_dims(
&self,
description: String,
max_width: u16,
max_height: u16,
min_width: u16,
) -> (u16, u16) {
// we will calculate the uncapped height, the real height
// will be capped by the available lines

let lines = self.create_description(description, false, max_width, max_height, min_width);
let height = lines.len() as u16;
let string = lines.first().cloned().unwrap_or_default();
let width = string.width() as u16;
(width, height)
}

Check warning on line 500 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L485-L500

Added lines #L485 - L500 were not covered by tests

fn create_value_string(
&self,
suggestion: &Suggestion,
index: usize,
use_ansi_coloring: bool,
padding: usize,
) -> String {
let border_width = if self.default_details.border.is_some() {
2

Check warning on line 510 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L502-L510

Added lines #L502 - L510 were not covered by tests
} else {
0

Check warning on line 512 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L512

Added line #L512 was not covered by tests
};

let vertical_border = self
.default_details
.border
.as_ref()
.map(|border| border.vertical)
.unwrap_or_default();

let padding_right = (self.working_details.completion_width as usize)
.saturating_sub(suggestion.value.chars().count() + border_width + padding);

let max_string_width =
(self.working_details.completion_width as usize).saturating_sub(border_width + padding);

Check warning on line 526 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L515-L526

Added lines #L515 - L526 were not covered by tests

let string = if suggestion.value.chars().count() > max_string_width {
let mut chars = suggestion
.value
.chars()
.take(max_string_width.saturating_sub(3))
.collect::<String>();
chars.push_str("...");
chars

Check warning on line 535 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L528-L535

Added lines #L528 - L535 were not covered by tests
} else {
suggestion.value.clone()

Check warning on line 537 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L537

Added line #L537 was not covered by tests
};

if use_ansi_coloring {
if index == self.index() {
format!(
"{}{}{}{}{}{}{}",
vertical_border,
self.color.selected_text_style.prefix(),
" ".repeat(padding),
string,
" ".repeat(padding_right),
RESET,
vertical_border,
)

Check warning on line 551 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L540-L551

Added lines #L540 - L551 were not covered by tests
} else {
format!(
"{}{}{}{}{}{}{}",
vertical_border,
self.color.text_style.prefix(),
" ".repeat(padding),
string,
" ".repeat(padding_right),
RESET,
vertical_border,
)

Check warning on line 562 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L553-L562

Added lines #L553 - L562 were not covered by tests
}
} else {
let marker = if index == self.index() { ">" } else { "" };

Check warning on line 565 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L565

Added line #L565 was not covered by tests

format!(
"{}{}{}{}{}{}",
vertical_border,
" ".repeat(padding),
marker,
string,
" ".repeat(padding_right),
vertical_border,
)

Check warning on line 575 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L567-L575

Added lines #L567 - L575 were not covered by tests
}
}

Check warning on line 577 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L577

Added line #L577 was not covered by tests
}

impl Menu for IdeMenu {
/// Menu name
fn name(&self) -> &str {
self.name.as_str()
}

Check warning on line 584 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L582-L584

Added lines #L582 - L584 were not covered by tests

/// Menu indicator
fn indicator(&self) -> &str {
self.marker.as_str()
}

Check warning on line 589 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L587-L589

Added lines #L587 - L589 were not covered by tests

/// Deactivates context menu
fn is_active(&self) -> bool {
self.active
}

Check warning on line 594 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L592-L594

Added lines #L592 - L594 were not covered by tests

/// The ide menu can to quick complete if there is only one element
fn can_quick_complete(&self) -> bool {
true
}

Check warning on line 599 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L597-L599

Added lines #L597 - L599 were not covered by tests

fn can_partially_complete(
&mut self,
values_updated: bool,
editor: &mut Editor,
completer: &mut dyn Completer,
) -> bool {
// If the values were already updated (e.g. quick completions are true)
// there is no need to update the values from the menu
if !values_updated {
self.update_values(editor, completer);
}

Check warning on line 611 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L601-L611

Added lines #L601 - L611 were not covered by tests

let values = self.get_values();
if let (Some(Suggestion { value, span, .. }), Some(index)) = find_common_string(values) {
let index = index.min(value.len());
let matching = &value[0..index];

// make sure that the partial completion does not overwrite user entered input
let extends_input = matching.starts_with(&editor.get_buffer()[span.start..span.end]);

if !matching.is_empty() && extends_input {
let mut line_buffer = editor.line_buffer().clone();
line_buffer.replace_range(span.start..span.end, matching);

Check warning on line 623 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L613-L623

Added lines #L613 - L623 were not covered by tests

let offset = if matching.len() < (span.end - span.start) {
line_buffer
.insertion_point()
.saturating_sub((span.end - span.start) - matching.len())

Check warning on line 628 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L625-L628

Added lines #L625 - L628 were not covered by tests
} else {
line_buffer.insertion_point() + matching.len() - (span.end - span.start)

Check warning on line 630 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L630

Added line #L630 was not covered by tests
};

line_buffer.set_insertion_point(offset);
editor.set_line_buffer(line_buffer, UndoBehavior::CreateUndoPoint);

// The values need to be updated because the spans need to be
// recalculated for accurate replacement in the string
self.update_values(editor, completer);

true

Check warning on line 640 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L633-L640

Added lines #L633 - L640 were not covered by tests
} else {
false

Check warning on line 642 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L642

Added line #L642 was not covered by tests
}
} else {
false

Check warning on line 645 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L645

Added line #L645 was not covered by tests
}
}

Check warning on line 647 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L647

Added line #L647 was not covered by tests

/// Selects what type of event happened with the menu
fn menu_event(&mut self, event: MenuEvent) {
match &event {
MenuEvent::Activate(_) => self.active = true,
MenuEvent::Deactivate => {
self.active = false;
self.input = None;
}
_ => {}

Check warning on line 657 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L650-L657

Added lines #L650 - L657 were not covered by tests
}

self.event = Some(event);
}

Check warning on line 661 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L660-L661

Added lines #L660 - L661 were not covered by tests

/// Update menu values
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
self.values = 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())

Check warning on line 669 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L666-L669

Added lines #L666 - L669 were not covered by tests
} else {
completer.complete("", editor.insertion_point())

Check warning on line 671 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L671

Added line #L671 was not covered by tests
}
} else {
completer.complete("", editor.insertion_point())

Check warning on line 674 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L674

Added line #L674 was not covered by tests
}
} else {
// If there is a new line character in the line buffer, the completer
Expand All @@ -690,157 +691,157 @@

/// The working details for the menu changes based on the size of the lines
/// collected from the completer
fn update_working_details(
&mut self,
editor: &mut Editor,
completer: &mut dyn Completer,
painter: &Painter,
) {
if let Some(event) = self.event.take() {

Check warning on line 700 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L694-L700

Added lines #L694 - L700 were not covered by tests
// The working value for the menu are updated first before executing any of the
match event {
MenuEvent::Activate(updated) => {
self.active = true;
self.reset_position();

Check warning on line 705 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L702-L705

Added lines #L702 - L705 were not covered by tests

self.input = if self.only_buffer_difference {
Some(editor.get_buffer().to_string())

Check warning on line 708 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L707-L708

Added lines #L707 - L708 were not covered by tests
} else {
None

Check warning on line 710 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L710

Added line #L710 was not covered by tests
};

if !updated {
self.update_values(editor, completer);
}

Check warning on line 715 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L713-L715

Added lines #L713 - L715 were not covered by tests
}
MenuEvent::Deactivate => self.active = false,
MenuEvent::Edit(updated) => {
self.reset_position();

if !updated {
self.update_values(editor, completer);
}

Check warning on line 723 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L717-L723

Added lines #L717 - L723 were not covered by tests
}
MenuEvent::NextElement | MenuEvent::MoveDown => self.move_next(),
MenuEvent::PreviousElement | MenuEvent::MoveUp => self.move_previous(),

Check warning on line 726 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L725-L726

Added lines #L725 - L726 were not covered by tests
MenuEvent::MoveLeft
| MenuEvent::MoveRight
| MenuEvent::PreviousPage
| MenuEvent::NextPage => {}

Check warning on line 730 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L730

Added line #L730 was not covered by tests
}

self.longest_suggestion = self.get_values().iter().fold(0, |prev, suggestion| {
if prev >= suggestion.value.len() {
prev

Check warning on line 735 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L733-L735

Added lines #L733 - L735 were not covered by tests
} else {
suggestion.value.len()

Check warning on line 737 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L737

Added line #L737 was not covered by tests
}
});

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

Check warning on line 742 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L739-L742

Added lines #L739 - L742 were not covered by tests

let border_width = if self.default_details.border.is_some() {
2

Check warning on line 745 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L744-L745

Added lines #L744 - L745 were not covered by tests
} else {
0

Check warning on line 747 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L747

Added line #L747 was not covered by tests
};

let description = self
.get_value()
.map(|v| {
if let Some(v) = v.description {
if v.is_empty() {
return None;

Check warning on line 755 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L750-L755

Added lines #L750 - L755 were not covered by tests
} else {
return Some(v);

Check warning on line 757 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L757

Added line #L757 was not covered by tests
}
}
None
})
.unwrap_or_default();

Check warning on line 762 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L759-L762

Added lines #L759 - L762 were not covered by tests

let mut min_description_width = if description.is_some() {
self.default_details.min_description_width

Check warning on line 765 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L764-L765

Added lines #L764 - L765 were not covered by tests
} else {
0

Check warning on line 767 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L767

Added line #L767 was not covered by tests
};

let completion_width = ((self.longest_suggestion.min(u16::MAX as usize) as u16)
+ 2 * self.default_details.padding
+ border_width)
.min(self.default_details.max_completion_width)
.max(self.default_details.min_completion_width)
.min(terminal_width.saturating_sub(min_description_width))
.max(3 + border_width); // Big enough to show "..."

let available_description_width = terminal_width
.saturating_sub(completion_width)
.min(self.default_details.max_description_width)
.max(self.default_details.min_description_width)
.min(terminal_width.saturating_sub(completion_width));

min_description_width = min_description_width.min(available_description_width);

Check warning on line 784 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L770-L784

Added lines #L770 - L784 were not covered by tests

let description_width = if let Some(description) = description {
self.description_dims(
description,
available_description_width,
u16::MAX,
min_description_width,
)
.0

Check warning on line 793 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L786-L793

Added lines #L786 - L793 were not covered by tests
} else {
0

Check warning on line 795 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L795

Added line #L795 was not covered by tests
};

let max_offset = terminal_width.saturating_sub(completion_width + description_width);

let description_offset = self.default_details.description_offset.min(max_offset);

self.working_details.completion_width = completion_width;
self.working_details.description_width = description_width;
self.working_details.description_offset = description_offset;
self.working_details.menu_width =
completion_width + description_offset + description_width;

let cursor_offset = self.default_details.cursor_offset;

self.working_details.description_is_right = match self.default_details.description_mode

Check warning on line 810 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L798-L810

Added lines #L798 - L810 were not covered by tests
{
DescriptionMode::Left => false,
DescriptionMode::Right => true,

Check warning on line 813 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L812-L813

Added lines #L812 - L813 were not covered by tests
DescriptionMode::PreferRight => {
// if there is enough space to the right of the cursor, the description is shown on the right
// otherwise it is shown on the left
let potential_right_distance = (terminal_width as i16)
.saturating_sub(
cursor_pos as i16
+ cursor_offset
+ description_offset as i16
+ completion_width as i16,
)
.max(0) as u16;

potential_right_distance >= description_width

Check warning on line 826 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L817-L826

Added lines #L817 - L826 were not covered by tests
}
};

let space_left = (if self.working_details.description_is_right {
cursor_pos as i16 + cursor_offset

Check warning on line 831 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L830-L831

Added lines #L830 - L831 were not covered by tests
} else {
(cursor_pos as i16 + cursor_offset)
.saturating_sub(description_width as i16 + description_offset as i16)

Check warning on line 834 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L833-L834

Added lines #L833 - L834 were not covered by tests
}
.max(0) as u16)
.min(terminal_width.saturating_sub(self.get_width()));

let space_right = terminal_width.saturating_sub(space_left + self.get_width());

self.working_details.space_left = space_left;
self.working_details.space_right = space_right;
}
}

Check warning on line 844 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L836-L844

Added lines #L836 - L844 were not covered by tests

/// The buffer gets replaced in the Span location
fn replace_in_buffer(&self, editor: &mut Editor) {
Expand All @@ -854,7 +855,7 @@
let start = span.start.min(editor.line_buffer().len());
let end = span.end.min(editor.line_buffer().len());
if append_whitespace {
value.push(' ');

Check warning on line 858 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L858

Added line #L858 was not covered by tests
}
let mut line_buffer = editor.line_buffer().clone();
line_buffer.replace_range(start..end, &value);
Expand All @@ -864,186 +865,186 @@
offset = offset.saturating_sub(end.saturating_sub(start));
line_buffer.set_insertion_point(offset);
editor.set_line_buffer(line_buffer, UndoBehavior::CreateUndoPoint);
}

Check warning on line 868 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L868

Added line #L868 was not covered by tests
}

/// Minimum rows that should be displayed by the menu
fn min_rows(&self) -> u16 {
self.get_rows()
}

Check warning on line 874 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L872-L874

Added lines #L872 - L874 were not covered by tests

fn get_values(&self) -> &[Suggestion] {
&self.values
}

Check warning on line 878 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L876-L878

Added lines #L876 - L878 were not covered by tests

fn menu_required_lines(&self, _terminal_columns: u16) -> u16 {
self.get_rows()
}

Check warning on line 882 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L880-L882

Added lines #L880 - L882 were not covered by tests

fn menu_string(&self, available_lines: u16, use_ansi_coloring: bool) -> String {
if self.get_values().is_empty() {
self.no_records_msg(use_ansi_coloring)

Check warning on line 886 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L884-L886

Added lines #L884 - L886 were not covered by tests
} else {
let border_width = if self.default_details.border.is_some() {
2

Check warning on line 889 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L888-L889

Added lines #L888 - L889 were not covered by tests
} else {
0

Check warning on line 891 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L891

Added line #L891 was not covered by tests
};

let available_lines = available_lines.min(self.default_details.max_completion_height);

Check warning on line 894 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L894

Added line #L894 was not covered by tests
// The skip values represent the number of lines that should be skipped
// while printing the menu
let skip_values = if self.selected >= available_lines.saturating_sub(border_width) {
let skip_lines = self
.selected
.saturating_sub(available_lines.saturating_sub(border_width))
+ 1;
skip_lines as usize

Check warning on line 902 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L897-L902

Added lines #L897 - L902 were not covered by tests
} else {
0

Check warning on line 904 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L904

Added line #L904 was not covered by tests
};

let available_values = available_lines.saturating_sub(border_width) as usize;

let max_padding = self.working_details.completion_width.saturating_sub(
self.longest_suggestion.min(u16::MAX as usize) as u16 + border_width,
) / 2;

let corrected_padding = self.default_details.padding.min(max_padding) as usize;

let mut strings = self
.get_values()
.iter()
.skip(skip_values)
.take(available_values)
.enumerate()
.map(|(index, suggestion)| {
// Correcting the enumerate index based on the number of skipped values

let index = index + skip_values;
self.create_value_string(
suggestion,
index,
use_ansi_coloring,
corrected_padding,
)
})
.collect::<Vec<String>>();

Check warning on line 932 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L907-L932

Added lines #L907 - L932 were not covered by tests

// Add top and bottom border
if let Some(border) = &self.default_details.border {
let inner_width = self.working_details.completion_width.saturating_sub(2) as usize;

strings.insert(
0,
format!(
"{}{}{}",
border.top_left,
border.horizontal.to_string().repeat(inner_width),
border.top_right,
),
);

strings.push(format!(
"{}{}{}",
border.bottom_left,
border.horizontal.to_string().repeat(inner_width),
border.bottom_right,
));
}

Check warning on line 954 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L935-L954

Added lines #L935 - L954 were not covered by tests

let decsription_height =
available_lines.min(self.default_details.max_description_height);
let description_lines = self
.get_value()
.and_then(|value| value.clone().description)
.map(|description| {
self.create_description(
description,
use_ansi_coloring,
self.working_details.description_width,
decsription_height,
self.working_details.description_width, // the width has already been calculated
)
})
.unwrap_or_default();

let distance_left = &" ".repeat(self.working_details.space_left as usize);

// Horizontally join the description lines with the suggestion lines
if self.working_details.description_is_right {
for (idx, pair) in strings
.clone()
.iter()
.zip_longest(description_lines.iter())
.enumerate()

Check warning on line 980 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L956-L980

Added lines #L956 - L980 were not covered by tests
{
match pair {
Both(_suggestion_line, description_line) => {
strings[idx] = format!(
"{}{}{}{}",
distance_left,
strings[idx],
" ".repeat(self.working_details.description_offset as usize),
description_line,
)

Check warning on line 990 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L982-L990

Added lines #L982 - L990 were not covered by tests
}
Left(suggestion_line) => {
strings[idx] = format!("{}{}", distance_left, suggestion_line);
}
Right(description_line) => strings.push(format!(
"{}{}",
" ".repeat(
(self.working_details.completion_width
+ self.working_details.description_offset)
as usize
) + distance_left,
description_line,
)),

Check warning on line 1003 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L992-L1003

Added lines #L992 - L1003 were not covered by tests
}
}
} else {
for (idx, pair) in strings
.clone()
.iter()
.zip_longest(description_lines.iter())
.enumerate()

Check warning on line 1011 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1007-L1011

Added lines #L1007 - L1011 were not covered by tests
{
match pair {
Both(suggestion_line, description_line) => {
strings[idx] = format!(
"{}{}{}{}",
distance_left,
description_line,
" ".repeat(self.working_details.description_offset as usize),
suggestion_line,
)

Check warning on line 1021 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1013-L1021

Added lines #L1013 - L1021 were not covered by tests
}
Left(suggestion_line) => {
strings[idx] = format!(
"{}{}",
" ".repeat(
(self.working_details.description_width
+ self.working_details.description_offset)
as usize
) + distance_left,
suggestion_line,
);
}
Right(description_line) => {
strings.push(format!("{}{}", distance_left, description_line,))

Check warning on line 1035 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1023-L1035

Added lines #L1023 - L1035 were not covered by tests
}
}
}
}

strings.join("\r\n")

Check warning on line 1041 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1041

Added line #L1041 was not covered by tests
}
}

Check warning on line 1043 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1043

Added line #L1043 was not covered by tests

fn set_cursor_pos(&mut self, pos: (u16, u16)) {
self.working_details.cursor_col = pos.0;
}

Check warning on line 1047 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1045-L1047

Added lines #L1045 - L1047 were not covered by tests
}

/// Split the input into strings that are at most `max_length` (in columns, not in chars) long
Expand Down Expand Up @@ -1459,7 +1460,7 @@
// After replacing the editor, make sure insertion_point is at the right spot
assert!(
editor.is_cursor_at_buffer_end(),
"cursor should be at the end after completion"

Check warning on line 1463 in src/menu/ide_menu.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/ide_menu.rs#L1463

Added line #L1463 was not covered by tests
);
}
}
1 change: 1 addition & 0 deletions src/menu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use crate::History;
use crate::{completion::history::HistoryCompleter, painting::Painter, Completer, Suggestion};
pub use columnar_menu::ColumnarMenu;
pub use ide_menu::DescriptionMode;
pub use ide_menu::IdeMenu;
pub use list_menu::ListMenu;
use nu_ansi_term::{Color, Style};
Expand Down Expand Up @@ -317,7 +318,7 @@
self.as_ref().get_values()
}

fn set_cursor_pos(&mut self, pos: (u16, u16)) {
self.as_mut().set_cursor_pos(pos);
}

Check warning on line 323 in src/menu/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/menu/mod.rs#L321-L323

Added lines #L321 - L323 were not covered by tests
}
111 changes: 105 additions & 6 deletions src/painting/prompt_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,28 @@ impl<'prompt> PromptLines<'prompt> {
/// The height is relative to the prompt
pub(crate) fn cursor_pos(&self, terminal_columns: u16) -> (u16, u16) {
// If we have a multiline prompt (e.g starship), we expect the cursor to be on the last line
let prompt_str = self.prompt_str_left.lines().last().unwrap_or_default();
let prompt_width = line_width(&format!("{}{}", prompt_str, self.prompt_indicator));
let buffer_width = line_width(&self.before_cursor);
let prompt_str = format!("{}{}", self.prompt_str_left, self.prompt_indicator);
// The Cursor position will be relative to this
let last_prompt_str = prompt_str.lines().last().unwrap_or_default();

let total_width = prompt_width + buffer_width;
let is_multiline = self.before_cursor.contains('\n');
let buffer_width = line_width(self.before_cursor.lines().last().unwrap_or_default());

let total_width = if is_multiline {
// The buffer already contains the multiline prompt
buffer_width
} else {
buffer_width + line_width(last_prompt_str)
};

let buffer_width_prompt = format!("{}{}", last_prompt_str, self.before_cursor);

let cursor_y = (estimate_required_lines(&buffer_width_prompt, terminal_columns) as u16)
.saturating_sub(1); // 0 based

let cursor_x = (total_width % terminal_columns as usize) as u16;
let cursor_y = (total_width / terminal_columns as usize) as u16;

(cursor_x, cursor_y)
(cursor_x, cursor_y as u16)
}

/// Total lines that the prompt uses considering that it may wrap the screen
Expand Down Expand Up @@ -154,3 +166,90 @@ impl<'prompt> PromptLines<'prompt> {
}
}
}

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

#[rstest]
#[case(
"~/path/",
"❯ ",
"",
100,
(9, 0)
)]
#[case(
"~/longer/path/\n",
"❯ ",
"test",
100,
(6, 0)
)]
#[case(
"~/longer/path/",
"\n❯ ",
"test",
100,
(6, 0)
)]
#[case(
"~/longer/path/\n",
"\n❯ ",
"test",
100,
(6, 0)
)]
#[case(
"~/path/",
"❯ ",
"very long input that does not fit in a single line",
40,
(19, 1)
)]
#[case(
"~/path/\n",
"\n❯\n ",
"very long input that does not fit in a single line",
10,
(1, 5)
)]
#[case(
"~/path/",
"❯ ",
"this is a text that contains newlines\n::: and a multiline prompt",
40,
(26, 2)
)]
#[case(
"~/path/",
"❯ ",
"this is a text that contains newlines\n::: and very loooooooooooooooong text that wraps",
40,
(8, 3)
)]

fn test_cursor_pos(
#[case] prompt_str_left: &str,
#[case] prompt_indicator: &str,
#[case] before_cursor: &str,
#[case] terminal_columns: u16,
#[case] expected: (u16, u16),
) {
let prompt_lines = PromptLines {
prompt_str_left: Cow::Borrowed(prompt_str_left),
prompt_str_right: Cow::Borrowed(""),
prompt_indicator: Cow::Borrowed(prompt_indicator),
before_cursor: Cow::Borrowed(before_cursor),
after_cursor: Cow::Borrowed(""),
hint: Cow::Borrowed(""),
right_prompt_on_last_line: false,
};

let pos = prompt_lines.cursor_pos(terminal_columns);

assert_eq!(pos, expected);
}
}
Loading