1use crossterm::{
2 cursor::MoveTo,
3 event::{read, Event, KeyCode, KeyEvent, KeyModifiers},
4 execute,
5 style::{Color, Print, SetForegroundColor},
6 terminal::{Clear, ClearType},
7};
8
9use crate::helper::Helper;
10
11pub struct TextInput {
12 text: String,
13 cursor_position: usize,
14 placeholder: Option<String>,
15 padding: usize,
16 label: String,
17 helper: Option<Helper>,
18 prefix: String,
19}
20
21impl TextInput {
22 pub fn new(
23 placeholder: Option<&str>,
24 padding: usize,
25 initial_text: &str,
26 label: &str,
27 helper_text: Option<&str>,
28 prefix: &str,
29 ) -> Self {
30 TextInput {
31 text: initial_text.to_string(),
32 cursor_position: initial_text.len(),
33 placeholder: placeholder.map(String::from),
34 padding,
35 label: label.to_string(),
36 helper: helper_text.map(|text| Helper::new(text)), prefix: prefix.to_string(),
38 }
39 }
40
41 pub fn insert_char(&mut self, c: char) {
42 if self.text == self.placeholder.as_ref().map_or("", String::as_str) || self.text.is_empty()
43 {
44 self.text.clear(); self.cursor_position = 0; }
47 self.text.insert(self.cursor_position, c);
48 self.cursor_position += 1;
49 }
50
51 pub fn delete_char(&mut self) {
52 if self.cursor_position > 0 {
53 self.text.remove(self.cursor_position - 1);
54 self.cursor_position -= 1;
55 }
56 }
57
58 pub fn move_cursor_left(&mut self) {
59 if self.cursor_position > 0 {
60 self.cursor_position -= 1;
61 }
62 }
63
64 pub fn move_cursor_right(&mut self) {
65 if self.cursor_position < self.text.len() {
66 self.cursor_position += 1;
67 }
68 }
69
70 pub fn render(&self, x: u16, y: u16) {
71 execute!(
73 std::io::stdout(),
74 MoveTo(x + self.padding as u16, y),
75 Clear(ClearType::CurrentLine),
76 Print(&self.label),
77 )
78 .unwrap();
79
80 execute!(
81 std::io::stdout(),
82 MoveTo(x, y + 2),
83 Clear(ClearType::CurrentLine)
84 )
85 .unwrap();
86 execute!(
87 std::io::stdout(),
88 MoveTo(x, y + 2),
89 Clear(ClearType::CurrentLine),
90 SetForegroundColor(Color::White),
91 Print(" ".repeat(self.padding)), Print(format!(
93 "{} ",
94 if self.prefix.is_empty() {
95 ""
96 } else {
97 self.prefix.as_str()
98 }
99 )), SetForegroundColor(Color::Grey),
101 Print(if self.text.is_empty() {
102 self.placeholder.as_deref().unwrap_or("")
103 } else {
104 &self.text
105 })
106 )
107 .unwrap();
108
109 if let Some(ref helper) = self.helper {
110 helper.render(x + self.padding as u16, y + 5);
111 }
113
114 execute!(
116 std::io::stdout(),
117 MoveTo(
118 x + self.padding as u16 + self.prefix.len() as u16 + self.cursor_position as u16,
119 y + 2
120 ),
121 SetForegroundColor(Color::Reset)
122 )
123 .unwrap();
124 execute!(
126 std::io::stdout(),
127 MoveTo(
128 x + self.padding as u16
129 + self.prefix.len() as u16
130 + self.cursor_position as u16
131 + 1,
132 y + 2
133 )
134 )
135 .unwrap();
136 execute!(std::io::stdout(), SetForegroundColor(Color::Reset)).unwrap();
138 }
139}
140
141pub fn handle_input(input: &mut TextInput, x: u16, y: u16) -> Option<String> {
142 input.render(x, y);
143 loop {
144 match read().unwrap() {
145 Event::Key(KeyEvent {
146 code: KeyCode::Char(c),
147 modifiers,
148 ..
149 }) => {
150 if modifiers.contains(KeyModifiers::CONTROL) && c == 'c' {
151 return None;
152 }
153
154 input.insert_char(c);
155 input.render(x, y);
156 }
157 Event::Key(KeyEvent {
158 code: KeyCode::Enter,
159 ..
160 }) => {
161 if !input.text.is_empty()
163 && input.text != input.placeholder.as_ref().map_or("", String::as_str)
164 {
165 return Some(input.text.clone());
166 }
167 }
168 Event::Key(KeyEvent {
169 code: KeyCode::Backspace,
170 ..
171 }) => {
172 input.delete_char();
173 input.render(x, y);
174 }
175 Event::Key(KeyEvent {
176 code: KeyCode::Left,
177 ..
178 }) => {
179 input.move_cursor_left();
180 input.render(x, y);
181 }
182 Event::Key(KeyEvent {
183 code: KeyCode::Right,
184 ..
185 }) => {
186 input.move_cursor_right();
187 input.render(x, y);
188 }
189 Event::Key(KeyEvent {
190 code: KeyCode::Esc, ..
191 }) => return None,
192 _ => {}
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*; #[test]
202 fn test_insert_char() {
203 let mut text_input = TextInput::new(None, 0, "", "Label", None, "");
204 text_input.insert_char('a');
205 assert_eq!(text_input.text, "a");
206 assert_eq!(text_input.cursor_position, 1);
207 }
208
209 #[test]
210 fn test_delete_char() {
211 let mut text_input = TextInput::new(None, 0, "a", "Label", None, "");
212 text_input.delete_char();
213 assert_eq!(text_input.text, "");
214 assert_eq!(text_input.cursor_position, 0);
215 }
216
217 #[test]
218 fn test_move_cursor_left() {
219 let mut text_input = TextInput::new(None, 0, "ab", "Label", None, "");
220 text_input.move_cursor_right(); text_input.move_cursor_left();
222 assert_eq!(text_input.cursor_position, 1);
223 }
224
225 #[test]
226 fn test_move_cursor_right() {
227 let mut text_input = TextInput::new(None, 0, "abc", "Label", None, "");
228 text_input.move_cursor_right();
229 text_input.move_cursor_right();
230 text_input.move_cursor_left();
231 text_input.move_cursor_right(); assert_eq!(text_input.cursor_position, 3);
233 }
234}