label_logger/
dialoguer.rs

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