1use ratatui::{
2 layout::{Position, Rect},
3 widgets::Paragraph,
4};
5use unicode_segmentation::UnicodeSegmentation;
6use unicode_width::UnicodeWidthStr;
7
8use crate::{config::InputConfig, utils::text::grapheme_index_to_byte_index};
9
10#[derive(Debug, Clone)]
11pub struct InputUI {
12 pub cursor: u16, pub input: String,
14 pub config: InputConfig,
15}
16
17impl InputUI {
18 pub fn new(config: InputConfig) -> Self {
19 Self {
20 cursor: 0,
21 input: "".into(),
22 config,
23 }
24 }
25
26 pub fn make_input(&self) -> Paragraph<'_> {
27 let mut input = Paragraph::new(format!("{}{}", &self.config.prompt, self.input.as_str()))
28 .style(self.config.input_fg);
29
30 input = input.block(self.config.border.as_block());
31
32 input
33 }
34
35 pub fn cursor_offset(&self, rect: &Rect) -> Position {
36 let border = self.config.border.sides;
37 Position::new(
38 rect.x + self.cursor + self.config.prompt.width() as u16 + !border.is_empty() as u16,
39 rect.y + !border.is_empty() as u16,
40 )
41 }
42
43 pub fn height(&self) -> u16 {
44 let mut height = 1;
45 height += 2 * !self.config.border.sides.is_empty() as u16;
46
47 height
48 }
49 pub fn forward_char(&mut self) {
50 if self.cursor < self.input.graphemes(true).count() as u16 {
52 self.cursor += 1;
53 }
54 }
55
56 pub fn backward_char(&mut self) {
57 if self.cursor > 0 {
58 self.cursor -= 1;
59 }
60 }
61
62 pub fn insert_char(&mut self, c: char) {
65 let old_grapheme_count = self.input.graphemes(true).count() as u16;
66 let byte_index = grapheme_index_to_byte_index(&self.input, self.cursor);
67 self.input.insert(byte_index, c);
68 let new_grapheme_count = self.input.graphemes(true).count() as u16;
69 if new_grapheme_count > old_grapheme_count {
70 self.cursor += 1;
71 }
72 }
73
74 pub fn set_input(&mut self, new_input: String, new_cursor: u16) {
75 let grapheme_count = new_input.graphemes(true).count() as u16;
76 self.input = new_input;
77 self.cursor = new_cursor.min(grapheme_count);
78 }
79
80 pub fn forward_word(&mut self) {
81 let post = self.input.graphemes(true).skip(self.cursor as usize);
82
83 let mut in_word = false;
84
85 for g in post {
86 self.cursor += 1;
87 if g.chars().all(|c| c.is_whitespace()) {
88 if in_word {
89 return;
90 }
91 } else {
92 in_word = true;
93 }
94 }
95 }
96
97 pub fn backward_word(&mut self) {
98 let mut in_word = false;
99
100 let pre: Vec<&str> = self
101 .input
102 .graphemes(true)
103 .take(self.cursor as usize)
104 .collect();
105
106 for g in pre.iter().rev() {
107 self.cursor -= 1;
108
109 if g.chars().all(|c| c.is_whitespace()) {
110 if in_word {
111 return;
112 }
113 } else {
114 in_word = true;
115 }
116 }
117
118 self.cursor = 0;
119 }
120
121 pub fn delete(&mut self) {
122 if self.cursor > 0 {
123 let byte_start = grapheme_index_to_byte_index(&self.input, self.cursor - 1);
124 let byte_end = grapheme_index_to_byte_index(&self.input, self.cursor);
125
126 self.input.replace_range(byte_start..byte_end, "");
127 self.cursor -= 1;
128 }
129 }
130
131 pub fn delete_word(&mut self) {
132 let old_cursor_grapheme = self.cursor;
133 self.backward_word();
134 let new_cursor_grapheme = self.cursor;
135
136 let byte_start = grapheme_index_to_byte_index(&self.input, new_cursor_grapheme);
137 let byte_end = grapheme_index_to_byte_index(&self.input, old_cursor_grapheme);
138
139 self.input.replace_range(byte_start..byte_end, "");
140 }
141
142 pub fn delete_line_start(&mut self) {
143 let byte_end = grapheme_index_to_byte_index(&self.input, self.cursor);
144
145 self.input.replace_range(0..byte_end, "");
146 self.cursor = 0;
147 }
148
149 pub fn delete_line_end(&mut self) {
150 let byte_index = grapheme_index_to_byte_index(&self.input, self.cursor);
151
152 self.input.truncate(byte_index);
154 }
155
156 pub fn cancel(&mut self) {
157 self.input.clear();
158 self.cursor = 0;
159 }
160}