reedline/painting/
styled_text.rs

1use nu_ansi_term::Style;
2
3use crate::Prompt;
4
5use super::utils::strip_ansi;
6
7/// A representation of a buffer with styling, used for doing syntax highlighting
8pub struct StyledText {
9    /// The component, styled parts of the text
10    pub buffer: Vec<(Style, String)>,
11}
12
13impl Default for StyledText {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl StyledText {
20    /// Construct a new `StyledText`
21    pub const fn new() -> Self {
22        Self { buffer: vec![] }
23    }
24
25    /// Add a new styled string to the buffer
26    pub fn push(&mut self, styled_string: (Style, String)) {
27        self.buffer.push(styled_string);
28    }
29
30    /// Render the styled string. We use the insertion point to render around so that
31    /// we can properly write out the styled string to the screen and find the correct
32    /// place to put the cursor. This assumes a logic that prints the first part of the
33    /// string, saves the cursor position, prints the second half, and then restores
34    /// the cursor position
35    ///
36    /// Also inserts the multiline continuation prompt
37    pub fn render_around_insertion_point(
38        &self,
39        insertion_point: usize,
40        prompt: &dyn Prompt,
41        // multiline_prompt: &str,
42        use_ansi_coloring: bool,
43    ) -> (String, String) {
44        let mut current_idx = 0;
45        let mut left_string = String::new();
46        let mut right_string = String::new();
47
48        let multiline_prompt = prompt.render_prompt_multiline_indicator();
49        let prompt_style = Style::new().fg(prompt.get_prompt_multiline_color());
50
51        for pair in &self.buffer {
52            if current_idx >= insertion_point {
53                right_string.push_str(&render_as_string(pair, &prompt_style, &multiline_prompt));
54            } else if pair.1.len() + current_idx <= insertion_point {
55                left_string.push_str(&render_as_string(pair, &prompt_style, &multiline_prompt));
56            } else if pair.1.len() + current_idx > insertion_point {
57                let offset = insertion_point - current_idx;
58
59                let left_side = pair.1[..offset].to_string();
60                let right_side = pair.1[offset..].to_string();
61
62                left_string.push_str(&render_as_string(
63                    &(pair.0, left_side),
64                    &prompt_style,
65                    &multiline_prompt,
66                ));
67                right_string.push_str(&render_as_string(
68                    &(pair.0, right_side),
69                    &prompt_style,
70                    &multiline_prompt,
71                ));
72            }
73            current_idx += pair.1.len();
74        }
75
76        if use_ansi_coloring {
77            (left_string, right_string)
78        } else {
79            (strip_ansi(&left_string), strip_ansi(&right_string))
80        }
81    }
82
83    /// Apply the ANSI style formatting to the full string.
84    pub fn render_simple(&self) -> String {
85        self.buffer
86            .iter()
87            .map(|(style, text)| style.paint(text).to_string())
88            .collect()
89    }
90
91    /// Get the unformatted text as a single continuous string.
92    pub fn raw_string(&self) -> String {
93        self.buffer.iter().map(|(_, str)| str.as_str()).collect()
94    }
95}
96
97fn render_as_string(
98    renderable: &(Style, String),
99    prompt_style: &Style,
100    multiline_prompt: &str,
101) -> String {
102    let mut rendered = String::new();
103    let formatted_multiline_prompt = format!("\n{multiline_prompt}");
104    for (line_number, line) in renderable.1.split('\n').enumerate() {
105        if line_number != 0 {
106            rendered.push_str(&prompt_style.paint(&formatted_multiline_prompt).to_string());
107        }
108        rendered.push_str(&renderable.0.paint(line).to_string());
109    }
110    rendered
111}