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
9pub(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 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}