reedline/painting/
prompt_lines.rs

1use super::utils::{coerce_crlf, estimate_required_lines, line_width};
2use crate::{
3    menu::{Menu, ReedlineMenu},
4    prompt::PromptEditMode,
5    Prompt, PromptHistorySearch,
6};
7use std::borrow::Cow;
8
9/// Aggregate of prompt and input string used by `Painter`
10#[derive(Debug)]
11pub(crate) struct PromptLines<'prompt> {
12    pub(crate) prompt_str_left: Cow<'prompt, str>,
13    pub(crate) prompt_str_right: Cow<'prompt, str>,
14    pub(crate) prompt_indicator: Cow<'prompt, str>,
15    pub(crate) before_cursor: Cow<'prompt, str>,
16    pub(crate) after_cursor: Cow<'prompt, str>,
17    pub(crate) hint: Cow<'prompt, str>,
18    pub(crate) right_prompt_on_last_line: bool,
19}
20
21impl<'prompt> PromptLines<'prompt> {
22    /// Splits the strings before and after the cursor as well as the hint
23    /// This vector with the str are used to calculate how many lines are
24    /// required to print after the prompt
25    pub fn new(
26        prompt: &'prompt dyn Prompt,
27        prompt_mode: PromptEditMode,
28        history_indicator: Option<PromptHistorySearch>,
29        before_cursor: &'prompt str,
30        after_cursor: &'prompt str,
31        hint: &'prompt str,
32    ) -> Self {
33        let prompt_str_left = prompt.render_prompt_left();
34        let prompt_str_right = prompt.render_prompt_right();
35
36        let prompt_indicator = match history_indicator {
37            Some(prompt_search) => prompt.render_prompt_history_search_indicator(prompt_search),
38            None => prompt.render_prompt_indicator(prompt_mode),
39        };
40
41        let before_cursor = coerce_crlf(before_cursor);
42        let after_cursor = coerce_crlf(after_cursor);
43        let hint = coerce_crlf(hint);
44        let right_prompt_on_last_line = prompt.right_prompt_on_last_line();
45
46        Self {
47            prompt_str_left,
48            prompt_str_right,
49            prompt_indicator,
50            before_cursor,
51            after_cursor,
52            hint,
53            right_prompt_on_last_line,
54        }
55    }
56
57    /// The required lines to paint the buffer are calculated by counting the
58    /// number of newlines in all the strings that form the prompt and buffer.
59    /// The plus 1 is to indicate that there should be at least one line.
60    pub(crate) fn required_lines(&self, terminal_columns: u16, menu: Option<&ReedlineMenu>) -> u16 {
61        let input = if menu.is_none() {
62            self.prompt_str_left.to_string()
63                + &self.prompt_indicator
64                + &self.before_cursor
65                + &self.after_cursor
66                + &self.hint
67        } else {
68            self.prompt_str_left.to_string()
69                + &self.prompt_indicator
70                + &self.before_cursor
71                + &self.after_cursor
72        };
73
74        let lines = estimate_required_lines(&input, terminal_columns);
75
76        if let Some(menu) = menu {
77            lines as u16 + menu.menu_required_lines(terminal_columns)
78        } else {
79            lines as u16
80        }
81    }
82
83    /// Estimated distance of the cursor to the prompt.
84    /// This considers line wrapping
85    pub(crate) fn distance_from_prompt(&self, terminal_columns: u16) -> u16 {
86        let input = self.prompt_str_left.to_string() + &self.prompt_indicator + &self.before_cursor;
87        let lines = estimate_required_lines(&input, terminal_columns);
88        lines.saturating_sub(1) as u16
89    }
90
91    /// Total lines that the prompt uses considering that it may wrap the screen
92    pub(crate) fn prompt_lines_with_wrap(&self, screen_width: u16) -> u16 {
93        let complete_prompt = self.prompt_str_left.to_string() + &self.prompt_indicator;
94        let lines = estimate_required_lines(&complete_prompt, screen_width);
95        lines.saturating_sub(1) as u16
96    }
97
98    /// Estimated width of the line where right prompt will be rendered
99    pub(crate) fn estimate_right_prompt_line_width(&self, terminal_columns: u16) -> u16 {
100        let first_line_left_prompt = self.prompt_str_left.lines().next();
101        let last_line_left_prompt = self.prompt_str_left.lines().last();
102
103        let prompt_lines_total = self.before_cursor.to_string() + &self.after_cursor + &self.hint;
104        let prompt_lines_first = prompt_lines_total.lines().next();
105
106        let mut estimate = 0; // space in front of the input
107
108        if self.right_prompt_on_last_line {
109            if let Some(last_line_left_prompt) = last_line_left_prompt {
110                estimate += line_width(last_line_left_prompt);
111                estimate += line_width(&self.prompt_indicator);
112
113                if let Some(prompt_lines_first) = prompt_lines_first {
114                    estimate += line_width(prompt_lines_first);
115                }
116            }
117        } else {
118            // Render right prompt on the first line
119            let required_lines = estimate_required_lines(&self.prompt_str_left, terminal_columns);
120            if let Some(first_line_left_prompt) = first_line_left_prompt {
121                estimate += line_width(first_line_left_prompt);
122            }
123
124            // A single line
125            if required_lines == 1 {
126                estimate += line_width(&self.prompt_indicator);
127
128                if let Some(prompt_lines_first) = prompt_lines_first {
129                    estimate += line_width(prompt_lines_first);
130                }
131            }
132        }
133
134        if estimate > u16::MAX as usize {
135            u16::MAX
136        } else {
137            estimate as u16
138        }
139    }
140}