tui_canvas/editor/
editing.rs1use crate::DataProvider;
3use crate::editor::EditorCore;
4
5impl<D: DataProvider> EditorCore<D> {
6 pub fn open_line_below(&mut self) -> anyhow::Result<()> {
8 let field_count = self.data_provider.field_count();
9 if field_count == 0 {
10 return Ok(());
11 }
12 let next_field = (self.ui_state.current_field + 1).min(field_count.saturating_sub(1));
13 self.transition_to_field(next_field)?;
14 self.set_cursor_raw(0);
15 self.enter_edit_mode();
16 Ok(())
17 }
18
19 pub fn open_line_above(&mut self) -> anyhow::Result<()> {
21 let prev_field = self.ui_state.current_field.saturating_sub(1);
22 self.transition_to_field(prev_field)?;
23 self.set_cursor_raw(0);
24 self.enter_edit_mode();
25 Ok(())
26 }
27
28 pub fn insert_char(&mut self, ch: char) -> anyhow::Result<()> {
30 if self.ui_state.current_mode != crate::canvas::modes::AppMode::Ins {
31 return Ok(());
32 }
33
34 #[cfg(feature = "validation")]
35 let field_index = self.ui_state.current_field;
36 #[cfg(feature = "validation")]
37 let raw_cursor_pos = self.ui_state.cursor_pos;
38 #[cfg(feature = "validation")]
39 let current_raw_text = self.data_provider.field_value(field_index);
40
41 #[cfg(not(feature = "validation"))]
42 let field_index = self.ui_state.current_field;
43 #[cfg(not(feature = "validation"))]
44 let raw_cursor_pos = self.ui_state.cursor_pos;
45 #[cfg(not(feature = "validation"))]
46 let current_raw_text = self.data_provider.field_value(field_index);
47
48 #[cfg(feature = "validation")]
49 {
50 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
51 if let Some(mask) = &cfg.display_mask {
52 let display_cursor_pos = mask.raw_pos_to_display_pos(raw_cursor_pos);
53
54 let pattern_char_len = mask.pattern().chars().count();
55 if display_cursor_pos >= pattern_char_len {
56 return Ok(());
57 }
58
59 if !mask.is_input_position(display_cursor_pos) {
60 return Ok(());
61 }
62
63 let input_slots = (0..pattern_char_len)
64 .filter(|&pos| mask.is_input_position(pos))
65 .count();
66 if current_raw_text.chars().count() >= input_slots {
67 return Ok(());
68 }
69 }
70 }
71 }
72
73 #[cfg(feature = "validation")]
74 {
75 let vr = self.ui_state.validation.validate_char_insertion(
76 field_index,
77 current_raw_text,
78 raw_cursor_pos,
79 ch,
80 );
81 if !vr.is_acceptable() {
82 return Ok(());
83 }
84 }
85
86 let new_raw_text = {
87 let mut temp = current_raw_text.to_string();
88 let byte_pos = Self::char_to_byte_index(current_raw_text, raw_cursor_pos);
89 temp.insert(byte_pos, ch);
90 temp
91 };
92
93 #[cfg(feature = "validation")]
94 {
95 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
96 if let Some(limits) = &cfg.character_limits {
97 if let Some(result) = limits.validate_content(&new_raw_text) {
98 if !result.is_acceptable() {
99 return Ok(());
100 }
101 }
102 }
103 if let Some(mask) = &cfg.display_mask {
104 let pattern_char_len = mask.pattern().chars().count();
105 let input_slots = (0..pattern_char_len)
106 .filter(|&pos| mask.is_input_position(pos))
107 .count();
108 if new_raw_text.chars().count() > input_slots {
109 return Ok(());
110 }
111 }
112 }
113 }
114
115 self.record_checkpoint(crate::editor::features::history::EditKind::Insert);
116
117 self.data_provider
118 .set_field_value(field_index, new_raw_text.clone());
119
120 #[cfg(feature = "validation")]
121 {
122 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
123 if let Some(mask) = &cfg.display_mask {
124 let new_raw_pos = raw_cursor_pos + 1;
125 let display_pos = mask.raw_pos_to_display_pos(new_raw_pos);
126 let next_input_display = mask.next_input_position(display_pos);
127 let next_raw_pos = mask.display_pos_to_raw_pos(next_input_display);
128 let max_raw = new_raw_text.chars().count();
129
130 self.set_cursor_raw(next_raw_pos.min(max_raw));
131 return Ok(());
132 }
133 }
134 }
135
136 self.set_cursor_raw(raw_cursor_pos + 1);
137
138 #[cfg(feature = "suggestions")]
139 self.check_suggestion_trigger();
140
141 Ok(())
142 }
143
144 pub fn insert_text(&mut self, text: &str) -> anyhow::Result<()> {
150 for ch in text.chars() {
151 self.insert_char(ch)?;
152 }
153 Ok(())
154 }
155
156 pub fn delete_backward(&mut self) -> anyhow::Result<()> {
158 if self.ui_state.current_mode != crate::canvas::modes::AppMode::Ins {
159 return Ok(());
160 }
161 if self.ui_state.cursor_pos == 0 {
162 return Ok(());
163 }
164
165 let field_index = self.ui_state.current_field;
166 let mut current_text = self.data_provider.field_value(field_index).to_string();
167
168 let new_cursor = self.ui_state.cursor_pos.saturating_sub(1);
169
170 let start = Self::char_to_byte_index(¤t_text, self.ui_state.cursor_pos - 1);
171 let end = Self::char_to_byte_index(¤t_text, self.ui_state.cursor_pos);
172 current_text.replace_range(start..end, "");
173
174 self.record_checkpoint(crate::editor::features::history::EditKind::Delete);
175
176 self.data_provider
177 .set_field_value(field_index, current_text.clone());
178
179 #[cfg(feature = "validation")]
180 let mut target_cursor = new_cursor;
181 #[cfg(not(feature = "validation"))]
182 let target_cursor = new_cursor;
183
184 #[cfg(feature = "validation")]
185 {
186 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
187 if let Some(mask) = &cfg.display_mask {
188 let display_pos = mask.raw_pos_to_display_pos(new_cursor);
189 if let Some(prev_input) = mask.prev_input_position(display_pos) {
190 target_cursor = mask.display_pos_to_raw_pos(prev_input);
191 }
192 }
193 }
194 }
195
196 self.set_cursor_raw(target_cursor);
197
198 #[cfg(feature = "validation")]
199 {
200 let _ = self
201 .ui_state
202 .validation
203 .validate_field_content(field_index, ¤t_text);
204 }
205
206 #[cfg(feature = "suggestions")]
207 self.check_suggestion_trigger();
208
209 Ok(())
210 }
211
212 pub fn delete_forward(&mut self) -> anyhow::Result<()> {
214 if self.ui_state.current_mode != crate::canvas::modes::AppMode::Ins {
215 return Ok(());
216 }
217
218 let field_index = self.ui_state.current_field;
219 let mut current_text = self.data_provider.field_value(field_index).to_string();
220
221 if self.ui_state.cursor_pos < current_text.chars().count() {
222 let start = Self::char_to_byte_index(¤t_text, self.ui_state.cursor_pos);
223 let end = Self::char_to_byte_index(¤t_text, self.ui_state.cursor_pos + 1);
224 current_text.replace_range(start..end, "");
225
226 self.record_checkpoint(crate::editor::features::history::EditKind::Delete);
227
228 self.data_provider
229 .set_field_value(field_index, current_text.clone());
230
231 #[cfg(feature = "validation")]
232 let mut target_cursor = self.ui_state.cursor_pos;
233 #[cfg(not(feature = "validation"))]
234 let target_cursor = self.ui_state.cursor_pos;
235
236 #[cfg(feature = "validation")]
237 {
238 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
239 if let Some(mask) = &cfg.display_mask {
240 let display_pos = mask.raw_pos_to_display_pos(self.ui_state.cursor_pos);
241 let next_input = mask.next_input_position(display_pos);
242 target_cursor = mask
243 .display_pos_to_raw_pos(next_input)
244 .min(current_text.chars().count());
245 }
246 }
247 }
248
249 self.set_cursor_raw(target_cursor);
250
251 #[cfg(feature = "validation")]
252 {
253 let _ = self
254 .ui_state
255 .validation
256 .validate_field_content(field_index, ¤t_text);
257 }
258
259 #[cfg(feature = "suggestions")]
260 self.check_suggestion_trigger();
261 }
262
263 Ok(())
264 }
265
266 pub fn enter_append_mode(&mut self) {
268 #[cfg(feature = "keybindings")]
269 {
270 use crate::editor::behavior::KeybindingParadigm;
271 match self.keybinding_paradigm() {
272 KeybindingParadigm::Helix => self.enter_append_mode_helix(),
273 KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
274 self.enter_append_mode_emacs()
275 }
276 KeybindingParadigm::Vim => self.enter_append_mode_vim(),
277 }
278 return;
279 }
280
281 #[cfg(not(feature = "keybindings"))]
282 self.enter_append_mode_vim();
283 }
284
285 pub fn set_current_field_value(&mut self, value: String) {
287 let field_index = self.ui_state.current_field;
288
289 self.record_checkpoint(crate::editor::features::history::EditKind::Other);
290
291 self.data_provider
292 .set_field_value(field_index, value.clone());
293 self.set_cursor_raw(0);
294
295 #[cfg(feature = "validation")]
296 {
297 let _ = self
298 .ui_state
299 .validation
300 .validate_field_content(field_index, &value);
301 }
302 }
303
304 pub fn set_field_value(&mut self, field_index: usize, value: String) {
306 if field_index < self.data_provider.field_count() {
307 self.record_checkpoint(crate::editor::features::history::EditKind::Other);
308
309 self.data_provider
310 .set_field_value(field_index, value.clone());
311 if field_index == self.ui_state.current_field {
312 self.set_cursor_raw(0);
313 }
314
315 #[cfg(feature = "validation")]
316 {
317 let _ = self
318 .ui_state
319 .validation
320 .validate_field_content(field_index, &value);
321 }
322 }
323 }
324
325 pub fn clear_current_field(&mut self) {
327 self.set_current_field_value(String::new());
328 }
329}