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.set_sticky_col(Some(buf_cursor_pos(ed.buffer()).col));
203 ed.sync_buffer_from_textarea();
204 }
205 hjkl_engine::MotionKind::FirstNonBlankUp => {
206 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
209 let mut sticky = ed.sticky_col();
210 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky);
211 ed.set_sticky_col(sticky);
212 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
213 ed.set_sticky_col(Some(buf_cursor_pos(ed.buffer()).col));
214 ed.sync_buffer_from_textarea();
215 }
216 hjkl_engine::MotionKind::WordForward => {
217 execute_motion_with_block_vcol(ed, Motion::WordFwd, count);
218 }
219 hjkl_engine::MotionKind::BigWordForward => {
220 execute_motion_with_block_vcol(ed, Motion::BigWordFwd, count);
221 }
222 hjkl_engine::MotionKind::WordBackward => {
223 execute_motion_with_block_vcol(ed, Motion::WordBack, count);
224 }
225 hjkl_engine::MotionKind::BigWordBackward => {
226 execute_motion_with_block_vcol(ed, Motion::BigWordBack, count);
227 }
228 hjkl_engine::MotionKind::WordEnd => {
229 execute_motion_with_block_vcol(ed, Motion::WordEnd, count);
230 }
231 hjkl_engine::MotionKind::BigWordEnd => {
232 execute_motion_with_block_vcol(ed, Motion::BigWordEnd, count);
233 }
234 hjkl_engine::MotionKind::LineStart => {
235 execute_motion_with_block_vcol(ed, Motion::LineStart, 1);
238 }
239 hjkl_engine::MotionKind::FirstNonBlank => {
240 execute_motion_with_block_vcol(ed, Motion::FirstNonBlank, 1);
243 }
244 hjkl_engine::MotionKind::GotoLine => {
245 execute_motion_with_block_vcol(ed, Motion::FileBottom, count);
254 }
255 hjkl_engine::MotionKind::LineEnd => {
256 execute_motion_with_block_vcol(ed, Motion::LineEnd, 1);
260 }
261 hjkl_engine::MotionKind::FindRepeat => {
262 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: false }, count);
266 }
267 hjkl_engine::MotionKind::FindRepeatReverse => {
268 execute_motion_with_block_vcol(ed, Motion::FindRepeat { reverse: true }, count);
272 }
273 hjkl_engine::MotionKind::BracketMatch => {
274 execute_motion_with_block_vcol(ed, Motion::MatchBracket, count);
279 }
280 hjkl_engine::MotionKind::ViewportTop => {
281 execute_motion_with_block_vcol(ed, Motion::ViewportTop, count);
284 }
285 hjkl_engine::MotionKind::ViewportMiddle => {
286 execute_motion_with_block_vcol(ed, Motion::ViewportMiddle, count);
289 }
290 hjkl_engine::MotionKind::ViewportBottom => {
291 execute_motion_with_block_vcol(ed, Motion::ViewportBottom, count);
294 }
295 hjkl_engine::MotionKind::HalfPageDown => {
296 {
300 let d = ed.viewport_half_rows(count) as isize;
301 ed.scroll_cursor_rows(d);
302 }
303 }
304 hjkl_engine::MotionKind::HalfPageUp => {
305 {
308 let d = -(ed.viewport_half_rows(count) as isize);
309 ed.scroll_cursor_rows(d);
310 }
311 }
312 hjkl_engine::MotionKind::FullPageDown => {
313 {
316 let d = ed.viewport_full_rows(count) as isize;
317 ed.scroll_cursor_rows(d);
318 }
319 }
320 hjkl_engine::MotionKind::FullPageUp => {
321 {
324 let d = -(ed.viewport_full_rows(count) as isize);
325 ed.scroll_cursor_rows(d);
326 }
327 }
328 hjkl_engine::MotionKind::FirstNonBlankLine => {
329 execute_motion_with_block_vcol(ed, Motion::FirstNonBlankLine, count);
330 }
331 hjkl_engine::MotionKind::SectionBackward => {
332 execute_motion_with_block_vcol(ed, Motion::SectionBackward, count);
333 }
334 hjkl_engine::MotionKind::SectionForward => {
335 execute_motion_with_block_vcol(ed, Motion::SectionForward, count);
336 }
337 hjkl_engine::MotionKind::SectionEndBackward => {
338 execute_motion_with_block_vcol(ed, Motion::SectionEndBackward, count);
339 }
340 hjkl_engine::MotionKind::SectionEndForward => {
341 execute_motion_with_block_vcol(ed, Motion::SectionEndForward, count);
342 }
343 _ => {}
348 }
349}
350pub(crate) fn apply_sticky_col<H: hjkl_engine::types::Host>(
355 ed: &mut Editor<hjkl_buffer::View, H>,
356 motion: &Motion,
357 pre_col: usize,
358) {
359 if is_vertical_motion(motion) {
360 let want = ed.sticky_col().unwrap_or(pre_col);
361 ed.set_sticky_col(Some(want));
364 let (row, _) = ed.cursor();
365 let line_len = buf_line_chars(ed.buffer(), row);
366 let max_col = line_len.saturating_sub(1);
370 let target = want.min(max_col);
371 buf_set_cursor_rc(ed.buffer_mut(), row, target);
375 } else {
376 ed.set_sticky_col(Some(ed.cursor().1));
379 }
380}
381pub(crate) fn is_vertical_motion(motion: &Motion) -> bool {
382 matches!(
386 motion,
387 Motion::Up | Motion::Down | Motion::ScreenUp | Motion::ScreenDown
388 )
389}
390pub(crate) fn apply_motion_cursor<H: hjkl_engine::types::Host>(
391 ed: &mut Editor<hjkl_buffer::View, H>,
392 motion: &Motion,
393 count: usize,
394) {
395 apply_motion_cursor_ctx(ed, motion, count, false)
396}
397pub(crate) fn apply_motion_cursor_ctx<H: hjkl_engine::types::Host>(
398 ed: &mut Editor<hjkl_buffer::View, H>,
399 motion: &Motion,
400 count: usize,
401 as_operator: bool,
402) {
403 let count = count
412 .min(MAX_COUNT)
413 .min(ed.buffer().rope().len_chars().saturating_add(1));
414 match motion {
415 Motion::Left => {
416 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
418 }
419 Motion::Right => {
420 if as_operator {
424 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
425 } else {
426 hjkl_engine::motions::move_right_in_line(ed.buffer_mut(), count);
427 }
428 }
429 Motion::SpaceFwd => {
430 if as_operator {
433 hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), count);
434 } else {
435 hjkl_engine::motions::move_space_fwd(ed.buffer_mut(), count);
436 }
437 }
438 Motion::BackspaceBack => {
439 if as_operator {
442 hjkl_engine::motions::move_left(ed.buffer_mut(), count);
443 } else {
444 hjkl_engine::motions::move_backspace_back(ed.buffer_mut(), count);
445 }
446 }
447 Motion::Up => {
448 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
452 let mut sticky = ed.sticky_col();
453 hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, count, &mut sticky);
454 ed.set_sticky_col(sticky);
455 }
456 Motion::Down => {
457 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
458 let mut sticky = ed.sticky_col();
459 hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, count, &mut sticky);
460 ed.set_sticky_col(sticky);
461 }
462 Motion::ScreenUp => {
463 let v = *ed.host().viewport();
464 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
465 let mut sticky = ed.sticky_col();
466 hjkl_engine::motions::move_screen_up(ed.buffer_mut(), &folds, &v, count, &mut sticky);
467 ed.set_sticky_col(sticky);
468 }
469 Motion::ScreenDown => {
470 let v = *ed.host().viewport();
471 let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
472 let mut sticky = ed.sticky_col();
473 hjkl_engine::motions::move_screen_down(ed.buffer_mut(), &folds, &v, count, &mut sticky);
474 ed.set_sticky_col(sticky);
475 }
476 Motion::WordFwd => {
477 let iskeyword = ed.settings().iskeyword.clone();
478 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), false, count, &iskeyword);
479 }
480 Motion::WordBack => {
481 let iskeyword = ed.settings().iskeyword.clone();
482 hjkl_engine::motions::move_word_back(ed.buffer_mut(), false, count, &iskeyword);
483 }
484 Motion::WordEnd => {
485 let iskeyword = ed.settings().iskeyword.clone();
486 hjkl_engine::motions::move_word_end(ed.buffer_mut(), false, count, &iskeyword);
487 }
488 Motion::BigWordFwd => {
489 let iskeyword = ed.settings().iskeyword.clone();
490 hjkl_engine::motions::move_word_fwd(ed.buffer_mut(), true, count, &iskeyword);
491 }
492 Motion::BigWordBack => {
493 let iskeyword = ed.settings().iskeyword.clone();
494 hjkl_engine::motions::move_word_back(ed.buffer_mut(), true, count, &iskeyword);
495 }
496 Motion::BigWordEnd => {
497 let iskeyword = ed.settings().iskeyword.clone();
498 hjkl_engine::motions::move_word_end(ed.buffer_mut(), true, count, &iskeyword);
499 }
500 Motion::WordEndBack => {
501 let iskeyword = ed.settings().iskeyword.clone();
502 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), false, count, &iskeyword);
503 }
504 Motion::BigWordEndBack => {
505 let iskeyword = ed.settings().iskeyword.clone();
506 hjkl_engine::motions::move_word_end_back(ed.buffer_mut(), true, count, &iskeyword);
507 }
508 Motion::LineStart => {
509 hjkl_engine::motions::move_line_start(ed.buffer_mut());
510 }
511 Motion::FirstNonBlank => {
512 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
513 }
514 Motion::LineEnd => {
515 hjkl_engine::motions::move_line_end(ed.buffer_mut());
517 }
518 Motion::FileTop => {
519 if count > 1 {
522 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
523 } else {
524 hjkl_engine::motions::move_top(ed.buffer_mut());
525 }
526 }
527 Motion::FileBottom => {
528 if count > 1 {
531 hjkl_engine::motions::move_bottom(ed.buffer_mut(), count);
532 } else {
533 hjkl_engine::motions::move_bottom(ed.buffer_mut(), 0);
534 }
535 }
536 Motion::Find { ch, forward, till } => {
537 let repeat = std::mem::take(&mut vim_mut(ed).find_repeat_skip);
541 for i in 0..count {
542 let skip_adjacent = repeat || i > 0;
543 if !find_char_on_line(ed, *ch, *forward, *till, skip_adjacent) {
544 break;
545 }
546 }
547 }
548 Motion::FindRepeat { .. } => {} Motion::MatchBracket => {
550 let _ = matching_bracket(ed);
551 }
552 Motion::UnmatchedBracket { forward, open } => {
553 goto_unmatched_bracket(ed, *forward, *open, count);
554 }
555 Motion::WordAtCursor {
556 forward,
557 whole_word,
558 } => {
559 word_at_cursor_search(ed, *forward, *whole_word, count);
560 }
561 Motion::SearchNext { reverse } => {
562 if let Some(pattern) = ed.last_search_pattern() {
566 ed.push_search_pattern(&pattern);
567 }
568 if ed.search_state().pattern.is_none() {
569 return;
570 }
571 let forward = ed.last_search_forward() != *reverse;
575 for _ in 0..count.max(1) {
576 if forward {
577 ed.search_advance_forward(true);
578 } else {
579 ed.search_advance_backward(true);
580 }
581 }
582 }
583 Motion::ViewportTop => {
584 let v = *ed.host().viewport();
585 hjkl_engine::motions::move_viewport_top(ed.buffer_mut(), &v, count.saturating_sub(1));
586 }
587 Motion::ViewportMiddle => {
588 let v = *ed.host().viewport();
589 hjkl_engine::motions::move_viewport_middle(ed.buffer_mut(), &v);
590 }
591 Motion::ViewportBottom => {
592 let v = *ed.host().viewport();
593 hjkl_engine::motions::move_viewport_bottom(
594 ed.buffer_mut(),
595 &v,
596 count.saturating_sub(1),
597 );
598 }
599 Motion::LastNonBlank => {
600 hjkl_engine::motions::move_last_non_blank(ed.buffer_mut());
601 }
602 Motion::LineMiddle => {
603 let row = ed.cursor().0;
604 let line_chars = buf_line_chars(ed.buffer(), row);
605 let target = line_chars / 2;
608 ed.jump_cursor(row, target);
609 }
610 Motion::ScreenLineMiddle => {
611 let row = ed.cursor().0;
614 let width = ed.host().viewport().width as usize;
615 let last = buf_line_chars(ed.buffer(), row).saturating_sub(1);
616 let target = (width / 2).min(last);
617 ed.jump_cursor(row, target);
618 }
619 Motion::ParagraphPrev => {
620 hjkl_engine::motions::move_paragraph_prev(ed.buffer_mut(), count);
621 }
622 Motion::ParagraphNext => {
623 hjkl_engine::motions::move_paragraph_next(ed.buffer_mut(), count);
624 }
625 Motion::SentencePrev => {
626 for _ in 0..count.max(1) {
627 if let Some((row, col)) = sentence_boundary(ed, false) {
628 ed.jump_cursor(row, col);
629 }
630 }
631 }
632 Motion::SentenceNext => {
633 for _ in 0..count.max(1) {
634 if let Some((row, col)) = sentence_boundary(ed, true) {
635 ed.jump_cursor(row, col);
636 }
637 }
638 }
639 Motion::SectionBackward => {
640 hjkl_engine::motions::move_section_backward(ed.buffer_mut(), count);
641 }
642 Motion::SectionForward => {
643 hjkl_engine::motions::move_section_forward(ed.buffer_mut(), count);
644 }
645 Motion::SectionEndBackward => {
646 hjkl_engine::motions::move_section_end_backward(ed.buffer_mut(), count);
647 }
648 Motion::SectionEndForward => {
649 hjkl_engine::motions::move_section_end_forward(ed.buffer_mut(), count);
650 }
651 Motion::FirstNonBlankNextLine => {
652 hjkl_engine::motions::move_first_non_blank_next_line(ed.buffer_mut(), count);
653 }
654 Motion::FirstNonBlankPrevLine => {
655 hjkl_engine::motions::move_first_non_blank_prev_line(ed.buffer_mut(), count);
656 }
657 Motion::FirstNonBlankLine => {
658 hjkl_engine::motions::move_first_non_blank_line(ed.buffer_mut(), count);
659 }
660 Motion::GotoColumn => {
661 hjkl_engine::motions::move_goto_column(ed.buffer_mut(), count);
662 }
663 }
664}
665pub(crate) fn move_first_non_whitespace<H: hjkl_engine::types::Host>(
666 ed: &mut Editor<hjkl_buffer::View, H>,
667) {
668 ed.sync_buffer_content_from_textarea();
674 hjkl_engine::motions::move_first_non_blank(ed.buffer_mut());
675}
676pub(crate) fn find_char_on_line<H: hjkl_engine::types::Host>(
677 ed: &mut Editor<hjkl_buffer::View, H>,
678 ch: char,
679 forward: bool,
680 till: bool,
681 skip_adjacent: bool,
682) -> bool {
683 hjkl_engine::motions::find_char_on_line(ed.buffer_mut(), ch, forward, till, skip_adjacent)
684}
685pub(crate) fn matching_bracket<H: hjkl_engine::types::Host>(
686 ed: &mut Editor<hjkl_buffer::View, H>,
687) -> bool {
688 hjkl_engine::motions::match_bracket(ed.buffer_mut())
689}
690pub(crate) fn goto_unmatched_bracket<H: hjkl_engine::types::Host>(
694 ed: &mut Editor<hjkl_buffer::View, H>,
695 forward: bool,
696 open: char,
697 count: usize,
698) {
699 let close = match open {
700 '(' => ')',
701 '{' => '}',
702 _ => return,
703 };
704 let cursor = buf_cursor_pos(ed.buffer());
705 let rows = buf_row_count(ed.buffer());
706 let target = count.max(1);
707 let mut found = 0usize;
708 let mut depth = 0i32;
709
710 if forward {
711 let mut r = cursor.row;
712 let mut from_col = cursor.col + 1;
713 while r < rows {
714 let line: Vec<char> = buf_line(ed.buffer(), r)
715 .unwrap_or_default()
716 .chars()
717 .collect();
718 let mut ci = from_col;
719 while ci < line.len() {
720 let ch = line[ci];
721 if ch == open {
722 depth += 1;
723 } else if ch == close {
724 if depth == 0 {
725 found += 1;
726 if found == target {
727 buf_set_cursor_rc(ed.buffer_mut(), r, ci);
728 return;
729 }
730 } else {
731 depth -= 1;
732 }
733 }
734 ci += 1;
735 }
736 r += 1;
737 from_col = 0;
738 }
739 } else {
740 let mut r = cursor.row as isize;
741 let mut from_col = cursor.col as isize - 1;
744 while r >= 0 {
745 let line: Vec<char> = buf_line(ed.buffer(), r as usize)
746 .unwrap_or_default()
747 .chars()
748 .collect();
749 let mut ci = from_col.min(line.len() as isize - 1);
750 while ci >= 0 {
751 let ch = line[ci as usize];
752 if ch == close {
753 depth += 1;
754 } else if ch == open {
755 if depth == 0 {
756 found += 1;
757 if found == target {
758 buf_set_cursor_rc(ed.buffer_mut(), r as usize, ci as usize);
759 return;
760 }
761 } else {
762 depth -= 1;
763 }
764 }
765 ci -= 1;
766 }
767 r -= 1;
768 from_col = isize::MAX;
769 }
770 }
771}
772pub(crate) fn word_at_cursor_search<H: hjkl_engine::types::Host>(
773 ed: &mut Editor<hjkl_buffer::View, H>,
774 forward: bool,
775 whole_word: bool,
776 count: usize,
777) {
778 let (row, col) = ed.cursor();
779 let line: String = buf_line(ed.buffer(), row).unwrap_or_default();
780 let chars: Vec<char> = line.chars().collect();
781 if chars.is_empty() {
782 return;
783 }
784 let spec = ed.settings().iskeyword.clone();
786 let is_word = |c: char| is_keyword_char(c, &spec);
787 let mut start = col.min(chars.len().saturating_sub(1));
788 if is_word(chars[start]) {
789 while start > 0 && is_word(chars[start - 1]) {
790 start -= 1;
791 }
792 } else {
793 while start < chars.len() && !is_word(chars[start]) {
802 start += 1;
803 }
804 if start >= chars.len() {
805 return;
806 }
807 buf_set_cursor_rc(ed.buffer_mut(), row, start);
808 }
809 let mut end = start;
810 while end < chars.len() && is_word(chars[end]) {
811 end += 1;
812 }
813 if end <= start {
814 return;
815 }
816 let word: String = chars[start..end].iter().collect();
817 let escaped = regex_escape(&word);
818 let pattern = if whole_word {
819 format!(r"\b{escaped}\b")
820 } else {
821 escaped
822 };
823 ed.push_search_pattern(&pattern);
824 if ed.search_state().pattern.is_none() {
825 return;
826 }
827 ed.set_last_search_pattern_only(Some(pattern));
829 ed.set_last_search_forward_only(forward);
830 for _ in 0..count.max(1) {
831 if forward {
832 ed.search_advance_forward(true);
833 } else {
834 ed.search_advance_backward(true);
835 }
836 }
837}
838pub(crate) fn regex_escape(s: &str) -> String {
839 let mut out = String::with_capacity(s.len());
840 for c in s.chars() {
841 if matches!(
842 c,
843 '.' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$' | '\\'
844 ) {
845 out.push('\\');
846 }
847 out.push(c);
848 }
849 out
850}