1use std::fmt;
3
4#[cfg(feature = "fuzzy-select")]
5use console::style;
6#[cfg(feature = "fuzzy-select")]
7use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
8
9mod colorful;
10pub(crate) mod render;
11mod simple;
12
13pub use colorful::ColorfulTheme;
14pub use simple::SimpleTheme;
15
16pub trait Theme {
18 #[inline]
20 fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
21 write!(f, "{}:", prompt)
22 }
23
24 #[inline]
26 fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
27 write!(f, "error: {}", err)
28 }
29
30 fn format_confirm_prompt(
32 &self,
33 f: &mut dyn fmt::Write,
34 prompt: &str,
35 default: Option<bool>,
36 ) -> fmt::Result {
37 if !prompt.is_empty() {
38 write!(f, "{} ", &prompt)?;
39 }
40 match default {
41 None => write!(f, "[y/n] ")?,
42 Some(true) => write!(f, "[Y/n] ")?,
43 Some(false) => write!(f, "[y/N] ")?,
44 }
45 Ok(())
46 }
47
48 fn format_confirm_prompt_selection(
50 &self,
51 f: &mut dyn fmt::Write,
52 prompt: &str,
53 selection: Option<bool>,
54 ) -> fmt::Result {
55 let selection = selection.map(|b| if b { "yes" } else { "no" });
56
57 match selection {
58 Some(selection) if prompt.is_empty() => {
59 write!(f, "{}", selection)
60 }
61 Some(selection) => {
62 write!(f, "{} {}", &prompt, selection)
63 }
64 None if prompt.is_empty() => Ok(()),
65 None => {
66 write!(f, "{}", &prompt)
67 }
68 }
69 }
70
71 fn format_input_prompt(
73 &self,
74 f: &mut dyn fmt::Write,
75 prompt: &str,
76 default: Option<&str>,
77 ) -> fmt::Result {
78 match default {
79 Some(default) if prompt.is_empty() => write!(f, "[{}]: ", default),
80 Some(default) => write!(f, "{} [{}]: ", prompt, default),
81 None => write!(f, "{}: ", prompt),
82 }
83 }
84
85 #[inline]
87 fn format_input_prompt_selection(
88 &self,
89 f: &mut dyn fmt::Write,
90 prompt: &str,
91 sel: &str,
92 ) -> fmt::Result {
93 write!(f, "{}: {}", prompt, sel)
94 }
95
96 #[inline]
98 #[cfg(feature = "password")]
99 fn format_password_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
100 self.format_input_prompt(f, prompt, None)
101 }
102
103 #[inline]
105 #[cfg(feature = "password")]
106 fn format_password_prompt_selection(
107 &self,
108 f: &mut dyn fmt::Write,
109 prompt: &str,
110 ) -> fmt::Result {
111 self.format_input_prompt_selection(f, prompt, "[hidden]")
112 }
113
114 #[inline]
116 fn format_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
117 self.format_prompt(f, prompt)
118 }
119
120 #[inline]
122 fn format_select_prompt_selection(
123 &self,
124 f: &mut dyn fmt::Write,
125 prompt: &str,
126 sel: &str,
127 ) -> fmt::Result {
128 self.format_input_prompt_selection(f, prompt, sel)
129 }
130
131 #[inline]
133 fn format_multi_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
134 self.format_prompt(f, prompt)
135 }
136
137 #[inline]
139 fn format_sort_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
140 self.format_prompt(f, prompt)
141 }
142
143 fn format_multi_select_prompt_selection(
145 &self,
146 f: &mut dyn fmt::Write,
147 prompt: &str,
148 selections: &[&str],
149 ) -> fmt::Result {
150 write!(f, "{}: ", prompt)?;
151 for (idx, sel) in selections.iter().enumerate() {
152 write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?;
153 }
154 Ok(())
155 }
156
157 #[inline]
159 fn format_sort_prompt_selection(
160 &self,
161 f: &mut dyn fmt::Write,
162 prompt: &str,
163 selections: &[&str],
164 ) -> fmt::Result {
165 self.format_multi_select_prompt_selection(f, prompt, selections)
166 }
167
168 fn format_select_prompt_item(
170 &self,
171 f: &mut dyn fmt::Write,
172 text: &str,
173 active: bool,
174 ) -> fmt::Result {
175 write!(f, "{} {}", if active { ">" } else { " " }, text)
176 }
177
178 fn format_multi_select_prompt_item(
180 &self,
181 f: &mut dyn fmt::Write,
182 text: &str,
183 checked: bool,
184 active: bool,
185 ) -> fmt::Result {
186 write!(
187 f,
188 "{} {}",
189 match (checked, active) {
190 (true, true) => "> [x]",
191 (true, false) => " [x]",
192 (false, true) => "> [ ]",
193 (false, false) => " [ ]",
194 },
195 text
196 )
197 }
198
199 fn format_sort_prompt_item(
201 &self,
202 f: &mut dyn fmt::Write,
203 text: &str,
204 picked: bool,
205 active: bool,
206 ) -> fmt::Result {
207 write!(
208 f,
209 "{} {}",
210 match (picked, active) {
211 (true, true) => "> [x]",
212 (false, true) => "> [ ]",
213 (_, false) => " [ ]",
214 },
215 text
216 )
217 }
218
219 #[cfg(feature = "fuzzy-select")]
221 fn format_fuzzy_select_prompt_item(
222 &self,
223 f: &mut dyn fmt::Write,
224 text: &str,
225 active: bool,
226 highlight_matches: bool,
227 matcher: &SkimMatcherV2,
228 search_term: &str,
229 ) -> fmt::Result {
230 write!(f, "{} ", if active { ">" } else { " " })?;
231
232 if highlight_matches {
233 if let Some((_score, indices)) = matcher.fuzzy_indices(text, search_term) {
234 for (idx, c) in text.chars().enumerate() {
235 if indices.contains(&idx) {
236 write!(f, "{}", style(c).for_stderr().bold())?;
237 } else {
238 write!(f, "{}", c)?;
239 }
240 }
241
242 return Ok(());
243 }
244 }
245
246 write!(f, "{}", text)
247 }
248
249 #[cfg(feature = "fuzzy-select")]
251 fn format_fuzzy_select_prompt(
252 &self,
253 f: &mut dyn fmt::Write,
254 prompt: &str,
255 search_term: &str,
256 bytes_pos: usize,
257 ) -> fmt::Result {
258 if !prompt.is_empty() {
259 write!(f, "{prompt} ")?;
260 }
261
262 let (st_head, st_tail) = search_term.split_at(bytes_pos);
263 write!(f, "{st_head}|{st_tail}")
264 }
265}