diff --git a/src/menu/columnar_menu.rs b/src/menu/columnar_menu.rs index ee357aa3..1f0f23e3 100644 --- a/src/menu/columnar_menu.rs +++ b/src/menu/columnar_menu.rs @@ -63,6 +63,9 @@ pub struct ColumnarMenu { col_pos: u16, /// row position in the menu. Starts from 0 row_pos: u16, + /// Number of values that are skipped when printing, + /// depending on selected value and terminal height + skip_values: u16, /// Event sent to the menu event: Option, /// Longest suggestion found in the values @@ -82,6 +85,7 @@ impl Default for ColumnarMenu { values: Vec::new(), col_pos: 0, row_pos: 0, + skip_values: 0, event: None, longest_suggestion: 0, input: None, @@ -619,6 +623,26 @@ impl Menu for ColumnarMenu { self.working_details.columns = possible_cols; } } + + let mut available_lines = painter.remaining_lines_real(); + // Handle the case where a prompt uses the entire screen. + // Drawing the menu has priority over the drawing the prompt. + if available_lines == 0 { + available_lines = painter.remaining_lines().min(self.min_rows()); + } + + let first_visible_row = self.skip_values / self.get_cols(); + + self.skip_values = if self.row_pos <= first_visible_row { + // Selection is above the visible area, scroll up + self.row_pos * self.get_cols() + } else if self.row_pos >= first_visible_row + available_lines { + // Selection is below the visible area, scroll down + (self.row_pos.saturating_sub(available_lines) + 1) * self.get_cols() + } else { + // Selection is within the visible area + self.skip_values + }; } } @@ -645,19 +669,12 @@ impl Menu for ColumnarMenu { if self.get_values().is_empty() { self.no_records_msg(use_ansi_coloring) } else { - // The skip values represent the number of lines that should be skipped - // while printing the menu - let skip_values = if self.row_pos >= available_lines { - let skip_lines = self.row_pos.saturating_sub(available_lines) + 1; - (skip_lines * self.get_cols()) as usize - } else { - 0 - }; - // It seems that crossterm prefers to have a complete string ready to be printed // rather than looping through the values and printing multiple things // This reduces the flickering when printing the menu let available_values = (available_lines * self.get_cols()) as usize; + let skip_values = self.skip_values as usize; + self.get_values() .iter() .skip(skip_values) diff --git a/src/menu/ide_menu.rs b/src/menu/ide_menu.rs index 0e8c03bc..f4fca0b1 100644 --- a/src/menu/ide_menu.rs +++ b/src/menu/ide_menu.rs @@ -144,6 +144,9 @@ pub struct IdeMenu { values: Vec, /// Selected value. Starts at 0 selected: u16, + /// Number of values that are skipped when printing, + /// depending on selected value and terminal height + skip_values: u16, /// Event sent to the menu event: Option, /// Longest suggestion found in the values @@ -161,6 +164,7 @@ impl Default for IdeMenu { working_details: IdeMenuDetails::default(), values: Vec::new(), selected: 0, + skip_values: 0, event: None, longest_suggestion: 0, input: None, @@ -807,6 +811,29 @@ impl Menu for IdeMenu { self.working_details.space_left = space_left; self.working_details.space_right = space_right; + + let mut available_lines = painter + .remaining_lines_real() + .min(self.default_details.max_completion_height); + + // Handle the case where a prompt uses the entire screen. + // Drawing the menu has priority over the drawing the prompt. + if available_lines == 0 { + available_lines = painter.remaining_lines().min(self.min_rows()); + } + + let visible_items = available_lines.saturating_sub(border_width); + + self.skip_values = if self.selected <= self.skip_values { + // Selection is above the visible area + self.selected + } else if self.selected >= self.skip_values + visible_items { + // Selection is below the visible area + self.selected.saturating_sub(visible_items) + 1 + } else { + // Selection is within the visible area + self.skip_values + } } } @@ -840,17 +867,7 @@ impl Menu for IdeMenu { }; let available_lines = available_lines.min(self.default_details.max_completion_height); - // 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 - } else { - 0 - }; + let skip_values = self.skip_values as usize; let available_values = available_lines.saturating_sub(border_width) as usize; diff --git a/src/painting/painter.rs b/src/painting/painter.rs index bf2917ec..647cf66e 100644 --- a/src/painting/painter.rs +++ b/src/painting/painter.rs @@ -91,6 +91,8 @@ pub struct Painter { // Stdout stdout: W, prompt_start_row: u16, + // The number of lines that the prompt takes up + prompt_height: u16, terminal_size: (u16, u16), last_required_lines: u16, large_buffer: bool, @@ -103,6 +105,7 @@ impl Painter { Painter { stdout, prompt_start_row: 0, + prompt_height: 0, terminal_size: (0, 0), last_required_lines: 0, large_buffer: false, @@ -121,7 +124,18 @@ impl Painter { self.terminal_size.0 } - /// Returns the available lines from the prompt down + /// Returns the empty lines from the prompt down. + pub fn remaining_lines_real(&self) -> u16 { + self.screen_height() + .saturating_sub(self.prompt_start_row) + .saturating_sub(self.prompt_height) + } + + /// Returns the number of lines that are available or can be made available by + /// stripping the prompt. + /// + /// If you want the number of empty lines below the prompt, + /// use [`Painter::remaining_lines_real`] instead. pub fn remaining_lines(&self) -> u16 { self.screen_height().saturating_sub(self.prompt_start_row) } @@ -199,6 +213,9 @@ impl Painter { let screen_width = self.screen_width(); let screen_height = self.screen_height(); + // We add one here as [`PromptLines::prompt_lines_with_wrap`] intentionally subtracts 1 from the real value. + self.prompt_height = lines.prompt_lines_with_wrap(screen_width) + 1; + // Handle resize for multi line prompt if self.just_resized { self.prompt_start_row = self.prompt_start_row.saturating_sub(