entrust_dialog/
input.rs

1mod completions;
2pub mod confirmation;
3pub mod cursor;
4pub mod mask;
5pub mod prompt;
6pub mod validator;
7mod widget;
8
9use std::borrow::Cow;
10use std::io;
11use std::time::Duration;
12
13use crate::dialog::{Dialog, DialogState};
14use crate::input::completions::Completions;
15use crate::input::confirmation::Confirmation;
16use crate::input::cursor::{Cursor, CursorMode};
17use crate::input::mask::InputMask;
18use crate::input::prompt::Prompt;
19use crate::input::validator::Validator;
20use crate::theme::Theme;
21use crate::{cancel_key_event, key_event_pattern as kep};
22use ratatui::Viewport;
23use ratatui::crossterm::event::Event::Key;
24use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
25use ratatui::prelude::*;
26use tracing::debug;
27
28#[derive(Debug, Default)]
29pub struct InputDialog<'p, 'c> {
30    content: Vec<char>,
31    cursor: Cursor,
32    completions: Completions,
33    mask: InputMask,
34    prompt: Prompt<'p>,
35    placeholder: &'static str,
36    timeout: Option<Duration>,
37    validator: Validator<'static>,
38    confirmation: Option<Confirmation<'c>>,
39    state: DialogState,
40    theme: Cow<'static, Theme>,
41}
42
43impl<'p, 'c> InputDialog<'p, 'c> {
44    pub fn with_content(mut self, text: &str) -> Self {
45        let text: Vec<char> = text.chars().collect();
46        let cursor_index = text.len();
47        self.content = text;
48        self.cursor.set_index(cursor_index);
49        self
50    }
51    pub fn with_prompt(mut self, prompt: Prompt<'p>) -> Self {
52        self.prompt = prompt;
53        self
54    }
55    pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
56        self.placeholder = placeholder;
57        self
58    }
59    pub fn with_timeout(mut self, timeout: Duration) -> Self {
60        self.timeout = Some(timeout);
61        self
62    }
63    pub fn with_cursor_mode(mut self, cursor_mode: CursorMode) -> Self {
64        self.cursor.set_cursor_mode(cursor_mode);
65        self
66    }
67    pub fn with_mask(mut self, mask: InputMask) -> Self {
68        self.mask = mask;
69        self
70    }
71    pub fn with_validator(mut self, validator: Validator<'static>) -> Self {
72        self.validator = validator;
73        self
74    }
75    pub fn with_confirmation(mut self, confirmation: Confirmation<'c>) -> Self {
76        self.confirmation = Some(confirmation);
77        self
78    }
79    pub fn with_theme<T: Into<Cow<'static, Theme>>>(mut self, theme: T) -> Self {
80        self.theme = theme.into();
81        self
82    }
83    pub fn with_completions(mut self, completions: Vec<Cow<'static, str>>) -> Self {
84        self.completions.list = completions;
85        self.completions.list.sort();
86        self
87    }
88
89    pub(crate) fn current_content(&self) -> String {
90        self.content.iter().collect()
91    }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq)]
95pub enum Update {
96    InsertChar(char),
97    DeleteBeforeCursor,
98    DeleteAfterCursor,
99    MoveCursorLeft,
100    MoveCursorRight,
101    ToggleMask,
102    Confirm,
103    Cancel,
104    CycleCompletions,
105    CycleCompletionsBackward,
106}
107
108impl<'p, 'c> Dialog for InputDialog<'p, 'c> {
109    type Update = Update;
110    type Output = String;
111
112    fn update_for_event(event: Event) -> Option<Self::Update> {
113        match event {
114            Key(ke) => match ke {
115                cancel_key_event!() => Update::Cancel.into(),
116                kep!(KeyCode::Char('h'), KeyModifiers::ALT) => Update::ToggleMask.into(),
117                kep!(KeyCode::Char('n'), KeyModifiers::ALT) => Update::InsertChar('\n').into(),
118                kep!(KeyCode::Char('r'), KeyModifiers::ALT) => Update::InsertChar('\r').into(),
119                kep!(KeyCode::Char(char)) => Update::InsertChar(char).into(),
120                kep!(KeyCode::Backspace) => Update::DeleteBeforeCursor.into(),
121                kep!(KeyCode::Delete) => Update::DeleteAfterCursor.into(),
122                kep!(KeyCode::Left) => Update::MoveCursorLeft.into(),
123                kep!(KeyCode::Right) => Update::MoveCursorRight.into(),
124                kep!(KeyCode::Enter) => Update::Confirm.into(),
125                kep!(KeyCode::Tab, KeyModifiers::SHIFT) | kep!(KeyCode::Up) => {
126                    Update::CycleCompletionsBackward.into()
127                }
128                kep!(KeyCode::Tab) | kep!(KeyCode::Down) => Update::CycleCompletions.into(),
129                _ => None,
130            },
131            _ => None,
132        }
133    }
134
135    fn perform_update(&mut self, update: Self::Update) -> io::Result<()> {
136        debug!(?update, ?self.content, ?self.cursor);
137        match update {
138            Update::InsertChar(char) => {
139                self.content.insert(self.cursor.index(), char);
140                self.cursor.move_by(1);
141            }
142            Update::DeleteBeforeCursor => {
143                if self.cursor.index() > 0 {
144                    self.content.remove(self.cursor.index() - 1);
145                    self.cursor.move_by(-1);
146                }
147            }
148            Update::DeleteAfterCursor => {
149                if self.cursor.index() < self.content.len() {
150                    self.content.remove(self.cursor.index());
151                }
152            }
153            Update::MoveCursorLeft => {
154                if self.cursor.index() > 0 {
155                    self.cursor.move_by(-1);
156                }
157            }
158            Update::MoveCursorRight => {
159                if self.cursor.index() < self.content.len() {
160                    self.cursor.move_by(1);
161                } else if let Some(completion) = self.get_full_completion() {
162                    self.content = completion.chars().collect();
163                    self.cursor.set_index(self.content.len());
164                }
165            }
166            Update::ToggleMask => self.mask.toggle(),
167            Update::Confirm => {
168                if self.validation_message().is_none() {
169                    self.confirm()
170                }
171            }
172            Update::Cancel => self.state = DialogState::Cancelled,
173            Update::CycleCompletions | Update::CycleCompletionsBackward => {
174                self.update_completions(update)
175            }
176        };
177        Ok(())
178    }
179
180    fn state(&self) -> DialogState {
181        self.state
182    }
183
184    fn output(self) -> Self::Output {
185        self.content.iter().collect()
186    }
187
188    fn viewport(&self) -> Viewport {
189        Viewport::Inline(3)
190    }
191
192    fn draw(&mut self, frame: &mut Frame) {
193        frame.render_widget(self, frame.area())
194    }
195
196    fn tick(&mut self) -> bool {
197        self.cursor.tick()
198    }
199
200    fn timeout(&self) -> Option<Duration> {
201        self.timeout
202    }
203}