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