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::Editor;
12use hjkl_engine::buf_helpers::{
13 buf_cursor_pos, buf_line, buf_line_chars, buf_row_count, buf_set_cursor_rc,
14};
15
16use hjkl_buffer::{char_col_to_visual_col, visual_col_to_char_col};
17
18pub fn parse_motion(input: &Input) -> Option<Motion> {
22 if input.ctrl {
23 if input.key == Key::Char('h') {
27 return Some(Motion::BackspaceBack);
28 }
29 return None;
30 }
31 match input.key {
32 Key::Char('h') | Key::Left => Some(Motion::Left),
33 Key::Char('l') | Key::Right => Some(Motion::Right),
34 Key::Char(' ') => Some(Motion::SpaceFwd),
38 Key::Backspace => Some(Motion::BackspaceBack),
39 Key::Char('j') | Key::Down => Some(Motion::Down),
40 Key::Char('+') | Key::Enter => Some(Motion::FirstNonBlankNextLine),
42 Key::Char('-') => Some(Motion::FirstNonBlankPrevLine),
44 Key::Char('_') => Some(Motion::FirstNonBlankLine),
46 Key::Char('k') | Key::Up => Some(Motion::Up),
47 Key::Char('w') => Some(Motion::WordFwd),
48 Key::Char('W') => Some(Motion::BigWordFwd),
49 Key::Char('b') => Some(Motion::WordBack),
50 Key::Char('B') => Some(Motion::BigWordBack),
51 Key::Char('e') => Some(Motion::WordEnd),
52 Key::Char('E') => Some(Motion::BigWordEnd),
53 Key::Char('0') | Key::Home => Some(Motion::LineStart),
54 Key::Char('^') => Some(Motion::FirstNonBlank),
55 Key::Char('$') | Key::End => Some(Motion::LineEnd),
56 Key::Char('G') => Some(Motion::FileBottom),
57 Key::Char('%') => Some(Motion::MatchBracket),
58 Key::Char(';') => Some(Motion::FindRepeat { reverse: false }),
59 Key::Char(',') => Some(Motion::FindRepeat { reverse: true }),
60 Key::Char('*') => Some(Motion::WordAtCursor {
61 forward: true,
62 whole_word: true,
63 }),
64 Key::Char('#') => Some(Motion::WordAtCursor {
65 forward: false,
66 whole_word: true,
67 }),
68 Key::Char('n') => Some(Motion::SearchNext { reverse: false }),
69 Key::Char('N') => Some(Motion::SearchNext { reverse: true }),
70 Key::Char('H') => Some(Motion::ViewportTop),
71 Key::Char('M') => Some(Motion::ViewportMiddle),
72 Key::Char('L') => Some(Motion::ViewportBottom),
73 Key::Char('{') => Some(Motion::ParagraphPrev),
74 Key::Char('}') => Some(Motion::ParagraphNext),
75 Key::Char('(') => Some(Motion::SentencePrev),
76 Key::Char(')') => Some(Motion::SentenceNext),
77 Key::Char('|') => Some(Motion::GotoColumn),
78 _ => None,
79 }
80}
81pub fn execute_motion<H: hjkl_engine::types::Host>(
82 ed: &mut Editor<hjkl_buffer::View, H>,
83 motion: Motion,
84 count: usize,
85) {
86 let count = count.clamp(1, MAX_COUNT);
87 if let Motion::FindRepeat { reverse } = motion
90 && vim(ed).last_horizontal_motion == LastHorizontalMotion::Sneak
91 {
92 if let Some(((c1, c2), fwd)) = vim(ed).last_sneak {
93 let effective_fwd = if reverse { !fwd } else { fwd };
94 apply_sneak(ed, c1, c2, effective_fwd, count);
95 }
96 return;
97 }
98 let motion = match motion {
102 Motion::FindRepeat { reverse } => match vim(ed).last_find {
103 Some((ch, forward, till)) => {
104 vim_mut(ed).find_repeat_skip = true;
105 Motion::Find {
106 ch,
107 forward: if reverse { !forward } else { forward },
108 till,
109 }
110 }
111 None => return,
112 },
113 other => other,
114 };
115 let pre_pos = ed.cursor();
116 let pre_col = pre_pos.1;
117 apply_motion_cursor(ed, &motion, count);
118 let post_pos = ed.cursor();
119 if is_big_jump(&motion) && pre_pos != post_pos {
120 ed.push_jump(pre_pos);
121 }
122 apply_sticky_col(ed, &motion, pre_col);
123 ed.sync_buffer_from_textarea();
128}
129pub fn execute_motion_with_block_vcol<H: hjkl_engine::types::Host>(
138 ed: &mut Editor<hjkl_buffer::View, H>,
139 motion: Motion,
140 count: usize,
141) {
142 let motion_copy = motion.clone();
143 execute_motion(ed, motion, count);
144 if vim(ed).mode == Mode::VisualBlock {
145 update_block_vcol(ed, &motion_copy);
146 }
147}
148pub fn apply_motion_kind<H: hjkl_engine::types::Host>(
176 ed: &mut Editor<hjkl_buffer::View, H>,
177 kind: hjkl_engine::MotionKind,
178 count: usize,
179) {
180 let count = count.max(1);
181 match kind {
182 hjkl_engine::MotionKind::CharLeft => {
183 execute_motion_with_block_vcol(ed, Motion::Left, count);
184 }
185 hjkl_engine::MotionKind::CharRight => {
186 execute_motion_with_block_vcol(ed, Motion::Right, count);
187 }
188 hjkl_engine::MotionKind::LineDown => {
189 execute_motion_with_block_vcol(ed, Motion::Down, count);
190 }
191 hjkl_engine::MotionKind::LineUp => {
192 execute_motion_with_block_vcol(ed, Motion::Up, count);
193 }
194 hjkl_engine::MotionKind::FirstNonBlankDown => {
195 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
200 let mut sticky = ed.sticky_col();
201 let tabstop = ed.settings().tabstop;
202 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
203 ed.set_sticky_col(sticky);
204 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
205 let pos = buf_cursor_pos(ed.buffer());
206 let line = buf_line(ed.buffer(), pos.row).unwrap_or_default();
207 ed.set_sticky_col(Some(char_col_to_visual_col(&line, pos.col, tabstop)));
208 ed.sync_buffer_from_textarea();
209 }
210 hjkl_engine::MotionKind::FirstNonBlankUp => {
211 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
214 let mut sticky = ed.sticky_col();
215 let tabstop = ed.settings().tabstop;
216 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
217 ed.set_sticky_col(sticky);
218 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
219 let pos = buf_cursor_pos(ed.buffer());
220 let line = buf_line(ed.buffer(), pos.row).unwrap_or_default();
221 ed.set_sticky_col(Some(char_col_to_visual_col(&line, pos.col, tabstop)));
222 ed.sync_buffer_from_textarea();
223 }
224 hjkl_engine::MotionKind::WordForward => {
225 execute_motion_with_block_vcol(ed, Motion::WordFwd, count);
226 }
227 hjkl_engine::MotionKind::BigWordForward => {
228 execute_motion_with_block_vcol(ed, Motion::BigWordFwd, count);
229 }
230 hjkl_engine::MotionKind::WordBackward => {
231 execute_motion_with_block_vcol(ed, Motion::WordBack, count);
232 }
233 hjkl_engine::MotionKind::BigWordBackward => {
234 execute_motion_with_block_vcol(ed, Motion::BigWordBack, count);
235 }
236 hjkl_engine::MotionKind::WordEnd => {
237 execute_motion_with_block_vcol(ed, Motion::WordEnd, count);
238 }
239 hjkl_engine::MotionKind::BigWordEnd => {
240 execute_motion_with_block_vcol(ed, Motion::BigWordEnd, count);
241 }
242 hjkl_engine::MotionKind::LineStart => {
243 execute_motion_with_block_vcol(ed, Motion::LineStart, 1);
246 }
247 hjkl_engine::MotionKind::FirstNonBlank => {
248 execute_motion_with_block_vcol(ed, Motion::FirstNonBlank, 1);
251 }
252 hjkl_engine::MotionKind::GotoLine => {
253 execute_motion_with_block_vcol(ed, Motion::FileBottom, count);
262 }
263 hjkl_engine::MotionKind::LineEnd => {
264 execute_motion_with_block_vcol(ed, Motion::LineEnd, count);
267 }
268 hjkl_engine::MotionKind::FindRepeat => {
269 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: false }, count);
273 }
274 hjkl_engine::MotionKind::FindRepeatReverse => {
275 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: true }, count);
279 }
280 hjkl_engine::MotionKind::BracketMatch => {
281 execute_motion_with_block_vcol(ed, Motion::MatchBracket, count);
286 }
287 hjkl_engine::MotionKind::ViewportTop => {
288 execute_motion_with_block_vcol(ed, Motion::ViewportTop, count);
291 }
292 hjkl_engine::MotionKind::ViewportMiddle => {
293 execute_motion_with_block_vcol(ed, Motion::ViewportMiddle, count);
296 }
297 hjkl_engine::MotionKind::ViewportBottom => {
298 execute_motion_with_block_vcol(ed, Motion::ViewportBottom, count);
301 }
302 hjkl_engine::MotionKind::HalfPageDown => {
303 {
307 let d = ed.viewport_half_rows(count) as isize;
308 ed.scroll_cursor_rows(d);
309 }
310 }
311 hjkl_engine::MotionKind::HalfPageUp => {
312 {
315 let d = -(ed.viewport_half_rows(count) as isize);
316 ed.scroll_cursor_rows(d);
317 }
318 }
319 hjkl_engine::MotionKind::FullPageDown => {
320 {
323 let d = ed.viewport_full_rows(count) as isize;
324 ed.scroll_cursor_rows(d);
325 }
326 }
327 hjkl_engine::MotionKind::FullPageUp => {
328 {
331 let d = -(ed.viewport_full_rows(count) as isize);
332 ed.scroll_cursor_rows(d);
333 }
334 }
335 hjkl_engine::MotionKind::FirstNonBlankLine => {
336 execute_motion_with_block_vcol(ed, Motion::FirstNonBlankLine, count);
337 }
338 hjkl_engine::MotionKind::SectionBackward => {
339 execute_motion_with_block_vcol(ed, Motion::SectionBackward, count);
340 }
341 hjkl_engine::MotionKind::SectionForward => {
342 execute_motion_with_block_vcol(ed, Motion::SectionForward, count);
343 }
344 hjkl_engine::MotionKind::SectionEndBackward => {
345 execute_motion_with_block_vcol(ed, Motion::SectionEndBackward, count);
346 }
347 hjkl_engine::MotionKind::SectionEndForward => {
348 execute_motion_with_block_vcol(ed, Motion::SectionEndForward, count);
349 }
350 _ => {}
355 }
356}
357pub fn apply_sticky_col<H: hjkl_engine::types::Host>(
362 ed: &mut Editor<hjkl_buffer::View, H>,
363 motion: &Motion,
364 pre_col: usize,
365) {
366 if is_vertical_motion(motion) {
367 let want = ed.sticky_col().unwrap_or_else(|| {
371 let (row, _) = ed.cursor();
372 let line = buf_line(ed.buffer(), row).unwrap_or_default();
373 char_col_to_visual_col(&line, pre_col, ed.settings().tabstop)
374 });
375 ed.set_sticky_col(Some(want));
378 let (row, _) = ed.cursor();
379 let line = buf_line(ed.buffer(), row).unwrap_or_default();
380 let char_col = visual_col_to_char_col(&line, want, ed.settings().tabstop);
384 let line_len = buf_line_chars(ed.buffer(), row);
385 let max_col = line_len.saturating_sub(1);
386 let target = char_col.min(max_col);
387 buf_set_cursor_rc(ed.buffer_mut(), row, target);
391 } else {
392 let (row, col) = ed.cursor();
395 let line = buf_line(ed.buffer(), row).unwrap_or_default();
396 ed.set_sticky_col(Some(char_col_to_visual_col(
397 &line,
398 col,
399 ed.settings().tabstop,
400 )));
401 }
402}
403pub fn is_vertical_motion(motion: &Motion) -> bool {
404 matches!(
408 motion,
409 Motion::Up | Motion::Down | Motion::ScreenUp | Motion::ScreenDown
410 )
411}
412pub fn apply_motion_cursor<H: hjkl_engine::types::Host>(
413 ed: &mut Editor<hjkl_buffer::View, H>,
414 motion: &Motion,
415 count: usize,
416) {
417 apply_motion_cursor_ctx(ed, motion, count, false)
418}
419pub fn apply_motion_cursor_ctx<H: hjkl_engine::types::Host>(
420 ed: &mut Editor<hjkl_buffer::View, H>,
421 motion: &Motion,
422 count: usize,
423 as_operator: bool,
424) {
425 let count = count
434 .min(MAX_COUNT)
435 .min(ed.buffer().rope().len_chars().saturating_add(1));
436 match motion {
437 Motion::Left => {
438 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
440 }
441 Motion::Right => {
442 if as_operator {
446 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
447 } else {
448 hjkl_engine::motions::move_right_in_line(ed.buffer_mut(), count);
449 }
450 }
451 Motion::SpaceFwd => {
452 if as_operator {
455 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
456 } else {
457 hjkl_engine::motions::move_space_fwd(ed.buffer_mut(), count);
458 }
459 }
460 Motion::BackspaceBack => {
461 if as_operator {
464 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
465 } else {
466 hjkl_engine::motions::move_backspace_back(ed.buffer_mut(), count);
467 }
468 }
469 Motion::Up => {
470 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
474 let mut sticky = ed.sticky_col();
475 let tabstop = ed.settings().tabstop;
476 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
477 ed.set_sticky_col(sticky);
478 }
479 Motion::Down => {
480 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
481 let mut sticky = ed.sticky_col();
482 let tabstop = ed.settings().tabstop;
483 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky, tabstop);
484 ed.set_sticky_col(sticky);
485 }
486 Motion::ScreenUp => {
487 let v = *ed.host().viewport();
488 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
489 let mut sticky = ed.sticky_col();
490 let tabstop = ed.settings().tabstop;
491 hjkl_engine::motions::move_screen_up(
492 ed.buffer_mut(),
493 &folds,
494 &v,
495 count,
496 &mut sticky,
497 tabstop,
498 );
499 ed.set_sticky_col(sticky);
500 }
501 Motion::ScreenDown => {
502 let v = *ed.host().viewport();
503 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
504 let mut sticky = ed.sticky_col();
505 let tabstop = ed.settings().tabstop;
506 hjkl_engine::motions::move_screen_down(
507 ed.buffer_mut(),
508 &folds,
509 &v,
510 count,
511 &mut sticky,
512 tabstop,
513 );
514 ed.set_sticky_col(sticky);
515 }
516 Motion::WordFwd => {
517 let iskeyword = ed.settings().iskeyword.clone();
518 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), false, count, &iskeyword);
519 if !as_operator {
522 let (row, col) = ed.cursor();
523 let line_len = buf_line_chars(ed.buffer(), row);
524 if col > line_len.saturating_sub(1) {
525 ed.jump_cursor(row, line_len.saturating_sub(1));
526 }
527 }
528 }
529 Motion::WordBack => {
530 let iskeyword = ed.settings().iskeyword.clone();
531 hjkl_engine::motions::move_word_back(ed.buffer_mut(), false, count, &iskeyword);
532 }
533 Motion::WordEnd => {
534 let iskeyword = ed.settings().iskeyword.clone();
535 hjkl_engine::motions::move_word_end(ed.buffer_mut(), false, count, &iskeyword);
536 }
537 Motion::BigWordFwd => {
538 let iskeyword = ed.settings().iskeyword.clone();
539 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), true, count, &iskeyword);
540 if !as_operator {
542 let (row, col) = ed.cursor();
543 let line_len = buf_line_chars(ed.buffer(), row);
544 if col > line_len.saturating_sub(1) {
545 ed.jump_cursor(row, line_len.saturating_sub(1));
546 }
547 }
548 }
549 Motion::BigWordBack => {
550 let iskeyword = ed.settings().iskeyword.clone();
551 hjkl_engine::motions::move_word_back(ed.buffer_mut(), true, count, &iskeyword);
552 }
553 Motion::BigWordEnd => {
554 let iskeyword = ed.settings().iskeyword.clone();
555 hjkl_engine::motions::move_word_end(ed.buffer_mut(), true, count, &iskeyword);
556 }
557 Motion::WordEndBack => {
558 let iskeyword = ed.settings().iskeyword.clone();
559 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), false, count, &iskeyword);
560 }
561 Motion::BigWordEndBack => {
562 let iskeyword = ed.settings().iskeyword.clone();
563 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), true, count, &iskeyword);
564 }
565 Motion::LineStart => {
566 hjkl_engine::motions::move_line_start(ed.buffer_mut());
567 }
568 Motion::FirstNonBlank => {
569 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
570 }
571 Motion::LineEnd => {
572 if count > 1 {
575 if ed.cursor().0 >= last_content_row(ed) {
583 return;
584 }
585 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
586 let mut sticky = ed.sticky_col();
587 let tabstop = ed.settings().tabstop;
588 hjkl_engine::motions::move_down(
589 ed.buffer_mut(),
590 &folds,
591 count - 1,
592 &mut sticky,
593 tabstop,
594 );
595 ed.set_sticky_col(sticky);
596 }
597 hjkl_engine::motions::move_line_end(ed.buffer_mut());
598 }
599 Motion::FileTop => {
600 if count > 1 {
603 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
604 } else {
605 hjkl_engine::motions::move_top(ed.buffer_mut());
606 }
607 }
608 Motion::FileBottom => {
609 if count > 1 {
612 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
613 } else {
614 hjkl_engine::motions::move_bottom(ed.buffer_mut(), 0);
615 }
616 }
617 Motion::Find { ch, forward, till } => {
618 let repeat = std::mem::take(&mut vim_mut(ed).find_repeat_skip);
622 for i in 0..count {
623 let skip_adjacent = repeat || i > 0;
624 if !find_char_on_line(ed, *ch, *forward, *till, skip_adjacent) {
625 break;
626 }
627 }
628 }
629 Motion::FindRepeat { .. } => {} Motion::MatchBracket => {
631 let _ = matching_bracket(ed);
632 }
633 Motion::UnmatchedBracket { forward, open } => {
634 goto_unmatched_bracket(ed, *forward, *open, count);
635 }
636 Motion::WordAtCursor {
637 forward,
638 whole_word,
639 } => {
640 word_at_cursor_search(ed, *forward, *whole_word, count);
641 }
642 Motion::SearchNext { reverse } => {
643 if let Some(pattern) = ed.last_search_pattern() {
647 ed.push_search_pattern(&pattern);
648 }
649 if ed.search_state().pattern.is_none() {
650 return;
651 }
652 let forward = ed.last_search_forward() != *reverse;
656 for _ in 0..count.max(1) {
657 if forward {
658 ed.search_advance_forward(true);
659 } else {
660 ed.search_advance_backward(true);
661 }
662 }
663 }
664 Motion::ViewportTop => {
665 let v = *ed.host().viewport();
666 hjkl_engine::motions::move_viewport_top(ed.buffer_mut(), &v, count.saturating_sub(1));
667 }
668 Motion::ViewportMiddle => {
669 let v = *ed.host().viewport();
670 hjkl_engine::motions::move_viewport_middle(ed.buffer_mut(), &v);
671 }
672 Motion::ViewportBottom => {
673 let v = *ed.host().viewport();
674 hjkl_engine::motions::move_viewport_bottom(
675 ed.buffer_mut(),
676 &v,
677 count.saturating_sub(1),
678 );
679 }
680 Motion::LastNonBlank => {
681 hjkl_engine::motions::move_last_non_blank(ed.buffer_mut());
682 }
683 Motion::LineMiddle => {
684 let row = ed.cursor().0;
685 let line_chars = buf_line_chars(ed.buffer(), row);
686 let target = line_chars / 2;
689 ed.jump_cursor(row, target);
690 }
691 Motion::ScreenLineMiddle => {
692 let row = ed.cursor().0;
695 let width = ed.host().viewport().width as usize;
696 let last = buf_line_chars(ed.buffer(), row).saturating_sub(1);
697 let target = (width / 2).min(last);
698 ed.jump_cursor(row, target);
699 }
700 Motion::ParagraphPrev => {
701 hjkl_engine::motions::move_paragraph_prev(ed.buffer_mut(), count);
702 }
703 Motion::ParagraphNext => {
704 hjkl_engine::motions::move_paragraph_next(ed.buffer_mut(), count);
705 }
706 Motion::SentencePrev => {
712 let start = ed.cursor();
713 for _ in 0..count.max(1) {
714 match sentence_boundary(ed, false) {
715 Some((row, col)) => ed.jump_cursor(row, col),
716 None => {
717 ed.jump_cursor(start.0, start.1);
718 break;
719 }
720 }
721 }
722 }
723 Motion::SentenceNext => {
724 use crate::vim::text_object::SentenceStep;
725 let start = ed.cursor();
726 let total = count.max(1);
727 for i in 0..total {
728 match crate::vim::text_object::sentence_step_forward(ed) {
729 SentenceStep::Boundary((row, col)) => ed.jump_cursor(row, col),
730 SentenceStep::AtEnd => {}
733 SentenceStep::EndOfBuffer((row, col)) => {
734 if i + 1 == total {
735 ed.jump_cursor(row, col);
736 } else {
737 ed.jump_cursor(start.0, start.1);
738 break;
739 }
740 }
741 }
742 }
743 }
744 Motion::SectionBackward => {
745 hjkl_engine::motions::move_section_backward(ed.buffer_mut(), count);
746 }
747 Motion::SectionForward => {
748 hjkl_engine::motions::move_section_forward(ed.buffer_mut(), count);
749 }
750 Motion::SectionEndBackward => {
751 hjkl_engine::motions::move_section_end_backward(ed.buffer_mut(), count);
752 }
753 Motion::SectionEndForward => {
754 hjkl_engine::motions::move_section_end_forward(ed.buffer_mut(), count);
755 }
756 Motion::FirstNonBlankNextLine => {
757 hjkl_engine::motions::move_first_non_blank_next_line(ed.buffer_mut(), count);
758 }
759 Motion::FirstNonBlankPrevLine => {
760 hjkl_engine::motions::move_first_non_blank_prev_line(ed.buffer_mut(), count);
761 }
762 Motion::FirstNonBlankLine => {
763 hjkl_engine::motions::move_first_non_blank_line(ed.buffer_mut(), count);
764 }
765 Motion::GotoColumn => {
766 hjkl_engine::motions::move_goto_column(ed.buffer_mut(), count);
767 }
768 }
769}
770pub fn move_first_non_whitespace<H: hjkl_engine::types::Host>(
771 ed: &mut Editor<hjkl_buffer::View, H>,
772) {
773 ed.sync_buffer_content_from_textarea();
779 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
780}
781pub fn find_char_on_line<H: hjkl_engine::types::Host>(
782 ed: &mut Editor<hjkl_buffer::View, H>,
783 ch: char,
784 forward: bool,
785 till: bool,
786 skip_adjacent: bool,
787) -> bool {
788 hjkl_engine::motions::find_char_on_line(ed.buffer_mut(), ch, forward, till, skip_adjacent)
789}
790pub fn matching_bracket<H: hjkl_engine::types::Host>(
791 ed: &mut Editor<hjkl_buffer::View, H>,
792) -> bool {
793 hjkl_engine::motions::match_bracket(ed.buffer_mut())
794}
795pub fn goto_unmatched_bracket<H: hjkl_engine::types::Host>(
799 ed: &mut Editor<hjkl_buffer::View, H>,
800 forward: bool,
801 open: char,
802 count: usize,
803) {
804 let close = match open {
805 '(' => ')',
806 '{' => '}',
807 _ => return,
808 };
809 let cursor = buf_cursor_pos(ed.buffer());
810 let rows = buf_row_count(ed.buffer());
811 let target = count.max(1);
812 let mut found = 0usize;
813 let mut depth = 0i32;
814
815 if forward {
816 let mut r = cursor.row;
817 let mut from_col = cursor.col + 1;
818 while r < rows {
819 let line: Vec<char> = buf_line(ed.buffer(), r)
820 .unwrap_or_default()
821 .chars()
822 .collect();
823 let mut ci = from_col;
824 while ci < line.len() {
825 let ch = line[ci];
826 if ch == open {
827 depth += 1;
828 } else if ch == close {
829 if depth == 0 {
830 found += 1;
831 if found == target {
832 buf_set_cursor_rc(ed.buffer_mut(), r, ci);
833 return;
834 }
835 } else {
836 depth -= 1;
837 }
838 }
839 ci += 1;
840 }
841 r += 1;
842 from_col = 0;
843 }
844 } else {
845 let mut r = cursor.row as isize;
846 let mut from_col = cursor.col as isize - 1;
849 while r >= 0 {
850 let line: Vec<char> = buf_line(ed.buffer(), r as usize)
851 .unwrap_or_default()
852 .chars()
853 .collect();
854 let mut ci = from_col.min(line.len() as isize - 1);
855 while ci >= 0 {
856 let ch = line[ci as usize];
857 if ch == close {
858 depth += 1;
859 } else if ch == open {
860 if depth == 0 {
861 found += 1;
862 if found == target {
863 buf_set_cursor_rc(ed.buffer_mut(), r as usize, ci as usize);
864 return;
865 }
866 } else {
867 depth -= 1;
868 }
869 }
870 ci -= 1;
871 }
872 r -= 1;
873 from_col = isize::MAX;
874 }
875 }
876}
877pub fn word_at_cursor_search<H: hjkl_engine::types::Host>(
878 ed: &mut Editor<hjkl_buffer::View, H>,
879 forward: bool,
880 whole_word: bool,
881 count: usize,
882) {
883 let (row, col) = ed.cursor();
884 let line: String = buf_line(ed.buffer(), row).unwrap_or_default();
885 let chars: Vec<char> = line.chars().collect();
886 if chars.is_empty() {
887 return;
888 }
889 let spec = ed.settings().iskeyword.clone();
891 let is_word = |c: char| is_keyword_char(c, &spec);
892 let mut start = col.min(chars.len().saturating_sub(1));
893 if is_word(chars[start]) {
894 while start > 0 && is_word(chars[start - 1]) {
895 start -= 1;
896 }
897 } else {
898 while start < chars.len() && !is_word(chars[start]) {
907 start += 1;
908 }
909 if start >= chars.len() {
910 return;
911 }
912 buf_set_cursor_rc(ed.buffer_mut(), row, start);
913 }
914 let mut end = start;
915 while end < chars.len() && is_word(chars[end]) {
916 end += 1;
917 }
918 if end <= start {
919 return;
920 }
921 let word: String = chars[start..end].iter().collect();
922 let escaped = regex_escape(&word);
923 let pattern = if whole_word {
924 format!(r"\b{escaped}\b")
925 } else {
926 escaped
927 };
928 ed.push_search_pattern(&pattern);
929 if ed.search_state().pattern.is_none() {
930 return;
931 }
932 ed.set_last_search_pattern_only(Some(pattern));
934 ed.set_last_search_forward_only(forward);
935 for _ in 0..count.max(1) {
936 if forward {
937 ed.search_advance_forward(true);
938 } else {
939 ed.search_advance_backward(true);
940 }
941 }
942}
943pub fn regex_escape(s: &str) -> String {
944 let mut out = String::with_capacity(s.len());
945 for c in s.chars() {
946 if matches!(
947 c,
948 '.' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$' | '\\'
949 ) {
950 out.push('\\');
951 }
952 out.push(c);
953 }
954 out
955}