rustic_rs/commands/tui/widgets/
text_input.rs1use super::{Draw, Event, Frame, KeyCode, KeyEvent, ProcessEvent, Rect, SizedWidget, Style};
2
3use crossterm::event::KeyModifiers;
4use ratatui_textarea::{CursorMove, TextArea};
5
6pub struct TextInput {
7 textarea: TextArea<'static>,
8 lines: u16,
9 changeable: bool,
10}
11
12pub enum TextInputResult {
13 Cancel,
14 Input(String),
15 None,
16}
17
18impl TextInput {
19 pub fn new(text: Option<&str>, initial: &str, lines: u16, changeable: bool) -> Self {
20 let mut textarea = TextArea::default();
21 textarea.set_style(Style::default());
22 if let Some(text) = text {
23 textarea.set_placeholder_text(text);
24 }
25 _ = textarea.insert_str(initial);
26 if !changeable {
27 textarea.move_cursor(CursorMove::Top);
28 textarea.move_cursor(CursorMove::Head);
29 }
30 Self {
31 textarea,
32 lines,
33 changeable,
34 }
35 }
36}
37
38impl SizedWidget for TextInput {
39 fn height(&self) -> Option<u16> {
40 Some(self.lines)
41 }
42}
43
44impl Draw for TextInput {
45 fn draw(&mut self, area: Rect, f: &mut Frame<'_>) {
46 f.render_widget(&self.textarea, area);
47 }
48}
49
50impl ProcessEvent for TextInput {
51 type Result = TextInputResult;
52 fn input(&mut self, event: Event) -> TextInputResult {
53 if let Event::Key(key) = event {
54 let KeyEvent {
55 code, modifiers, ..
56 } = key;
57 if self.changeable {
58 match (code, modifiers) {
59 (KeyCode::Esc, _) => return TextInputResult::Cancel,
60 (KeyCode::Enter, _) if self.lines == 1 => {
61 return TextInputResult::Input(self.textarea.lines().join("\n"));
62 }
63 (KeyCode::Char('s'), KeyModifiers::CONTROL) => {
64 return TextInputResult::Input(self.textarea.lines().join("\n"));
65 }
66 _ => {
67 _ = self.textarea.input(event);
68 }
69 }
70 } else {
71 match (code, modifiers) {
72 (KeyCode::Esc | KeyCode::Enter | KeyCode::Char('q' | 'x'), _) => {
73 return TextInputResult::Cancel;
74 }
75 (KeyCode::Home, _) => {
76 self.textarea.move_cursor(CursorMove::Top);
77 }
78 (KeyCode::End, _) => {
79 self.textarea.move_cursor(CursorMove::Bottom);
80 }
81 (
82 KeyCode::PageDown
83 | KeyCode::PageUp
84 | KeyCode::Up
85 | KeyCode::Down
86 | KeyCode::Left
87 | KeyCode::Right,
88 _,
89 ) => {
90 _ = self.textarea.input(key);
91 }
92 _ => {}
93 }
94 }
95 }
96 TextInputResult::None
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use crossterm::event::{KeyEvent, KeyModifiers};
103
104 use super::*;
105
106 fn key(code: KeyCode) -> Event {
107 Event::Key(KeyEvent::new(code, KeyModifiers::NONE))
108 }
109
110 #[test]
111 fn read_only_text_can_be_scrolled_horizontally() {
112 let mut input = TextInput::new(None, "a long line", 1, false);
113
114 assert_eq!(input.textarea.cursor(), (0, 0));
115
116 _ = input.input(key(KeyCode::Right));
117 _ = input.input(key(KeyCode::Right));
118 assert_eq!(input.textarea.cursor(), (0, 2));
119
120 _ = input.input(key(KeyCode::Left));
121 assert_eq!(input.textarea.cursor(), (0, 1));
122 }
123}