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}