dialoguer/theme/
render.rs

1use std::{fmt, io};
2
3use console::{measure_text_width, Term};
4#[cfg(feature = "fuzzy-select")]
5use fuzzy_matcher::skim::SkimMatcherV2;
6
7use crate::{theme::Theme, Result};
8
9/// Helper struct to conveniently render a theme.
10pub(crate) struct TermThemeRenderer<'a> {
11    term: &'a Term,
12    theme: &'a dyn Theme,
13    height: usize,
14    prompt_height: usize,
15    prompts_reset_height: bool,
16}
17
18impl<'a> TermThemeRenderer<'a> {
19    pub fn new(term: &'a Term, theme: &'a dyn Theme) -> TermThemeRenderer<'a> {
20        TermThemeRenderer {
21            term,
22            theme,
23            height: 0,
24            prompt_height: 0,
25            prompts_reset_height: true,
26        }
27    }
28
29    #[cfg(feature = "password")]
30    pub fn set_prompts_reset_height(&mut self, val: bool) {
31        self.prompts_reset_height = val;
32    }
33
34    #[cfg(feature = "password")]
35    pub fn term(&self) -> &Term {
36        self.term
37    }
38
39    pub fn add_line(&mut self) {
40        self.height += 1;
41    }
42
43    fn write_formatted_str<
44        F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
45    >(
46        &mut self,
47        f: F,
48    ) -> Result<usize> {
49        let mut buf = String::new();
50        f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
51        self.height += buf.chars().filter(|&x| x == '\n').count();
52        self.term.write_str(&buf)?;
53        Ok(measure_text_width(&buf))
54    }
55
56    fn write_formatted_line<
57        F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
58    >(
59        &mut self,
60        f: F,
61    ) -> Result {
62        let mut buf = String::new();
63        f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
64        self.height += buf.chars().filter(|&x| x == '\n').count() + 1;
65        Ok(self.term.write_line(&buf)?)
66    }
67
68    fn write_formatted_prompt<
69        F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
70    >(
71        &mut self,
72        f: F,
73    ) -> Result {
74        self.write_formatted_line(f)?;
75        if self.prompts_reset_height {
76            self.prompt_height = self.height;
77            self.height = 0;
78        }
79        Ok(())
80    }
81
82    fn write_paging_info(buf: &mut dyn fmt::Write, paging_info: (usize, usize)) -> fmt::Result {
83        write!(buf, " [Page {}/{}] ", paging_info.0, paging_info.1)
84    }
85
86    pub fn error(&mut self, err: &str) -> Result {
87        self.write_formatted_line(|this, buf| this.theme.format_error(buf, err))
88    }
89
90    pub fn confirm_prompt(&mut self, prompt: &str, default: Option<bool>) -> Result<usize> {
91        self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default))
92    }
93
94    pub fn confirm_prompt_selection(&mut self, prompt: &str, sel: Option<bool>) -> Result {
95        self.write_formatted_prompt(|this, buf| {
96            this.theme.format_confirm_prompt_selection(buf, prompt, sel)
97        })
98    }
99
100    #[cfg(feature = "fuzzy-select")]
101    pub fn fuzzy_select_prompt(
102        &mut self,
103        prompt: &str,
104        search_term: &str,
105        cursor_pos: usize,
106    ) -> Result {
107        self.write_formatted_prompt(|this, buf| {
108            this.theme
109                .format_fuzzy_select_prompt(buf, prompt, search_term, cursor_pos)
110        })
111    }
112
113    pub fn input_prompt(&mut self, prompt: &str, default: Option<&str>) -> Result<usize> {
114        self.write_formatted_str(|this, buf| this.theme.format_input_prompt(buf, prompt, default))
115    }
116
117    pub fn input_prompt_selection(&mut self, prompt: &str, sel: &str) -> Result {
118        self.write_formatted_prompt(|this, buf| {
119            this.theme.format_input_prompt_selection(buf, prompt, sel)
120        })
121    }
122
123    #[cfg(feature = "password")]
124    pub fn password_prompt(&mut self, prompt: &str) -> Result<usize> {
125        self.write_formatted_str(|this, buf| {
126            write!(buf, "\r")?;
127            this.theme.format_password_prompt(buf, prompt)
128        })
129    }
130
131    #[cfg(feature = "password")]
132    pub fn password_prompt_selection(&mut self, prompt: &str) -> Result {
133        self.write_formatted_prompt(|this, buf| {
134            this.theme.format_password_prompt_selection(buf, prompt)
135        })
136    }
137
138    pub fn select_prompt(&mut self, prompt: &str, paging_info: Option<(usize, usize)>) -> Result {
139        self.write_formatted_prompt(|this, buf| {
140            this.theme.format_select_prompt(buf, prompt)?;
141
142            if let Some(paging_info) = paging_info {
143                TermThemeRenderer::write_paging_info(buf, paging_info)?;
144            }
145
146            Ok(())
147        })
148    }
149
150    pub fn select_prompt_selection(&mut self, prompt: &str, sel: &str) -> Result {
151        self.write_formatted_prompt(|this, buf| {
152            this.theme.format_select_prompt_selection(buf, prompt, sel)
153        })
154    }
155
156    pub fn select_prompt_item(&mut self, text: &str, active: bool) -> Result {
157        self.write_formatted_line(|this, buf| {
158            this.theme.format_select_prompt_item(buf, text, active)
159        })
160    }
161
162    #[cfg(feature = "fuzzy-select")]
163    pub fn fuzzy_select_prompt_item(
164        &mut self,
165        text: &str,
166        active: bool,
167        highlight: bool,
168        matcher: &SkimMatcherV2,
169        search_term: &str,
170    ) -> Result {
171        self.write_formatted_line(|this, buf| {
172            this.theme.format_fuzzy_select_prompt_item(
173                buf,
174                text,
175                active,
176                highlight,
177                matcher,
178                search_term,
179            )
180        })
181    }
182
183    pub fn multi_select_prompt(
184        &mut self,
185        prompt: &str,
186        paging_info: Option<(usize, usize)>,
187    ) -> Result {
188        self.write_formatted_prompt(|this, buf| {
189            this.theme.format_multi_select_prompt(buf, prompt)?;
190
191            if let Some(paging_info) = paging_info {
192                TermThemeRenderer::write_paging_info(buf, paging_info)?;
193            }
194
195            Ok(())
196        })
197    }
198
199    pub fn multi_select_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> Result {
200        self.write_formatted_prompt(|this, buf| {
201            this.theme
202                .format_multi_select_prompt_selection(buf, prompt, sel)
203        })
204    }
205
206    pub fn multi_select_prompt_item(&mut self, text: &str, checked: bool, active: bool) -> Result {
207        self.write_formatted_line(|this, buf| {
208            this.theme
209                .format_multi_select_prompt_item(buf, text, checked, active)
210        })
211    }
212
213    pub fn sort_prompt(&mut self, prompt: &str, paging_info: Option<(usize, usize)>) -> Result {
214        self.write_formatted_prompt(|this, buf| {
215            this.theme.format_sort_prompt(buf, prompt)?;
216
217            if let Some(paging_info) = paging_info {
218                TermThemeRenderer::write_paging_info(buf, paging_info)?;
219            }
220
221            Ok(())
222        })
223    }
224
225    pub fn sort_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> Result {
226        self.write_formatted_prompt(|this, buf| {
227            this.theme.format_sort_prompt_selection(buf, prompt, sel)
228        })
229    }
230
231    pub fn sort_prompt_item(&mut self, text: &str, picked: bool, active: bool) -> Result {
232        self.write_formatted_line(|this, buf| {
233            this.theme
234                .format_sort_prompt_item(buf, text, picked, active)
235        })
236    }
237
238    pub fn clear(&mut self) -> Result {
239        self.term
240            .clear_last_lines(self.height + self.prompt_height)?;
241        self.height = 0;
242        self.prompt_height = 0;
243        Ok(())
244    }
245
246    pub fn clear_preserve_prompt(&mut self, size_vec: &[usize]) -> Result {
247        let mut new_height = self.height;
248        let prefix_width = 2;
249        //Check each item size, increment on finding an overflow
250        for size in size_vec {
251            if *size > self.term.size().1 as usize {
252                new_height += (((*size as f64 + prefix_width as f64) / self.term.size().1 as f64)
253                    .ceil()) as usize
254                    - 1;
255            }
256        }
257
258        self.term.clear_last_lines(new_height)?;
259        self.height = 0;
260        Ok(())
261    }
262}