diff --git a/src/engine.rs b/src/engine.rs index 30bb43bc..442d8ea0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1663,7 +1663,7 @@ impl Reedline { // Needs to add return carriage to newlines because when not in raw mode // some OS don't fully return the carriage - let lines = PromptLines::new( + let mut lines = PromptLines::new( prompt, self.prompt_edit_mode(), None, @@ -1675,6 +1675,11 @@ impl Reedline { // Updating the working details of the active menu for menu in self.menus.iter_mut() { if menu.is_active() { + lines.prompt_indicator = menu.indicator().to_owned().into(); + // If the menu requires the cursor position, update it (ide menu) + let cursor_pos = lines.cursor_pos(self.painter.screen_width()); + menu.set_cursor_pos(cursor_pos); + menu.update_working_details( &mut self.editor, self.completer.as_mut(), diff --git a/src/menu/columnar_menu.rs b/src/menu/columnar_menu.rs index 980c706e..b30ce360 100644 --- a/src/menu/columnar_menu.rs +++ b/src/menu/columnar_menu.rs @@ -728,6 +728,10 @@ 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)] diff --git a/src/menu/ide_menu.rs b/src/menu/ide_menu.rs index 3673191e..c931833b 100644 --- a/src/menu/ide_menu.rs +++ b/src/menu/ide_menu.rs @@ -96,6 +96,8 @@ impl Default for DefaultIdeMenuDetails { #[derive(Default)] struct IdeMenuDetails { + /// Column of the cursor + pub cursor_col: u16, /// Width of the menu, including the padding and border and the description pub menu_width: u16, /// width of the completion box, including the padding and border @@ -736,7 +738,7 @@ impl Menu for IdeMenu { }); let terminal_width = painter.screen_width(); - let cursor_pos = crossterm::cursor::position().unwrap().0; + let cursor_pos = self.working_details.cursor_col; let border_width = if self.default_details.border.is_some() { 2 @@ -1038,6 +1040,10 @@ impl Menu for IdeMenu { strings.join("\r\n") } } + + fn set_cursor_pos(&mut self, pos: (u16, u16)) { + self.working_details.cursor_col = pos.0; + } } /// Split the input into strings that are at most `max_length` (in columns, not in chars) long diff --git a/src/menu/list_menu.rs b/src/menu/list_menu.rs index d90e3672..81f12c29 100644 --- a/src/menu/list_menu.rs +++ b/src/menu/list_menu.rs @@ -670,6 +670,10 @@ 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 { diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 05d74685..65a56470 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -121,6 +121,8 @@ 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)); } /// Allowed menus in Reedline @@ -314,4 +316,8 @@ impl Menu for ReedlineMenu { fn get_values(&self) -> &[Suggestion] { self.as_ref().get_values() } + + fn set_cursor_pos(&mut self, pos: (u16, u16)) { + self.as_mut().set_cursor_pos(pos); + } } diff --git a/src/painting/painter.rs b/src/painting/painter.rs index 1c6769be..0432b3b0 100644 --- a/src/painting/painter.rs +++ b/src/painting/painter.rs @@ -271,17 +271,13 @@ impl Painter { self.stdout .queue(Print(&coerce_crlf(&lines.prompt_str_left)))?; - let prompt_indicator = match menu { - Some(menu) => menu.indicator(), - None => &lines.prompt_indicator, - }; - if use_ansi_coloring { self.stdout .queue(SetForegroundColor(prompt.get_indicator_color()))?; } - self.stdout.queue(Print(&coerce_crlf(prompt_indicator)))?; + self.stdout + .queue(Print(&coerce_crlf(&lines.prompt_indicator)))?; if use_ansi_coloring { self.stdout @@ -327,12 +323,7 @@ impl Painter { // indicator is printed in the same line as the first line of the buffer let prompt_lines = lines.prompt_lines_with_wrap(screen_width) as usize; - let prompt_indicator = match menu { - Some(menu) => menu.indicator(), - None => &lines.prompt_indicator, - }; - - let prompt_indicator_lines = prompt_indicator.lines().count(); + let prompt_indicator_lines = &lines.prompt_indicator.lines().count(); let before_cursor_lines = lines.before_cursor.lines().count(); let total_lines_before = prompt_lines + prompt_indicator_lines + before_cursor_lines - 1; @@ -357,7 +348,7 @@ impl Painter { // Adjusting extra_rows base on the calculated prompt line size let extra_rows = extra_rows.saturating_sub(prompt_lines); - let indicator_skipped = skip_buffer_lines(prompt_indicator, extra_rows, None); + let indicator_skipped = skip_buffer_lines(&lines.prompt_indicator, extra_rows, None); self.stdout.queue(Print(&coerce_crlf(indicator_skipped)))?; if use_ansi_coloring { diff --git a/src/painting/prompt_lines.rs b/src/painting/prompt_lines.rs index aa9c35de..2df8964b 100644 --- a/src/painting/prompt_lines.rs +++ b/src/painting/prompt_lines.rs @@ -88,6 +88,22 @@ impl<'prompt> PromptLines<'prompt> { lines.saturating_sub(1) as u16 } + /// Calculate the cursor pos, based on the buffer and 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 total_width = prompt_width + buffer_width; + + 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) + } + /// Total lines that the prompt uses considering that it may wrap the screen pub(crate) fn prompt_lines_with_wrap(&self, screen_width: u16) -> u16 { let complete_prompt = self.prompt_str_left.to_string() + &self.prompt_indicator;