1use std::iter::once;
2
3use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
4use itertools::chain;
5use ratatui_core::layout::Rect;
6use ratatui_core::terminal::Frame;
7use ratatui_core::widgets::StatefulWidget;
8use unicode_width::UnicodeWidthChar;
9
10use crate::Status;
11
12pub trait Prompt: StatefulWidget {
14 fn draw(self, frame: &mut Frame, area: Rect, state: &mut Self::State);
22}
23
24#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
26pub enum FocusState {
27 #[default]
28 Unfocused,
29 Focused,
30}
31
32pub trait State {
46 fn status(&self) -> Status;
48
49 fn status_mut(&mut self) -> &mut Status;
51
52 fn focus_state_mut(&mut self) -> &mut FocusState;
54
55 fn focus_state(&self) -> FocusState;
57
58 fn focus(&mut self) {
60 *self.focus_state_mut() = FocusState::Focused;
61 }
62
63 fn blur(&mut self) {
65 *self.focus_state_mut() = FocusState::Unfocused;
66 }
67
68 fn is_focused(&self) -> bool {
70 self.focus_state() == FocusState::Focused
71 }
72
73 fn position(&self) -> usize;
75
76 fn position_mut(&mut self) -> &mut usize;
78
79 fn cursor(&self) -> (u16, u16);
81
82 fn cursor_mut(&mut self) -> &mut (u16, u16);
84
85 fn value(&self) -> &str;
87
88 fn value_mut(&mut self) -> &mut String;
90
91 fn len(&self) -> usize {
92 self.value().chars().count()
93 }
94
95 fn is_empty(&self) -> bool {
96 self.value().len() == 0
97 }
98
99 fn handle_key_event(&mut self, key_event: KeyEvent) {
100 if key_event.kind == KeyEventKind::Release {
101 return;
102 }
103
104 match (key_event.code, key_event.modifiers) {
105 (KeyCode::Enter, _) => self.complete(),
106 (KeyCode::Esc, _) | (KeyCode::Char('c'), KeyModifiers::CONTROL) => self.abort(),
107 (KeyCode::Left, _) | (KeyCode::Char('b'), KeyModifiers::CONTROL) => self.move_left(),
108 (KeyCode::Right, _) | (KeyCode::Char('f'), KeyModifiers::CONTROL) => self.move_right(),
109 (KeyCode::Home, _) | (KeyCode::Char('a'), KeyModifiers::CONTROL) => self.move_start(),
110 (KeyCode::End, _) | (KeyCode::Char('e'), KeyModifiers::CONTROL) => self.move_end(),
111 (KeyCode::Backspace, _) | (KeyCode::Char('h'), KeyModifiers::CONTROL) => {
112 self.backspace();
113 }
114 (KeyCode::Delete, _) | (KeyCode::Char('d'), KeyModifiers::CONTROL) => self.delete(),
115 (KeyCode::Char('k'), KeyModifiers::CONTROL) => self.kill(),
116 (KeyCode::Char('u'), KeyModifiers::CONTROL) => self.truncate(),
117 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => self.push(c),
118 _ => {}
119 }
120 }
121
122 fn complete(&mut self) {
123 *self.status_mut() = Status::Done;
124 }
125
126 fn abort(&mut self) {
127 *self.status_mut() = Status::Aborted;
128 }
129
130 fn delete(&mut self) {
131 let position = self.position();
132 if position == self.len() {
133 return;
134 }
135 *self.value_mut() = chain!(
136 self.value().chars().take(position),
137 self.value().chars().skip(position + 1)
138 )
139 .collect();
140 }
141
142 fn backspace(&mut self) {
143 let position = self.position();
144 if position == 0 {
145 return;
146 }
147 *self.value_mut() = chain!(
148 self.value().chars().take(position.saturating_sub(1)),
149 self.value().chars().skip(position)
150 )
151 .collect();
152 *self.position_mut() = position.saturating_sub(1);
153 }
154
155 fn move_right(&mut self) {
156 if self.position() == self.len() {
157 return;
158 }
159 *self.position_mut() = self.position().saturating_add(1);
160 }
161
162 fn move_left(&mut self) {
163 *self.position_mut() = self.position().saturating_sub(1);
164 }
165
166 fn move_end(&mut self) {
167 *self.position_mut() = self.len();
168 }
169
170 fn move_start(&mut self) {
171 *self.position_mut() = 0;
172 }
173
174 fn kill(&mut self) {
175 let position = self.position();
176 self.value_mut().truncate(position);
177 }
178
179 fn truncate(&mut self) {
180 self.value_mut().clear();
181 *self.position_mut() = 0;
182 }
183
184 fn push(&mut self, c: char) {
185 if self.position() == self.len() {
186 self.value_mut().push(c);
187 } else {
188 *self.value_mut() = chain![
193 self.value().chars().take(self.position()),
194 once(c),
195 self.value().chars().skip(self.position())
196 ]
197 .collect();
198 }
199 *self.position_mut() = self.position().saturating_add(1);
200 }
201
202 fn width_to_pos(&self, pos: usize) -> usize {
204 self.value()
205 .chars()
206 .take(pos)
207 .map(|x| x.width().unwrap_or(0))
209 .sum()
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use ratatui_core::style::Stylize;
216
217 use super::*;
218
219 #[test]
220 fn status_symbols() {
221 assert_eq!(Status::Pending.symbol(), "?".cyan());
222 assert_eq!(Status::Aborted.symbol(), "✘".red());
223 assert_eq!(Status::Done.symbol(), "✔".green());
224 }
225}