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
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(crate) 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 let pre_col = pre_pos.1;
115 apply_motion_cursor(ed, &motion, count);
116 let post_pos = ed.cursor();
117 if is_big_jump(&motion) && pre_pos != post_pos {
118 ed.push_jump(pre_pos);
119 }
120 apply_sticky_col(ed, &motion, pre_col);
121 ed.sync_buffer_from_textarea();
126}
127pub(crate) fn execute_motion_with_block_vcol<H: hjkl_engine::types::Host>(
136 ed: &mut Editor<hjkl_buffer::View, H>,
137 motion: Motion,
138 count: usize,
139) {
140 let motion_copy = motion.clone();
141 execute_motion(ed, motion, count);
142 if vim(ed).mode == Mode::VisualBlock {
143 update_block_vcol(ed, &motion_copy);
144 }
145}
146pub(crate) fn apply_motion_kind<H: hjkl_engine::types::Host>(
174 ed: &mut Editor<hjkl_buffer::View, H>,
175 kind: hjkl_engine::MotionKind,
176 count: usize,
177) {
178 let count = count.max(1);
179 match kind {
180 hjkl_engine::MotionKind::CharLeft => {
181 execute_motion_with_block_vcol(ed, Motion::Left, count);
182 }
183 hjkl_engine::MotionKind::CharRight => {
184 execute_motion_with_block_vcol(ed, Motion::Right, count);
185 }
186 hjkl_engine::MotionKind::LineDown => {
187 execute_motion_with_block_vcol(ed, Motion::Down, count);
188 }
189 hjkl_engine::MotionKind::LineUp => {
190 execute_motion_with_block_vcol(ed, Motion::Up, count);
191 }
192 hjkl_engine::MotionKind::FirstNonBlankDown => {
193 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
198 let mut sticky = ed.sticky_col();
199 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky);
200 ed.set_sticky_col(sticky);
201 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
202 ed.push_buffer_cursor_to_textarea();
203 ed.set_sticky_col(Some(buf_cursor_pos(ed.buffer()).col));
204 ed.sync_buffer_from_textarea();
205 }
206 hjkl_engine::MotionKind::FirstNonBlankUp => {
207 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
210 let mut sticky = ed.sticky_col();
211 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky);
212 ed.set_sticky_col(sticky);
213 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
214 ed.push_buffer_cursor_to_textarea();
215 ed.set_sticky_col(Some(buf_cursor_pos(ed.buffer()).col));
216 ed.sync_buffer_from_textarea();
217 }
218 hjkl_engine::MotionKind::WordForward => {
219 execute_motion_with_block_vcol(ed, Motion::WordFwd, count);
220 }
221 hjkl_engine::MotionKind::BigWordForward => {
222 execute_motion_with_block_vcol(ed, Motion::BigWordFwd, count);
223 }
224 hjkl_engine::MotionKind::WordBackward => {
225 execute_motion_with_block_vcol(ed, Motion::WordBack, count);
226 }
227 hjkl_engine::MotionKind::BigWordBackward => {
228 execute_motion_with_block_vcol(ed, Motion::BigWordBack, count);
229 }
230 hjkl_engine::MotionKind::WordEnd => {
231 execute_motion_with_block_vcol(ed, Motion::WordEnd, count);
232 }
233 hjkl_engine::MotionKind::BigWordEnd => {
234 execute_motion_with_block_vcol(ed, Motion::BigWordEnd, count);
235 }
236 hjkl_engine::MotionKind::LineStart => {
237 execute_motion_with_block_vcol(ed, Motion::LineStart, 1);
240 }
241 hjkl_engine::MotionKind::FirstNonBlank => {
242 execute_motion_with_block_vcol(ed, Motion::FirstNonBlank, 1);
245 }
246 hjkl_engine::MotionKind::GotoLine => {
247 execute_motion_with_block_vcol(ed, Motion::FileBottom, count);
256 }
257 hjkl_engine::MotionKind::LineEnd => {
258 execute_motion_with_block_vcol(ed, Motion::LineEnd, 1);
262 }
263 hjkl_engine::MotionKind::FindRepeat => {
264 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: false }, count);
268 }
269 hjkl_engine::MotionKind::FindRepeatReverse => {
270 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: true }, count);
274 }
275 hjkl_engine::MotionKind::BracketMatch => {
276 execute_motion_with_block_vcol(ed, Motion::MatchBracket, count);
281 }
282 hjkl_engine::MotionKind::ViewportTop => {
283 execute_motion_with_block_vcol(ed, Motion::ViewportTop, count);
286 }
287 hjkl_engine::MotionKind::ViewportMiddle => {
288 execute_motion_with_block_vcol(ed, Motion::ViewportMiddle, count);
291 }
292 hjkl_engine::MotionKind::ViewportBottom => {
293 execute_motion_with_block_vcol(ed, Motion::ViewportBottom, count);
296 }
297 hjkl_engine::MotionKind::HalfPageDown => {
298 {
302 let d = ed.viewport_half_rows(count) as isize;
303 ed.scroll_cursor_rows(d);
304 }
305 }
306 hjkl_engine::MotionKind::HalfPageUp => {
307 {
310 let d = -(ed.viewport_half_rows(count) as isize);
311 ed.scroll_cursor_rows(d);
312 }
313 }
314 hjkl_engine::MotionKind::FullPageDown => {
315 {
318 let d = ed.viewport_full_rows(count) as isize;
319 ed.scroll_cursor_rows(d);
320 }
321 }
322 hjkl_engine::MotionKind::FullPageUp => {
323 {
326 let d = -(ed.viewport_full_rows(count) as isize);
327 ed.scroll_cursor_rows(d);
328 }
329 }
330 hjkl_engine::MotionKind::FirstNonBlankLine => {
331 execute_motion_with_block_vcol(ed, Motion::FirstNonBlankLine, count);
332 }
333 hjkl_engine::MotionKind::SectionBackward => {
334 execute_motion_with_block_vcol(ed, Motion::SectionBackward, count);
335 }
336 hjkl_engine::MotionKind::SectionForward => {
337 execute_motion_with_block_vcol(ed, Motion::SectionForward, count);
338 }
339 hjkl_engine::MotionKind::SectionEndBackward => {
340 execute_motion_with_block_vcol(ed, Motion::SectionEndBackward, count);
341 }
342 hjkl_engine::MotionKind::SectionEndForward => {
343 execute_motion_with_block_vcol(ed, Motion::SectionEndForward, count);
344 }
345 _ => {}
350 }
351}
352pub(crate) fn apply_sticky_col<H: hjkl_engine::types::Host>(
357 ed: &mut Editor<hjkl_buffer::View, H>,
358 motion: &Motion,
359 pre_col: usize,
360) {
361 if is_vertical_motion(motion) {
362 let want = ed.sticky_col().unwrap_or(pre_col);
363 ed.set_sticky_col(Some(want));
366 let (row, _) = ed.cursor();
367 let line_len = buf_line_chars(ed.buffer(), row);
368 let max_col = line_len.saturating_sub(1);
372 let target = want.min(max_col);
373 buf_set_cursor_rc(ed.buffer_mut(), row, target);
377 } else {
378 ed.set_sticky_col(Some(ed.cursor().1));
381 }
382}
383pub(crate) fn is_vertical_motion(motion: &Motion) -> bool {
384 matches!(
388 motion,
389 Motion::Up | Motion::Down | Motion::ScreenUp | Motion::ScreenDown
390 )
391}
392pub(crate) fn apply_motion_cursor<H: hjkl_engine::types::Host>(
393 ed: &mut Editor<hjkl_buffer::View, H>,
394 motion: &Motion,
395 count: usize,
396) {
397 apply_motion_cursor_ctx(ed, motion, count, false)
398}
399pub(crate) fn apply_motion_cursor_ctx<H: hjkl_engine::types::Host>(
400 ed: &mut Editor<hjkl_buffer::View, H>,
401 motion: &Motion,
402 count: usize,
403 as_operator: bool,
404) {
405 let count = count
414 .min(MAX_COUNT)
415 .min(ed.buffer().rope().len_chars().saturating_add(1));
416 match motion {
417 Motion::Left => {
418 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
420 ed.push_buffer_cursor_to_textarea();
421 }
422 Motion::Right => {
423 if as_operator {
427 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
428 } else {
429 hjkl_engine::motions::move_right_in_line(ed.buffer_mut(), count);
430 }
431 ed.push_buffer_cursor_to_textarea();
432 }
433 Motion::SpaceFwd => {
434 if as_operator {
437 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
438 } else {
439 hjkl_engine::motions::move_space_fwd(ed.buffer_mut(), count);
440 }
441 ed.push_buffer_cursor_to_textarea();
442 }
443 Motion::BackspaceBack => {
444 if as_operator {
447 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
448 } else {
449 hjkl_engine::motions::move_backspace_back(ed.buffer_mut(), count);
450 }
451 ed.push_buffer_cursor_to_textarea();
452 }
453 Motion::Up => {
454 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
458 let mut sticky = ed.sticky_col();
459 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky);
460 ed.set_sticky_col(sticky);
461 ed.push_buffer_cursor_to_textarea();
462 }
463 Motion::Down => {
464 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
465 let mut sticky = ed.sticky_col();
466 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky);
467 ed.set_sticky_col(sticky);
468 ed.push_buffer_cursor_to_textarea();
469 }
470 Motion::ScreenUp => {
471 let v = *ed.host().viewport();
472 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
473 let mut sticky = ed.sticky_col();
474 hjkl_engine::motions::move_screen_up(ed.buffer_mut(), &folds, &v, count, &mut sticky);
475 ed.set_sticky_col(sticky);
476 ed.push_buffer_cursor_to_textarea();
477 }
478 Motion::ScreenDown => {
479 let v = *ed.host().viewport();
480 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
481 let mut sticky = ed.sticky_col();
482 hjkl_engine::motions::move_screen_down(ed.buffer_mut(), &folds, &v, count, &mut sticky);
483 ed.set_sticky_col(sticky);
484 ed.push_buffer_cursor_to_textarea();
485 }
486 Motion::WordFwd => {
487 let iskeyword = ed.settings().iskeyword.clone();
488 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), false, count, &iskeyword);
489 ed.push_buffer_cursor_to_textarea();
490 }
491 Motion::WordBack => {
492 let iskeyword = ed.settings().iskeyword.clone();
493 hjkl_engine::motions::move_word_back(ed.buffer_mut(), false, count, &iskeyword);
494 ed.push_buffer_cursor_to_textarea();
495 }
496 Motion::WordEnd => {
497 let iskeyword = ed.settings().iskeyword.clone();
498 hjkl_engine::motions::move_word_end(ed.buffer_mut(), false, count, &iskeyword);
499 ed.push_buffer_cursor_to_textarea();
500 }
501 Motion::BigWordFwd => {
502 let iskeyword = ed.settings().iskeyword.clone();
503 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), true, count, &iskeyword);
504 ed.push_buffer_cursor_to_textarea();
505 }
506 Motion::BigWordBack => {
507 let iskeyword = ed.settings().iskeyword.clone();
508 hjkl_engine::motions::move_word_back(ed.buffer_mut(), true, count, &iskeyword);
509 ed.push_buffer_cursor_to_textarea();
510 }
511 Motion::BigWordEnd => {
512 let iskeyword = ed.settings().iskeyword.clone();
513 hjkl_engine::motions::move_word_end(ed.buffer_mut(), true, count, &iskeyword);
514 ed.push_buffer_cursor_to_textarea();
515 }
516 Motion::WordEndBack => {
517 let iskeyword = ed.settings().iskeyword.clone();
518 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), false, count, &iskeyword);
519 ed.push_buffer_cursor_to_textarea();
520 }
521 Motion::BigWordEndBack => {
522 let iskeyword = ed.settings().iskeyword.clone();
523 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), true, count, &iskeyword);
524 ed.push_buffer_cursor_to_textarea();
525 }
526 Motion::LineStart => {
527 hjkl_engine::motions::move_line_start(ed.buffer_mut());
528 ed.push_buffer_cursor_to_textarea();
529 }
530 Motion::FirstNonBlank => {
531 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
532 ed.push_buffer_cursor_to_textarea();
533 }
534 Motion::LineEnd => {
535 hjkl_engine::motions::move_line_end(ed.buffer_mut());
537 ed.push_buffer_cursor_to_textarea();
538 }
539 Motion::FileTop => {
540 if count > 1 {
543 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
544 } else {
545 hjkl_engine::motions::move_top(ed.buffer_mut());
546 }
547 ed.push_buffer_cursor_to_textarea();
548 }
549 Motion::FileBottom => {
550 if count > 1 {
553 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
554 } else {
555 hjkl_engine::motions::move_bottom(ed.buffer_mut(), 0);
556 }
557 ed.push_buffer_cursor_to_textarea();
558 }
559 Motion::Find { ch, forward, till } => {
560 let repeat = std::mem::take(&mut vim_mut(ed).find_repeat_skip);
564 for i in 0..count {
565 let skip_adjacent = repeat || i > 0;
566 if !find_char_on_line(ed, *ch, *forward, *till, skip_adjacent) {
567 break;
568 }
569 }
570 }
571 Motion::FindRepeat { .. } => {} Motion::MatchBracket => {
573 let _ = matching_bracket(ed);
574 }
575 Motion::UnmatchedBracket { forward, open } => {
576 goto_unmatched_bracket(ed, *forward, *open, count);
577 }
578 Motion::WordAtCursor {
579 forward,
580 whole_word,
581 } => {
582 word_at_cursor_search(ed, *forward, *whole_word, count);
583 }
584 Motion::SearchNext { reverse } => {
585 if let Some(pattern) = ed.last_search_pattern() {
589 ed.push_search_pattern(&pattern);
590 }
591 if ed.search_state().pattern.is_none() {
592 return;
593 }
594 let forward = ed.last_search_forward() != *reverse;
598 for _ in 0..count.max(1) {
599 if forward {
600 ed.search_advance_forward(true);
601 } else {
602 ed.search_advance_backward(true);
603 }
604 }
605 ed.push_buffer_cursor_to_textarea();
606 }
607 Motion::ViewportTop => {
608 let v = *ed.host().viewport();
609 hjkl_engine::motions::move_viewport_top(ed.buffer_mut(), &v, count.saturating_sub(1));
610 ed.push_buffer_cursor_to_textarea();
611 }
612 Motion::ViewportMiddle => {
613 let v = *ed.host().viewport();
614 hjkl_engine::motions::move_viewport_middle(ed.buffer_mut(), &v);
615 ed.push_buffer_cursor_to_textarea();
616 }
617 Motion::ViewportBottom => {
618 let v = *ed.host().viewport();
619 hjkl_engine::motions::move_viewport_bottom(
620 ed.buffer_mut(),
621 &v,
622 count.saturating_sub(1),
623 );
624 ed.push_buffer_cursor_to_textarea();
625 }
626 Motion::LastNonBlank => {
627 hjkl_engine::motions::move_last_non_blank(ed.buffer_mut());
628 ed.push_buffer_cursor_to_textarea();
629 }
630 Motion::LineMiddle => {
631 let row = ed.cursor().0;
632 let line_chars = buf_line_chars(ed.buffer(), row);
633 let target = line_chars / 2;
636 ed.jump_cursor(row, target);
637 }
638 Motion::ScreenLineMiddle => {
639 let row = ed.cursor().0;
642 let width = ed.host().viewport().width as usize;
643 let last = buf_line_chars(ed.buffer(), row).saturating_sub(1);
644 let target = (width / 2).min(last);
645 ed.jump_cursor(row, target);
646 }
647 Motion::ParagraphPrev => {
648 hjkl_engine::motions::move_paragraph_prev(ed.buffer_mut(), count);
649 ed.push_buffer_cursor_to_textarea();
650 }
651 Motion::ParagraphNext => {
652 hjkl_engine::motions::move_paragraph_next(ed.buffer_mut(), count);
653 ed.push_buffer_cursor_to_textarea();
654 }
655 Motion::SentencePrev => {
656 for _ in 0..count.max(1) {
657 if let Some((row, col)) = sentence_boundary(ed, false) {
658 ed.jump_cursor(row, col);
659 }
660 }
661 }
662 Motion::SentenceNext => {
663 for _ in 0..count.max(1) {
664 if let Some((row, col)) = sentence_boundary(ed, true) {
665 ed.jump_cursor(row, col);
666 }
667 }
668 }
669 Motion::SectionBackward => {
670 hjkl_engine::motions::move_section_backward(ed.buffer_mut(), count);
671 ed.push_buffer_cursor_to_textarea();
672 }
673 Motion::SectionForward => {
674 hjkl_engine::motions::move_section_forward(ed.buffer_mut(), count);
675 ed.push_buffer_cursor_to_textarea();
676 }
677 Motion::SectionEndBackward => {
678 hjkl_engine::motions::move_section_end_backward(ed.buffer_mut(), count);
679 ed.push_buffer_cursor_to_textarea();
680 }
681 Motion::SectionEndForward => {
682 hjkl_engine::motions::move_section_end_forward(ed.buffer_mut(), count);
683 ed.push_buffer_cursor_to_textarea();
684 }
685 Motion::FirstNonBlankNextLine => {
686 hjkl_engine::motions::move_first_non_blank_next_line(ed.buffer_mut(), count);
687 ed.push_buffer_cursor_to_textarea();
688 }
689 Motion::FirstNonBlankPrevLine => {
690 hjkl_engine::motions::move_first_non_blank_prev_line(ed.buffer_mut(), count);
691 ed.push_buffer_cursor_to_textarea();
692 }
693 Motion::FirstNonBlankLine => {
694 hjkl_engine::motions::move_first_non_blank_line(ed.buffer_mut(), count);
695 ed.push_buffer_cursor_to_textarea();
696 }
697 Motion::GotoColumn => {
698 hjkl_engine::motions::move_goto_column(ed.buffer_mut(), count);
699 ed.push_buffer_cursor_to_textarea();
700 }
701 }
702}
703pub(crate) fn move_first_non_whitespace<H: hjkl_engine::types::Host>(
704 ed: &mut Editor<hjkl_buffer::View, H>,
705) {
706 ed.sync_buffer_content_from_textarea();
712 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
713 ed.push_buffer_cursor_to_textarea();
714}
715pub(crate) fn find_char_on_line<H: hjkl_engine::types::Host>(
716 ed: &mut Editor<hjkl_buffer::View, H>,
717 ch: char,
718 forward: bool,
719 till: bool,
720 skip_adjacent: bool,
721) -> bool {
722 let moved =
723 hjkl_engine::motions::find_char_on_line(ed.buffer_mut(), ch, forward, till, skip_adjacent);
724 if moved {
725 ed.push_buffer_cursor_to_textarea();
726 }
727 moved
728}
729pub(crate) fn matching_bracket<H: hjkl_engine::types::Host>(
730 ed: &mut Editor<hjkl_buffer::View, H>,
731) -> bool {
732 let moved = hjkl_engine::motions::match_bracket(ed.buffer_mut());
733 if moved {
734 ed.push_buffer_cursor_to_textarea();
735 }
736 moved
737}
738pub(crate) fn goto_unmatched_bracket<H: hjkl_engine::types::Host>(
742 ed: &mut Editor<hjkl_buffer::View, H>,
743 forward: bool,
744 open: char,
745 count: usize,
746) {
747 let close = match open {
748 '(' => ')',
749 '{' => '}',
750 _ => return,
751 };
752 let cursor = buf_cursor_pos(ed.buffer());
753 let rows = buf_row_count(ed.buffer());
754 let target = count.max(1);
755 let mut found = 0usize;
756 let mut depth = 0i32;
757
758 if forward {
759 let mut r = cursor.row;
760 let mut from_col = cursor.col + 1;
761 while r < rows {
762 let line: Vec<char> = buf_line(ed.buffer(), r)
763 .unwrap_or_default()
764 .chars()
765 .collect();
766 let mut ci = from_col;
767 while ci < line.len() {
768 let ch = line[ci];
769 if ch == open {
770 depth += 1;
771 } else if ch == close {
772 if depth == 0 {
773 found += 1;
774 if found == target {
775 buf_set_cursor_rc(ed.buffer_mut(), r, ci);
776 ed.push_buffer_cursor_to_textarea();
777 return;
778 }
779 } else {
780 depth -= 1;
781 }
782 }
783 ci += 1;
784 }
785 r += 1;
786 from_col = 0;
787 }
788 } else {
789 let mut r = cursor.row as isize;
790 let mut from_col = cursor.col as isize - 1;
793 while r >= 0 {
794 let line: Vec<char> = buf_line(ed.buffer(), r as usize)
795 .unwrap_or_default()
796 .chars()
797 .collect();
798 let mut ci = from_col.min(line.len() as isize - 1);
799 while ci >= 0 {
800 let ch = line[ci as usize];
801 if ch == close {
802 depth += 1;
803 } else if ch == open {
804 if depth == 0 {
805 found += 1;
806 if found == target {
807 buf_set_cursor_rc(ed.buffer_mut(), r as usize, ci as usize);
808 ed.push_buffer_cursor_to_textarea();
809 return;
810 }
811 } else {
812 depth -= 1;
813 }
814 }
815 ci -= 1;
816 }
817 r -= 1;
818 from_col = isize::MAX;
819 }
820 }
821}
822pub(crate) fn word_at_cursor_search<H: hjkl_engine::types::Host>(
823 ed: &mut Editor<hjkl_buffer::View, H>,
824 forward: bool,
825 whole_word: bool,
826 count: usize,
827) {
828 let (row, col) = ed.cursor();
829 let line: String = buf_line(ed.buffer(), row).unwrap_or_default();
830 let chars: Vec<char> = line.chars().collect();
831 if chars.is_empty() {
832 return;
833 }
834 let spec = ed.settings().iskeyword.clone();
836 let is_word = |c: char| is_keyword_char(c, &spec);
837 let mut start = col.min(chars.len().saturating_sub(1));
838 while start > 0 && is_word(chars[start - 1]) {
839 start -= 1;
840 }
841 let mut end = start;
842 while end < chars.len() && is_word(chars[end]) {
843 end += 1;
844 }
845 if end <= start {
846 return;
847 }
848 let word: String = chars[start..end].iter().collect();
849 let escaped = regex_escape(&word);
850 let pattern = if whole_word {
851 format!(r"\b{escaped}\b")
852 } else {
853 escaped
854 };
855 ed.push_search_pattern(&pattern);
856 if ed.search_state().pattern.is_none() {
857 return;
858 }
859 ed.set_last_search_pattern_only(Some(pattern));
861 ed.set_last_search_forward_only(forward);
862 for _ in 0..count.max(1) {
863 if forward {
864 ed.search_advance_forward(true);
865 } else {
866 ed.search_advance_backward(true);
867 }
868 }
869 ed.push_buffer_cursor_to_textarea();
870}
871pub(crate) fn regex_escape(s: &str) -> String {
872 let mut out = String::with_capacity(s.len());
873 for c in s.chars() {
874 if matches!(
875 c,
876 '.' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$' | '\\'
877 ) {
878 out.push('\\');
879 }
880 out.push(c);
881 }
882 out
883}