Skip to main content

tui_canvas/editor/
editing.rs

1// src/editor/editing.rs
2use crate::DataProvider;
3use crate::editor::EditorCore;
4
5impl<D: DataProvider> EditorCore<D> {
6    /// Open new line below (vim o)
7    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    /// Open new line above (vim O)
20    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    /// Handle character insertion (mask/limit-aware)
29    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    /// Insert plain text at the current cursor position.
145    ///
146    /// This intentionally treats the input as a character stream and reuses
147    /// `insert_char` so validation, masking, and suggestions continue to flow
148    /// through the existing editing logic.
149    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    /// Delete backward (backspace)
157    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(&current_text, self.ui_state.cursor_pos - 1);
171        let end = Self::char_to_byte_index(&current_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, &current_text);
204        }
205
206        #[cfg(feature = "suggestions")]
207        self.check_suggestion_trigger();
208
209        Ok(())
210    }
211
212    /// Delete forward (Delete key)
213    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(&current_text, self.ui_state.cursor_pos);
223            let end = Self::char_to_byte_index(&current_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, &current_text);
257            }
258
259            #[cfg(feature = "suggestions")]
260            self.check_suggestion_trigger();
261        }
262
263        Ok(())
264    }
265
266    /// Enter edit mode with cursor positioned for append (vim 'a')
267    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    /// Set current field value (validates under feature flag)
286    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    /// Set specific field value by index (validates under feature flag)
305    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    /// Clear the current field
326    pub fn clear_current_field(&mut self) {
327        self.set_current_field_value(String::new());
328    }
329}