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