1use hjkl_vim_types::{LastHorizontalMotion, Mode, Motion};
6
7use hjkl_engine::input::{Input, Key};
8
9use super::*;
10use crate::vim_state::{vim, vim_mut};
11use hjkl_engine::buf_helpers::{
12 buf_cursor_pos, buf_line, buf_line_chars, buf_row_count, buf_set_cursor_rc,
13};
14use hjkl_engine::{Editor, Move};
15
16pub fn parse_motion(input: &Input) -> Option<Motion> {
20 if input.ctrl {
21 if input.key == Key::Char('h') {
25 return Some(Motion::BackspaceBack);
26 }
27 return None;
28 }
29 match input.key {
30 Key::Char('h') | Key::Left => Some(Motion::Left),
31 Key::Char('l') | Key::Right => Some(Motion::Right),
32 Key::Char(' ') => Some(Motion::SpaceFwd),
36 Key::Backspace => Some(Motion::BackspaceBack),
37 Key::Char('j') | Key::Down => Some(Motion::Down),
38 Key::Char('+') | Key::Enter => Some(Motion::FirstNonBlankNextLine),
40 Key::Char('-') => Some(Motion::FirstNonBlankPrevLine),
42 Key::Char('_') => Some(Motion::FirstNonBlankLine),
44 Key::Char('k') | Key::Up => Some(Motion::Up),
45 Key::Char('w') => Some(Motion::WordFwd),
46 Key::Char('W') => Some(Motion::BigWordFwd),
47 Key::Char('b') => Some(Motion::WordBack),
48 Key::Char('B') => Some(Motion::BigWordBack),
49 Key::Char('e') => Some(Motion::WordEnd),
50 Key::Char('E') => Some(Motion::BigWordEnd),
51 Key::Char('0') | Key::Home => Some(Motion::LineStart),
52 Key::Char('^') => Some(Motion::FirstNonBlank),
53 Key::Char('$') | Key::End => Some(Motion::LineEnd),
54 Key::Char('G') => Some(Motion::FileBottom),
55 Key::Char('%') => Some(Motion::MatchBracket),
56 Key::Char(';') => Some(Motion::FindRepeat { reverse: false }),
57 Key::Char(',') => Some(Motion::FindRepeat { reverse: true }),
58 Key::Char('*') => Some(Motion::WordAtCursor {
59 forward: true,
60 whole_word: true,
61 }),
62 Key::Char('#') => Some(Motion::WordAtCursor {
63 forward: false,
64 whole_word: true,
65 }),
66 Key::Char('n') => Some(Motion::SearchNext { reverse: false }),
67 Key::Char('N') => Some(Motion::SearchNext { reverse: true }),
68 Key::Char('H') => Some(Motion::ViewportTop),
69 Key::Char('M') => Some(Motion::ViewportMiddle),
70 Key::Char('L') => Some(Motion::ViewportBottom),
71 Key::Char('{') => Some(Motion::ParagraphPrev),
72 Key::Char('}') => Some(Motion::ParagraphNext),
73 Key::Char('(') => Some(Motion::SentencePrev),
74 Key::Char(')') => Some(Motion::SentenceNext),
75 Key::Char('|') => Some(Motion::GotoColumn),
76 _ => None,
77 }
78}
79pub fn execute_motion<H: hjkl_engine::types::Host>(
80 ed: &mut Editor<hjkl_buffer::View, H>,
81 motion: Motion,
82 count: usize,
83) {
84 let count = count.clamp(1, MAX_COUNT);
85 if let Motion::FindRepeat { reverse } = motion
88 && vim(ed).last_horizontal_motion == LastHorizontalMotion::Sneak
89 {
90 if let Some(((c1, c2), fwd)) = vim(ed).last_sneak {
91 let effective_fwd = if reverse { !fwd } else { fwd };
92 apply_sneak(ed, c1, c2, effective_fwd, count);
93 }
94 return;
95 }
96 let motion = match motion {
100 Motion::FindRepeat { reverse } => match vim(ed).last_find {
101 Some((ch, forward, till)) => {
102 vim_mut(ed).find_repeat_skip = true;
103 Motion::Find {
104 ch,
105 forward: if reverse { !forward } else { forward },
106 till,
107 }
108 }
109 None => return,
110 },
111 other => other,
112 };
113 let pre_pos = ed.cursor();
114 apply_motion_cursor(ed, &motion, count);
115 let post_pos = ed.cursor();
116 if is_big_jump(&motion) && pre_pos != post_pos {
117 ed.push_jump(pre_pos);
118 }
119 match motion_class(&motion) {
125 MotionClass::Vertical => {
126 ed.move_cursor(Move::Vertical { row: ed.cursor().0 });
130 }
131 MotionClass::Jump => {
132 let pos = ed.cursor();
135 ed.move_cursor(Move::Jump {
136 row: pos.0,
137 col: pos.1,
138 });
139 }
140 MotionClass::Horizontal => {
141 ed.move_cursor(Move::Horizontal { col: ed.cursor().1 });
143 }
144 }
145 ed.sync_buffer_from_textarea();
150}
151pub fn execute_motion_with_block_vcol<H: hjkl_engine::types::Host>(
160 ed: &mut Editor<hjkl_buffer::View, H>,
161 motion: Motion,
162 count: usize,
163) {
164 let motion_copy = motion.clone();
165 execute_motion(ed, motion, count);
166 if vim(ed).mode == Mode::VisualBlock {
167 update_block_vcol(ed, &motion_copy);
168 }
169}
170pub fn apply_motion_kind<H: hjkl_engine::types::Host>(
198 ed: &mut Editor<hjkl_buffer::View, H>,
199 kind: hjkl_engine::MotionKind,
200 count: usize,
201) {
202 let count = count.max(1);
203 match kind {
204 hjkl_engine::MotionKind::CharLeft => {
205 execute_motion_with_block_vcol(ed, Motion::Left, count);
206 }
207 hjkl_engine::MotionKind::CharRight => {
208 execute_motion_with_block_vcol(ed, Motion::Right, count);
209 }
210 hjkl_engine::MotionKind::LineDown => {
211 execute_motion_with_block_vcol(ed, Motion::Down, count);
212 }
213 hjkl_engine::MotionKind::LineUp => {
214 execute_motion_with_block_vcol(ed, Motion::Up, count);
215 }
216 hjkl_engine::MotionKind::FirstNonBlankDown => {
217 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
224 let mut sticky = ed.sticky_col();
225 let tabstop = ed.settings().tabstop;
226 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
227 ed.set_sticky_col(sticky);
228 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
229 let pos = ed.cursor();
230 ed.move_cursor(Move::Jump {
231 row: pos.0,
232 col: pos.1,
233 });
234 ed.sync_buffer_from_textarea();
235 }
236 hjkl_engine::MotionKind::FirstNonBlankUp => {
237 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
240 let mut sticky = ed.sticky_col();
241 let tabstop = ed.settings().tabstop;
242 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
243 ed.set_sticky_col(sticky);
244 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
245 let pos = ed.cursor();
246 ed.move_cursor(Move::Jump {
247 row: pos.0,
248 col: pos.1,
249 });
250 ed.sync_buffer_from_textarea();
251 }
252 hjkl_engine::MotionKind::WordForward => {
253 execute_motion_with_block_vcol(ed, Motion::WordFwd, count);
254 }
255 hjkl_engine::MotionKind::BigWordForward => {
256 execute_motion_with_block_vcol(ed, Motion::BigWordFwd, count);
257 }
258 hjkl_engine::MotionKind::WordBackward => {
259 execute_motion_with_block_vcol(ed, Motion::WordBack, count);
260 }
261 hjkl_engine::MotionKind::BigWordBackward => {
262 execute_motion_with_block_vcol(ed, Motion::BigWordBack, count);
263 }
264 hjkl_engine::MotionKind::WordEnd => {
265 execute_motion_with_block_vcol(ed, Motion::WordEnd, count);
266 }
267 hjkl_engine::MotionKind::BigWordEnd => {
268 execute_motion_with_block_vcol(ed, Motion::BigWordEnd, count);
269 }
270 hjkl_engine::MotionKind::LineStart => {
271 execute_motion_with_block_vcol(ed, Motion::LineStart, 1);
274 }
275 hjkl_engine::MotionKind::FirstNonBlank => {
276 execute_motion_with_block_vcol(ed, Motion::FirstNonBlank, 1);
279 }
280 hjkl_engine::MotionKind::GotoLine => {
281 execute_motion_with_block_vcol(ed, Motion::FileBottom, count);
290 }
291 hjkl_engine::MotionKind::LineEnd => {
292 execute_motion_with_block_vcol(ed, Motion::LineEnd, count);
295 }
296 hjkl_engine::MotionKind::FindRepeat => {
297 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: false }, count);
301 }
302 hjkl_engine::MotionKind::FindRepeatReverse => {
303 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: true }, count);
307 }
308 hjkl_engine::MotionKind::BracketMatch => {
309 execute_motion_with_block_vcol(ed, Motion::MatchBracket, count);
314 }
315 hjkl_engine::MotionKind::ViewportTop => {
316 execute_motion_with_block_vcol(ed, Motion::ViewportTop, count);
319 }
320 hjkl_engine::MotionKind::ViewportMiddle => {
321 execute_motion_with_block_vcol(ed, Motion::ViewportMiddle, count);
324 }
325 hjkl_engine::MotionKind::ViewportBottom => {
326 execute_motion_with_block_vcol(ed, Motion::ViewportBottom, count);
329 }
330 hjkl_engine::MotionKind::HalfPageDown => {
331 {
335 let d = ed.viewport_half_rows(count) as isize;
336 ed.scroll_cursor_rows(d);
337 }
338 }
339 hjkl_engine::MotionKind::HalfPageUp => {
340 {
343 let d = -(ed.viewport_half_rows(count) as isize);
344 ed.scroll_cursor_rows(d);
345 }
346 }
347 hjkl_engine::MotionKind::FullPageDown => {
348 {
351 let d = ed.viewport_full_rows(count) as isize;
352 ed.scroll_cursor_rows(d);
353 }
354 }
355 hjkl_engine::MotionKind::FullPageUp => {
356 {
359 let d = -(ed.viewport_full_rows(count) as isize);
360 ed.scroll_cursor_rows(d);
361 }
362 }
363 hjkl_engine::MotionKind::FirstNonBlankLine => {
364 execute_motion_with_block_vcol(ed, Motion::FirstNonBlankLine, count);
365 }
366 hjkl_engine::MotionKind::SectionBackward => {
367 execute_motion_with_block_vcol(ed, Motion::SectionBackward, count);
368 }
369 hjkl_engine::MotionKind::SectionForward => {
370 execute_motion_with_block_vcol(ed, Motion::SectionForward, count);
371 }
372 hjkl_engine::MotionKind::SectionEndBackward => {
373 execute_motion_with_block_vcol(ed, Motion::SectionEndBackward, count);
374 }
375 hjkl_engine::MotionKind::SectionEndForward => {
376 execute_motion_with_block_vcol(ed, Motion::SectionEndForward, count);
377 }
378 _ => {}
383 }
384}
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
400enum MotionClass {
401 Vertical,
404 Jump,
407 Horizontal,
410}
411
412fn motion_class(motion: &Motion) -> MotionClass {
413 use MotionClass::*;
414 match motion {
415 Motion::Up | Motion::Down | Motion::ScreenUp | Motion::ScreenDown => Vertical,
419 Motion::FileTop
424 | Motion::FileBottom
425 | Motion::MatchBracket
426 | Motion::UnmatchedBracket { .. }
427 | Motion::WordAtCursor { .. }
428 | Motion::SearchNext { .. }
429 | Motion::ViewportTop
430 | Motion::ViewportMiddle
431 | Motion::ViewportBottom
432 | Motion::ParagraphPrev
433 | Motion::ParagraphNext
434 | Motion::SentencePrev
435 | Motion::SentenceNext
436 | Motion::SectionBackward
437 | Motion::SectionForward
438 | Motion::SectionEndBackward
439 | Motion::SectionEndForward
440 | Motion::FirstNonBlankNextLine
441 | Motion::FirstNonBlankPrevLine
442 | Motion::FirstNonBlankLine => Jump,
443 Motion::Left
447 | Motion::Right
448 | Motion::SpaceFwd
449 | Motion::BackspaceBack
450 | Motion::WordFwd
451 | Motion::BigWordFwd
452 | Motion::WordBack
453 | Motion::BigWordBack
454 | Motion::WordEnd
455 | Motion::BigWordEnd
456 | Motion::WordEndBack
457 | Motion::BigWordEndBack
458 | Motion::LineStart
459 | Motion::FirstNonBlank
460 | Motion::LineEnd
461 | Motion::Find { .. }
462 | Motion::FindRepeat { .. }
463 | Motion::LastNonBlank
464 | Motion::LineMiddle
465 | Motion::ScreenLineMiddle
466 | Motion::GotoColumn => Horizontal,
467 }
469}
470pub fn apply_motion_cursor<H: hjkl_engine::types::Host>(
471 ed: &mut Editor<hjkl_buffer::View, H>,
472 motion: &Motion,
473 count: usize,
474) {
475 apply_motion_cursor_ctx(ed, motion, count, false)
476}
477pub fn apply_motion_cursor_ctx<H: hjkl_engine::types::Host>(
478 ed: &mut Editor<hjkl_buffer::View, H>,
479 motion: &Motion,
480 count: usize,
481 as_operator: bool,
482) {
483 let count = count
492 .min(MAX_COUNT)
493 .min(ed.buffer().rope().len_chars().saturating_add(1));
494 match motion {
495 Motion::Left => {
496 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
498 }
499 Motion::Right => {
500 if as_operator {
504 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
505 } else {
506 hjkl_engine::motions::move_right_in_line(ed.buffer_mut(), count);
507 }
508 }
509 Motion::SpaceFwd => {
510 if as_operator {
513 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
514 } else {
515 hjkl_engine::motions::move_space_fwd(ed.buffer_mut(), count);
516 }
517 }
518 Motion::BackspaceBack => {
519 if as_operator {
522 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
523 } else {
524 hjkl_engine::motions::move_backspace_back(ed.buffer_mut(), count);
525 }
526 }
527 Motion::Up => {
528 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
532 let mut sticky = ed.sticky_col();
533 let tabstop = ed.settings().tabstop;
534 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
535 ed.set_sticky_col(sticky);
536 }
537 Motion::Down => {
538 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
539 let mut sticky = ed.sticky_col();
540 let tabstop = ed.settings().tabstop;
541 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
542 ed.set_sticky_col(sticky);
543 }
544 Motion::ScreenUp => {
545 let v = *ed.host().viewport();
546 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
547 let mut sticky = ed.sticky_col();
548 let tabstop = ed.settings().tabstop;
549 hjkl_engine::motions::move_screen_up(
550 ed.buffer_mut(),
551 &folds,
552 &v,
553 count,
554 &mut sticky,
555 tabstop,
556 );
557 ed.set_sticky_col(sticky);
558 }
559 Motion::ScreenDown => {
560 let v = *ed.host().viewport();
561 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
562 let mut sticky = ed.sticky_col();
563 let tabstop = ed.settings().tabstop;
564 hjkl_engine::motions::move_screen_down(
565 ed.buffer_mut(),
566 &folds,
567 &v,
568 count,
569 &mut sticky,
570 tabstop,
571 );
572 ed.set_sticky_col(sticky);
573 }
574 Motion::WordFwd => {
575 let iskeyword = ed.settings().iskeyword.clone();
576 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), false, count, &iskeyword);
577 if !as_operator {
580 let (row, col) = ed.cursor();
581 let line_len = buf_line_chars(ed.buffer(), row);
582 if col > line_len.saturating_sub(1) {
583 ed.jump_cursor(row, line_len.saturating_sub(1));
584 }
585 }
586 }
587 Motion::WordBack => {
588 let iskeyword = ed.settings().iskeyword.clone();
589 hjkl_engine::motions::move_word_back(ed.buffer_mut(), false, count, &iskeyword);
590 }
591 Motion::WordEnd => {
592 let iskeyword = ed.settings().iskeyword.clone();
593 hjkl_engine::motions::move_word_end(ed.buffer_mut(), false, count, &iskeyword);
594 }
595 Motion::BigWordFwd => {
596 let iskeyword = ed.settings().iskeyword.clone();
597 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), true, count, &iskeyword);
598 if !as_operator {
600 let (row, col) = ed.cursor();
601 let line_len = buf_line_chars(ed.buffer(), row);
602 if col > line_len.saturating_sub(1) {
603 ed.jump_cursor(row, line_len.saturating_sub(1));
604 }
605 }
606 }
607 Motion::BigWordBack => {
608 let iskeyword = ed.settings().iskeyword.clone();
609 hjkl_engine::motions::move_word_back(ed.buffer_mut(), true, count, &iskeyword);
610 }
611 Motion::BigWordEnd => {
612 let iskeyword = ed.settings().iskeyword.clone();
613 hjkl_engine::motions::move_word_end(ed.buffer_mut(), true, count, &iskeyword);
614 }
615 Motion::WordEndBack => {
616 let iskeyword = ed.settings().iskeyword.clone();
617 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), false, count, &iskeyword);
618 }
619 Motion::BigWordEndBack => {
620 let iskeyword = ed.settings().iskeyword.clone();
621 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), true, count, &iskeyword);
622 }
623 Motion::LineStart => {
624 hjkl_engine::motions::move_line_start(ed.buffer_mut());
625 }
626 Motion::FirstNonBlank => {
627 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
628 }
629 Motion::LineEnd => {
630 if count > 1 {
633 if ed.cursor().0 >= last_content_row(ed) {
641 return;
642 }
643 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
644 let mut sticky = ed.sticky_col();
645 let tabstop = ed.settings().tabstop;
646 hjkl_engine::motions::move_down(
647 ed.buffer_mut(),
648 &folds,
649 count - 1,
650 &mut sticky,
651 tabstop,
652 );
653 ed.set_sticky_col(sticky);
654 }
655 hjkl_engine::motions::move_line_end(ed.buffer_mut());
656 }
657 Motion::FileTop => {
658 if count > 1 {
661 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
662 } else {
663 hjkl_engine::motions::move_top(ed.buffer_mut());
664 }
665 }
666 Motion::FileBottom => {
667 if count > 1 {
670 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
671 } else {
672 hjkl_engine::motions::move_bottom(ed.buffer_mut(), 0);
673 }
674 }
675 Motion::Find { ch, forward, till } => {
676 let repeat = std::mem::take(&mut vim_mut(ed).find_repeat_skip);
680 for i in 0..count {
681 let skip_adjacent = repeat || i > 0;
682 if !find_char_on_line(ed, *ch, *forward, *till, skip_adjacent) {
683 break;
684 }
685 }
686 }
687 Motion::FindRepeat { .. } => {} Motion::MatchBracket => {
689 let _ = matching_bracket(ed);
690 }
691 Motion::UnmatchedBracket { forward, open } => {
692 goto_unmatched_bracket(ed, *forward, *open, count);
693 }
694 Motion::WordAtCursor {
695 forward,
696 whole_word,
697 } => {
698 word_at_cursor_search(ed, *forward, *whole_word, count);
699 }
700 Motion::SearchNext { reverse } => {
701 if let Some(pattern) = ed.last_search_pattern() {
705 ed.push_search_pattern(&pattern);
706 }
707 if ed.search_state().pattern.is_none() {
708 return;
709 }
710 let forward = ed.last_search_forward() != *reverse;
714 for _ in 0..count.max(1) {
715 if forward {
716 ed.search_advance_forward(true);
717 } else {
718 ed.search_advance_backward(true);
719 }
720 }
721 }
722 Motion::ViewportTop => {
723 let v = *ed.host().viewport();
724 hjkl_engine::motions::move_viewport_top(ed.buffer_mut(), &v, count.saturating_sub(1));
725 }
726 Motion::ViewportMiddle => {
727 let v = *ed.host().viewport();
728 hjkl_engine::motions::move_viewport_middle(ed.buffer_mut(), &v);
729 }
730 Motion::ViewportBottom => {
731 let v = *ed.host().viewport();
732 hjkl_engine::motions::move_viewport_bottom(
733 ed.buffer_mut(),
734 &v,
735 count.saturating_sub(1),
736 );
737 }
738 Motion::LastNonBlank => {
739 hjkl_engine::motions::move_last_non_blank(ed.buffer_mut());
740 }
741 Motion::LineMiddle => {
742 let row = ed.cursor().0;
743 let line_chars = buf_line_chars(ed.buffer(), row);
744 let target = line_chars / 2;
747 ed.jump_cursor(row, target);
748 }
749 Motion::ScreenLineMiddle => {
750 let row = ed.cursor().0;
753 let width = ed.host().viewport().width as usize;
754 let last = buf_line_chars(ed.buffer(), row).saturating_sub(1);
755 let target = (width / 2).min(last);
756 ed.jump_cursor(row, target);
757 }
758 Motion::ParagraphPrev => {
759 hjkl_engine::motions::move_paragraph_prev(ed.buffer_mut(), count);
760 }
761 Motion::ParagraphNext => {
762 hjkl_engine::motions::move_paragraph_next(ed.buffer_mut(), count);
763 }
764 Motion::SentencePrev => {
770 let start = ed.cursor();
771 for _ in 0..count.max(1) {
772 match sentence_boundary(ed, false) {
773 Some((row, col)) => ed.jump_cursor(row, col),
774 None => {
775 ed.jump_cursor(start.0, start.1);
776 break;
777 }
778 }
779 }
780 }
781 Motion::SentenceNext => {
782 use crate::vim::text_object::SentenceStep;
783 let start = ed.cursor();
784 let total = count.max(1);
785 for i in 0..total {
786 match crate::vim::text_object::sentence_step_forward(ed) {
787 SentenceStep::Boundary((row, col)) => ed.jump_cursor(row, col),
788 SentenceStep::AtEnd => {}
791 SentenceStep::EndOfBuffer((row, col)) => {
792 if i + 1 == total {
793 ed.jump_cursor(row, col);
794 } else {
795 ed.jump_cursor(start.0, start.1);
796 break;
797 }
798 }
799 }
800 }
801 }
802 Motion::SectionBackward => {
803 hjkl_engine::motions::move_section_backward(ed.buffer_mut(), count);
804 }
805 Motion::SectionForward => {
806 hjkl_engine::motions::move_section_forward(ed.buffer_mut(), count);
807 }
808 Motion::SectionEndBackward => {
809 hjkl_engine::motions::move_section_end_backward(ed.buffer_mut(), count);
810 }
811 Motion::SectionEndForward => {
812 hjkl_engine::motions::move_section_end_forward(ed.buffer_mut(), count);
813 }
814 Motion::FirstNonBlankNextLine => {
815 hjkl_engine::motions::move_first_non_blank_next_line(ed.buffer_mut(), count);
816 }
817 Motion::FirstNonBlankPrevLine => {
818 hjkl_engine::motions::move_first_non_blank_prev_line(ed.buffer_mut(), count);
819 }
820 Motion::FirstNonBlankLine => {
821 hjkl_engine::motions::move_first_non_blank_line(ed.buffer_mut(), count);
822 }
823 Motion::GotoColumn => {
824 hjkl_engine::motions::move_goto_column(ed.buffer_mut(), count);
825 }
826 }
827}
828pub fn move_first_non_whitespace<H: hjkl_engine::types::Host>(
829 ed: &mut Editor<hjkl_buffer::View, H>,
830) {
831 ed.sync_buffer_content_from_textarea();
837 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
838}
839pub fn find_char_on_line<H: hjkl_engine::types::Host>(
840 ed: &mut Editor<hjkl_buffer::View, H>,
841 ch: char,
842 forward: bool,
843 till: bool,
844 skip_adjacent: bool,
845) -> bool {
846 hjkl_engine::motions::find_char_on_line(ed.buffer_mut(), ch, forward, till, skip_adjacent)
847}
848pub fn matching_bracket<H: hjkl_engine::types::Host>(
849 ed: &mut Editor<hjkl_buffer::View, H>,
850) -> bool {
851 hjkl_engine::motions::match_bracket(ed.buffer_mut())
852}
853pub fn goto_unmatched_bracket<H: hjkl_engine::types::Host>(
857 ed: &mut Editor<hjkl_buffer::View, H>,
858 forward: bool,
859 open: char,
860 count: usize,
861) {
862 let close = match open {
863 '(' => ')',
864 '{' => '}',
865 _ => return,
866 };
867 let cursor = buf_cursor_pos(ed.buffer());
868 let rows = buf_row_count(ed.buffer());
869 let target = count.max(1);
870 let mut found = 0usize;
871 let mut depth = 0i32;
872
873 if forward {
874 let mut r = cursor.row;
875 let mut from_col = cursor.col + 1;
876 while r < rows {
877 let line: Vec<char> = buf_line(ed.buffer(), r)
878 .unwrap_or_default()
879 .chars()
880 .collect();
881 let mut ci = from_col;
882 while ci < line.len() {
883 let ch = line[ci];
884 if ch == open {
885 depth += 1;
886 } else if ch == close {
887 if depth == 0 {
888 found += 1;
889 if found == target {
890 buf_set_cursor_rc(ed.buffer_mut(), r, ci);
891 return;
892 }
893 } else {
894 depth -= 1;
895 }
896 }
897 ci += 1;
898 }
899 r += 1;
900 from_col = 0;
901 }
902 } else {
903 let mut r = cursor.row as isize;
904 let mut from_col = cursor.col as isize - 1;
907 while r >= 0 {
908 let line: Vec<char> = buf_line(ed.buffer(), r as usize)
909 .unwrap_or_default()
910 .chars()
911 .collect();
912 let mut ci = from_col.min(line.len() as isize - 1);
913 while ci >= 0 {
914 let ch = line[ci as usize];
915 if ch == close {
916 depth += 1;
917 } else if ch == open {
918 if depth == 0 {
919 found += 1;
920 if found == target {
921 buf_set_cursor_rc(ed.buffer_mut(), r as usize, ci as usize);
922 return;
923 }
924 } else {
925 depth -= 1;
926 }
927 }
928 ci -= 1;
929 }
930 r -= 1;
931 from_col = isize::MAX;
932 }
933 }
934}
935pub fn word_at_cursor_search<H: hjkl_engine::types::Host>(
936 ed: &mut Editor<hjkl_buffer::View, H>,
937 forward: bool,
938 whole_word: bool,
939 count: usize,
940) {
941 let (row, col) = ed.cursor();
942 let line: String = buf_line(ed.buffer(), row).unwrap_or_default();
943 let chars: Vec<char> = line.chars().collect();
944 if chars.is_empty() {
945 return;
946 }
947 let spec = ed.settings().iskeyword.clone();
949 let is_word = |c: char| is_keyword_char(c, &spec);
950 let mut start = col.min(chars.len().saturating_sub(1));
951 if is_word(chars[start]) {
952 while start > 0 && is_word(chars[start - 1]) {
953 start -= 1;
954 }
955 } else {
956 while start < chars.len() && !is_word(chars[start]) {
965 start += 1;
966 }
967 if start >= chars.len() {
968 return;
969 }
970 buf_set_cursor_rc(ed.buffer_mut(), row, start);
971 }
972 let mut end = start;
973 while end < chars.len() && is_word(chars[end]) {
974 end += 1;
975 }
976 if end <= start {
977 return;
978 }
979 let word: String = chars[start..end].iter().collect();
980 let escaped = regex_escape(&word);
981 let pattern = if whole_word {
982 format!(r"\b{escaped}\b")
983 } else {
984 escaped
985 };
986 ed.push_search_pattern(&pattern);
987 if ed.search_state().pattern.is_none() {
988 return;
989 }
990 ed.set_last_search_pattern_only(Some(pattern));
992 ed.set_last_search_forward_only(forward);
993 for _ in 0..count.max(1) {
994 if forward {
995 ed.search_advance_forward(true);
996 } else {
997 ed.search_advance_backward(true);
998 }
999 }
1000}
1001pub fn regex_escape(s: &str) -> String {
1002 let mut out = String::with_capacity(s.len());
1003 for c in s.chars() {
1004 if matches!(
1005 c,
1006 '.' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$' | '\\'
1007 ) {
1008 out.push('\\');
1009 }
1010 out.push(c);
1011 }
1012 out
1013}