1use std::{
2 io::{self, Write},
3 ops::Range,
4};
5
6use unicode_segmentation::UnicodeSegmentation;
7
8use crate::{
9 backend::Backend,
10 events::{KeyCode, KeyEvent, KeyModifiers, Movement},
11 layout::Layout,
12};
13
14#[derive(Debug, Clone)]
23pub struct StringInput<F = super::widgets::FilterMapChar> {
24 value: String,
25 mask: Option<char>,
26 hide_output: bool,
27 value_len: usize,
29 at: usize,
31 filter_map: F,
32}
33
34impl StringInput {
35 pub fn new() -> Self {
37 Self::with_filter_map(crate::widgets::no_filter)
38 }
39}
40
41impl<F> StringInput<F> {
42 pub fn with_filter_map(filter_map: F) -> Self {
44 Self {
45 value: String::new(),
46 value_len: 0,
47 at: 0,
48 filter_map,
49 mask: None,
50 hide_output: false,
51 }
52 }
53
54 pub fn mask(mut self, mask: char) -> Self {
58 self.mask = Some(mask);
59 self
60 }
61
62 pub fn hide_output(mut self) -> Self {
66 self.hide_output = true;
67 self
68 }
69
70 pub fn password(self, mask: Option<char>) -> Self {
72 match mask {
73 Some(mask) => self.mask(mask),
74 None => self.hide_output(),
75 }
76 }
77
78 pub fn get_at(&self) -> usize {
80 self.at
81 }
82
83 pub fn set_at(&mut self, at: usize) {
85 self.at = at.min(self.value_len);
86 }
87
88 pub fn value(&self) -> &str {
90 &self.value
91 }
92
93 pub fn set_value(&mut self, value: String) {
95 self.value_len = value.chars().count();
96 self.value = value;
97 self.set_at(self.at);
98 }
99
100 pub fn replace_with<W: FnOnce(String) -> String>(&mut self, with: W) {
102 self.value = with(std::mem::take(&mut self.value));
103 let old_len = self.value_len;
104 self.value_len = self.value.chars().count();
105 if self.at == old_len {
106 self.at = self.value_len;
107 } else {
108 self.set_at(self.at);
109 }
110 }
111
112 pub fn finish(self) -> String {
114 self.value
115 }
116
117 fn get_byte_i(&self, index: usize) -> usize {
119 self.value
120 .char_indices()
121 .nth(index)
122 .map(|(i, _)| i)
123 .unwrap_or_else(|| self.value.len())
124 }
125
126 fn get_char_i(&self, byte_i: usize) -> usize {
128 self.value
129 .char_indices()
130 .position(|(i, _)| i == byte_i)
131 .unwrap_or_else(|| self.value.char_indices().count())
132 }
133
134 fn word_iter(&self, r: Range<usize>) -> impl DoubleEndedIterator<Item = (usize, &str)> {
136 self.value[r]
137 .split_word_bound_indices()
138 .filter(|(_, s)| !s.chars().next().map(char::is_whitespace).unwrap_or(true))
139 }
140
141 fn find_word_left(&self, byte_i: usize) -> usize {
143 self.word_iter(0..byte_i)
144 .next_back()
145 .map(|(new_byte_i, _)| new_byte_i)
146 .unwrap_or(0)
147 }
148
149 fn find_word_right(&self, byte_i: usize) -> usize {
151 self.word_iter(byte_i..self.value.len())
152 .nth(1)
153 .map(|(new_byte_i, _)| new_byte_i + byte_i)
154 .unwrap_or_else(|| self.value.len())
155 }
156
157 fn get_delete_movement(&self, key: KeyEvent) -> Option<Movement> {
158 let mov = match key.code {
159 KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => Movement::Home,
160 KeyCode::Backspace if key.modifiers.contains(KeyModifiers::ALT) => Movement::PrevWord,
161 KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::ALT) => Movement::PrevWord,
162 KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) => Movement::Left,
163 KeyCode::Backspace => Movement::Left,
164
165 KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => Movement::End,
166
167 KeyCode::Delete if key.modifiers.contains(KeyModifiers::ALT) => Movement::NextWord,
168 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::ALT) => Movement::NextWord,
169 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => Movement::Right,
170 KeyCode::Delete => Movement::Right,
171
172 _ => return None,
173 };
174
175 match mov {
176 Movement::Home | Movement::PrevWord | Movement::Left if self.at != 0 => Some(mov),
177 Movement::End | Movement::NextWord | Movement::Right if self.at != self.value_len => {
178 Some(mov)
179 }
180 _ => None,
181 }
182 }
183}
184
185impl<F> super::Widget for StringInput<F>
186where
187 F: Fn(char) -> Option<char>,
188{
189 fn handle_key(&mut self, key: KeyEvent) -> bool {
190 if let Some(movement) = self.get_delete_movement(key) {
191 match movement {
192 Movement::Home => {
193 let byte_i = self.get_byte_i(self.at);
194 self.value_len -= self.at;
195 self.at = 0;
196 self.value.replace_range(..byte_i, "");
197 return true;
198 }
199 Movement::PrevWord => {
200 let was_at = self.at;
201 let byte_i = self.get_byte_i(self.at);
202 let prev_word = self.find_word_left(byte_i);
203 self.at = self.get_char_i(prev_word);
204 self.value_len -= was_at - self.at;
205 self.value.replace_range(prev_word..byte_i, "");
206 return true;
207 }
208 Movement::Left if self.at == self.value_len => {
209 self.at -= 1;
210 self.value_len -= 1;
211 self.value.pop();
212 return true;
213 }
214 Movement::Left => {
215 self.at -= 1;
216 let byte_i = self.get_byte_i(self.at);
217 self.value_len -= 1;
218 self.value.remove(byte_i);
219 return true;
220 }
221
222 Movement::End => {
223 let byte_i = self.get_byte_i(self.at);
224 self.value_len = self.at;
225 self.value.truncate(byte_i);
226 return true;
227 }
228 Movement::NextWord => {
229 let byte_i = self.get_byte_i(self.at);
230 let next_word = self.find_word_right(byte_i);
231 self.value_len -= self.get_char_i(next_word) - self.at;
232 self.value.replace_range(byte_i..next_word, "");
233 return true;
234 }
235 Movement::Right if self.at == self.value_len - 1 => {
236 self.value_len -= 1;
237 self.value.pop();
238 return true;
239 }
240 Movement::Right => {
241 let byte_i = self.get_byte_i(self.at);
242 self.value_len -= 1;
243 self.value.remove(byte_i);
244 return true;
245 }
246
247 _ => {}
248 }
249 }
250
251 match key.code {
252 KeyCode::Char(c)
255 if !key
256 .modifiers
257 .intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) =>
258 {
259 if let Some(c) = (self.filter_map)(c) {
260 if self.at == self.value_len {
261 self.value.push(c);
262 } else {
263 let byte_i = self.get_byte_i(self.at);
264 self.value.insert(byte_i, c);
265 };
266
267 self.at += 1;
268 self.value_len += 1;
269 return true;
270 }
271 }
272
273 _ => {}
274 }
275
276 match Movement::try_from_key(key) {
277 Some(Movement::PrevWord) if self.at != 0 => {
278 self.at = self.get_char_i(self.find_word_left(self.get_byte_i(self.at)));
279 }
280 Some(Movement::Left) if self.at != 0 => {
281 self.at -= 1;
282 }
283
284 Some(Movement::NextWord) if self.at != self.value_len => {
285 self.at = self.get_char_i(self.find_word_right(self.get_byte_i(self.at)));
286 }
287 Some(Movement::Right) if self.at != self.value_len => {
288 self.at += 1;
289 }
290
291 Some(Movement::Home) if self.at != 0 => {
292 self.at = 0;
293 }
294 Some(Movement::End) if self.at != self.value_len => {
295 self.at = self.value_len;
296 }
297 _ => return false,
298 }
299
300 true
301 }
302
303 fn render<B: Backend>(&mut self, layout: &mut Layout, backend: &mut B) -> io::Result<()> {
307 if self.hide_output {
308 return Ok(());
309 }
310
311 if let Some(mask) = self.mask {
312 print_mask(self.value_len, mask, backend)?;
313 } else {
314 backend.write_all(self.value.as_bytes())?;
316 }
317
318 self.height(layout);
320
321 Ok(())
322 }
323
324 fn height(&mut self, layout: &mut Layout) -> u16 {
325 if self.hide_output {
326 return 1;
327 }
328
329 let mut width = textwrap::core::display_width(&self.value) as u16;
330
331 if width > layout.line_width() {
332 width -= layout.line_width();
333
334 layout.line_offset = width % layout.width;
335 layout.offset_y += 1 + width / layout.width;
336
337 2 + width / layout.width
338 } else {
339 layout.line_offset += width;
340 1
341 }
342 }
343
344 fn cursor_pos(&mut self, layout: Layout) -> (u16, u16) {
345 let display_at =
346 textwrap::core::display_width(&self.value[..self.get_byte_i(self.at)]) as u16;
347
348 let relative_pos = if self.hide_output {
349 (layout.line_offset, 0)
351 } else if layout.line_width() > display_at {
352 (layout.line_offset + display_at, 0)
354 } else {
355 let at = display_at - layout.line_width();
356
357 (at % layout.width, 1 + at / layout.width)
358 };
359
360 layout.offset_cursor(relative_pos)
361 }
362}
363
364impl Default for StringInput {
365 fn default() -> Self {
366 Self::new()
367 }
368}
369
370fn print_mask<W: Write>(len: usize, mask: char, w: &mut W) -> io::Result<()> {
371 let mut buf = [0; 4];
372 let mask = mask.encode_utf8(&mut buf[..]);
373
374 for _ in 0..len {
375 w.write_all(mask.as_bytes())?;
376 }
377
378 Ok(())
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use crate::{backend::TestBackend, events::KeyModifiers, test_consts::*, Widget};
385
386 #[test]
387 fn test_print_mask() {
388 fn test(mask: char) {
389 let mut buf = [0u8; 100];
390 let mask_len = mask.len_utf8();
391 print_mask(25, mask, &mut &mut buf[..]).unwrap();
392 assert!(std::str::from_utf8(&buf[0..(25 * mask_len)])
393 .unwrap()
394 .chars()
395 .all(|c| c == mask));
396 assert!(buf[(25 * mask_len)..].iter().all(|&b| b == 0));
397 }
398
399 test('*');
400 test('‣');
401 }
402
403 #[test]
404 fn test_delete_movement() {
405 let mut input = StringInput::default();
406
407 let backspace_movements = [
408 (
409 KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL),
410 Movement::Home,
411 ),
412 (
413 KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT),
414 Movement::PrevWord,
415 ),
416 (
417 KeyEvent::new(KeyCode::Char('w'), KeyModifiers::ALT),
418 Movement::PrevWord,
419 ),
420 (
421 KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL),
422 Movement::Left,
423 ),
424 (
425 KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()),
426 Movement::Left,
427 ),
428 ];
429
430 let delete_movements = [
431 (
432 KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL),
433 Movement::End,
434 ),
435 (
436 KeyEvent::new(KeyCode::Delete, KeyModifiers::ALT),
437 Movement::NextWord,
438 ),
439 (
440 KeyEvent::new(KeyCode::Char('d'), KeyModifiers::ALT),
441 Movement::NextWord,
442 ),
443 (
444 KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL),
445 Movement::Right,
446 ),
447 (
448 KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()),
449 Movement::Right,
450 ),
451 ];
452
453 assert!(backspace_movements
454 .iter()
455 .copied()
456 .all(|(key, _)| input.get_delete_movement(key).is_none()));
457
458 assert!(delete_movements
459 .iter()
460 .copied()
461 .all(|(key, _)| input.get_delete_movement(key).is_none()));
462
463 input.set_value("Hello world".into());
464
465 assert!(backspace_movements
466 .iter()
467 .copied()
468 .all(|(key, _)| input.get_delete_movement(key).is_none()));
469 assert!(delete_movements
470 .iter()
471 .copied()
472 .all(|(key, mov)| input.get_delete_movement(key).unwrap() == mov));
473
474 input.set_at(11);
475
476 assert!(backspace_movements
477 .iter()
478 .copied()
479 .all(|(key, mov)| input.get_delete_movement(key).unwrap() == mov));
480 assert!(delete_movements
481 .iter()
482 .copied()
483 .all(|(key, _)| input.get_delete_movement(key).is_none()));
484
485 input.set_at(5);
486
487 assert!(backspace_movements
488 .iter()
489 .copied()
490 .all(|(key, mov)| input.get_delete_movement(key).unwrap() == mov));
491 assert!(delete_movements
492 .iter()
493 .copied()
494 .all(|(key, mov)| input.get_delete_movement(key).unwrap() == mov));
495 }
496
497 #[test]
498 fn test_render() {
499 fn test(text: &str, line_offset: u16, offset_y: u16) {
500 let size = (100, 20).into();
501 let mut layout = Layout::new(0, size);
502
503 let mut backend = TestBackend::new(size);
504 let mut input = StringInput::default();
505 input.set_value(text.into());
506 input.render(&mut layout, &mut backend).unwrap();
507
508 crate::assert_backend_snapshot!(backend);
509
510 assert_eq!(
511 layout,
512 Layout::new(0, size)
513 .with_line_offset(line_offset)
514 .with_offset(0, offset_y)
515 );
516 }
517
518 test("Hello, World!", 13, 0);
519 test(LOREM, 70, 4);
520 test(UNICODE, 70, 4);
521 }
522
523 #[test]
524 fn test_handle_key() {
525 let mut input = StringInput::with_filter_map(|c| if c == 'i' { None } else { Some(c) });
526 input.set_value(LOREM.into());
527 input.set_at(40);
528
529 input.handle_key(KeyEvent::new(KeyCode::Left, KeyModifiers::empty()));
530 assert_eq!(input.get_at(), 39);
531 input.handle_key(KeyEvent::new(KeyCode::Left, KeyModifiers::CONTROL));
532 assert_eq!(input.get_at(), 28);
533 input.handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::empty()));
534 assert_eq!(input.get_at(), 29);
535 input.handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::CONTROL));
536 assert_eq!(input.get_at(), 41);
537 input.handle_key(KeyEvent::new(KeyCode::Home, KeyModifiers::empty()));
538 assert_eq!(input.get_at(), 0);
539 input.handle_key(KeyEvent::new(KeyCode::End, KeyModifiers::empty()));
540 assert_eq!(input.get_at(), 470);
541
542 input.set_at(40);
543 input.handle_key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()));
544 assert_eq!(input.get_at(), 39);
545 assert_eq!(input.value().chars().count(), 469);
546 input.handle_key(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::empty()));
547 assert_eq!(input.get_at(), 40);
548 assert_eq!(input.value(), LOREM);
549
550 input.handle_key(KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL));
551 assert_eq!(input.get_at(), 0);
552 assert_eq!(input.value().chars().count(), 430);
553 input.set_at(400);
554 input.handle_key(KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL));
555 assert_eq!(input.get_at(), 400);
556 assert_eq!(input.value().chars().count(), 400);
557 input.handle_key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT));
558 assert_eq!(input.get_at(), 394);
559 assert_eq!(input.value().chars().count(), 394);
560
561 input.set_at(40);
562 input.handle_key(KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()));
563 assert_eq!(input.get_at(), 40);
564 assert_eq!(input.value().chars().count(), 393);
565 input.handle_key(KeyEvent::new(KeyCode::Delete, KeyModifiers::ALT));
566 assert_eq!(input.get_at(), 40);
567 assert_eq!(input.value().chars().count(), 388);
568
569 input.handle_key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::empty()));
570 assert_eq!(input.get_at(), 40);
571 assert_eq!(input.value().chars().count(), 388);
572 input.handle_key(KeyEvent::new(KeyCode::Char('I'), KeyModifiers::empty()));
573 assert_eq!(input.get_at(), 41);
574 assert_eq!(input.value().chars().count(), 389);
575
576 let mut input = StringInput::with_filter_map(|c| if c == 'ȼ' { None } else { Some(c) });
577 input.set_value(UNICODE.into());
578 input.set_at(40);
579
580 input.handle_key(KeyEvent::new(KeyCode::Left, KeyModifiers::empty()));
581 assert_eq!(input.get_at(), 39);
582 input.handle_key(KeyEvent::new(KeyCode::Left, KeyModifiers::CONTROL));
583 assert_eq!(input.get_at(), 31);
584 input.handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::empty()));
585 assert_eq!(input.get_at(), 32);
586 input.handle_key(KeyEvent::new(KeyCode::Right, KeyModifiers::CONTROL));
587 assert_eq!(input.get_at(), 39);
588 input.handle_key(KeyEvent::new(KeyCode::Home, KeyModifiers::empty()));
589 assert_eq!(input.get_at(), 0);
590 input.handle_key(KeyEvent::new(KeyCode::End, KeyModifiers::empty()));
591 assert_eq!(input.get_at(), 470);
592
593 input.set_at(41);
594 input.handle_key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()));
595 assert_eq!(input.get_at(), 40);
596 assert_eq!(input.value().chars().count(), 469);
597 input.handle_key(KeyEvent::new(KeyCode::Char('Æ'), KeyModifiers::empty()));
598 assert_eq!(input.get_at(), 41);
599 assert_eq!(input.value(), UNICODE);
600 input.set_at(40);
601
602 input.handle_key(KeyEvent::new(KeyCode::Char('u'), KeyModifiers::CONTROL));
603 assert_eq!(input.get_at(), 0);
604 assert_eq!(input.value().chars().count(), 430);
605 input.set_at(400);
606 input.handle_key(KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL));
607 assert_eq!(input.get_at(), 400);
608 assert_eq!(input.value().chars().count(), 400);
609 input.handle_key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::ALT));
610 assert_eq!(input.get_at(), 397);
611 assert_eq!(input.value().chars().count(), 397);
612
613 input.set_at(40);
614 input.handle_key(KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()));
615 assert_eq!(input.get_at(), 40);
616 assert_eq!(input.value().chars().count(), 396);
617 input.handle_key(KeyEvent::new(KeyCode::Delete, KeyModifiers::ALT));
618 assert_eq!(input.get_at(), 40);
619 assert_eq!(input.value().chars().count(), 386);
620
621 input.handle_key(KeyEvent::new(KeyCode::Char('ȼ'), KeyModifiers::empty()));
622 assert_eq!(input.get_at(), 40);
623 assert_eq!(input.value().chars().count(), 386);
624 }
625
626 #[test]
627 fn test_height() {
628 fn test(text: &str, indent: usize, max_width: usize, height: u16) {
629 let mut layout = Layout::new(indent as u16, (max_width as u16, 40).into());
630 let mut input = StringInput::default();
631 input.set_value(text.into());
632 assert_eq!(input.height(&mut layout), height);
633 }
634
635 test("Hello, World!", 0, 100, 1);
636 test("Hello, World!", 0, 7, 2);
637 test("Hello, World!", 2, 7, 3);
638
639 test(LOREM, 0, 100, 5);
640 test(LOREM, 40, 100, 6);
641 }
642
643 #[test]
644 fn test_cursor_pos() {
645 let mut layout = Layout::new(0, (100, 20).into());
646 let mut input = StringInput::default();
647 input.set_value("Hello, World!".into());
648 input.set_at(0);
649 assert_eq!(input.cursor_pos(layout), (0, 0));
650 input.set_at(4);
651 assert_eq!(input.cursor_pos(layout), (4, 0));
652
653 layout.line_offset = 5;
654 assert_eq!(input.cursor_pos(layout), (9, 0));
655 input.set_at(0);
656 assert_eq!(input.cursor_pos(layout), (5, 0));
657
658 layout.line_offset = 0;
659 input.set_value(LOREM.into());
660 assert_eq!(input.cursor_pos(layout), (0, 0));
661 input.set_at(4);
662 assert_eq!(input.cursor_pos(layout), (4, 0));
663
664 layout.line_offset = 5;
665 assert_eq!(input.cursor_pos(layout), (9, 0));
666 input.set_at(0);
667 assert_eq!(input.cursor_pos(layout), (5, 0));
668
669 input.set_at(130);
670 assert_eq!(input.cursor_pos(layout), (35, 1));
671
672 layout.line_offset = 0;
673 input.set_at(0);
674 input.set_value(UNICODE.into());
675 assert_eq!(input.cursor_pos(layout), (0, 0));
676 input.set_at(4);
677 assert_eq!(input.cursor_pos(layout), (4, 0));
678
679 layout.line_offset = 5;
680 assert_eq!(input.cursor_pos(layout), (9, 0));
681 input.set_at(0);
682 assert_eq!(input.cursor_pos(layout), (5, 0));
683
684 input.set_at(130);
685 assert_eq!(input.cursor_pos(layout), (35, 1));
686
687 layout.offset_y = 3;
688 assert_eq!(input.cursor_pos(layout), (35, 4));
689 }
690}