tui_canvas/editor/
mode.rs1#[cfg(feature = "cursor-style")]
4use crate::cursor::CursorManager;
5
6use crate::DataProvider;
7use crate::canvas::modes::AppMode;
8use crate::canvas::state::SelectionState;
9use crate::editor::EditorCore;
10#[cfg(feature = "keybindings")]
11use crate::editor::behavior::KeybindingParadigm;
12
13impl<D: DataProvider> EditorCore<D> {
14 pub(crate) fn set_highlight_mode_selection(&mut self, selection: SelectionState) {
15 self.ui_state.current_mode = AppMode::Sel;
16 self.ui_state.selection = selection;
17
18 #[cfg(feature = "cursor-style")]
19 {
20 let _ = CursorManager::update_for_mode(AppMode::Sel);
21 }
22 }
23
24 pub fn set_mode(&mut self, mode: AppMode) {
26 #[cfg(not(feature = "textmode-normal"))]
31 if self.ui_state.current_mode != mode {
32 self.break_undo_coalescing();
33 }
34
35 #[cfg(feature = "textmode-normal")]
37 let _ = mode;
38
39 #[cfg(feature = "textmode-normal")]
41 {
42 self.ui_state.current_mode = AppMode::Ins;
43 self.ui_state.selection = SelectionState::None;
44
45 #[cfg(feature = "cursor-style")]
46 {
47 let _ = CursorManager::update_for_mode(AppMode::Ins);
48 }
49 }
50
51 #[cfg(not(feature = "textmode-normal"))]
53 {
54 #[cfg(feature = "keybindings")]
55 {
56 match self.keybinding_paradigm() {
57 KeybindingParadigm::Helix => self.set_mode_helix(mode),
58 KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
59 self.set_mode_emacs(mode)
60 }
61 KeybindingParadigm::Vim => self.set_mode_vim(mode),
62 }
63 }
64 #[cfg(not(feature = "keybindings"))]
65 self.set_mode_vim(mode);
66 }
67 }
68
69 pub fn exit_edit_mode(&mut self) -> anyhow::Result<()> {
71 #[cfg(feature = "validation")]
72 {
73 let current_text = self.current_text();
74 if !self
75 .ui_state
76 .validation
77 .allows_field_switch(self.ui_state.current_field, current_text)
78 {
79 if let Some(reason) = self
80 .ui_state
81 .validation
82 .field_switch_block_reason(self.ui_state.current_field, current_text)
83 {
84 self.ui_state
85 .validation
86 .set_last_switch_block(reason.clone());
87 return Err(anyhow::anyhow!("Cannot exit edit mode: {}", reason));
88 }
89 }
90 }
91
92 let current_text = self.current_text();
93 if !current_text.is_empty() {
94 let max_normal_pos = current_text.chars().count().saturating_sub(1);
95 if self.ui_state.cursor_pos > max_normal_pos {
96 self.set_cursor_raw(max_normal_pos);
97 }
98 }
99
100 #[cfg(feature = "validation")]
101 {
102 let field_index = self.ui_state.current_field;
103 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
104 if cfg.external_validation_enabled {
105 let text = self.current_text().to_string();
106 if !text.is_empty() {
107 self.set_external_validation(
108 field_index,
109 crate::validation::ExternalValidationState::Validating,
110 );
111 if let Some(cb) = self.external_validation_callback.as_mut() {
112 let final_state = cb(field_index, &text);
113 self.set_external_validation(field_index, final_state);
114 }
115 }
116 }
117 }
118 }
119
120 #[cfg(feature = "textmode-normal")]
122 {
123 #[cfg(feature = "suggestions")]
124 {
125 self.dismiss_suggestions();
126 }
127 Ok(())
128 }
129
130 #[cfg(not(feature = "textmode-normal"))]
132 {
133 self.set_mode(AppMode::Nor);
134 #[cfg(feature = "suggestions")]
135 {
136 self.dismiss_suggestions();
137 }
138 Ok(())
139 }
140 }
141
142 pub fn enter_edit_mode(&mut self) {
144 #[cfg(feature = "computed")]
145 {
146 if let Some(computed_state) = &self.ui_state.computed {
147 if computed_state.is_computed_field(self.ui_state.current_field) {
148 return;
149 }
150 }
151 }
152
153 #[cfg(feature = "textmode-normal")]
155 {
156 self.ui_state.current_mode = AppMode::Ins;
157 self.ui_state.selection = SelectionState::None;
158 #[cfg(feature = "cursor-style")]
159 {
160 let _ = CursorManager::update_for_mode(AppMode::Ins);
161 }
162 }
163
164 #[cfg(not(feature = "textmode-normal"))]
166 {
167 #[cfg(feature = "keybindings")]
168 match self.keybinding_paradigm() {
169 KeybindingParadigm::Helix => self.enter_edit_mode_helix(),
170 KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
171 self.enter_edit_mode_emacs()
172 }
173 KeybindingParadigm::Vim => self.enter_edit_mode_vim(),
174 }
175 #[cfg(not(feature = "keybindings"))]
176 self.enter_edit_mode_vim();
177 }
178
179 #[cfg(feature = "suggestions")]
181 self.check_suggestion_trigger();
182 }
183
184 pub fn enter_highlight_mode(&mut self) {
187 #[cfg(feature = "textmode-normal")]
188 {}
189
190 #[cfg(not(feature = "textmode-normal"))]
191 {
192 #[cfg(feature = "keybindings")]
193 match self.keybinding_paradigm() {
194 KeybindingParadigm::Helix => self.enter_highlight_mode_helix(),
195 KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
196 self.enter_highlight_mode_emacs()
197 }
198 KeybindingParadigm::Vim => self.enter_highlight_mode_vim(),
199 }
200 #[cfg(not(feature = "keybindings"))]
201 self.enter_highlight_mode_vim();
202 }
203 }
204
205 pub fn enter_highlight_line_mode(&mut self) {
206 #[cfg(feature = "textmode-normal")]
207 {}
208
209 #[cfg(not(feature = "textmode-normal"))]
210 {
211 #[cfg(feature = "keybindings")]
212 match self.keybinding_paradigm() {
213 KeybindingParadigm::Helix => self.enter_highlight_line_mode_helix(),
214 KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
215 self.enter_highlight_line_mode_emacs()
216 }
217 KeybindingParadigm::Vim => self.enter_highlight_line_mode_vim(),
218 }
219 #[cfg(not(feature = "keybindings"))]
220 self.enter_highlight_line_mode_vim();
221 }
222 }
223
224 pub fn exit_highlight_mode(&mut self) {
225 #[cfg(feature = "textmode-normal")]
226 {}
227
228 #[cfg(not(feature = "textmode-normal"))]
229 {
230 #[cfg(feature = "keybindings")]
231 match self.keybinding_paradigm() {
232 KeybindingParadigm::Helix => self.exit_highlight_mode_helix(),
233 KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
234 self.exit_highlight_mode_emacs()
235 }
236 KeybindingParadigm::Vim => self.exit_highlight_mode_vim(),
237 }
238 #[cfg(not(feature = "keybindings"))]
239 self.exit_highlight_mode_vim();
240 }
241 }
242
243 pub fn is_highlight_mode(&self) -> bool {
244 #[cfg(feature = "textmode-normal")]
245 {
246 false
247 }
248 #[cfg(not(feature = "textmode-normal"))]
249 {
250 return self.ui_state.current_mode == AppMode::Sel;
251 }
252 }
253
254 pub fn selection_state(&self) -> &SelectionState {
255 &self.ui_state.selection
256 }
257
258 pub fn move_left_with_selection(&mut self) {
262 let _ = self.move_left();
263 }
264
265 pub fn move_right_with_selection(&mut self) {
266 let _ = self.move_right();
267 }
268
269 pub fn move_up_with_selection(&mut self) {
270 let _ = self.move_up();
271 }
272
273 pub fn move_down_with_selection(&mut self) {
274 let _ = self.move_down();
275 }
276
277 pub fn move_word_next_with_selection(&mut self) {
278 self.move_word_next();
279 }
280
281 pub fn move_word_end_with_selection(&mut self) {
282 self.move_word_end();
283 }
284
285 pub fn move_word_prev_with_selection(&mut self) {
286 self.move_word_prev();
287 }
288
289 pub fn move_word_end_prev_with_selection(&mut self) {
290 self.move_word_end_prev();
291 }
292
293 pub fn move_big_word_next_with_selection(&mut self) {
294 self.move_big_word_next();
295 }
296
297 pub fn move_big_word_end_with_selection(&mut self) {
298 self.move_big_word_end();
299 }
300
301 pub fn move_big_word_prev_with_selection(&mut self) {
302 self.move_big_word_prev();
303 }
304
305 pub fn move_big_word_end_prev_with_selection(&mut self) {
306 self.move_big_word_end_prev();
307 }
308
309 pub fn move_line_start_with_selection(&mut self) {
310 self.move_line_start();
311 }
312
313 pub fn move_line_end_with_selection(&mut self) {
314 self.move_line_end();
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::canvas::modes::AppMode;
322
323 #[derive(Clone)]
324 struct TestProvider {
325 fields: Vec<(&'static str, String)>,
326 }
327
328 impl TestProvider {
329 fn new(values: &[&'static str]) -> Self {
330 Self {
331 fields: values
332 .iter()
333 .enumerate()
334 .map(|(i, value)| {
335 let name = match i {
336 0 => "a",
337 1 => "b",
338 _ => "c",
339 };
340 (name, (*value).to_string())
341 })
342 .collect(),
343 }
344 }
345 }
346
347 impl DataProvider for TestProvider {
348 fn field_count(&self) -> usize {
349 self.fields.len()
350 }
351
352 fn field_name(&self, index: usize) -> &str {
353 self.fields[index].0
354 }
355
356 fn field_value(&self, index: usize) -> &str {
357 &self.fields[index].1
358 }
359
360 fn set_field_value(&mut self, index: usize, value: String) {
361 self.fields[index].1 = value;
362 }
363 }
364
365 #[test]
366 fn visual_characterwise_toggles_and_switches_from_linewise() {
367 let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta"]));
368
369 editor.enter_highlight_mode();
370 assert_eq!(editor.mode(), AppMode::Sel);
371 assert!(matches!(
372 editor.selection_state(),
373 SelectionState::Characterwise { anchor: (0, 0) }
374 ));
375
376 editor.enter_highlight_mode();
377 assert_eq!(editor.mode(), AppMode::Nor);
378 assert!(matches!(editor.selection_state(), SelectionState::None));
379
380 editor.enter_highlight_line_mode();
381 assert!(matches!(
382 editor.selection_state(),
383 SelectionState::Linewise { anchor_field: 0 }
384 ));
385
386 editor.move_down();
387 editor.enter_highlight_mode();
388 assert!(matches!(
389 editor.selection_state(),
390 SelectionState::Characterwise { anchor: (1, 0) }
391 ));
392 }
393
394 #[test]
395 fn visual_linewise_toggles_and_switches_from_characterwise() {
396 let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta"]));
397
398 editor.enter_highlight_line_mode();
399 assert_eq!(editor.mode(), AppMode::Sel);
400 assert!(matches!(
401 editor.selection_state(),
402 SelectionState::Linewise { anchor_field: 0 }
403 ));
404
405 editor.enter_highlight_line_mode();
406 assert_eq!(editor.mode(), AppMode::Nor);
407 assert!(matches!(editor.selection_state(), SelectionState::None));
408
409 editor.enter_highlight_mode();
410 editor.move_down();
411 editor.enter_highlight_line_mode();
412 assert!(matches!(
413 editor.selection_state(),
414 SelectionState::Linewise { anchor_field: 0 }
415 ));
416 }
417
418 #[test]
419 fn visual_linewise_switch_preserves_selected_line_range() {
420 let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta", "gamma"]));
421
422 editor.enter_highlight_mode();
423 editor.move_down();
424 editor.enter_highlight_line_mode();
425
426 assert_eq!(editor.current_field(), 1);
427 assert!(matches!(
428 editor.selection_state(),
429 SelectionState::Linewise { anchor_field: 0 }
430 ));
431 }
432}