1use crate::styles::{paint, ACCENT, DIM, WHITE};
4use std::io::Write;
5
6use super::core::{Frame, Key, Prompt, RenderCtx, Step};
7use super::cursor::LineBuf;
8use super::error::{PromptError, Result};
9use super::theme;
10use super::validate::Validator;
11
12pub struct TextPrompt {
19 label: String,
20 default: Option<String>,
21 placeholder: Option<String>,
22 hint: Option<String>,
23 validate: Option<Validator>,
24 buf: LineBuf,
25}
26
27pub fn text(label: impl Into<String>) -> TextPrompt {
39 TextPrompt {
40 label: label.into(),
41 default: None,
42 placeholder: None,
43 hint: None,
44 validate: None,
45 buf: LineBuf::new(),
46 }
47}
48
49impl TextPrompt {
50 pub fn default(mut self, v: impl Into<String>) -> Self {
52 self.default = Some(v.into());
53 self
54 }
55 pub fn placeholder(mut self, v: impl Into<String>) -> Self {
57 self.placeholder = Some(v.into());
58 self
59 }
60 pub fn hint(mut self, v: impl Into<String>) -> Self {
64 self.hint = Some(v.into());
65 self
66 }
67 pub fn validate<F>(mut self, f: F) -> Self
70 where
71 F: Fn(&str) -> std::result::Result<(), String> + Send + Sync + 'static,
72 {
73 self.validate = Some(Validator::new(f));
74 self
75 }
76 pub fn rule(mut self, v: Validator) -> Self {
80 self.validate = Some(v);
81 self
82 }
83
84 #[deprecated(since = "0.1.0", note = "use `.rule(...)` instead")]
86 pub fn validate_with(self, v: Validator) -> Self {
87 self.rule(v)
88 }
89
90 pub fn run(self) -> Result<String> {
92 super::core::run(self)
93 }
94
95 fn submit_value(&self) -> String {
96 if self.buf.is_empty() {
97 self.default.clone().unwrap_or_default()
98 } else {
99 self.buf.value().to_string()
100 }
101 }
102}
103
104impl Prompt for TextPrompt {
105 type Output = String;
106
107 fn handle(&mut self, key: Key) -> Step<String> {
108 match key {
109 Key::Enter => {
110 let value = self.submit_value();
111 match self.validate.as_ref().map(|v| v.check(&value)) {
112 Some(Err(msg)) => Step::Reject(msg),
113 _ => Step::Submit(value),
114 }
115 }
116 Key::Char(c) => {
117 self.buf.insert(c);
118 Step::Continue
119 }
120 Key::Backspace => {
121 self.buf.backspace();
122 Step::Continue
123 }
124 Key::Delete => {
125 self.buf.delete();
126 Step::Continue
127 }
128 Key::DeleteWordBack => {
129 self.buf.delete_word_back();
130 Step::Continue
131 }
132 Key::Left => {
133 self.buf.left();
134 Step::Continue
135 }
136 Key::Right => {
137 self.buf.right();
138 Step::Continue
139 }
140 Key::Home => {
141 self.buf.home();
142 Step::Continue
143 }
144 Key::End => {
145 self.buf.end();
146 Step::Continue
147 }
148 Key::Escape | Key::Interrupt => Step::Cancel,
149 _ => Step::Continue,
150 }
151 }
152
153 fn render(&self, ctx: RenderCtx) -> Frame {
154 let mut f: Frame = Vec::with_capacity(4);
155 let err = ctx.error;
156 f.push(theme::label_state(&self.label, err.is_some()));
157 if super::settings::get().show_hints {
158 if let Some(ref h) = self.hint {
159 f.push(theme::hint(h));
160 }
161 }
162 let placeholder = self
163 .placeholder
164 .as_deref()
165 .or(self.default.as_deref())
166 .unwrap_or("");
167 f.push(format!(
168 "{} {}",
169 theme::input_bar(err.is_some()),
170 self.buf.with_placeholder(placeholder)
171 ));
172 f.push(theme::frame_bot(err));
173 f
174 }
175
176 fn render_answered(&self, value: &String) -> Frame {
177 theme::answered(&self.label, value)
178 .split("\r\n")
179 .map(String::from)
180 .collect()
181 }
182
183 fn run_fallback(self) -> Result<String> {
184 let hint = self
185 .default
186 .as_deref()
187 .map(|d| format!(" (default: {d})"))
188 .unwrap_or_default();
189 eprint!(
190 "{} {}{}: ",
191 paint(ACCENT, "◇"),
192 paint(WHITE, &self.label),
193 paint(DIM, &hint)
194 );
195 std::io::stderr().flush().map_err(PromptError::Io)?;
196 loop {
197 let line = super::engine::fallback::read_line_raw()?;
198 let value = if line.is_empty() {
199 self.default.clone().unwrap_or_default()
200 } else {
201 line
202 };
203 if let Some(ref v) = self.validate {
204 if let Err(msg) = v.check(&value) {
205 eprint!(" ✘ {msg}\n Try again: ");
206 std::io::stderr().flush().map_err(PromptError::Io)?;
207 continue;
208 }
209 }
210 return Ok(value);
211 }
212 }
213}
214
215pub struct SecretPrompt {
221 label: String,
222 allow_empty: bool,
223 hint: Option<String>,
224 validate: Option<Validator>,
225 buf: String,
226}
227
228pub fn secret(label: impl Into<String>) -> SecretPrompt {
240 SecretPrompt {
241 label: label.into(),
242 allow_empty: false,
243 hint: None,
244 validate: None,
245 buf: String::new(),
246 }
247}
248
249impl SecretPrompt {
250 pub fn allow_empty(mut self, v: bool) -> Self {
252 self.allow_empty = v;
253 self
254 }
255 pub fn hint(mut self, v: impl Into<String>) -> Self {
258 self.hint = Some(v.into());
259 self
260 }
261 pub fn validate<F>(mut self, f: F) -> Self
263 where
264 F: Fn(&str) -> std::result::Result<(), String> + Send + Sync + 'static,
265 {
266 self.validate = Some(Validator::new(f));
267 self
268 }
269 pub fn rule(mut self, v: Validator) -> Self {
271 self.validate = Some(v);
272 self
273 }
274
275 #[deprecated(since = "0.1.0", note = "use `.rule(...)` instead")]
277 pub fn validate_with(self, v: Validator) -> Self {
278 self.rule(v)
279 }
280
281 pub fn run(self) -> Result<String> {
283 super::core::run(self)
284 }
285}
286
287impl Prompt for SecretPrompt {
288 type Output = String;
289
290 fn handle(&mut self, key: Key) -> Step<String> {
291 match key {
292 Key::Enter => {
293 if self.buf.is_empty() && !self.allow_empty {
294 return Step::Reject("Value is required".into());
295 }
296 match self.validate.as_ref().map(|v| v.check(&self.buf)) {
297 Some(Err(msg)) => Step::Reject(msg),
298 _ => Step::Submit(self.buf.clone()),
299 }
300 }
301 Key::Char(c) => {
302 self.buf.push(c);
303 Step::Continue
304 }
305 Key::Backspace => {
306 self.buf.pop();
307 Step::Continue
308 }
309 Key::Escape | Key::Interrupt => Step::Cancel,
310 _ => Step::Continue,
311 }
312 }
313
314 fn render(&self, ctx: RenderCtx) -> Frame {
315 let err = ctx.error;
316 let mut f: Frame = Vec::with_capacity(4);
317 f.push(theme::label_state(&self.label, err.is_some()));
318 if super::settings::get().show_hints {
319 if let Some(ref h) = self.hint {
320 f.push(theme::hint(h));
321 }
322 }
323 let dots = "•".repeat(self.buf.chars().count().min(32));
324 f.push(format!(
325 "{} {}",
326 theme::input_bar(err.is_some()),
327 paint(super::settings::colors().input, &dots)
328 ));
329 f.push(theme::frame_bot(err));
330 f
331 }
332
333 fn render_answered(&self, _value: &String) -> Frame {
334 theme::answered(&self.label, "••••••••")
335 .split("\r\n")
336 .map(String::from)
337 .collect()
338 }
339
340 fn run_fallback(self) -> Result<String> {
341 eprint!(
342 "{} {} {}: ",
343 paint(ACCENT, "◇"),
344 paint(WHITE, &self.label),
345 paint(DIM, "(input will be visible)"),
346 );
347 std::io::stderr().flush().map_err(PromptError::Io)?;
348 loop {
349 let line = super::engine::fallback::read_line_raw()?;
350 if line.is_empty() && !self.allow_empty {
351 eprint!(" Value cannot be empty. Try again: ");
352 std::io::stderr().flush().map_err(PromptError::Io)?;
353 continue;
354 }
355 if let Some(ref v) = self.validate {
356 if let Err(msg) = v.check(&line) {
357 eprint!(" ✘ {msg}\n Try again: ");
358 std::io::stderr().flush().map_err(PromptError::Io)?;
359 continue;
360 }
361 }
362 return Ok(line);
363 }
364 }
365}