1use std::fmt;
2
3use dialoguer::{
4 console::{style, Style, StyledObject},
5 theme::Theme,
6};
7
8pub struct ColorfulTheme {
9 pub defaults_style: Style,
11 pub prompt_style: Style,
13 pub prompt_prefix: StyledObject<String>,
15 pub prompt_suffix: StyledObject<String>,
17 pub success_prefix: StyledObject<String>,
19 pub success_suffix: StyledObject<String>,
21 pub error_prefix: StyledObject<String>,
23 pub error_style: Style,
25 pub hint_style: Style,
27 pub values_style: Style,
29 pub active_item_style: Style,
31 pub inactive_item_style: Style,
33 pub active_item_prefix: StyledObject<String>,
35 pub inactive_item_prefix: StyledObject<String>,
37 pub checked_item_prefix: StyledObject<String>,
39 pub unchecked_item_prefix: StyledObject<String>,
41 pub picked_item_prefix: StyledObject<String>,
43 pub unpicked_item_prefix: StyledObject<String>,
45 pub inline_selections: bool,
47}
48
49impl Default for ColorfulTheme {
50 fn default() -> ColorfulTheme {
51 ColorfulTheme {
52 defaults_style: Style::new().for_stderr().cyan(),
53 prompt_style: Style::new().for_stderr().bold(),
54 prompt_prefix: style("?".to_string()).for_stderr().yellow(),
55 prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
56 success_prefix: style("✔".to_string()).for_stderr().green(),
57 success_suffix: style("·".to_string()).for_stderr().black().bright(),
58 error_prefix: style("✘".to_string()).for_stderr().red(),
59 error_style: Style::new().for_stderr().red(),
60 hint_style: Style::new().for_stderr().black().bright(),
61 values_style: Style::new().for_stderr().green(),
62 active_item_style: Style::new().for_stderr().cyan(),
63 inactive_item_style: Style::new().for_stderr(),
64 active_item_prefix: style("❯".to_string()).for_stderr().cyan(),
65 inactive_item_prefix: style(" ".to_string()).for_stderr(),
66 checked_item_prefix: style("✔".to_string()).for_stderr().green(),
67 unchecked_item_prefix: style("✔".to_string()).for_stderr().black(),
68 picked_item_prefix: style("❯".to_string()).for_stderr().green(),
69 unpicked_item_prefix: style(" ".to_string()).for_stderr(),
70 inline_selections: true,
71 }
72 }
73}
74
75impl Theme for ColorfulTheme {
76 fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
78 if !prompt.is_empty() {
79 write!(
80 f,
81 "{} {} ",
82 &self.prompt_prefix,
83 self.prompt_style.apply_to(prompt)
84 )?;
85 }
86
87 write!(f, "{}", &self.prompt_suffix)
88 }
89
90 fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
92 write!(
93 f,
94 "{} {}",
95 &self.error_prefix,
96 self.error_style.apply_to(err)
97 )
98 }
99
100 fn format_input_prompt(
102 &self,
103 f: &mut dyn fmt::Write,
104 prompt: &str,
105 default: Option<&str>,
106 ) -> fmt::Result {
107 if !prompt.is_empty() {
108 write!(
109 f,
110 "{} {} ",
111 &self.prompt_prefix,
112 self.prompt_style.apply_to(prompt)
113 )?;
114 }
115
116 match default {
117 Some(default) => write!(
118 f,
119 "{} {} ",
120 self.hint_style.apply_to(&format!("({default})")),
121 &self.prompt_suffix
122 ),
123 None => write!(f, "{} ", &self.prompt_suffix),
124 }
125 }
126
127 fn format_confirm_prompt(
129 &self,
130 f: &mut dyn fmt::Write,
131 prompt: &str,
132 default: Option<bool>,
133 ) -> fmt::Result {
134 if !prompt.is_empty() {
135 write!(
136 f,
137 "{} {} ",
138 &self.prompt_prefix,
139 self.prompt_style.apply_to(prompt)
140 )?;
141 }
142
143 match default {
144 None => write!(
145 f,
146 "{} {}",
147 self.hint_style.apply_to("(y/n)"),
148 &self.prompt_suffix
149 ),
150 Some(true) => write!(
151 f,
152 "{} {} {}",
153 self.hint_style.apply_to("(y/n)"),
154 &self.prompt_suffix,
155 self.defaults_style.apply_to("yes")
156 ),
157 Some(false) => write!(
158 f,
159 "{} {} {}",
160 self.hint_style.apply_to("(y/n)"),
161 &self.prompt_suffix,
162 self.defaults_style.apply_to("no")
163 ),
164 }
165 }
166
167 fn format_confirm_prompt_selection(
169 &self,
170 f: &mut dyn fmt::Write,
171 prompt: &str,
172 selection: Option<bool>,
173 ) -> fmt::Result {
174 if !prompt.is_empty() {
175 write!(
176 f,
177 "{} {} ",
178 &self.success_prefix,
179 self.prompt_style.apply_to(prompt)
180 )?;
181 }
182 let selection = selection.map(|b| if b { "yes" } else { "no" });
183
184 match selection {
185 Some(selection) => {
186 write!(
187 f,
188 "{} {}",
189 &self.success_suffix,
190 self.values_style.apply_to(selection)
191 )
192 }
193 None => {
194 write!(f, "{}", &self.success_suffix)
195 }
196 }
197 }
198
199 fn format_input_prompt_selection(
201 &self,
202 f: &mut dyn fmt::Write,
203 prompt: &str,
204 sel: &str,
205 ) -> fmt::Result {
206 if !prompt.is_empty() {
207 write!(
208 f,
209 "{} {} ",
210 &self.success_prefix,
211 self.prompt_style.apply_to(prompt)
212 )?;
213 }
214
215 write!(
216 f,
217 "{} {}",
218 &self.success_suffix,
219 self.values_style.apply_to(sel)
220 )
221 }
222
223 fn format_multi_select_prompt_selection(
225 &self,
226 f: &mut dyn fmt::Write,
227 prompt: &str,
228 selections: &[&str],
229 ) -> fmt::Result {
230 if !prompt.is_empty() {
231 write!(
232 f,
233 "{} {} ",
234 &self.success_prefix,
235 self.prompt_style.apply_to(prompt)
236 )?;
237 }
238
239 write!(f, "{} ", &self.success_suffix)?;
240
241 if self.inline_selections {
242 for (idx, sel) in selections.iter().enumerate() {
243 write!(
244 f,
245 "{}{}",
246 if idx == 0 { "" } else { ", " },
247 self.values_style.apply_to(sel)
248 )?;
249 }
250 }
251
252 Ok(())
253 }
254
255 fn format_select_prompt_item(
257 &self,
258 f: &mut dyn fmt::Write,
259 text: &str,
260 active: bool,
261 ) -> fmt::Result {
262 let (text, desc) = text.split_once('-').unwrap_or((text, ""));
263
264 if active {
265 write!(
266 f,
267 "{} {}",
268 self.active_item_prefix,
269 self.active_item_style.apply_to(&format!("{}{}",
270 text.trim(),
271 if !desc.is_empty() {
272 format!(" - {}", desc.trim())
273 } else {
274 String::new()
275 }
276 ))
277 )
278 } else {
279 write!(
280 f,
281 "{} {}{}",
282 self.inactive_item_prefix,
283 self.inactive_item_style.apply_to(text.trim()),
284 if !desc.is_empty() {
285 format!(" - {}", self.hint_style.apply_to(desc.trim()))
286 } else {
287 String::new()
288 }
289 )
290 }
291 }
292
293 fn format_multi_select_prompt_item(
295 &self,
296 f: &mut dyn fmt::Write,
297 text: &str,
298 checked: bool,
299 active: bool,
300 ) -> fmt::Result {
301 let details = match (checked, active) {
302 (true, true) => (
303 &self.checked_item_prefix,
304 self.active_item_style.apply_to(text),
305 ),
306 (true, false) => (
307 &self.checked_item_prefix,
308 self.inactive_item_style.apply_to(text),
309 ),
310 (false, true) => (
311 &self.unchecked_item_prefix,
312 self.active_item_style.apply_to(text),
313 ),
314 (false, false) => (
315 &self.unchecked_item_prefix,
316 self.inactive_item_style.apply_to(text),
317 ),
318 };
319
320 write!(f, "{} {}", details.0, details.1)
321 }
322
323 fn format_sort_prompt_item(
325 &self,
326 f: &mut dyn fmt::Write,
327 text: &str,
328 picked: bool,
329 active: bool,
330 ) -> fmt::Result {
331 let details = match (picked, active) {
332 (true, true) => (
333 &self.picked_item_prefix,
334 self.active_item_style.apply_to(text),
335 ),
336 (false, true) => (
337 &self.unpicked_item_prefix,
338 self.active_item_style.apply_to(text),
339 ),
340 (_, false) => (
341 &self.unpicked_item_prefix,
342 self.inactive_item_style.apply_to(text),
343 ),
344 };
345
346 write!(f, "{} {}", details.0, details.1)
347 }
348}