dialoguer/theme/
colorful.rs

1use std::fmt;
2
3use console::{style, Style, StyledObject};
4#[cfg(feature = "fuzzy-select")]
5use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
6
7use crate::theme::Theme;
8
9/// A colorful theme
10pub struct ColorfulTheme {
11    /// The style for default values
12    pub defaults_style: Style,
13    /// The style for prompt
14    pub prompt_style: Style,
15    /// Prompt prefix value and style
16    pub prompt_prefix: StyledObject<String>,
17    /// Prompt suffix value and style
18    pub prompt_suffix: StyledObject<String>,
19    /// Prompt on success prefix value and style
20    pub success_prefix: StyledObject<String>,
21    /// Prompt on success suffix value and style
22    pub success_suffix: StyledObject<String>,
23    /// Error prefix value and style
24    pub error_prefix: StyledObject<String>,
25    /// The style for error message
26    pub error_style: Style,
27    /// The style for hints
28    pub hint_style: Style,
29    /// The style for values on prompt success
30    pub values_style: Style,
31    /// The style for active items
32    pub active_item_style: Style,
33    /// The style for inactive items
34    pub inactive_item_style: Style,
35    /// Active item in select prefix value and style
36    pub active_item_prefix: StyledObject<String>,
37    /// Inctive item in select prefix value and style
38    pub inactive_item_prefix: StyledObject<String>,
39    /// Checked item in multi select prefix value and style
40    pub checked_item_prefix: StyledObject<String>,
41    /// Unchecked item in multi select prefix value and style
42    pub unchecked_item_prefix: StyledObject<String>,
43    /// Picked item in sort prefix value and style
44    pub picked_item_prefix: StyledObject<String>,
45    /// Unpicked item in sort prefix value and style
46    pub unpicked_item_prefix: StyledObject<String>,
47    /// Formats the cursor for a fuzzy select prompt
48    #[cfg(feature = "fuzzy-select")]
49    pub fuzzy_cursor_style: Style,
50    // Formats the highlighting if matched characters
51    #[cfg(feature = "fuzzy-select")]
52    pub fuzzy_match_highlight_style: Style,
53}
54
55impl Default for ColorfulTheme {
56    fn default() -> ColorfulTheme {
57        ColorfulTheme {
58            defaults_style: Style::new().for_stderr().cyan(),
59            prompt_style: Style::new().for_stderr().bold(),
60            prompt_prefix: style("?".to_string()).for_stderr().yellow(),
61            prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
62            success_prefix: style("✔".to_string()).for_stderr().green(),
63            success_suffix: style("·".to_string()).for_stderr().black().bright(),
64            error_prefix: style("✘".to_string()).for_stderr().red(),
65            error_style: Style::new().for_stderr().red(),
66            hint_style: Style::new().for_stderr().black().bright(),
67            values_style: Style::new().for_stderr().green(),
68            active_item_style: Style::new().for_stderr().cyan(),
69            inactive_item_style: Style::new().for_stderr(),
70            active_item_prefix: style("❯".to_string()).for_stderr().green(),
71            inactive_item_prefix: style(" ".to_string()).for_stderr(),
72            checked_item_prefix: style("✔".to_string()).for_stderr().green(),
73            unchecked_item_prefix: style("⬚".to_string()).for_stderr().magenta(),
74            picked_item_prefix: style("❯".to_string()).for_stderr().green(),
75            unpicked_item_prefix: style(" ".to_string()).for_stderr(),
76            #[cfg(feature = "fuzzy-select")]
77            fuzzy_cursor_style: Style::new().for_stderr().black().on_white(),
78            #[cfg(feature = "fuzzy-select")]
79            fuzzy_match_highlight_style: Style::new().for_stderr().bold(),
80        }
81    }
82}
83
84impl Theme for ColorfulTheme {
85    /// Formats a prompt.
86    fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
87        if !prompt.is_empty() {
88            write!(
89                f,
90                "{} {} ",
91                &self.prompt_prefix,
92                self.prompt_style.apply_to(prompt)
93            )?;
94        }
95
96        write!(f, "{}", &self.prompt_suffix)
97    }
98
99    /// Formats an error
100    fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
101        write!(
102            f,
103            "{} {}",
104            &self.error_prefix,
105            self.error_style.apply_to(err)
106        )
107    }
108
109    /// Formats an input prompt.
110    fn format_input_prompt(
111        &self,
112        f: &mut dyn fmt::Write,
113        prompt: &str,
114        default: Option<&str>,
115    ) -> fmt::Result {
116        if !prompt.is_empty() {
117            write!(
118                f,
119                "{} {} ",
120                &self.prompt_prefix,
121                self.prompt_style.apply_to(prompt)
122            )?;
123        }
124
125        match default {
126            Some(default) => write!(
127                f,
128                "{} {} ",
129                self.hint_style.apply_to(&format!("({})", default)),
130                &self.prompt_suffix
131            ),
132            None => write!(f, "{} ", &self.prompt_suffix),
133        }
134    }
135
136    /// Formats a confirm prompt.
137    fn format_confirm_prompt(
138        &self,
139        f: &mut dyn fmt::Write,
140        prompt: &str,
141        default: Option<bool>,
142    ) -> fmt::Result {
143        if !prompt.is_empty() {
144            write!(
145                f,
146                "{} {} ",
147                &self.prompt_prefix,
148                self.prompt_style.apply_to(prompt)
149            )?;
150        }
151
152        match default {
153            None => write!(
154                f,
155                "{} {}",
156                self.hint_style.apply_to("(y/n)"),
157                &self.prompt_suffix
158            ),
159            Some(true) => write!(
160                f,
161                "{} {} {}",
162                self.hint_style.apply_to("(y/n)"),
163                &self.prompt_suffix,
164                self.defaults_style.apply_to("yes")
165            ),
166            Some(false) => write!(
167                f,
168                "{} {} {}",
169                self.hint_style.apply_to("(y/n)"),
170                &self.prompt_suffix,
171                self.defaults_style.apply_to("no")
172            ),
173        }
174    }
175
176    /// Formats a confirm prompt after selection.
177    fn format_confirm_prompt_selection(
178        &self,
179        f: &mut dyn fmt::Write,
180        prompt: &str,
181        selection: Option<bool>,
182    ) -> fmt::Result {
183        if !prompt.is_empty() {
184            write!(
185                f,
186                "{} {} ",
187                &self.success_prefix,
188                self.prompt_style.apply_to(prompt)
189            )?;
190        }
191        let selection = selection.map(|b| if b { "yes" } else { "no" });
192
193        match selection {
194            Some(selection) => {
195                write!(
196                    f,
197                    "{} {}",
198                    &self.success_suffix,
199                    self.values_style.apply_to(selection)
200                )
201            }
202            None => {
203                write!(f, "{}", &self.success_suffix)
204            }
205        }
206    }
207
208    /// Formats an input prompt after selection.
209    fn format_input_prompt_selection(
210        &self,
211        f: &mut dyn fmt::Write,
212        prompt: &str,
213        sel: &str,
214    ) -> fmt::Result {
215        if !prompt.is_empty() {
216            write!(
217                f,
218                "{} {} ",
219                &self.success_prefix,
220                self.prompt_style.apply_to(prompt)
221            )?;
222        }
223
224        write!(
225            f,
226            "{} {}",
227            &self.success_suffix,
228            self.values_style.apply_to(sel)
229        )
230    }
231
232    /// Formats a password prompt after selection.
233    #[cfg(feature = "password")]
234    fn format_password_prompt_selection(
235        &self,
236        f: &mut dyn fmt::Write,
237        prompt: &str,
238    ) -> fmt::Result {
239        self.format_input_prompt_selection(f, prompt, "********")
240    }
241
242    /// Formats a multi select prompt after selection.
243    fn format_multi_select_prompt_selection(
244        &self,
245        f: &mut dyn fmt::Write,
246        prompt: &str,
247        selections: &[&str],
248    ) -> fmt::Result {
249        if !prompt.is_empty() {
250            write!(
251                f,
252                "{} {} ",
253                &self.success_prefix,
254                self.prompt_style.apply_to(prompt)
255            )?;
256        }
257
258        write!(f, "{} ", &self.success_suffix)?;
259
260        for (idx, sel) in selections.iter().enumerate() {
261            write!(
262                f,
263                "{}{}",
264                if idx == 0 { "" } else { ", " },
265                self.values_style.apply_to(sel)
266            )?;
267        }
268
269        Ok(())
270    }
271
272    /// Formats a select prompt item.
273    fn format_select_prompt_item(
274        &self,
275        f: &mut dyn fmt::Write,
276        text: &str,
277        active: bool,
278    ) -> fmt::Result {
279        let details = if active {
280            (
281                &self.active_item_prefix,
282                self.active_item_style.apply_to(text),
283            )
284        } else {
285            (
286                &self.inactive_item_prefix,
287                self.inactive_item_style.apply_to(text),
288            )
289        };
290
291        write!(f, "{} {}", details.0, details.1)
292    }
293
294    /// Formats a multi select prompt item.
295    fn format_multi_select_prompt_item(
296        &self,
297        f: &mut dyn fmt::Write,
298        text: &str,
299        checked: bool,
300        active: bool,
301    ) -> fmt::Result {
302        let details = match (checked, active) {
303            (true, true) => (
304                &self.checked_item_prefix,
305                self.active_item_style.apply_to(text),
306            ),
307            (true, false) => (
308                &self.checked_item_prefix,
309                self.inactive_item_style.apply_to(text),
310            ),
311            (false, true) => (
312                &self.unchecked_item_prefix,
313                self.active_item_style.apply_to(text),
314            ),
315            (false, false) => (
316                &self.unchecked_item_prefix,
317                self.inactive_item_style.apply_to(text),
318            ),
319        };
320
321        write!(f, "{} {}", details.0, details.1)
322    }
323
324    /// Formats a sort prompt item.
325    fn format_sort_prompt_item(
326        &self,
327        f: &mut dyn fmt::Write,
328        text: &str,
329        picked: bool,
330        active: bool,
331    ) -> fmt::Result {
332        let details = match (picked, active) {
333            (true, true) => (
334                &self.picked_item_prefix,
335                self.active_item_style.apply_to(text),
336            ),
337            (false, true) => (
338                &self.unpicked_item_prefix,
339                self.active_item_style.apply_to(text),
340            ),
341            (_, false) => (
342                &self.unpicked_item_prefix,
343                self.inactive_item_style.apply_to(text),
344            ),
345        };
346
347        write!(f, "{} {}", details.0, details.1)
348    }
349
350    /// Formats a fuzzy select prompt item.
351    #[cfg(feature = "fuzzy-select")]
352    fn format_fuzzy_select_prompt_item(
353        &self,
354        f: &mut dyn fmt::Write,
355        text: &str,
356        active: bool,
357        highlight_matches: bool,
358        matcher: &SkimMatcherV2,
359        search_term: &str,
360    ) -> fmt::Result {
361        write!(
362            f,
363            "{} ",
364            if active {
365                &self.active_item_prefix
366            } else {
367                &self.inactive_item_prefix
368            }
369        )?;
370
371        if highlight_matches {
372            if let Some((_score, indices)) = matcher.fuzzy_indices(text, search_term) {
373                for (idx, c) in text.chars().enumerate() {
374                    if indices.contains(&idx) {
375                        if active {
376                            write!(
377                                f,
378                                "{}",
379                                self.active_item_style
380                                    .apply_to(self.fuzzy_match_highlight_style.apply_to(c))
381                            )?;
382                        } else {
383                            write!(f, "{}", self.fuzzy_match_highlight_style.apply_to(c))?;
384                        }
385                    } else if active {
386                        write!(f, "{}", self.active_item_style.apply_to(c))?;
387                    } else {
388                        write!(f, "{}", c)?;
389                    }
390                }
391
392                return Ok(());
393            }
394        }
395
396        write!(f, "{}", text)
397    }
398
399    /// Formats a fuzzy-selectprompt after selection.
400    #[cfg(feature = "fuzzy-select")]
401    fn format_fuzzy_select_prompt(
402        &self,
403        f: &mut dyn fmt::Write,
404        prompt: &str,
405        search_term: &str,
406        bytes_pos: usize,
407    ) -> fmt::Result {
408        if !prompt.is_empty() {
409            write!(
410                f,
411                "{} {} ",
412                self.prompt_prefix,
413                self.prompt_style.apply_to(prompt)
414            )?;
415        }
416
417        let (st_head, remaining) = search_term.split_at(bytes_pos);
418        let mut chars = remaining.chars();
419        let chr = chars.next().unwrap_or(' ');
420        let st_cursor = self.fuzzy_cursor_style.apply_to(chr);
421        let st_tail = chars.as_str();
422
423        let prompt_suffix = &self.prompt_suffix;
424        write!(f, "{prompt_suffix} {st_head}{st_cursor}{st_tail}",)
425    }
426}