inquire/ui/
backend.rs

1use std::{collections::BTreeSet, fmt::Display, io::Result};
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::{
6    error::InquireResult,
7    input::Input,
8    list_option::ListOption,
9    terminal::Terminal,
10    ui::{IndexPrefix, Key, RenderConfig, Styled},
11    utils::{int_log10, Page},
12    validator::ErrorMessage,
13};
14
15use super::{frame_renderer::FrameRenderer, InputReader};
16
17pub trait CommonBackend: InputReader {
18    fn frame_setup(&mut self) -> Result<()>;
19    fn frame_finish(&mut self, is_last_frame: bool) -> Result<()>;
20
21    fn render_canceled_prompt(&mut self, prompt: &str) -> Result<()>;
22    fn render_prompt_with_answer(&mut self, prompt: &str, answer: &str) -> Result<()>;
23
24    fn render_error_message(&mut self, error: &ErrorMessage) -> Result<()>;
25    fn render_help_message(&mut self, help: &str) -> Result<()>;
26}
27
28pub trait TextBackend: CommonBackend {
29    fn render_prompt(
30        &mut self,
31        prompt: &str,
32        default: Option<&str>,
33        cur_input: &Input,
34    ) -> Result<()>;
35    fn render_suggestions<D: Display>(&mut self, page: Page<'_, ListOption<D>>) -> Result<()>;
36}
37
38#[cfg(feature = "editor")]
39pub trait EditorBackend: CommonBackend {
40    fn render_prompt(&mut self, prompt: &str, editor_command: &str) -> Result<()>;
41}
42
43pub trait SelectBackend: CommonBackend {
44    fn render_select_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()>;
45    fn render_options<D: Display>(&mut self, page: Page<'_, ListOption<D>>) -> Result<()>;
46}
47
48pub trait MultiSelectBackend: CommonBackend {
49    fn render_multiselect_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()>;
50    fn render_options<D: Display>(
51        &mut self,
52        page: Page<'_, ListOption<D>>,
53        checked: &BTreeSet<usize>,
54    ) -> Result<()>;
55}
56
57pub trait CustomTypeBackend: CommonBackend {
58    fn render_prompt(
59        &mut self,
60        prompt: &str,
61        default: Option<&str>,
62        cur_input: &Input,
63    ) -> Result<()>;
64}
65
66pub trait PasswordBackend: CommonBackend {
67    fn render_prompt(&mut self, prompt: &str) -> Result<()>;
68    fn render_prompt_with_masked_input(&mut self, prompt: &str, cur_input: &Input) -> Result<()>;
69    fn render_prompt_with_full_input(&mut self, prompt: &str, cur_input: &Input) -> Result<()>;
70}
71
72#[derive(Clone, Copy, Debug, Default)]
73pub struct Position {
74    pub row: u16,
75    pub col: u16,
76}
77
78pub struct Backend<'a, I, T>
79where
80    I: InputReader,
81    T: Terminal,
82{
83    frame_renderer: FrameRenderer<T>,
84    input_reader: I,
85    render_config: RenderConfig<'a>,
86}
87
88impl<'a, I, T> Backend<'a, I, T>
89where
90    I: InputReader,
91    T: Terminal,
92{
93    #[allow(clippy::large_types_passed_by_value)]
94    pub fn new(input_reader: I, terminal: T, render_config: RenderConfig<'a>) -> Result<Self> {
95        let backend = Self {
96            frame_renderer: FrameRenderer::new(terminal)?,
97            input_reader,
98            render_config,
99        };
100
101        Ok(backend)
102    }
103
104    fn print_option_prefix<D: Display>(
105        &mut self,
106        option_relative_index: usize,
107        page: &Page<'_, ListOption<D>>,
108    ) -> Result<()> {
109        let empty_prefix = Styled::new(" ");
110
111        let x = if page.cursor == Some(option_relative_index) {
112            self.render_config.highlighted_option_prefix
113        } else if option_relative_index == 0 && !page.first {
114            self.render_config.scroll_up_prefix
115        } else if (option_relative_index + 1) == page.content.len() && !page.last {
116            self.render_config.scroll_down_prefix
117        } else {
118            empty_prefix
119        };
120
121        self.frame_renderer.write_styled(x)
122    }
123
124    fn print_option_value<D: Display>(
125        &mut self,
126        option_relative_index: usize,
127        option: &ListOption<D>,
128        page: &Page<'_, ListOption<D>>,
129    ) -> Result<()> {
130        let stylesheet = if let Some(selected_option_style) = self.render_config.selected_option {
131            match page.cursor {
132                Some(cursor) if cursor == option_relative_index => selected_option_style,
133                _ => self.render_config.option,
134            }
135        } else {
136            self.render_config.option
137        };
138
139        self.frame_renderer
140            .write_styled(Styled::new(&option.value).with_style_sheet(stylesheet))
141    }
142
143    fn print_option_index_prefix(&mut self, index: usize, max_index: usize) -> Option<Result<()>> {
144        let index = index.saturating_add(1);
145
146        let content = match self.render_config.option_index_prefix {
147            IndexPrefix::None => None,
148            IndexPrefix::Simple => Some(format!("{index})")),
149            IndexPrefix::SpacePadded => {
150                let width = int_log10(max_index.saturating_add(1));
151                Some(format!("{index:width$})"))
152            }
153            IndexPrefix::ZeroPadded => {
154                let width = int_log10(max_index.saturating_add(1));
155                Some(format!("{index:0width$})"))
156            }
157        };
158
159        content.map(|prefix| {
160            self.frame_renderer
161                .write_styled(Styled::new(prefix).with_style_sheet(self.render_config.option))
162        })
163    }
164
165    fn print_default_value(&mut self, value: &str) -> Result<()> {
166        let content = format!("({value})");
167        let token = Styled::new(content).with_style_sheet(self.render_config.default_value);
168
169        self.frame_renderer.write_styled(token)
170    }
171
172    fn print_prompt_with_prefix(&mut self, prefix: Styled<&str>, prompt: &str) -> Result<()> {
173        self.frame_renderer.write_styled(prefix)?;
174
175        self.frame_renderer.write(" ")?;
176
177        self.frame_renderer
178            .write_styled(Styled::new(prompt).with_style_sheet(self.render_config.prompt))?;
179
180        Ok(())
181    }
182
183    fn print_prompt(&mut self, prompt: &str) -> Result<()> {
184        self.print_prompt_with_prefix(self.render_config.prompt_prefix, prompt)
185    }
186
187    fn print_input(&mut self, input: &Input) -> Result<()> {
188        self.frame_renderer.write(" ")?;
189
190        // The cursor is at the beginning of the input line.
191        // From here it's easier to mark the wanted cursor position
192        // (based on the underlying input struct), as it's a simple
193        // cur_pos + offset calculation.
194        self.frame_renderer
195            .mark_cursor_position(input.pre_cursor().width() as isize);
196
197        if input.is_empty() {
198            match input.placeholder() {
199                Some("") | None => {}
200                Some(p) => self.frame_renderer.write_styled(
201                    Styled::new(p).with_style_sheet(self.render_config.placeholder),
202                )?,
203            }
204        } else {
205            self.frame_renderer.write_styled(
206                Styled::new(input.content()).with_style_sheet(self.render_config.text_input),
207            )?;
208        }
209
210        // if cursor is at end of input, we need to add
211        // a space, otherwise the cursor will render on the
212        // \n character, on the next line.
213        if input.cursor() == input.length() {
214            self.frame_renderer.write(' ')?;
215        }
216
217        Ok(())
218    }
219
220    fn print_prompt_with_input(
221        &mut self,
222        prompt: &str,
223        default: Option<&str>,
224        input: &Input,
225    ) -> Result<()> {
226        self.print_prompt(prompt)?;
227
228        if let Some(default) = default {
229            self.frame_renderer.write(" ")?;
230            self.print_default_value(default)?;
231        }
232
233        self.print_input(input)?;
234
235        self.new_line()?;
236
237        Ok(())
238    }
239
240    fn new_line(&mut self) -> Result<()> {
241        self.frame_renderer.write("\n")?;
242        Ok(())
243    }
244}
245
246impl<'a, I, T> CommonBackend for Backend<'a, I, T>
247where
248    I: InputReader,
249    T: Terminal,
250{
251    fn frame_setup(&mut self) -> Result<()> {
252        self.frame_renderer.start_frame()
253    }
254
255    fn frame_finish(&mut self, is_last_frame: bool) -> Result<()> {
256        self.frame_renderer.finish_current_frame(is_last_frame)
257    }
258
259    fn render_canceled_prompt(&mut self, prompt: &str) -> Result<()> {
260        self.print_prompt(prompt)?;
261
262        self.frame_renderer.write(" ")?;
263
264        self.frame_renderer
265            .write_styled(self.render_config.canceled_prompt_indicator)?;
266
267        self.new_line()?;
268
269        Ok(())
270    }
271
272    fn render_prompt_with_answer(&mut self, prompt: &str, answer: &str) -> Result<()> {
273        self.print_prompt_with_prefix(self.render_config.answered_prompt_prefix, prompt)?;
274
275        self.frame_renderer.write(" ")?;
276
277        let token = Styled::new(answer).with_style_sheet(self.render_config.answer);
278        self.frame_renderer.write_styled(token)?;
279
280        self.new_line()?;
281
282        Ok(())
283    }
284
285    fn render_error_message(&mut self, error: &ErrorMessage) -> Result<()> {
286        self.frame_renderer
287            .write_styled(self.render_config.error_message.prefix)?;
288
289        self.frame_renderer.write_styled(
290            Styled::new(" ").with_style_sheet(self.render_config.error_message.separator),
291        )?;
292
293        let message = match error {
294            ErrorMessage::Default => self.render_config.error_message.default_message,
295            ErrorMessage::Custom(msg) => msg,
296        };
297
298        self.frame_renderer.write_styled(
299            Styled::new(message).with_style_sheet(self.render_config.error_message.message),
300        )?;
301
302        self.new_line()?;
303
304        Ok(())
305    }
306
307    fn render_help_message(&mut self, help: &str) -> Result<()> {
308        self.new_line()?;
309
310        self.frame_renderer
311            .write_styled(Styled::new(help).with_style_sheet(self.render_config.help_message))?;
312
313        self.new_line()?;
314
315        Ok(())
316    }
317}
318
319impl<'a, I, T> TextBackend for Backend<'a, I, T>
320where
321    I: InputReader,
322    T: Terminal,
323{
324    fn render_prompt(
325        &mut self,
326        prompt: &str,
327        default: Option<&str>,
328        cur_input: &Input,
329    ) -> Result<()> {
330        self.print_prompt_with_input(prompt, default, cur_input)
331    }
332
333    fn render_suggestions<D: Display>(&mut self, page: Page<'_, ListOption<D>>) -> Result<()> {
334        for (idx, option) in page.content.iter().enumerate() {
335            self.print_option_prefix(idx, &page)?;
336
337            self.frame_renderer.write(" ")?;
338            self.print_option_value(idx, option, &page)?;
339
340            self.new_line()?;
341        }
342
343        Ok(())
344    }
345}
346
347#[cfg(feature = "editor")]
348impl<'a, I, T> EditorBackend for Backend<'a, I, T>
349where
350    I: InputReader,
351    T: Terminal,
352{
353    fn render_prompt(&mut self, prompt: &str, editor_command: &str) -> Result<()> {
354        self.print_prompt(prompt)?;
355
356        self.frame_renderer.write(" ")?;
357
358        let message = format!("[(e) to open {}, (enter) to submit]", editor_command);
359        let token = Styled::new(message).with_style_sheet(self.render_config.editor_prompt);
360        self.frame_renderer.write_styled(token)?;
361
362        self.new_line()?;
363
364        Ok(())
365    }
366}
367
368impl<'a, I, T> SelectBackend for Backend<'a, I, T>
369where
370    I: InputReader,
371    T: Terminal,
372{
373    fn render_select_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()> {
374        if let Some(input) = cur_input {
375            self.print_prompt_with_input(prompt, None, input)
376        } else {
377            self.print_prompt(prompt)
378        }
379    }
380
381    fn render_options<D: Display>(&mut self, page: Page<'_, ListOption<D>>) -> Result<()> {
382        for (idx, option) in page.content.iter().enumerate() {
383            self.print_option_prefix(idx, &page)?;
384
385            self.frame_renderer.write(" ")?;
386
387            if let Some(res) = self.print_option_index_prefix(option.index, page.total) {
388                res?;
389                self.frame_renderer.write(" ")?;
390            }
391
392            self.print_option_value(idx, option, &page)?;
393
394            self.new_line()?;
395        }
396
397        Ok(())
398    }
399}
400
401impl<'a, I, T> MultiSelectBackend for Backend<'a, I, T>
402where
403    I: InputReader,
404    T: Terminal,
405{
406    fn render_multiselect_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()> {
407        if let Some(input) = cur_input {
408            self.print_prompt_with_input(prompt, None, input)
409        } else {
410            self.print_prompt(prompt)
411        }
412    }
413
414    fn render_options<D: Display>(
415        &mut self,
416        page: Page<'_, ListOption<D>>,
417        checked: &BTreeSet<usize>,
418    ) -> Result<()> {
419        for (idx, option) in page.content.iter().enumerate() {
420            self.print_option_prefix(idx, &page)?;
421
422            self.frame_renderer.write(" ")?;
423
424            if let Some(res) = self.print_option_index_prefix(option.index, page.total) {
425                res?;
426                self.frame_renderer.write(" ")?;
427            }
428
429            let mut checkbox = match checked.contains(&option.index) {
430                true => self.render_config.selected_checkbox,
431                false => self.render_config.unselected_checkbox,
432            };
433
434            match (self.render_config.selected_option, page.cursor) {
435                (Some(stylesheet), Some(cursor)) if cursor == idx => checkbox.style = stylesheet,
436                _ => {}
437            }
438
439            self.frame_renderer.write_styled(checkbox)?;
440
441            self.frame_renderer.write(" ")?;
442
443            self.print_option_value(idx, option, &page)?;
444
445            self.new_line()?;
446        }
447
448        Ok(())
449    }
450}
451
452#[cfg(feature = "date")]
453pub mod date {
454    use std::{io::Result, ops::Sub};
455
456    use chrono::{Datelike, Duration};
457
458    use crate::{
459        date_utils::get_start_date,
460        terminal::Terminal,
461        ui::{InputReader, Styled},
462    };
463
464    use super::{Backend, CommonBackend};
465
466    pub trait DateSelectBackend: CommonBackend {
467        fn render_calendar_prompt(&mut self, prompt: &str) -> Result<()>;
468
469        #[allow(clippy::too_many_arguments)]
470        fn render_calendar(
471            &mut self,
472            month: chrono::Month,
473            year: i32,
474            week_start: chrono::Weekday,
475            today: chrono::NaiveDate,
476            selected_date: chrono::NaiveDate,
477            min_date: Option<chrono::NaiveDate>,
478            max_date: Option<chrono::NaiveDate>,
479        ) -> Result<()>;
480    }
481
482    impl<'a, I, T> DateSelectBackend for Backend<'a, I, T>
483    where
484        I: InputReader,
485        T: Terminal,
486    {
487        fn render_calendar_prompt(&mut self, prompt: &str) -> Result<()> {
488            self.print_prompt(prompt)?;
489            self.new_line()?;
490            Ok(())
491        }
492
493        fn render_calendar(
494            &mut self,
495            month: chrono::Month,
496            year: i32,
497            week_start: chrono::Weekday,
498            today: chrono::NaiveDate,
499            selected_date: chrono::NaiveDate,
500            min_date: Option<chrono::NaiveDate>,
501            max_date: Option<chrono::NaiveDate>,
502        ) -> Result<()> {
503            macro_rules! write_prefix {
504                () => {{
505                    self.frame_renderer
506                        .write_styled(self.render_config.calendar.prefix)?;
507                    self.frame_renderer.write(" ")
508                }};
509            }
510
511            // print header (month year)
512            let header = format!("{} {}", month.name().to_lowercase(), year);
513            let header = format!("{header:^20}");
514            let header = Styled::new(header).with_style_sheet(self.render_config.calendar.header);
515
516            write_prefix!()?;
517
518            self.frame_renderer.write_styled(header)?;
519
520            self.new_line()?;
521
522            // print week header
523            let mut current_weekday = week_start;
524            let mut week_days: Vec<String> = vec![];
525            for _ in 0..7 {
526                let mut formatted = format!("{current_weekday}");
527                formatted.make_ascii_lowercase();
528                formatted.pop();
529                week_days.push(formatted);
530
531                current_weekday = current_weekday.succ();
532            }
533
534            let week_days = Styled::new(week_days.join(" "))
535                .with_style_sheet(self.render_config.calendar.week_header);
536
537            write_prefix!()?;
538
539            self.frame_renderer.write_styled(week_days)?;
540            self.new_line()?;
541
542            // print dates
543            let mut date_it = get_start_date(month, year);
544            // first date of week-line is possibly in the previous month
545            if date_it.weekday() == week_start {
546                date_it = date_it.sub(
547                    Duration::try_weeks(1).expect("overflow when calculating duration of 1 week"),
548                );
549            } else {
550                while date_it.weekday() != week_start {
551                    date_it = match date_it.pred_opt() {
552                        Some(date) => date,
553                        None => break,
554                    };
555                }
556            }
557
558            for _ in 0..6 {
559                write_prefix!()?;
560
561                for i in 0..7 {
562                    if i > 0 {
563                        self.frame_renderer.write(" ")?;
564                    }
565
566                    let date = format!("{:2}", date_it.day());
567
568                    let cursor_offset = if date_it.day() < 10 { 1 } else { 0 };
569
570                    let mut style_sheet = crate::ui::StyleSheet::empty();
571
572                    if date_it == selected_date {
573                        self.frame_renderer.mark_cursor_position(cursor_offset);
574                        if let Some(custom_style_sheet) = self.render_config.calendar.selected_date
575                        {
576                            style_sheet = custom_style_sheet;
577                        }
578                    } else if date_it == today {
579                        style_sheet = self.render_config.calendar.today_date;
580                    } else if date_it.month() != month.number_from_month() {
581                        style_sheet = self.render_config.calendar.different_month_date;
582                    }
583
584                    if let Some(min_date) = min_date {
585                        if date_it < min_date {
586                            style_sheet = self.render_config.calendar.unavailable_date;
587                        }
588                    }
589
590                    if let Some(max_date) = max_date {
591                        if date_it > max_date {
592                            style_sheet = self.render_config.calendar.unavailable_date;
593                        }
594                    }
595
596                    let token = Styled::new(date).with_style_sheet(style_sheet);
597                    self.frame_renderer.write_styled(token)?;
598
599                    date_it = date_it.succ_opt().unwrap_or(date_it);
600                }
601
602                self.new_line()?;
603            }
604
605            Ok(())
606        }
607    }
608}
609
610impl<'a, I, T> CustomTypeBackend for Backend<'a, I, T>
611where
612    I: InputReader,
613    T: Terminal,
614{
615    fn render_prompt(
616        &mut self,
617        prompt: &str,
618        default: Option<&str>,
619        cur_input: &Input,
620    ) -> Result<()> {
621        self.print_prompt_with_input(prompt, default, cur_input)
622    }
623}
624
625impl<'a, I, T> PasswordBackend for Backend<'a, I, T>
626where
627    I: InputReader,
628    T: Terminal,
629{
630    fn render_prompt(&mut self, prompt: &str) -> Result<()> {
631        self.print_prompt(prompt)?;
632        self.new_line()?;
633        Ok(())
634    }
635
636    fn render_prompt_with_masked_input(&mut self, prompt: &str, cur_input: &Input) -> Result<()> {
637        let masked_string: String = (0..cur_input.length())
638            .map(|_| self.render_config.password_mask)
639            .collect();
640
641        let masked_input = Input::new_with(masked_string).with_cursor(cur_input.cursor());
642
643        self.print_prompt_with_input(prompt, None, &masked_input)
644    }
645
646    fn render_prompt_with_full_input(&mut self, prompt: &str, cur_input: &Input) -> Result<()> {
647        self.print_prompt_with_input(prompt, None, cur_input)
648    }
649}
650
651impl<'a, I, T> InputReader for Backend<'a, I, T>
652where
653    I: InputReader,
654    T: Terminal,
655{
656    fn read_key(&mut self) -> InquireResult<Key> {
657        self.input_reader.read_key()
658    }
659}
660
661#[cfg(test)]
662pub(crate) mod test {
663    use std::collections::VecDeque;
664
665    use chrono::{Month, NaiveDate, Weekday};
666
667    use crate::{
668        input::Input,
669        ui::{InputReader, Key},
670        validator::ErrorMessage,
671    };
672
673    use super::{CommonBackend, CustomTypeBackend};
674
675    #[derive(Debug, Clone, PartialEq)]
676    pub enum Token {
677        Prompt(String),
678        DefaultValue(String),
679        Input(Input),
680        CanceledPrompt(String),
681        AnsweredPrompt(String, String),
682        ErrorMessage(ErrorMessage),
683        HelpMessage(String),
684        Calendar {
685            month: Month,
686            year: i32,
687            week_start: Weekday,
688            today: NaiveDate,
689            selected_date: NaiveDate,
690            min_date: Option<NaiveDate>,
691            max_date: Option<NaiveDate>,
692        },
693        PromptEnd,
694    }
695
696    #[derive(Default, Debug, Clone)]
697    pub struct Frame {
698        content: Vec<Token>,
699    }
700
701    impl Frame {
702        pub fn has_token(&self, token: &Token) -> bool {
703            self.content.iter().any(|t| t == token)
704        }
705
706        pub fn tokens(&self) -> &[Token] {
707            &self.content
708        }
709    }
710
711    #[derive(Default, Debug, Clone)]
712    pub struct FakeBackend {
713        pub input: VecDeque<Key>,
714        pub frames: Vec<Frame>,
715        pub cur_frame: Option<Frame>,
716    }
717
718    impl FakeBackend {
719        pub fn new(input: Vec<Key>) -> Self {
720            Self {
721                input: input.into(),
722                frames: vec![],
723                cur_frame: None,
724            }
725        }
726
727        fn push_token(&mut self, token: Token) {
728            if let Some(frame) = self.cur_frame.as_mut() {
729                frame.content.push(token);
730            } else {
731                panic!("No frame to push token");
732            }
733        }
734        pub fn frames(&self) -> &[Frame] {
735            &self.frames
736        }
737    }
738
739    impl InputReader for FakeBackend {
740        fn read_key(&mut self) -> crate::error::InquireResult<Key> {
741            self.input
742                .pop_front()
743                .ok_or(crate::error::InquireError::IO(std::io::Error::new(
744                    std::io::ErrorKind::UnexpectedEof,
745                    "No more keys in input",
746                )))
747        }
748    }
749
750    impl CommonBackend for FakeBackend {
751        fn frame_setup(&mut self) -> std::io::Result<()> {
752            self.cur_frame = Some(Frame::default());
753            Ok(())
754        }
755
756        fn frame_finish(&mut self, is_last_frame: bool) -> std::io::Result<()> {
757            if is_last_frame {
758                self.push_token(Token::PromptEnd);
759            }
760
761            if let Some(frame) = self.cur_frame.take() {
762                self.frames.push(frame);
763            } else {
764                panic!("No frame to finish");
765            }
766            Ok(())
767        }
768
769        fn render_canceled_prompt(&mut self, prompt: &str) -> std::io::Result<()> {
770            self.push_token(Token::CanceledPrompt(prompt.to_string()));
771            Ok(())
772        }
773
774        fn render_prompt_with_answer(&mut self, prompt: &str, answer: &str) -> std::io::Result<()> {
775            self.push_token(Token::AnsweredPrompt(
776                prompt.to_string(),
777                answer.to_string(),
778            ));
779            Ok(())
780        }
781
782        fn render_error_message(&mut self, error: &ErrorMessage) -> std::io::Result<()> {
783            self.push_token(Token::ErrorMessage(error.clone()));
784            Ok(())
785        }
786
787        fn render_help_message(&mut self, help: &str) -> std::io::Result<()> {
788            self.push_token(Token::HelpMessage(help.to_string()));
789            Ok(())
790        }
791    }
792
793    #[cfg(feature = "date")]
794    impl crate::ui::date::DateSelectBackend for FakeBackend {
795        fn render_calendar_prompt(&mut self, prompt: &str) -> std::io::Result<()> {
796            self.push_token(Token::Prompt(prompt.to_string()));
797            Ok(())
798        }
799
800        fn render_calendar(
801            &mut self,
802            month: Month,
803            year: i32,
804            week_start: Weekday,
805            today: NaiveDate,
806            selected_date: NaiveDate,
807            min_date: Option<NaiveDate>,
808            max_date: Option<NaiveDate>,
809        ) -> std::io::Result<()> {
810            self.push_token(Token::Calendar {
811                month,
812                year,
813                week_start,
814                today,
815                selected_date,
816                min_date,
817                max_date,
818            });
819            Ok(())
820        }
821    }
822
823    impl CustomTypeBackend for FakeBackend {
824        fn render_prompt(
825            &mut self,
826            prompt: &str,
827            default: Option<&str>,
828            cur_input: &Input,
829        ) -> std::io::Result<()> {
830            self.push_token(Token::Prompt(prompt.to_string()));
831            if let Some(default) = default {
832                self.push_token(Token::DefaultValue(default.to_string()));
833            }
834            self.push_token(Token::Input(cur_input.clone()));
835            Ok(())
836        }
837    }
838}