1use std::sync::Arc;
16use std::sync::mpsc::{Receiver, Sender, channel};
17use std::time::Duration;
18
19use rich::Segment;
20
21use crate::component::{Component, Context, Flow, View};
22use crate::components::{Theme, fit, highlight, plain, question, text};
23use crate::event::{Event, Key, KeyCode, Modifiers};
24use crate::fuzzy::rank;
25use crate::keymap::{Keymap, keys};
26use crate::kit::TextBuffer;
27use crate::policy::{LineIo, NotInteractive};
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct Suggestion {
32 pub value: String,
33 pub description: Option<String>,
34}
35
36impl Suggestion {
37 pub fn new(value: impl Into<String>) -> Suggestion {
38 Suggestion {
39 value: value.into(),
40 description: None,
41 }
42 }
43
44 #[must_use]
45 pub fn description(mut self, description: impl Into<String>) -> Suggestion {
46 self.description = Some(description.into());
47 self
48 }
49}
50
51impl From<&str> for Suggestion {
52 fn from(value: &str) -> Suggestion {
53 Suggestion::new(value)
54 }
55}
56
57impl From<String> for Suggestion {
58 fn from(value: String) -> Suggestion {
59 Suggestion::new(value)
60 }
61}
62
63pub type Provider = Arc<dyn Fn(&str) -> Vec<Suggestion> + Send + Sync>;
65type Validator = Box<dyn Fn(&str) -> Result<(), String>>;
66
67pub fn input_keymap() -> Keymap {
69 Keymap::new("input")
70 .bind("submit", keys("enter"), "submit")
71 .bind("cancel", keys("escape"), "cancel")
72 .bind("complete", keys("tab"), "accept a suggestion")
73 .bind("up", keys("up"), "previous suggestion or answer")
74 .bind("down", keys("down"), "next suggestion or answer")
75 .bind("left", keys("left"), "move left")
76 .bind("right", keys("right"), "move right")
77 .bind("home", keys("home ctrl+a"), "go to the start")
78 .bind("end", keys("end ctrl+e"), "go to the end")
79 .bind("delete-to-start", keys("ctrl+u"), "delete to the start")
80 .bind("delete-word", keys("ctrl+w"), "delete a word")
81 .bind("backspace", keys("backspace"), "delete back")
82 .bind("delete", keys("delete"), "delete forward")
83}
84
85#[must_use = "a component does nothing until it is run or mounted"]
87pub struct Input {
88 prompt: String,
89 buffer: TextBuffer,
91 keymap: Keymap,
92 placeholder: Option<String>,
93 default: Option<String>,
94 help: Option<String>,
95 mask: Option<char>,
96 validator: Option<Validator>,
97 error: Option<String>,
98 history: Vec<String>,
99 recall: Option<(usize, String)>,
101 suggestions: Vec<Suggestion>,
102 provider: Option<Provider>,
103 requests: Option<Sender<(u64, String)>>,
105 sender: Sender<(u64, Vec<Suggestion>)>,
107 receiver: Receiver<(u64, Vec<Suggestion>)>,
108 generation: u64,
109 pending: bool,
110 shown: Vec<(Suggestion, Vec<usize>)>,
112 selected: Option<usize>,
113 limit: usize,
114 theme: Theme,
115 answer: Option<Option<String>>,
116}
117
118impl Input {
119 pub fn new(prompt: impl Into<String>) -> Input {
120 let (sender, receiver) = channel();
121 Input {
122 prompt: prompt.into(),
123 buffer: TextBuffer::new(),
124 keymap: input_keymap(),
125 placeholder: None,
126 default: None,
127 help: None,
128 mask: None,
129 validator: None,
130 error: None,
131 history: Vec::new(),
132 recall: None,
133 suggestions: Vec::new(),
134 provider: None,
135 requests: None,
136 sender,
137 receiver,
138 generation: 0,
139 pending: false,
140 shown: Vec::new(),
141 selected: None,
142 limit: 5,
143 theme: Theme::default(),
144 answer: None,
145 }
146 }
147
148 pub fn masked(prompt: impl Into<String>) -> Input {
151 Input::new(prompt).mask('•')
152 }
153
154 pub fn mask(mut self, mask: char) -> Self {
155 self.mask = Some(mask);
156 self
157 }
158
159 pub fn value(mut self, value: impl Into<String>) -> Self {
161 self.buffer.set_text(value);
162 self.refresh();
163 self
164 }
165
166 pub fn rebind(mut self, action: &str, keys: impl IntoIterator<Item = Key>) -> Self {
169 self.keymap.rebind(action, keys);
170 self
171 }
172
173 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
175 self.placeholder = Some(placeholder.into());
176 self
177 }
178
179 pub fn default(mut self, default: impl Into<String>) -> Self {
182 self.default = Some(default.into());
183 self
184 }
185
186 pub fn help(mut self, help: impl Into<String>) -> Self {
188 self.help = Some(help.into());
189 self
190 }
191
192 pub fn validate(mut self, validator: impl Fn(&str) -> Result<(), String> + 'static) -> Self {
194 self.validator = Some(Box::new(validator));
195 self
196 }
197
198 pub fn history<I: IntoIterator<Item = S>, S: Into<String>>(mut self, history: I) -> Self {
200 self.history = history.into_iter().map(Into::into).collect();
201 self
202 }
203
204 pub fn suggestions<I: IntoIterator<Item = S>, S: Into<Suggestion>>(
206 mut self,
207 suggestions: I,
208 ) -> Self {
209 self.suggestions = suggestions.into_iter().map(Into::into).collect();
210 self.refresh();
211 self
212 }
213
214 pub fn provider(
217 mut self,
218 provider: impl Fn(&str) -> Vec<Suggestion> + Send + Sync + 'static,
219 ) -> Self {
220 self.provider = Some(Arc::new(provider));
221 self.refresh();
222 self
223 }
224
225 pub fn limit(mut self, rows: usize) -> Self {
227 self.limit = rows.max(1);
228 self
229 }
230
231 pub fn theme(mut self, theme: Theme) -> Self {
232 self.theme = theme;
233 self
234 }
235
236 pub fn text(&self) -> &str {
238 self.buffer.text()
239 }
240
241 pub fn buffer(&self) -> &TextBuffer {
243 &self.buffer
244 }
245
246 fn insert(&mut self, text: &str) {
247 self.buffer.insert(text);
248 self.changed();
249 }
250
251 fn changed(&mut self) {
252 self.error = None;
253 self.recall = None;
254 self.refresh();
255 }
256
257 fn refresh(&mut self) {
259 self.selected = None;
260 if let Some(provider) = &self.provider {
261 self.generation += 1;
262 self.pending = true;
263 let requests = self.requests.get_or_insert_with(|| {
264 let (requests, queue) = channel::<(u64, String)>();
265 let (provider, sender) = (Arc::clone(provider), self.sender.clone());
266 std::thread::spawn(move || {
268 while let Ok(mut request) = queue.recv() {
269 while let Ok(newer) = queue.try_recv() {
271 request = newer;
272 }
273 let (generation, query) = request;
274 if sender.send((generation, provider(&query))).is_err() {
275 return;
276 }
277 }
278 });
279 requests
280 });
281 let _ = requests.send((self.generation, self.buffer.text().to_string()));
282 let current: Vec<Suggestion> = self.shown.iter().map(|(s, _)| s.clone()).collect();
284 self.show(current);
285 return;
286 }
287 self.show(self.suggestions.clone());
288 }
289
290 fn show(&mut self, suggestions: Vec<Suggestion>) {
291 let texts: Vec<&str> = suggestions.iter().map(|s| s.value.as_str()).collect();
292 let value = self.buffer.text();
293 self.shown = if value.is_empty() {
294 Vec::new()
295 } else {
296 rank(value, texts)
297 .into_iter()
298 .map(|(index, found)| (suggestions[index].clone(), found.positions))
299 .filter(|(suggestion, _)| suggestion.value != value)
300 .take(self.limit)
301 .collect()
302 };
303 }
304
305 fn receive(&mut self) {
307 let mut latest = None;
308 if self.pending
311 && let Ok((generation, results)) = self.receiver.recv_timeout(Duration::from_millis(5))
312 && generation == self.generation
313 {
314 latest = Some(results);
315 }
316 while let Ok((generation, results)) = self.receiver.try_recv() {
317 if generation == self.generation {
318 latest = Some(results);
319 }
320 }
321 if let Some(results) = latest {
322 self.pending = false;
323 self.show(results);
324 }
325 }
326
327 fn accept(&mut self, index: usize) {
328 if let Some((suggestion, _)) = self.shown.get(index) {
329 self.buffer.set_text(suggestion.value.clone());
330 self.changed();
331 }
332 }
333
334 fn recall(&mut self, older: bool) {
335 if self.history.is_empty() {
336 return;
337 }
338 let next = match (&self.recall, older) {
339 (None, true) => Some(self.history.len() - 1),
340 (None, false) => return,
341 (Some((0, _)), true) => Some(0),
342 (Some((index, _)), true) => Some(index - 1),
343 (Some((index, _)), false) if index + 1 < self.history.len() => Some(index + 1),
344 (Some(_), false) => None,
345 };
346 let typed = match self.recall.take() {
347 Some((_, typed)) => typed,
348 None => self.buffer.text().to_string(),
349 };
350 self.buffer.set_text(match next {
351 Some(index) => self.history[index].clone(),
352 None => typed.clone(),
353 });
354 self.error = None;
355 self.recall = next.map(|index| (index, typed));
356 }
357
358 fn submit(&mut self) -> Flow<String> {
359 if let Some(index) = self.selected {
360 self.accept(index);
361 }
362 let mut answer = self.buffer.text().to_string();
363 if answer.is_empty()
364 && let Some(default) = &self.default
365 {
366 answer = default.clone();
367 }
368 if let Some(validator) = &self.validator
369 && let Err(message) = validator(&answer)
370 {
371 self.error = Some(message);
372 return Flow::Continue;
373 }
374 self.answer = Some(Some(answer.clone()));
375 Flow::Done(answer)
376 }
377
378 pub fn resolve(&self) -> Result<String, String> {
381 let mut answer = self.buffer.text().to_string();
382 if answer.is_empty()
383 && let Some(default) = &self.default
384 {
385 answer = default.clone();
386 }
387 if let Some(validator) = &self.validator {
388 validator(&answer)?;
389 }
390 Ok(answer)
391 }
392
393 pub fn field(&self, available: usize) -> (Vec<Segment>, usize) {
397 if self.buffer.is_empty() {
398 let hint = self.placeholder.clone().or_else(|| self.default_hint());
399 return (
400 hint.map(|hint| vec![text(hint, &self.theme.hint)])
401 .unwrap_or_default(),
402 0,
403 );
404 }
405 let (shown, column) = self.buffer.window(available, self.mask);
406 (vec![plain(shown)], column)
407 }
408
409 fn default_hint(&self) -> Option<String> {
413 let default = self.default.as_ref()?;
414 Some(match self.mask {
415 Some(_) => "(default set)".to_string(),
416 None => format!("({default})"),
417 })
418 }
419
420 pub fn label(&self) -> &str {
422 &self.prompt
423 }
424
425 pub fn suggesting(&self) -> bool {
428 !self.shown.is_empty()
429 }
430
431 pub fn has_selection(&self) -> bool {
433 self.selected.is_some()
434 }
435
436 pub fn accept_selected(&mut self) {
438 if let Some(index) = self.selected {
439 self.accept(index);
440 }
441 }
442
443 pub fn suggestion_rows(&self, width: usize, indent: &str) -> Vec<Vec<Segment>> {
445 let theme = &self.theme;
446 let mut lines = Vec::new();
447 for (index, (suggestion, positions)) in self.shown.iter().enumerate() {
448 let selected = self.selected == Some(index);
449 let mut row: Vec<Segment> = vec![plain(indent.to_string())];
450 row.push(if selected {
451 text(format!(" {} ", theme.pointer), &theme.pointer_style)
452 } else {
453 plain(" ")
454 });
455 let base = selected.then_some(&theme.focused);
456 row.extend(highlight(
457 &suggestion.value,
458 positions,
459 base,
460 &theme.matched,
461 ));
462 if let Some(description) = &suggestion.description {
463 row.push(text(format!(" {description}"), &theme.hint));
464 }
465 lines.push(fit(row, width));
466 }
467 if self.pending && self.shown.is_empty() && !self.buffer.is_empty() {
468 lines.push(vec![text(format!("{indent} …"), &theme.hint)]);
469 }
470 lines
471 }
472
473 pub fn display_value(&self, value: &str) -> String {
475 self.shown_value(value)
476 }
477
478 fn shown_value(&self, value: &str) -> String {
479 TextBuffer::masked(value, self.mask)
480 }
481
482 fn key_action(&self, key: Key) -> Option<&str> {
485 self.keymap.action(key).or_else(|| {
486 let typing = matches!(key.code, KeyCode::Char(_));
487 (!typing && key.modifiers != Modifiers::NONE)
488 .then(|| self.keymap.action(Key::new(key.code)))
489 .flatten()
490 })
491 }
492}
493
494impl Component for Input {
495 type Output = String;
496
497 fn handle(&mut self, event: &Event, _: &Context<'_>) -> Flow<String> {
498 if let Event::Tick = event {
499 self.receive();
500 return Flow::Continue;
501 }
502 if let Event::Paste(text) = event {
503 self.insert(&crate::components::pasted(text, " "));
504 return Flow::Continue;
505 }
506 let Some(key) = event.key() else {
507 return Flow::Ignored;
508 };
509 match self.key_action(key) {
510 Some("submit") => return self.submit(),
511 Some("cancel") => {
512 self.answer = Some(None);
513 return Flow::Cancel;
514 }
515 Some("complete") if self.shown.is_empty() => return Flow::Ignored,
518 Some("complete") => {
519 let index = self.selected.unwrap_or(0);
520 self.accept(index);
521 }
522 Some(direction @ ("up" | "down")) if !self.shown.is_empty() => {
523 let last = self.shown.len() - 1;
524 self.selected = Some(match (self.selected, direction == "up") {
525 (None, true) => last,
526 (None, false) => 0,
527 (Some(0), true) => last,
528 (Some(index), true) => index - 1,
529 (Some(index), false) => (index + 1) % (last + 1),
530 });
531 }
532 Some("up") => self.recall(true),
533 Some("down") => self.recall(false),
534 Some("left") => self.buffer.left(),
535 Some("right") => self.buffer.right(),
536 Some("home") => self.buffer.home(),
537 Some("end") => self.buffer.end(),
538 Some("delete-to-start") => {
539 self.buffer.delete_to_start();
540 self.changed();
541 }
542 Some("delete-word") => {
543 self.buffer.delete_word();
544 self.changed();
545 }
546 Some("backspace") => {
547 if self.buffer.backspace() {
548 self.changed();
549 }
550 }
551 Some("delete") => {
552 if self.buffer.delete() {
553 self.changed();
554 }
555 }
556 _ => match key.code {
557 KeyCode::Char(c) if !key.modifiers.ctrl && !key.modifiers.alt => {
558 let mut buffer = [0; 4];
559 self.insert(c.encode_utf8(&mut buffer));
560 }
561 _ => return Flow::Ignored,
562 },
563 }
564 Flow::Continue
565 }
566
567 fn render(&self, context: &Context<'_>) -> View {
568 let width = context.width;
569 let theme = &self.theme;
570 let mut line = question(theme, &self.prompt);
571 if let Some(answer) = &self.answer {
572 line.push(match answer {
573 Some(answer) => text(self.shown_value(answer), &theme.answer),
574 None => text("cancelled", &theme.hint),
575 });
576 return View::new(vec![fit(line, width)]);
577 }
578 let start = crate::components::width(&line);
579 let (field, caret) = self.field(width.saturating_sub(start));
580 line.extend(field);
581 let column = start + caret;
582 let mut lines = vec![fit(line, width)];
583 if let Some(error) = &self.error {
584 lines.push(fit(vec![text(format!(" ✗ {error}"), &theme.error)], width));
585 } else if let Some(help) = &self.help {
586 lines.push(fit(vec![text(format!(" {help}"), &theme.hint)], width));
587 }
588 lines.extend(self.suggestion_rows(width, ""));
589 View::new(lines).with_cursor(0, column.min(width.saturating_sub(1)))
590 }
591
592 fn tick(&self) -> Option<Duration> {
593 self.provider.as_ref().map(|_| Duration::from_millis(30))
594 }
595
596 fn keymap(&self) -> Keymap {
597 self.keymap.clone()
598 }
599
600 fn default_value(&self) -> Option<String> {
601 self.default.clone()
602 }
603
604 fn prompt(&mut self, io: &mut dyn LineIo) -> Result<Option<String>, NotInteractive> {
605 let mut prompt = self.prompt.clone();
606 if let Some(hint) = self.default_hint() {
607 prompt.push_str(&format!(" [{}]", &hint[1..hint.len() - 1]));
608 }
609 let prompt = format!("{prompt}: ");
610 let read = if self.mask.is_some() {
613 io.prompt_secret(&prompt)
614 } else {
615 io.write(&prompt);
616 io.read_line().map(Some).ok_or(NotInteractive::Ended)
617 };
618 let mut line = match read {
619 Ok(Some(line)) => line,
620 Ok(None) => return Ok(None),
621 Err(NotInteractive::Ended) => match &self.default {
623 Some(default) => default.clone(),
624 None => return Err(NotInteractive::Ended),
625 },
626 Err(error) => return Err(error),
627 };
628 if line.is_empty()
629 && let Some(default) = &self.default
630 {
631 line = default.clone();
632 }
633 if let Some(validator) = &self.validator {
634 validator(&line).map_err(NotInteractive::Invalid)?;
635 }
636 Ok(Some(line))
637 }
638}