1#[cfg(feature = "crossterm")]
2use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
3#[cfg(feature = "cursor-style")]
4use std::io;
5
6use std::ops::{Deref, DerefMut};
7
8#[cfg(feature = "cursor-style")]
9use crate::CursorManager;
10use crate::canvas::actions::{ActionResult, CanvasAction};
11#[cfg(feature = "keybindings")]
12use crate::canvas::state::SelectionState;
13#[cfg(feature = "gui")]
14use crate::gui_utils::{display_cols_up_to, display_width};
15use crate::{DataProvider, editor::EditorCore};
16#[cfg(feature = "gui")]
17use ratatui::{layout::Rect, widgets::Block};
18
19#[cfg(feature = "keybindings")]
20use crate::{
21 editor::{
22 behavior::{KeybindingParadigm, VimOperator, VimPendingOperator, YankRegister},
23 paradigm::helix_word::HelixWordTarget,
24 product::{KeybindingProduct, handle_product_key_event},
25 },
26 integration::focus_handoff::{BoundaryExit, key_outcome_for_vertical_navigation},
27 keybindings::{CanvasKeyAction, CanvasKeyBindings, KeyEventOutcome},
28};
29
30#[cfg(feature = "keybindings")]
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32enum TextFormActionPolicy {
33 SharedCore,
34 ProductHandled,
35 StructuralNoOp,
36 Unsupported,
37}
38
39#[cfg(feature = "keybindings")]
40fn textform_action_policy(action: &CanvasKeyAction) -> TextFormActionPolicy {
41 use CanvasKeyAction::*;
42
43 match action {
44 MoveLeft
45 | MoveRight
46 | MoveUp
47 | MoveDown
48 | MoveLineStart
49 | MoveLineEnd
50 | MoveHalfPageUp
51 | MoveHalfPageDown
52 | MoveFirstLine
53 | MoveLastLine
54 | MoveWordNext
55 | MoveWordPrev
56 | MoveWordEnd
57 | MoveWordEndPrev
58 | MoveBigWordNext
59 | MoveBigWordPrev
60 | MoveBigWordEnd
61 | MoveBigWordEndPrev
62 | DeleteCharBackward
63 | DeleteCharForward
64 | Undo
65 | Redo
66 | OpenSuggestions
67 | ApplySuggestion
68 | ExitSuggestions
69 | SuggestionDown
70 | SuggestionUp
71 | EnterEditModeBefore
72 | EnterEditModeAfter
73 | ExitEditMode
74 | EnterHighlightMode
75 | EnterHighlightModeLinewise
76 | ExitHighlightMode => TextFormActionPolicy::SharedCore,
77
78 NextField
79 | PrevField
80 | OpenLineBelow
81 | OpenLineAbove
82 | EnterEditModeLineStart
83 | EnterEditModeLineEnd
84 | DeleteLine
85 | DeleteToLineEnd
86 | ChangeLine
87 | ChangeToLineEnd
88 | OperatorDelete
89 | OperatorChange
90 | OperatorYank
91 | YankLine
92 | CopyLine
93 | CutLine
94 | PasteAfter
95 | PasteBefore
96 | DeleteSelection
97 | DeleteSelectionNoYank
98 | ChangeSelection
99 | ChangeSelectionNoYank
100 | YankSelection
101 | CollapseSelection
102 | ExtendLineBelow
103 | ExtendToLineBounds => TextFormActionPolicy::ProductHandled,
104
105 JoinLineBelow | MoveLineUp | MoveLineDown | DuplicateLineUp | DuplicateLineDown => {
106 TextFormActionPolicy::StructuralNoOp
107 }
108
109 EnterDecider
110 | Exit
111 | SearchNext
112 | SearchPrev
113 | SelectAll
114 | FlipSelections
115 | SwitchCase
116 | SwitchToLowercase
117 | SwitchToUppercase
118 | TrimSelections
119 | GotoFirstNonWhitespace
120 | MovePageUp
121 | MovePageDown
122 | SearchSelection
123 | EnsureSelectionForward
124 | MatchBrackets
125 | IndentSelection
126 | UnindentSelection
127 | IncrementNumber
128 | DecrementNumber
129 | FindNextChar
130 | FindPrevChar
131 | TillNextChar
132 | TillPrevChar
133 | ReplaceChar
134 | RepeatLastFind
135 | RepeatLastFindReverse
136 | SurroundAdd
137 | SurroundDelete
138 | SurroundReplace
139 | DeleteWordBackward
140 | DeleteToLineStart
141 | DeleteWordForward
142 | ClearSearch
143 | SelectLeft
144 | SelectRight
145 | SelectUp
146 | SelectDown
147 | SelectWordPrev
148 | SelectWordNext
149 | SelectLineStart
150 | SelectLineEnd
151 | SelectDocStart
152 | SelectDocEnd
153 | Unknown(_) => TextFormActionPolicy::Unsupported,
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum TextFormEventOutcome {
159 Ignored,
160 Handled,
161 Submitted,
162}
163
164#[derive(Debug)]
165pub struct TextFormState<D: DataProvider> {
166 pub(crate) core: EditorCore<D>,
167 fixed_field_count: usize,
168}
169
170impl<D: DataProvider + Default> Default for TextFormState<D> {
171 fn default() -> Self {
172 Self::new(D::default())
173 }
174}
175
176impl<D: DataProvider> TextFormState<D> {
177 pub fn new(data_provider: D) -> Self {
178 let fixed_field_count = data_provider.field_count();
179 Self {
180 core: EditorCore::new(data_provider),
184 fixed_field_count,
185 }
186 }
187
188 pub fn with_provider(data_provider: D) -> Self {
189 Self::new(data_provider)
190 }
191
192 pub fn core(&self) -> &EditorCore<D> {
193 &self.core
194 }
195
196 pub fn fixed_field_count(&self) -> usize {
197 self.fixed_field_count
198 }
199
200 fn sync_fixed_rows(&mut self) {
201 let actual = self.core.data_provider().field_count();
202 self.fixed_field_count = actual;
203 self.core
204 .clamp_current_field_to_count(self.fixed_field_count);
205 }
206
207 fn with_fixed_rows<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
208 self.sync_fixed_rows();
209 let result = f(self);
210 self.sync_fixed_rows();
211 result
212 }
213
214 #[cfg(feature = "crossterm")]
215 pub fn handle_event(&mut self, event: Event) -> TextFormEventOutcome {
216 self.with_fixed_rows(|this| match event {
217 Event::Key(key) => this.input(key),
218 Event::Paste(text) => this.paste(&text),
219 _ => TextFormEventOutcome::Ignored,
220 })
221 }
222
223 pub fn paste(&mut self, text: &str) -> TextFormEventOutcome {
224 self.with_fixed_rows(|this| {
225 let filtered: String = text
226 .chars()
227 .filter(|&ch| ch != '\n' && ch != '\r')
228 .collect();
229
230 if filtered.is_empty() {
231 return TextFormEventOutcome::Ignored;
232 }
233
234 this.core.enter_edit_mode();
235 let _ = this.core.insert_text(&filtered);
236 TextFormEventOutcome::Handled
237 })
238 }
239
240 #[cfg(feature = "crossterm")]
241 pub fn input(&mut self, key: KeyEvent) -> TextFormEventOutcome {
242 self.with_fixed_rows(|this| {
243 if key.kind != KeyEventKind::Press {
244 return TextFormEventOutcome::Ignored;
245 }
246
247 match (key.code, key.modifiers) {
248 (KeyCode::Enter, _) => this.enter_next_field_or_submit(),
249 (KeyCode::Tab, _) => {
250 let _ = this.core.next_field();
251 TextFormEventOutcome::Handled
252 }
253 (KeyCode::BackTab, _) => {
254 let _ = this.core.prev_field();
255 TextFormEventOutcome::Handled
256 }
257 (KeyCode::Backspace, _) => {
258 let _ = this.core.delete_backward();
259 TextFormEventOutcome::Handled
260 }
261 (KeyCode::Delete, _) => {
262 let _ = this.core.delete_forward();
263 TextFormEventOutcome::Handled
264 }
265 (KeyCode::Left, m) if m.contains(KeyModifiers::CONTROL) => {
266 this.core.move_word_prev();
267 TextFormEventOutcome::Handled
268 }
269 (KeyCode::Right, m) if m.contains(KeyModifiers::CONTROL) => {
270 this.core.move_word_next();
271 TextFormEventOutcome::Handled
272 }
273 (KeyCode::Left, _) => {
274 let _ = this.core.move_left();
275 TextFormEventOutcome::Handled
276 }
277 (KeyCode::Right, _) => {
278 let _ = this.core.move_right();
279 TextFormEventOutcome::Handled
280 }
281 (KeyCode::Up, _) => {
282 let _ = this.core.move_up();
283 TextFormEventOutcome::Handled
284 }
285 (KeyCode::Down, _) => {
286 let _ = this.core.move_down();
287 TextFormEventOutcome::Handled
288 }
289 (KeyCode::Home, _) | (KeyCode::Char('a'), KeyModifiers::CONTROL) => {
290 this.core.move_line_start();
291 TextFormEventOutcome::Handled
292 }
293 (KeyCode::End, _) | (KeyCode::Char('e'), KeyModifiers::CONTROL) => {
294 this.core.move_line_end();
295 TextFormEventOutcome::Handled
296 }
297 (KeyCode::Char('b'), KeyModifiers::ALT) => {
298 this.core.move_word_prev();
299 TextFormEventOutcome::Handled
300 }
301 (KeyCode::Char('f'), KeyModifiers::ALT) => {
302 this.core.move_word_next();
303 TextFormEventOutcome::Handled
304 }
305 (KeyCode::Char('e'), KeyModifiers::ALT) => {
306 this.core.move_word_end();
307 TextFormEventOutcome::Handled
308 }
309 (KeyCode::Esc, _) => {
310 if this.core.mode() == crate::canvas::modes::AppMode::Ins {
311 let _ = this.core.exit_edit_mode();
312 TextFormEventOutcome::Handled
313 } else {
314 TextFormEventOutcome::Ignored
315 }
316 }
317 (KeyCode::Char(c), m)
318 if !m.contains(KeyModifiers::CONTROL) && !m.contains(KeyModifiers::ALT) =>
319 {
320 this.core.enter_edit_mode();
321 let _ = this.core.insert_char(c);
322 TextFormEventOutcome::Handled
323 }
324 _ => TextFormEventOutcome::Ignored,
325 }
326 })
327 }
328
329 #[cfg(feature = "keybindings")]
330 pub fn handle_key_event(&mut self, evt: KeyEvent) -> KeyEventOutcome {
331 self.with_fixed_rows(|this| handle_product_key_event(this, evt))
332 }
333
334 #[cfg(feature = "keybindings")]
338 pub fn is_sequence_pending(&self) -> bool {
339 self.core.is_sequence_pending()
340 }
341
342 #[cfg(feature = "cursor-style")]
343 pub fn update_cursor_style(&self) -> io::Result<()> {
344 CursorManager::update_for_mode(self.core.mode())
345 }
346
347 #[cfg(not(feature = "cursor-style"))]
348 pub fn update_cursor_style(&self) -> std::io::Result<()> {
349 Ok(())
350 }
351
352 #[cfg(feature = "gui")]
353 pub fn cursor(&self, area: Rect, block: Option<&Block<'_>>) -> (u16, u16) {
354 let inner = if let Some(block) = block {
355 block.inner(area)
356 } else {
357 area
358 };
359 let provider = self.core.data_provider();
360 let label_width = (0..provider.field_count())
361 .map(|index| display_width(provider.field_name(index)))
362 .max()
363 .unwrap_or(0)
364 .saturating_add(1);
365 let row = self.core.current_field() as u16;
366 let current_text = self.core.current_text();
367 let cursor_cols = display_cols_up_to(current_text, self.core.display_cursor_position());
368
369 (
370 inner
371 .x
372 .saturating_add(label_width)
373 .saturating_add(cursor_cols),
374 inner.y.saturating_add(row),
375 )
376 }
377
378 #[cfg(feature = "keybindings")]
379 pub fn use_keybinding_preset(
380 &mut self,
381 preset: crate::keybindings::BuiltinCanvasKeybindingPreset,
382 ) {
383 self.core.set_keybinding_preset(preset);
384 }
385
386 #[cfg(feature = "keybindings")]
387 pub fn set_keybindings(&mut self, keybindings: CanvasKeyBindings) {
388 self.core.set_keybindings(keybindings);
389 }
390
391 pub fn clear_current_field(&mut self) {
392 self.with_fixed_rows(|this| this.core.set_current_field_value(String::new()))
393 }
394
395 pub fn clear_field(&mut self, field_index: usize) {
396 self.with_fixed_rows(|this| {
397 if field_index < this.fixed_field_count {
398 this.core.set_field_value(field_index, String::new());
399 }
400 })
401 }
402
403 pub fn clear_current_and_following_fields(&mut self, count: usize) {
404 self.with_fixed_rows(|this| {
405 if this.fixed_field_count == 0 {
406 return;
407 }
408
409 let start = this.core.current_field();
410 let end = start
411 .saturating_add(count.max(1))
412 .saturating_sub(1)
413 .min(this.fixed_field_count - 1);
414 this.clear_field_range(start, end);
415 })
416 }
417
418 #[cfg(feature = "keybindings")]
419 fn selected_fixed_field_values(&self, start: usize, end: usize) -> Vec<String> {
420 if self.fixed_field_count == 0 || start > end {
421 return Vec::new();
422 }
423
424 let end = end.min(self.fixed_field_count - 1);
425 (start..=end)
426 .map(|field_index| {
427 self.core
428 .data_provider()
429 .field_value(field_index)
430 .to_string()
431 })
432 .collect()
433 }
434
435 #[cfg(feature = "keybindings")]
436 fn yank_current_and_following_fields(&mut self, count: usize) {
437 if self.fixed_field_count == 0 {
438 return;
439 }
440
441 let start = self.core.current_field();
442 let end = start
443 .saturating_add(count.max(1))
444 .saturating_sub(1)
445 .min(self.fixed_field_count - 1);
446 let lines = self.selected_fixed_field_values(start, end);
447 if !lines.is_empty() {
448 self.core.behavior_state.yank_mut().set_line_register(lines);
449 }
450 }
451
452 #[cfg(feature = "keybindings")]
453 fn cut_current_and_following_fields(&mut self, count: usize) {
454 self.yank_current_and_following_fields(count);
455 self.clear_current_and_following_fields(count);
456 }
457
458 fn clear_field_range(&mut self, start: usize, end: usize) {
459 if self.fixed_field_count == 0 {
460 return;
461 }
462
463 let start = start.min(self.fixed_field_count - 1);
464 let end = end.min(self.fixed_field_count - 1);
465 if start > end {
466 return;
467 }
468
469 for field_index in start..=end {
470 self.core.set_field_value(field_index, String::new());
471 }
472 }
473
474 #[cfg(feature = "keybindings")]
475 fn field_char_len(&self, field_index: usize) -> usize {
476 self.core
477 .data_provider()
478 .field_value(field_index)
479 .chars()
480 .count()
481 }
482
483 #[cfg(feature = "keybindings")]
484 fn delete_selection_helix(&mut self, yank: bool, count: usize) {
485 for _ in 0..count.max(1) {
486 if !self.core.delete_selection_once_fixed(yank) {
487 break;
488 }
489 }
490 self.core.finish_helix_selection_edit();
491 }
492
493 #[cfg(feature = "keybindings")]
494 fn change_selection_helix(&mut self, yank: bool, count: usize) {
495 for _ in 0..count.max(1) {
496 if !self.core.delete_selection_once_fixed(yank) {
497 break;
498 }
499 }
500 self.core.enter_edit_mode();
501 }
502
503 #[cfg(feature = "keybindings")]
504 fn character_paste_position_helix(&self, after: bool) -> (usize, usize) {
505 match self.core.selection_state() {
506 SelectionState::Characterwise { anchor } => {
507 let cursor = (self.core.current_field(), self.core.cursor_position());
508 let start = (*anchor).min(cursor);
509 let end = (*anchor).max(cursor);
510 if after {
511 (end.0, end.1.saturating_add(1))
512 } else {
513 start
514 }
515 }
516 _ => {
517 let field = self.core.current_field();
518 let len = self.field_char_len(field);
519 let cursor = self.core.cursor_position().min(len);
520 if after {
521 (field, cursor.saturating_add(1).min(len))
522 } else {
523 (field, cursor)
524 }
525 }
526 }
527 }
528
529 #[cfg(feature = "keybindings")]
530 fn paste_register_helix(&mut self, after: bool, count: usize) {
531 let Some(register) = self.core.behavior_state.yank().register().cloned() else {
532 return;
533 };
534
535 match register {
536 YankRegister::Lines(lines) => {
537 self.core.paste_lines_fixed(after, count, lines);
538 self.core.ensure_helix_primary_selection();
539 }
540 YankRegister::Text(lines) => {
541 let text = crate::editor::rows::repeated_text(&lines, count);
542 if text.is_empty() || self.fixed_field_count == 0 {
543 return;
544 }
545 let (field, col) = self.character_paste_position_helix(after);
546 if field >= self.fixed_field_count {
547 return;
548 }
549 let (target_field, target_col) = self.core.insert_text_fixed(field, col, &text);
550 let _ = self.core.transition_to_field(target_field);
551 self.core.set_cursor_position(target_col);
552 self.core.ensure_helix_primary_selection();
553 }
554 }
555 }
556
557 pub fn change_current_field(&mut self) {
558 self.with_fixed_rows(|this| {
559 this.core.set_current_field_value(String::new());
560 this.core.enter_edit_mode();
561 })
562 }
563
564 pub fn delete_to_field_end(&mut self) {
565 self.with_fixed_rows(|this| {
566 let cursor = this.core.cursor_position();
567 let kept: String = this.core.current_text().chars().take(cursor).collect();
568 this.core.set_current_field_value(kept);
569 this.core.set_cursor_position(cursor);
570 })
571 }
572
573 pub fn execute(&mut self, action: CanvasAction) -> ActionResult {
574 self.with_fixed_rows(|this| match action {
575 CanvasAction::OpenLineBelow => {
576 let _ = this.core.open_line_below();
577 ActionResult::Success
578 }
579 CanvasAction::OpenLineAbove => {
580 let _ = this.core.open_line_above();
581 ActionResult::Success
582 }
583 other => this.core.execute(other),
584 })
585 }
586
587 pub fn undo(&mut self) -> bool {
588 self.with_fixed_rows(|this| this.core.undo())
589 }
590
591 pub fn redo(&mut self) -> bool {
592 self.with_fixed_rows(|this| this.core.redo())
593 }
594
595 #[cfg(feature = "crossterm")]
596 fn enter_next_field_or_submit(&mut self) -> TextFormEventOutcome {
597 let last = self.core.data_provider().field_count().saturating_sub(1);
598 if self.core.current_field() >= last {
599 TextFormEventOutcome::Submitted
600 } else {
601 let _ = self.core.next_field();
602 TextFormEventOutcome::Handled
603 }
604 }
605
606 #[cfg(feature = "keybindings")]
607 fn move_next_field_count(&mut self, count: usize) {
608 for _ in 0..count {
609 let _ = self.core.next_field();
610 }
611 }
612
613 #[cfg(feature = "keybindings")]
614 fn move_prev_field_count(&mut self, count: usize) {
615 for _ in 0..count {
616 let _ = self.core.prev_field();
617 }
618 }
619
620 #[cfg(feature = "keybindings")]
621 fn execute_canvas_key_action(
622 &mut self,
623 action: &CanvasKeyAction,
624 count: usize,
625 ) -> KeyEventOutcome {
626 let Some(canvas_action) = action.to_canvas_action() else {
627 return KeyEventOutcome::NotMatched;
628 };
629
630 let boundary = match action {
631 CanvasKeyAction::MoveUp | CanvasKeyAction::PrevField => Some(BoundaryExit::Top),
632 CanvasKeyAction::MoveDown | CanvasKeyAction::NextField => Some(BoundaryExit::Bottom),
633 _ => None,
634 };
635 let before_field = self.core.current_field();
636
637 let mut result = ActionResult::Success;
638 for _ in 0..count {
639 result = self.execute(canvas_action.clone());
640 }
641
642 if let Some(boundary) = boundary {
643 let moved = self.core.current_field() != before_field;
644 return key_outcome_for_vertical_navigation(moved, boundary);
645 }
646
647 match result {
648 ActionResult::Success => KeyEventOutcome::Consumed(None),
649 ActionResult::Message(msg) | ActionResult::Error(msg) => {
650 KeyEventOutcome::Consumed(Some(msg))
651 }
652 }
653 }
654
655 #[cfg(feature = "keybindings")]
656 fn begin_operator_vim(&mut self, operator: VimOperator, count: usize) {
657 let anchor = (self.core.current_field(), self.core.cursor_position());
658 self.core
659 .behavior_state
660 .vim_mut()
661 .set_pending_operator(VimPendingOperator {
662 operator,
663 count: count.max(1),
664 anchor,
665 });
666 }
667
668 #[cfg(feature = "keybindings")]
669 fn apply_operator_motion_vim(
670 &mut self,
671 action: &CanvasKeyAction,
672 motion_count: usize,
673 ) -> KeyEventOutcome {
674 let Some(pending) = self.core.behavior_state.vim().pending_operator() else {
675 return self.execute_canvas_key_action(action, motion_count);
676 };
677
678 self.core.behavior_state.vim_mut().clear_pending_operator();
679 let total = pending.count.saturating_mul(motion_count.max(1)).max(1);
680
681 if matches!(
682 action,
683 CanvasKeyAction::OperatorDelete
684 | CanvasKeyAction::OperatorChange
685 | CanvasKeyAction::OperatorYank
686 ) {
687 let start = pending.anchor.0;
688 let end = start.saturating_add(total).saturating_sub(1);
689 match pending.operator {
690 VimOperator::Delete => self.clear_field_range(start, end),
691 VimOperator::Change => {
692 self.clear_field_range(start, end);
693 self.core.enter_edit_mode();
694 }
695 VimOperator::Yank => {
696 let lines = self.selected_fixed_field_values(start, end);
697 if !lines.is_empty() {
698 self.core.behavior_state.yank_mut().set_line_register(lines);
699 }
700 }
701 }
702 return KeyEventOutcome::Consumed(None);
703 }
704
705 let linewise_target = match action {
706 CanvasKeyAction::MoveUp => Some(pending.anchor.0.saturating_sub(total)),
707 CanvasKeyAction::MoveDown => Some(
708 pending
709 .anchor
710 .0
711 .saturating_add(total)
712 .min(self.fixed_field_count.saturating_sub(1)),
713 ),
714 CanvasKeyAction::MoveFirstLine => Some(0),
715 CanvasKeyAction::MoveLastLine => Some(self.fixed_field_count.saturating_sub(1)),
716 _ => None,
717 };
718
719 if let Some(target) = linewise_target {
720 let start = pending.anchor.0.min(target);
721 let end = pending.anchor.0.max(target);
722 match pending.operator {
723 VimOperator::Delete => self.clear_field_range(start, end),
724 VimOperator::Change => {
725 self.clear_field_range(start, end);
726 self.core.enter_edit_mode();
727 }
728 VimOperator::Yank => {
729 let lines = self.selected_fixed_field_values(start, end);
730 if !lines.is_empty() {
731 self.core.behavior_state.yank_mut().set_line_register(lines);
732 }
733 }
734 }
735 return KeyEventOutcome::Consumed(None);
736 }
737
738 match pending.operator {
739 VimOperator::Delete => {
740 self.delete_to_field_end();
741 KeyEventOutcome::Consumed(None)
742 }
743 VimOperator::Change => {
744 self.delete_to_field_end();
745 self.core.enter_edit_mode();
746 KeyEventOutcome::Consumed(None)
747 }
748 VimOperator::Yank => {
749 let field = self.core.current_field();
750 let line = self.core.data_provider().field_value(field).to_string();
751 self.core
752 .behavior_state
753 .yank_mut()
754 .set_text_register(vec![line]);
755 KeyEventOutcome::Consumed(None)
756 }
757 }
758 }
759}
760
761impl<D: DataProvider> Deref for TextFormState<D> {
762 type Target = EditorCore<D>;
763
764 fn deref(&self) -> &Self::Target {
765 &self.core
766 }
767}
768
769impl<D: DataProvider> DerefMut for TextFormState<D> {
770 fn deref_mut(&mut self) -> &mut Self::Target {
771 self.sync_fixed_rows();
772 &mut self.core
773 }
774}
775
776#[cfg(feature = "keybindings")]
777impl<D: DataProvider> KeybindingProduct for TextFormState<D> {
778 type Provider = D;
779
780 fn core(&self) -> &EditorCore<Self::Provider> {
781 &self.core
782 }
783
784 fn core_mut(&mut self) -> &mut EditorCore<Self::Provider> {
785 &mut self.core
786 }
787
788 fn handle_insert_enter(&mut self) -> KeyEventOutcome {
789 self.move_next_field_count(1);
790 KeyEventOutcome::Consumed(None)
791 }
792
793 fn handle_insert_tab(&mut self) -> KeyEventOutcome {
794 self.move_next_field_count(1);
795 KeyEventOutcome::Consumed(None)
796 }
797
798 fn handle_plain_insert_char(&mut self, ch: char) -> KeyEventOutcome {
799 self.core.enter_edit_mode();
800 if self.core.insert_char(ch).is_ok() {
801 KeyEventOutcome::Consumed(None)
802 } else {
803 KeyEventOutcome::NotMatched
804 }
805 }
806
807 fn dispatch_product_key_action(
808 &mut self,
809 action: &CanvasKeyAction,
810 count: usize,
811 ) -> KeyEventOutcome {
812 let policy = textform_action_policy(action);
813
814 if self.core.behavior_state.vim().has_pending_operator() {
815 return self.apply_operator_motion_vim(action, count);
816 }
817
818 if self.core.keybinding_paradigm() == KeybindingParadigm::Helix {
819 match action {
820 CanvasKeyAction::MoveWordNext => {
821 self.core
822 .select_word_motion_helix(count, HelixWordTarget::NextWordStart);
823 return KeyEventOutcome::Consumed(None);
824 }
825 CanvasKeyAction::MoveWordPrev => {
826 self.core
827 .select_word_motion_helix(count, HelixWordTarget::PrevWordStart);
828 return KeyEventOutcome::Consumed(None);
829 }
830 CanvasKeyAction::MoveWordEnd => {
831 self.core
832 .select_word_motion_helix(count, HelixWordTarget::NextWordEnd);
833 return KeyEventOutcome::Consumed(None);
834 }
835 CanvasKeyAction::MoveWordEndPrev => {
836 self.core
837 .select_word_motion_helix(count, HelixWordTarget::PrevWordEnd);
838 return KeyEventOutcome::Consumed(None);
839 }
840 CanvasKeyAction::MoveBigWordNext => {
841 self.core
842 .select_word_motion_helix(count, HelixWordTarget::NextLongWordStart);
843 return KeyEventOutcome::Consumed(None);
844 }
845 CanvasKeyAction::MoveBigWordPrev => {
846 self.core
847 .select_word_motion_helix(count, HelixWordTarget::PrevLongWordStart);
848 return KeyEventOutcome::Consumed(None);
849 }
850 CanvasKeyAction::MoveBigWordEnd => {
851 self.core
852 .select_word_motion_helix(count, HelixWordTarget::NextLongWordEnd);
853 return KeyEventOutcome::Consumed(None);
854 }
855 CanvasKeyAction::MoveBigWordEndPrev => {
856 self.core
857 .select_word_motion_helix(count, HelixWordTarget::PrevLongWordEnd);
858 return KeyEventOutcome::Consumed(None);
859 }
860 _ => {}
861 }
862 }
863
864 match action {
865 CanvasKeyAction::OperatorDelete => {
866 self.begin_operator_vim(VimOperator::Delete, count);
867 KeyEventOutcome::Consumed(None)
868 }
869 CanvasKeyAction::OperatorChange => {
870 self.begin_operator_vim(VimOperator::Change, count);
871 KeyEventOutcome::Consumed(None)
872 }
873 CanvasKeyAction::OperatorYank => {
874 self.begin_operator_vim(VimOperator::Yank, count);
875 KeyEventOutcome::Consumed(None)
876 }
877 CanvasKeyAction::NextField => {
878 self.move_next_field_count(count);
879 KeyEventOutcome::Consumed(None)
880 }
881 CanvasKeyAction::PrevField => {
882 self.move_prev_field_count(count);
883 KeyEventOutcome::Consumed(None)
884 }
885 CanvasKeyAction::OpenLineBelow => {
886 self.move_next_field_count(count);
887 self.core.enter_edit_mode();
888 KeyEventOutcome::Consumed(None)
889 }
890 CanvasKeyAction::OpenLineAbove => {
891 self.move_prev_field_count(count);
892 self.core.enter_edit_mode();
893 KeyEventOutcome::Consumed(None)
894 }
895 CanvasKeyAction::EnterEditModeLineStart => {
896 self.core.move_line_start();
897 self.core.enter_edit_mode();
898 KeyEventOutcome::Consumed(None)
899 }
900 CanvasKeyAction::EnterEditModeLineEnd => {
901 self.core.move_line_end();
902 self.core.enter_edit_mode();
903 KeyEventOutcome::Consumed(None)
904 }
905 CanvasKeyAction::DeleteLine => {
906 self.clear_current_and_following_fields(count);
907 KeyEventOutcome::Consumed(None)
908 }
909 CanvasKeyAction::DeleteToLineEnd => {
910 self.delete_to_field_end();
911 KeyEventOutcome::Consumed(None)
912 }
913 CanvasKeyAction::ChangeLine => {
914 self.change_current_field();
915 KeyEventOutcome::Consumed(None)
916 }
917 CanvasKeyAction::ChangeToLineEnd => {
918 self.delete_to_field_end();
919 self.core.enter_edit_mode();
920 KeyEventOutcome::Consumed(None)
921 }
922 CanvasKeyAction::YankLine | CanvasKeyAction::CopyLine => {
923 self.yank_current_and_following_fields(count);
924 KeyEventOutcome::Consumed(None)
925 }
926 CanvasKeyAction::CutLine => {
927 self.cut_current_and_following_fields(count);
928 KeyEventOutcome::Consumed(None)
929 }
930 CanvasKeyAction::ExtendLineBelow => {
931 for _ in 0..count {
932 self.core.extend_line_below_helix();
933 }
934 KeyEventOutcome::Consumed(None)
935 }
936 CanvasKeyAction::ExtendToLineBounds => {
937 for _ in 0..count {
938 self.core.extend_to_line_bounds_helix();
939 }
940 KeyEventOutcome::Consumed(None)
941 }
942 CanvasKeyAction::CollapseSelection => {
943 self.core.collapse_selection_to_cursor();
944 KeyEventOutcome::Consumed(None)
945 }
946 CanvasKeyAction::DeleteSelection | CanvasKeyAction::DeleteSelectionNoYank => {
947 self.delete_selection_helix(
948 matches!(action, CanvasKeyAction::DeleteSelection),
949 count,
950 );
951 KeyEventOutcome::Consumed(None)
952 }
953 CanvasKeyAction::ChangeSelection | CanvasKeyAction::ChangeSelectionNoYank => {
954 self.change_selection_helix(
955 matches!(action, CanvasKeyAction::ChangeSelection),
956 count,
957 );
958 KeyEventOutcome::Consumed(None)
959 }
960 CanvasKeyAction::YankSelection => {
961 if self.core.keybinding_paradigm() == KeybindingParadigm::Vim {
962 for _ in 0..count {
965 self.core.yank_selection_core();
966 }
967 self.core.exit_highlight_mode_vim();
968 } else {
969 for _ in 0..count {
971 self.core.yank_primary_selection_helix();
972 }
973 }
974 KeyEventOutcome::Consumed(None)
975 }
976 CanvasKeyAction::JoinLineBelow
977 | CanvasKeyAction::MoveLineUp
978 | CanvasKeyAction::MoveLineDown
979 | CanvasKeyAction::DuplicateLineUp
980 | CanvasKeyAction::DuplicateLineDown => KeyEventOutcome::Consumed(None),
981 CanvasKeyAction::PasteAfter => {
982 self.paste_register_helix(true, count);
983 KeyEventOutcome::Consumed(None)
984 }
985 CanvasKeyAction::PasteBefore => {
986 self.paste_register_helix(false, count);
987 KeyEventOutcome::Consumed(None)
988 }
989 _ => match policy {
990 TextFormActionPolicy::SharedCore => self.execute_canvas_key_action(action, count),
991 TextFormActionPolicy::StructuralNoOp => KeyEventOutcome::Consumed(None),
992 TextFormActionPolicy::Unsupported => KeyEventOutcome::NotMatched,
993 TextFormActionPolicy::ProductHandled => KeyEventOutcome::Consumed(Some(format!(
994 "Unhandled textform action: {}",
995 action.as_str()
996 ))),
997 },
998 }
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 #[cfg(feature = "crossterm")]
1005 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
1006
1007 #[cfg(feature = "crossterm")]
1008 use super::TextFormEventOutcome;
1009 use super::TextFormState;
1010 use crate::DataProvider;
1011
1012 #[derive(Default)]
1013 struct TestProvider {
1014 fields: [String; 2],
1015 }
1016
1017 impl DataProvider for TestProvider {
1018 fn field_count(&self) -> usize {
1019 2
1020 }
1021
1022 fn field_name(&self, index: usize) -> &str {
1023 match index {
1024 0 => "first",
1025 1 => "second",
1026 _ => "",
1027 }
1028 }
1029
1030 fn field_value(&self, index: usize) -> &str {
1031 self.fields.get(index).map(String::as_str).unwrap_or("")
1032 }
1033
1034 fn set_field_value(&mut self, index: usize, value: String) {
1035 if let Some(field) = self.fields.get_mut(index) {
1036 *field = value;
1037 }
1038 }
1039 }
1040
1041 #[derive(Default)]
1042 struct VecProvider {
1043 fields: Vec<String>,
1044 }
1045
1046 impl DataProvider for VecProvider {
1047 fn field_count(&self) -> usize {
1048 self.fields.len()
1049 }
1050
1051 fn field_name(&self, index: usize) -> &str {
1052 match index {
1053 0 => "first",
1054 1 => "second",
1055 _ => "",
1056 }
1057 }
1058
1059 fn field_value(&self, index: usize) -> &str {
1060 self.fields.get(index).map(String::as_str).unwrap_or("")
1061 }
1062
1063 fn set_field_value(&mut self, index: usize, value: String) {
1064 if let Some(field) = self.fields.get_mut(index) {
1065 *field = value;
1066 }
1067 }
1068 }
1069
1070 #[derive(Default)]
1071 struct StrictVecProvider {
1072 fields: Vec<String>,
1073 }
1074
1075 impl DataProvider for StrictVecProvider {
1076 fn field_count(&self) -> usize {
1077 self.fields.len()
1078 }
1079
1080 fn field_name(&self, index: usize) -> &str {
1081 match index {
1082 0 => "first",
1083 1 => "second",
1084 _ => "",
1085 }
1086 }
1087
1088 fn field_value(&self, index: usize) -> &str {
1089 &self.fields[index]
1090 }
1091
1092 fn set_field_value(&mut self, index: usize, value: String) {
1093 self.fields[index] = value;
1094 }
1095 }
1096
1097 #[cfg(feature = "keybindings")]
1098 #[derive(Clone)]
1099 struct FixedPolicyProvider {
1100 names: [&'static str; 3],
1101 fields: [String; 3],
1102 }
1103
1104 #[cfg(feature = "keybindings")]
1105 impl FixedPolicyProvider {
1106 fn new() -> Self {
1107 Self {
1108 names: ["first", "second", "third"],
1109 fields: ["alpha beta".into(), "gamma delta".into(), "epsilon".into()],
1110 }
1111 }
1112
1113 fn names(&self) -> Vec<String> {
1114 self.names.iter().map(|name| (*name).to_string()).collect()
1115 }
1116 }
1117
1118 #[cfg(feature = "keybindings")]
1119 impl DataProvider for FixedPolicyProvider {
1120 fn field_count(&self) -> usize {
1121 self.fields.len()
1122 }
1123
1124 fn field_name(&self, index: usize) -> &str {
1125 self.names.get(index).copied().unwrap_or("")
1126 }
1127
1128 fn field_value(&self, index: usize) -> &str {
1129 self.fields.get(index).map(String::as_str).unwrap_or("")
1130 }
1131
1132 fn set_field_value(&mut self, index: usize, value: String) {
1133 if let Some(field) = self.fields.get_mut(index) {
1134 *field = value;
1135 }
1136 }
1137 }
1138
1139 #[cfg(feature = "keybindings")]
1140 fn all_known_key_actions() -> Vec<crate::keybindings::CanvasKeyAction> {
1141 use crate::keybindings::CanvasKeyAction::*;
1142
1143 vec![
1144 MoveLeft,
1145 MoveRight,
1146 MoveUp,
1147 MoveDown,
1148 NextField,
1149 PrevField,
1150 MoveLineStart,
1151 MoveLineEnd,
1152 MoveHalfPageUp,
1153 MoveHalfPageDown,
1154 MoveFirstLine,
1155 MoveLastLine,
1156 MoveWordNext,
1157 MoveWordPrev,
1158 MoveWordEnd,
1159 MoveWordEndPrev,
1160 MoveBigWordNext,
1161 MoveBigWordPrev,
1162 MoveBigWordEnd,
1163 MoveBigWordEndPrev,
1164 DeleteCharBackward,
1165 DeleteCharForward,
1166 Undo,
1167 Redo,
1168 OpenLineBelow,
1169 OpenLineAbove,
1170 EnterEditModeLineStart,
1171 EnterEditModeLineEnd,
1172 DeleteLine,
1173 DeleteToLineEnd,
1174 ChangeLine,
1175 ChangeToLineEnd,
1176 OperatorDelete,
1177 OperatorChange,
1178 OperatorYank,
1179 JoinLineBelow,
1180 YankLine,
1181 PasteAfter,
1182 PasteBefore,
1183 OpenSuggestions,
1184 ApplySuggestion,
1185 EnterDecider,
1186 SuggestionDown,
1187 SuggestionUp,
1188 EnterEditModeBefore,
1189 EnterEditModeAfter,
1190 Exit,
1191 ExitEditMode,
1192 EnterHighlightMode,
1193 EnterHighlightModeLinewise,
1194 ExitHighlightMode,
1195 DeleteSelection,
1196 DeleteSelectionNoYank,
1197 ChangeSelection,
1198 ChangeSelectionNoYank,
1199 YankSelection,
1200 CollapseSelection,
1201 ExtendLineBelow,
1202 ExtendToLineBounds,
1203 SearchNext,
1204 SearchPrev,
1205 SelectAll,
1206 FlipSelections,
1207 SwitchCase,
1208 SwitchToLowercase,
1209 SwitchToUppercase,
1210 TrimSelections,
1211 GotoFirstNonWhitespace,
1212 MovePageUp,
1213 MovePageDown,
1214 SearchSelection,
1215 EnsureSelectionForward,
1216 MatchBrackets,
1217 IndentSelection,
1218 UnindentSelection,
1219 IncrementNumber,
1220 DecrementNumber,
1221 FindNextChar,
1222 FindPrevChar,
1223 TillNextChar,
1224 TillPrevChar,
1225 ReplaceChar,
1226 RepeatLastFind,
1227 RepeatLastFindReverse,
1228 SurroundAdd,
1229 SurroundDelete,
1230 SurroundReplace,
1231 DeleteWordBackward,
1232 DeleteToLineStart,
1233 DeleteWordForward,
1234 ClearSearch,
1235 MoveLineUp,
1236 MoveLineDown,
1237 DuplicateLineUp,
1238 DuplicateLineDown,
1239 CopyLine,
1240 CutLine,
1241 SelectLeft,
1242 SelectRight,
1243 SelectUp,
1244 SelectDown,
1245 SelectWordPrev,
1246 SelectWordNext,
1247 SelectLineStart,
1248 SelectLineEnd,
1249 SelectDocStart,
1250 SelectDocEnd,
1251 ]
1252 }
1253
1254 #[cfg(feature = "crossterm")]
1255 #[test]
1256 fn enter_moves_between_fields_then_submits() {
1257 let mut form = TextFormState::new(TestProvider::default());
1258
1259 let first = form.input(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
1260 assert_eq!(first, TextFormEventOutcome::Handled);
1261 assert_eq!(form.current_field(), 1);
1262
1263 let second = form.input(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
1264 assert_eq!(second, TextFormEventOutcome::Submitted);
1265 assert_eq!(form.current_field(), 1);
1266 }
1267
1268 #[cfg(feature = "keybindings")]
1269 #[test]
1270 fn every_known_key_action_preserves_textform_fixed_slots() {
1271 use crate::editor::product::KeybindingProduct;
1272 use crate::keybindings::{BuiltinCanvasKeybindingPreset, KeyEventOutcome};
1273
1274 for action in all_known_key_actions() {
1275 let provider = FixedPolicyProvider::new();
1276 let expected_names = provider.names();
1277 let mut form = TextFormState::new(provider);
1278 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1279 form.core
1280 .behavior_state
1281 .yank_mut()
1282 .set_text_register(vec!["paste".to_string()]);
1283
1284 let outcome = form.dispatch_product_key_action(&action, 1);
1285 assert!(
1286 !matches!(
1287 outcome,
1288 KeyEventOutcome::Consumed(Some(ref msg))
1289 if msg.starts_with("Unhandled textform action:")
1290 ),
1291 "action is classified as product-handled but not implemented: {}",
1292 action.as_str()
1293 );
1294
1295 assert_eq!(
1296 form.data_provider().field_count(),
1297 3,
1298 "action changed field count: {}",
1299 action.as_str()
1300 );
1301 assert_eq!(
1302 form.fixed_field_count(),
1303 3,
1304 "action changed fixed count: {}",
1305 action.as_str()
1306 );
1307 let names: Vec<String> = (0..form.data_provider().field_count())
1308 .map(|index| form.data_provider().field_name(index).to_string())
1309 .collect();
1310 assert_eq!(
1311 names,
1312 expected_names,
1313 "action changed field identity/order: {}",
1314 action.as_str()
1315 );
1316 }
1317 }
1318
1319 #[cfg(feature = "keybindings")]
1320 #[test]
1321 fn structural_noop_actions_leave_fixed_field_values_unchanged() {
1322 use crate::editor::product::KeybindingProduct;
1323 use crate::keybindings::CanvasKeyAction;
1324
1325 let actions = [
1326 CanvasKeyAction::JoinLineBelow,
1327 CanvasKeyAction::MoveLineUp,
1328 CanvasKeyAction::MoveLineDown,
1329 CanvasKeyAction::DuplicateLineUp,
1330 CanvasKeyAction::DuplicateLineDown,
1331 ];
1332
1333 for action in actions {
1334 let provider = FixedPolicyProvider::new();
1335 let expected = provider.capture_content();
1336 let mut form = TextFormState::new(provider);
1337
1338 let _ = form.dispatch_product_key_action(&action, 1);
1339
1340 assert_eq!(
1341 form.data_provider().capture_content(),
1342 expected,
1343 "structural no-op changed fixed field values: {}",
1344 action.as_str()
1345 );
1346 }
1347 }
1348
1349 #[test]
1350 fn delete_line_clears_current_field_without_removing_it() {
1351 let mut form = TextFormState::new(TestProvider {
1352 fields: ["abc".to_string(), "def".to_string()],
1353 });
1354 form.clear_current_field();
1355
1356 assert_eq!(form.data_provider().field_count(), 2);
1357 assert_eq!(form.current_text(), "");
1358 }
1359
1360 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1361 #[test]
1362 fn keybinding_enter_moves_to_next_field_without_splitting_rows() {
1363 use crate::keybindings::KeyEventOutcome;
1364
1365 let mut form = TextFormState::new(TestProvider {
1366 fields: ["abc".to_string(), "def".to_string()],
1367 });
1368 form.enter_edit_mode();
1369
1370 let outcome = form.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
1371
1372 assert_eq!(outcome, KeyEventOutcome::Consumed(None));
1373 assert_eq!(form.data_provider().field_count(), 2);
1374 assert_eq!(form.current_field(), 1);
1375 assert_eq!(form.data_provider().field_value(0), "abc");
1376 assert_eq!(form.current_text(), "def");
1377 }
1378
1379 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1380 #[test]
1381 fn vim_esc_exits_select_mode() {
1382 use crate::canvas::modes::AppMode;
1383
1384 let mut form = TextFormState::new(TestProvider {
1385 fields: ["row1".to_string(), "row2".to_string()],
1386 });
1387 form.set_keybindings(crate::keybindings::CanvasKeyBindings::vim_defaults());
1388
1389 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));
1390 assert_eq!(form.mode(), AppMode::Sel);
1391
1392 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
1393 assert_eq!(form.mode(), AppMode::Nor);
1394 }
1395
1396 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1397 #[test]
1398 fn helix_v_up_delete_collapses_selection() {
1399 use crate::canvas::state::SelectionState;
1400 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1401
1402 let mut form = TextFormState::new(TestProvider {
1403 fields: ["row1".to_string(), "row2".to_string()],
1404 });
1405 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1406
1407 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('j'), KeyModifiers::NONE));
1409 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));
1410 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE));
1411 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1412
1413 match form.selection_state() {
1415 SelectionState::None => {}
1416 SelectionState::Characterwise { anchor } => {
1417 assert_eq!(*anchor, (form.current_field(), form.cursor_position()));
1418 }
1419 other => panic!("expected collapsed selection, got {other:?}"),
1420 }
1421 }
1422
1423 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1424 #[test]
1425 fn helix_esc_exits_select_but_keeps_selection_in_normal_mode() {
1426 use crate::canvas::modes::AppMode;
1427 use crate::canvas::state::SelectionState;
1428 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1429
1430 let mut form = TextFormState::new(TestProvider {
1431 fields: ["row1".to_string(), "row2".to_string()],
1432 });
1433 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1434
1435 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));
1437 assert_eq!(form.mode(), AppMode::Sel, "v should enter select");
1438 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
1439 assert_eq!(form.mode(), AppMode::Nor, "esc from v-select");
1440
1441 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1444 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
1445 assert!(
1446 matches!(form.selection_state(), SelectionState::Linewise { .. }),
1447 "esc in Nor must keep the x-selection, got {:?}",
1448 form.selection_state()
1449 );
1450 }
1451
1452 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1453 #[test]
1454 fn vim_dd_clears_current_field_without_shifting_following_fields() {
1455 let mut form = TextFormState::new(TestProvider {
1456 fields: ["row1".to_string(), "row2".to_string()],
1457 });
1458 form.set_keybindings(crate::keybindings::CanvasKeyBindings::vim_defaults());
1459
1460 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1461 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1462
1463 assert_eq!(form.fixed_field_count(), 2);
1464 assert_eq!(form.data_provider().field_count(), 2);
1465 assert_eq!(form.data_provider().field_value(0), "");
1466 assert_eq!(form.data_provider().field_value(1), "row2");
1467 assert_eq!(form.current_field(), 0);
1468 }
1469
1470 #[cfg(feature = "keybindings")]
1471 #[test]
1472 fn cut_line_clears_fixed_slot_without_shifting_later_fields() {
1473 use crate::editor::product::KeybindingProduct;
1474 use crate::keybindings::CanvasKeyAction;
1475
1476 let mut form = TextFormState::new(VecProvider {
1477 fields: vec!["row1".to_string(), "row2".to_string(), "row3".to_string()],
1478 });
1479 let _ = form.transition_to_field(1);
1480
1481 let _ = form.dispatch_product_key_action(&CanvasKeyAction::CutLine, 1);
1482
1483 assert_eq!(form.fixed_field_count(), 3);
1484 assert_eq!(
1485 form.data_provider().capture_content(),
1486 vec!["row1", "", "row3"]
1487 );
1488 assert_eq!(form.current_field(), 1);
1489 }
1490
1491 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1492 #[test]
1493 fn vim_counted_dd_clears_fixed_slots_without_shifting_later_fields() {
1494 let mut form = TextFormState::new(VecProvider {
1495 fields: vec!["row1".to_string(), "row2".to_string(), "row3".to_string()],
1496 });
1497 form.set_keybindings(crate::keybindings::CanvasKeyBindings::vim_defaults());
1498
1499 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('2'), KeyModifiers::NONE));
1500 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1501 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1502
1503 assert_eq!(form.fixed_field_count(), 3);
1504 assert_eq!(form.data_provider().capture_content(), vec!["", "", "row3"]);
1505 assert_eq!(form.current_field(), 0);
1506 }
1507
1508 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1509 #[test]
1510 fn vim_visual_yank_copies_selection_and_exits_to_normal() {
1511 use super::YankRegister;
1512 use crate::canvas::modes::AppMode;
1513
1514 let mut form = TextFormState::new(VecProvider {
1515 fields: vec!["hello world".to_string(), "second".to_string()],
1516 });
1517 form.set_keybindings(crate::keybindings::CanvasKeyBindings::vim_defaults());
1518
1519 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));
1521 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::NONE));
1522 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::NONE));
1523 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
1524
1525 assert_eq!(
1527 form.core.behavior_state.yank().register().cloned(),
1528 Some(YankRegister::Text(vec!["hel".to_string()]))
1529 );
1530 assert_eq!(form.mode(), AppMode::Nor);
1532 }
1533
1534 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1535 #[test]
1536 fn vim_join_line_below_does_not_merge_or_remove_fixed_fields() {
1537 let mut form = TextFormState::new(TestProvider {
1538 fields: ["row1".to_string(), "row2".to_string()],
1539 });
1540 form.set_keybindings(crate::keybindings::CanvasKeyBindings::vim_defaults());
1541
1542 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('J'), KeyModifiers::SHIFT));
1543
1544 assert_eq!(form.fixed_field_count(), 2);
1545 assert_eq!(form.data_provider().field_value(0), "row1");
1546 assert_eq!(form.data_provider().field_value(1), "row2");
1547 assert_eq!(form.current_field(), 0);
1548 }
1549
1550 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1551 #[test]
1552 fn helix_x_then_d_clears_selected_fixed_slot_without_shifting_following_fields() {
1553 let mut form = TextFormState::new(TestProvider {
1554 fields: ["row1".to_string(), "row2".to_string()],
1555 });
1556 form.use_keybinding_preset(crate::keybindings::BuiltinCanvasKeybindingPreset::Helix);
1557
1558 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1559 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1560
1561 assert_eq!(form.fixed_field_count(), 2);
1562 assert_eq!(form.data_provider().field_count(), 2);
1563 assert_eq!(form.data_provider().field_value(0), "");
1564 assert_eq!(form.data_provider().field_value(1), "row2");
1565 assert_eq!(form.current_field(), 0);
1566 }
1567
1568 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1569 #[test]
1570 fn helix_extended_line_delete_clears_fixed_slots_without_shifting_later_fields() {
1571 let mut form = TextFormState::new(VecProvider {
1572 fields: vec!["row1".to_string(), "row2".to_string(), "row3".to_string()],
1573 });
1574 form.use_keybinding_preset(crate::keybindings::BuiltinCanvasKeybindingPreset::Helix);
1575
1576 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1577 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1578 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1579
1580 assert_eq!(form.fixed_field_count(), 3);
1581 assert_eq!(form.data_provider().capture_content(), vec!["", "", "row3"]);
1582 assert_eq!(form.current_field(), 0);
1583 }
1584
1585 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1586 #[test]
1587 fn helix_word_motion_sets_characterwise_selection_for_highlight() {
1588 use crate::canvas::state::SelectionState;
1589 use crate::keybindings::{BuiltinCanvasKeybindingPreset, KeyEventOutcome};
1590
1591 let mut form = TextFormState::new(TestProvider {
1592 fields: ["one two three".to_string(), "row2".to_string()],
1593 });
1594 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1595
1596 let outcome = form.handle_key_event(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
1597
1598 assert!(matches!(outcome, KeyEventOutcome::Consumed(None)));
1599 assert_eq!(form.cursor_position(), 3);
1600 assert!(matches!(
1601 form.selection_state(),
1602 SelectionState::Characterwise { anchor: (0, 0) }
1603 ));
1604 assert_eq!(form.data_provider().field_value(1), "row2");
1605 }
1606
1607 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1608 #[test]
1609 fn helix_word_then_delete_removes_selection_without_clearing_field() {
1610 use crate::keybindings::{BuiltinCanvasKeybindingPreset, KeyEventOutcome};
1611
1612 let mut form = TextFormState::new(TestProvider {
1613 fields: ["one two three".to_string(), "row2".to_string()],
1614 });
1615 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1616
1617 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
1618 let outcome = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1619
1620 assert!(matches!(outcome, KeyEventOutcome::Consumed(None)));
1621 assert_eq!(form.fixed_field_count(), 2);
1622 assert_eq!(form.data_provider().field_value(0), "two three");
1623 assert_eq!(form.data_provider().field_value(1), "row2");
1624 assert_eq!(form.current_field(), 0);
1625 assert_eq!(form.cursor_position(), 0);
1626 }
1627
1628 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1629 #[test]
1630 fn helix_cross_field_character_delete_does_not_merge_fixed_fields() {
1631 use crate::canvas::state::SelectionState;
1632 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1633
1634 let mut form = TextFormState::new(TestProvider {
1635 fields: ["abc".to_string(), "def".to_string()],
1636 });
1637 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1638 let _ = form.transition_to_field(1);
1639 form.set_cursor_position(1);
1640 form.core.ui_state.selection = SelectionState::Characterwise { anchor: (0, 1) };
1641
1642 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1643
1644 assert_eq!(form.fixed_field_count(), 2);
1645 assert_eq!(form.data_provider().field_value(0), "a");
1646 assert_eq!(form.data_provider().field_value(1), "f");
1647 assert_eq!(form.current_field(), 0);
1648 assert_eq!(form.cursor_position(), 0);
1649 }
1650
1651 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1652 #[test]
1653 fn helix_characterwise_yank_then_paste_inserts_inside_field() {
1654 use crate::keybindings::{BuiltinCanvasKeybindingPreset, KeyEventOutcome};
1655
1656 let mut form = TextFormState::new(TestProvider {
1657 fields: ["one two".to_string(), "row2".to_string()],
1658 });
1659 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1660
1661 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::NONE));
1662 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
1663 let outcome = form.handle_key_event(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
1664
1665 assert!(matches!(outcome, KeyEventOutcome::Consumed(None)));
1666 assert_eq!(form.fixed_field_count(), 2);
1667 assert_eq!(form.data_provider().field_value(0), "one one two");
1668 assert_eq!(form.data_provider().field_value(1), "row2");
1669 }
1670
1671 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1672 #[test]
1673 fn helix_linewise_yank_then_paste_writes_fixed_slots_without_shifting() {
1674 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1675
1676 let mut form = TextFormState::new(VecProvider {
1677 fields: vec!["row1".to_string(), "row2".to_string(), "row3".to_string()],
1678 });
1679 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1680
1681 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1682 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
1683 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
1684
1685 assert_eq!(form.fixed_field_count(), 3);
1686 assert_eq!(
1687 form.data_provider().capture_content(),
1688 vec!["row1", "row1", "row3"]
1689 );
1690 }
1691
1692 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1693 #[test]
1694 fn helix_extend_line_stays_in_normal_mode_like_textarea() {
1695 use crate::canvas::modes::AppMode;
1696 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1697
1698 let mut form = TextFormState::new(TestProvider {
1699 fields: ["row1".to_string(), "row2".to_string()],
1700 });
1701 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1702
1703 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1706 assert_eq!(form.mode(), AppMode::Nor);
1707 }
1708
1709 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1710 #[test]
1711 fn helix_delete_after_extend_returns_to_normal_mode() {
1712 use crate::canvas::modes::AppMode;
1713 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1714
1715 let mut form = TextFormState::new(TestProvider {
1716 fields: ["row1".to_string(), "row2".to_string()],
1717 });
1718 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1719
1720 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE));
1722 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::NONE));
1723 assert_eq!(form.mode(), AppMode::Nor);
1724 assert_eq!(form.data_provider().field_value(0), "");
1725 assert_eq!(form.data_provider().field_value(1), "row2");
1726 }
1727
1728 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1729 #[test]
1730 fn helix_highlight_yank_returns_to_normal_mode() {
1731 use crate::canvas::modes::AppMode;
1732 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1733
1734 let mut form = TextFormState::new(TestProvider {
1735 fields: ["row1".to_string(), "row2".to_string()],
1736 });
1737 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1738
1739 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE));
1742 assert_eq!(form.mode(), AppMode::Sel);
1743
1744 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));
1745 assert_eq!(form.mode(), AppMode::Nor);
1746 }
1747
1748 #[cfg(all(feature = "keybindings", feature = "crossterm"))]
1749 #[test]
1750 fn helix_append_on_last_character_inserts_after_it() {
1751 use crate::canvas::modes::AppMode;
1752 use crate::keybindings::BuiltinCanvasKeybindingPreset;
1753
1754 let mut form = TextFormState::new(TestProvider {
1755 fields: ["abc".to_string(), "row2".to_string()],
1756 });
1757 form.use_keybinding_preset(BuiltinCanvasKeybindingPreset::Helix);
1758
1759 form.move_line_end();
1762 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
1763 assert_eq!(form.mode(), AppMode::Ins);
1764 assert_eq!(form.cursor_position(), 3);
1765
1766 let _ = form.handle_key_event(KeyEvent::new(KeyCode::Char('X'), KeyModifiers::NONE));
1767 assert_eq!(form.current_text(), "abcX");
1768 }
1769
1770 #[cfg(feature = "keybindings")]
1771 #[test]
1772 fn multiline_text_register_paste_preserves_suffix_in_fixed_slot() {
1773 use crate::editor::product::KeybindingProduct;
1774 use crate::keybindings::CanvasKeyAction;
1775
1776 let mut form = TextFormState::new(VecProvider {
1777 fields: vec!["aaZZ".to_string(), "row2".to_string(), "row3".to_string()],
1778 });
1779 form.set_cursor_position(2);
1780 form.core
1781 .behavior_state
1782 .yank_mut()
1783 .set_text_register(vec!["X".to_string(), "Y".to_string()]);
1784
1785 let _ = form.dispatch_product_key_action(&CanvasKeyAction::PasteBefore, 1);
1786
1787 assert_eq!(form.fixed_field_count(), 3);
1788 assert_eq!(
1789 form.data_provider().capture_content(),
1790 vec!["aaX", "YZZ", "row3"]
1791 );
1792 }
1793
1794 #[test]
1795 fn guard_resyncs_field_count_changes_before_textform_mutation() {
1796 let mut form = TextFormState::new(VecProvider {
1797 fields: vec!["one".to_string(), "two".to_string()],
1798 });
1799 form.core.data_provider_mut().fields.pop();
1800
1801 let _ = form.paste("x");
1802
1803 assert_eq!(form.fixed_field_count(), 1);
1804 assert_eq!(form.data_provider().capture_content(), vec!["xone"]);
1805 }
1806
1807 #[test]
1808 fn deref_mut_resyncs_field_count_changes_before_core_method() {
1809 let mut form = TextFormState::new(StrictVecProvider {
1810 fields: vec!["one".to_string(), "two".to_string()],
1811 });
1812 let _ = form.transition_to_field(1);
1813 form.core.data_provider_mut().fields.pop();
1814
1815 let moved = form.move_down();
1816
1817 assert!(!moved);
1818 assert_eq!(form.fixed_field_count(), 1);
1819 assert_eq!(form.current_field(), 0);
1820 }
1821
1822 #[cfg(feature = "validation")]
1823 #[test]
1824 fn transition_clamps_stale_previous_field_before_validation() {
1825 let mut form = TextFormState::new(StrictVecProvider {
1826 fields: vec!["one".to_string(), "two".to_string()],
1827 });
1828 let _ = form.transition_to_field(1);
1829 form.core.data_provider_mut().fields.pop();
1830
1831 assert!(form.core.transition_to_field(0).is_ok());
1832 assert_eq!(form.core.current_field(), 0);
1833 }
1834}