1use hjkl_vim_types::{InsertReason, LastChange, Mode, Motion, Operator, RangeKind, VisualExtent};
6
7use hjkl_engine::rope_util::rope_line_to_str;
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 apply_visual_operator<H: hjkl_engine::types::Host>(
17 ed: &mut Editor<hjkl_buffer::View, H>,
18 op: Operator,
19 count: usize,
20) {
21 include_closed_folds_in_selection(ed);
22 visual_operator_body(ed, op, count);
23 remember_operator_column(ed);
24}
25
26fn include_closed_folds_in_selection<H: hjkl_engine::types::Host>(
31 ed: &mut Editor<hjkl_buffer::View, H>,
32) {
33 let mode = vim(ed).mode;
34 let anchor = match mode {
35 Mode::Visual => vim(ed).visual_anchor,
36 Mode::VisualLine => (vim(ed).visual_line_anchor, 0),
37 Mode::VisualBlock => vim(ed).block_anchor,
38 _ => return,
39 };
40 let cursor = ed.cursor();
41 let fold_edge = |ed: &Editor<hjkl_buffer::View, H>, pos: (usize, usize), last: bool| {
42 let (first, end) = expand_linewise_over_closed_folds(ed.buffer(), pos.0, pos.0);
43 if first == end {
44 pos
45 } else if last {
46 (end, buf_line_chars(ed.buffer(), end))
47 } else {
48 (first, 0)
49 }
50 };
51 let anchor_first = anchor < cursor;
52 let new_anchor = fold_edge(ed, anchor, !anchor_first);
53 let new_cursor = fold_edge(ed, cursor, anchor_first);
54 if (new_anchor, new_cursor) == (anchor, cursor) {
55 return;
56 }
57 match mode {
58 Mode::Visual => vim_mut(ed).visual_anchor = new_anchor,
59 Mode::VisualLine => vim_mut(ed).visual_line_anchor = new_anchor.0,
60 _ => {
61 vim_mut(ed).block_anchor = new_anchor;
62 vim_mut(ed).block_vcol = new_cursor.1;
63 }
64 }
65 buf_set_cursor_rc(ed.buffer_mut(), new_cursor.0, new_cursor.1);
66}
67
68pub fn remember_operator_column<H: hjkl_engine::types::Host>(
75 ed: &mut Editor<hjkl_buffer::View, H>,
76) {
77 if vim(ed).mode == Mode::Normal {
78 remember_cursor_column(ed);
79 }
80}
81
82pub fn remember_cursor_column<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>) {
86 let (row, col) = ed.cursor();
87 let want = ed.cursor_want(row, col);
88 ed.set_sticky_col(Some(want));
89}
90
91pub fn visual_op_start<H: hjkl_engine::types::Host>(
96 ed: &Editor<hjkl_buffer::View, H>,
97) -> (usize, usize) {
98 let cursor = ed.cursor();
99 match vim(ed).mode {
100 Mode::Visual => order(vim(ed).visual_anchor, cursor).0,
101 Mode::VisualLine if cursor.0 < vim(ed).visual_line_anchor => cursor,
102 Mode::VisualLine => (vim(ed).visual_line_anchor, 0),
103 Mode::VisualBlock => {
104 let (top, _, left, _) = block_bounds(ed);
105 (top, left)
106 }
107 _ => cursor,
108 }
109}
110
111fn visual_operator_body<H: hjkl_engine::types::Host>(
112 ed: &mut Editor<hjkl_buffer::View, H>,
113 op: Operator,
114 count: usize,
115) {
116 let levels = count.max(1);
119 let register = vim(ed).pending_register;
122 if matches!(op, Operator::Delete | Operator::Yank | Operator::Change)
123 && refuse_read_only_register(ed)
124 {
125 let start = visual_op_start(ed);
126 refused_operator(ed, op, start, RangeKind::Exclusive);
127 return;
128 }
129 match vim(ed).mode {
130 Mode::VisualLine => {
131 let (cursor_row, cursor_col) = ed.cursor();
132 let anchor_row = vim(ed).visual_line_anchor;
133 let top = cursor_row.min(anchor_row);
134 let bot = cursor_row.max(anchor_row);
135 ed.set_yank_linewise(true);
136 let start_col = if cursor_row < anchor_row {
142 cursor_col
143 } else {
144 0
145 };
146 if !matches!(op, Operator::Reflow | Operator::ReflowKeepCursor) {
147 buf_set_cursor_rc(ed.buffer_mut(), top, start_col);
148 }
149 match op {
150 Operator::Yank => {
151 let text = read_vim_range(ed, (top, 0), (bot, 0), RangeKind::Linewise);
152 if !text.is_empty() {
153 ed.record_yank_to_host(text.clone());
154 let target = vim_mut(ed).pending_register.take();
155 ed.record_yank(text, true, target);
156 }
157 vim_mut(ed).mode = Mode::Normal;
158 }
159 Operator::Delete => {
160 ed.push_undo();
161 let total = buf_row_count(ed.buffer());
169 let deleted_through_last = bot + 1 >= total;
170 cut_vim_range(ed, (top, 0), (bot, 0), RangeKind::Linewise);
171 land_after_linewise_delete(ed, top, deleted_through_last);
172 record_visual_last_change(
173 ed,
174 op,
175 VisualExtent::Line {
176 lines: bot - top + 1,
177 },
178 register,
179 1,
180 );
181 vim_mut(ed).mode = Mode::Normal;
182 }
183 Operator::Change => {
184 record_visual_last_change(
189 ed,
190 op,
191 VisualExtent::Line {
192 lines: bot - top + 1,
193 },
194 register,
195 1,
196 );
197 change_linewise_rows(ed, top, bot, ed.cursor().1);
198 }
199 Operator::Uppercase
200 | Operator::Lowercase
201 | Operator::ToggleCase
202 | Operator::Rot13 => {
203 apply_case_op_to_selection(ed, op, (top, 0), (bot, 0), RangeKind::Linewise);
204 buf_set_cursor_rc(ed.buffer_mut(), top, 0);
205 record_visual_last_change(
206 ed,
207 op,
208 VisualExtent::Line {
209 lines: bot - top + 1,
210 },
211 register,
212 1,
213 );
214 }
215 Operator::Indent | Operator::Outdent => {
216 ed.push_undo();
217 if op == Operator::Indent {
218 indent_rows(ed, top, bot, levels);
219 } else {
220 outdent_rows(ed, top, bot, levels);
221 }
222 record_visual_last_change(
223 ed,
224 op,
225 VisualExtent::Line {
226 lines: bot - top + 1,
227 },
228 register,
229 levels,
230 );
231 vim_mut(ed).mode = Mode::Normal;
232 }
233 Operator::Reflow => {
234 ed.push_undo();
235 let (cursor_row, _) = ed.cursor();
236 let bot = cursor_row.max(vim(ed).visual_line_anchor);
237 reflow_rows(ed, top, bot);
238 vim_mut(ed).mode = Mode::Normal;
239 }
240 Operator::ReflowKeepCursor => {
241 let saved = ed.cursor();
242 ed.push_undo();
243 let (cursor_row, _) = ed.cursor();
244 let bot = cursor_row.max(vim(ed).visual_line_anchor);
245 let (before, after, opts) = reflow_rows_keep_cursor(ed, top, bot);
246 let (new_row, new_col) =
247 reflow_keep_cursor(top, saved.0, saved.1, &before, &after, opts);
248 buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
249 vim_mut(ed).mode = Mode::Normal;
250 }
251 Operator::AutoIndent => {
252 ed.push_undo();
253 auto_indent_rows(ed, top, bot);
254 vim_mut(ed).mode = Mode::Normal;
255 }
256 Operator::Filter => {}
258 Operator::Comment => {}
260 Operator::Fold => unreachable!("Visual zf takes its own path"),
263 }
264 }
265 Mode::Visual => {
266 ed.set_yank_linewise(false);
267 let anchor = vim(ed).visual_anchor;
268 let cursor = ed.cursor();
269 let (top, bot) = order(anchor, cursor);
270 if !matches!(op, Operator::Reflow | Operator::ReflowKeepCursor) {
274 buf_set_cursor_rc(ed.buffer_mut(), top.0, top.1);
275 }
276 match op {
277 Operator::Yank => {
278 let (from, to, kind) = charwise_visual_range(ed, top, bot);
279 let text = read_vim_range(ed, from, to, kind);
280 if !text.is_empty() {
281 ed.record_yank_to_host(text.clone());
282 let target = vim_mut(ed).pending_register.take();
283 ed.record_yank(text, false, target);
284 }
285 buf_set_cursor_rc(ed.buffer_mut(), top.0, top.1);
286 vim_mut(ed).mode = Mode::Normal;
287 }
288 Operator::Delete => {
289 ed.push_undo();
290 let (from, to, kind) = charwise_visual_range(ed, top, bot);
291 cut_vim_range(ed, from, to, kind);
292 clamp_cursor_to_normal_mode(ed);
295 record_visual_last_change(ed, op, charwise_extent(top, bot), register, 1);
296 vim_mut(ed).mode = Mode::Normal;
297 }
298 Operator::Change => {
299 ed.push_undo();
300 record_visual_last_change(ed, op, charwise_extent(top, bot), register, 1);
303 let (from, to, kind) = charwise_visual_range(ed, top, bot);
304 cut_vim_range(ed, from, to, kind);
305 begin_insert_noundo(ed, 1, InsertReason::AfterChange);
306 }
307 Operator::Uppercase
308 | Operator::Lowercase
309 | Operator::ToggleCase
310 | Operator::Rot13 => {
311 let (from, to, kind) = charwise_visual_range(ed, top, bot);
312 apply_case_op_to_selection(ed, op, from, to, kind);
313 record_visual_last_change(ed, op, charwise_extent(top, bot), register, 1);
314 }
315 Operator::Indent | Operator::Outdent => {
316 ed.push_undo();
317 if op == Operator::Indent {
318 indent_rows(ed, top.0, bot.0, levels);
319 } else {
320 outdent_rows(ed, top.0, bot.0, levels);
321 }
322 record_visual_last_change(
326 ed,
327 op,
328 VisualExtent::Line {
329 lines: bot.0 - top.0 + 1,
330 },
331 register,
332 levels,
333 );
334 vim_mut(ed).mode = Mode::Normal;
335 }
336 Operator::Reflow => {
337 ed.push_undo();
338 let anchor = vim(ed).visual_anchor;
339 let cursor = ed.cursor();
340 let (top, bot) = order(anchor, cursor);
341 reflow_rows(ed, top.0, bot.0);
342 vim_mut(ed).mode = Mode::Normal;
343 }
344 Operator::ReflowKeepCursor => {
345 let saved = ed.cursor();
346 ed.push_undo();
347 let anchor = vim(ed).visual_anchor;
348 let cursor = ed.cursor();
349 let (top, bot) = order(anchor, cursor);
350 let (before, after, opts) = reflow_rows_keep_cursor(ed, top.0, bot.0);
351 let (new_row, new_col) =
352 reflow_keep_cursor(top.0, saved.0, saved.1, &before, &after, opts);
353 buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
354 vim_mut(ed).mode = Mode::Normal;
355 }
356 Operator::AutoIndent => {
357 ed.push_undo();
358 let anchor = vim(ed).visual_anchor;
359 let cursor = ed.cursor();
360 let (top, bot) = order(anchor, cursor);
361 auto_indent_rows(ed, top.0, bot.0);
362 vim_mut(ed).mode = Mode::Normal;
363 }
364 Operator::Filter => {}
366 Operator::Comment => {}
368 Operator::Fold => unreachable!("Visual zf takes its own path"),
369 }
370 }
371 Mode::VisualBlock => apply_block_operator(ed, op, levels),
372 _ => {}
373 }
374}
375fn record_visual_last_change<H: hjkl_engine::types::Host>(
378 ed: &mut Editor<hjkl_buffer::View, H>,
379 op: Operator,
380 extent: VisualExtent,
381 register: Option<char>,
382 levels: usize,
383) {
384 if !vim(ed).replaying {
385 vim_mut(ed).last_change = Some(LastChange::VisualOp {
386 op,
387 extent,
388 inserted: None,
389 register,
390 levels,
391 });
392 }
393}
394pub fn charwise_visual_range<H: hjkl_engine::types::Host>(
401 ed: &Editor<hjkl_buffer::View, H>,
402 top: (usize, usize),
403 bot: (usize, usize),
404) -> ((usize, usize), (usize, usize), RangeKind) {
405 let rows = buf_row_count(ed.buffer());
406 let next_is_line =
407 bot.0 + 1 < rows && !(bot.0 + 2 == rows && buf_line_chars(ed.buffer(), bot.0 + 1) == 0);
408 if bot.1 >= buf_line_chars(ed.buffer(), bot.0) && next_is_line {
409 (top, (bot.0 + 1, 0), RangeKind::Exclusive)
410 } else {
411 (top, bot, RangeKind::Inclusive)
412 }
413}
414fn charwise_extent(top: (usize, usize), bot: (usize, usize)) -> VisualExtent {
421 if top.0 == bot.0 {
422 VisualExtent::Char {
423 lines: 1,
424 width: bot.1 - top.1 + 1,
425 }
426 } else {
427 VisualExtent::Char {
428 lines: bot.0 - top.0 + 1,
429 width: bot.1 + 1,
430 }
431 }
432}
433fn block_extent(top: usize, bot: usize, cols: BlockCols) -> VisualExtent {
440 let to_eol = cols.right == usize::MAX;
441 VisualExtent::Block {
442 rows: bot - top + 1,
443 cols: if to_eol {
444 1
445 } else {
446 cols.right + 1 - cols.left
447 },
448 to_eol,
449 }
450}
451pub fn block_bounds<H: hjkl_engine::types::Host>(
456 ed: &Editor<hjkl_buffer::View, H>,
457) -> (usize, usize, usize, usize) {
458 let (ar, ac) = vim(ed).block_anchor;
459 let (cr, _) = ed.cursor();
460 let cc = vim(ed).block_vcol;
461 let top = ar.min(cr);
462 let bot = ar.max(cr);
463 let left = ac.min(cc);
464 let right = ac.max(cc);
465 (top, bot, left, right)
466}
467pub fn update_block_vcol<H: hjkl_engine::types::Host>(
477 ed: &mut Editor<hjkl_buffer::View, H>,
478 motion: &Motion,
479) {
480 match motion {
481 Motion::LineEnd => {
482 vim_mut(ed).block_vcol = ed.cursor().1;
483 vim_mut(ed).block_to_eol = true;
484 }
485 Motion::Left
486 | Motion::Right
487 | Motion::SpaceFwd
488 | Motion::BackspaceBack
489 | Motion::WordFwd
490 | Motion::BigWordFwd
491 | Motion::WordBack
492 | Motion::BigWordBack
493 | Motion::WordEnd
494 | Motion::BigWordEnd
495 | Motion::WordEndBack
496 | Motion::BigWordEndBack
497 | Motion::LineStart
498 | Motion::Home
499 | Motion::FirstNonBlank
500 | Motion::LastNonBlank
501 | Motion::LineMiddle
502 | Motion::ScreenLineMiddle
503 | Motion::ScreenLineStart
504 | Motion::ScreenLineFirstNonBlank
505 | Motion::ScreenLineEnd
506 | Motion::WordAtCursor { .. }
507 | Motion::Find { .. }
508 | Motion::FindRepeat { .. }
509 | Motion::MatchBracket
510 | Motion::FileTop
515 | Motion::FileBottom
516 | Motion::ParagraphPrev
519 | Motion::ParagraphNext
520 | Motion::SentencePrev
521 | Motion::SentenceNext
522 | Motion::SectionBackward
523 | Motion::SectionForward
524 | Motion::SectionEndBackward
525 | Motion::SectionEndForward
526 | Motion::UnmatchedBracket { .. }
527 | Motion::FirstNonBlankNextLine
528 | Motion::FirstNonBlankPrevLine
529 | Motion::FirstNonBlankLine
530 | Motion::GotoColumn
531 | Motion::SearchNext { .. } => {
532 vim_mut(ed).block_vcol = ed.cursor().1;
533 vim_mut(ed).block_to_eol = false;
534 }
535 Motion::Up | Motion::Down => {
540 vim_mut(ed).block_vcol = ed.cursor().1;
541 }
542 _ => {}
544 }
545}
546pub fn visual_selection_for_search<H: hjkl_engine::types::Host>(
547 ed: &mut Editor<hjkl_buffer::View, H>,
548) -> ((usize, usize), String) {
549 match vim(ed).mode {
550 Mode::Visual => {
551 let (top, bot) = order(vim(ed).visual_anchor, ed.cursor());
552 (top, read_vim_range(ed, top, bot, RangeKind::Inclusive))
553 }
554 Mode::VisualLine => {
555 let top = vim(ed).visual_line_anchor.min(ed.cursor().0);
556 let bot = vim(ed).visual_line_anchor.max(ed.cursor().0);
557 let text = read_vim_range(ed, (top, 0), (bot, 0), RangeKind::Linewise);
558 (
559 (top, 0),
560 text.strip_suffix('\n').unwrap_or(&text).to_owned(),
561 )
562 }
563 Mode::VisualBlock => {
564 let (top, bot, cols) = block_rect(ed);
565 (
566 (top, block_start_col(ed, top, cols)),
567 block_yank(ed, top, bot, cols),
568 )
569 }
570 Mode::Normal | Mode::Insert => (ed.cursor(), String::new()),
571 }
572}
573
574pub fn apply_block_operator<H: hjkl_engine::types::Host>(
582 ed: &mut Editor<hjkl_buffer::View, H>,
583 op: Operator,
584 count: usize,
585) {
586 let register = vim(ed).pending_register;
589 let (top, bot, cols) = block_rect(ed);
592 let left_col =
593 block_start_col(ed, top, cols).min(buf_line_chars(ed.buffer(), top).saturating_sub(1));
594 if matches!(op, Operator::Delete | Operator::Yank | Operator::Change)
595 && refuse_read_only_register(ed)
596 {
597 refused_operator(ed, op, (top, left_col), RangeKind::Exclusive);
598 return;
599 }
600 let yank = block_yank(ed, top, bot, cols);
602 let block_width = block_register_width(&yank, cols);
603 ed.jump_cursor(top, left_col);
607
608 match op {
609 Operator::Yank => {
610 if !yank.is_empty() {
611 ed.record_yank_to_host(yank.clone());
612 let target = vim_mut(ed).pending_register.take();
613 ed.record_yank_block(yank, block_width, target);
614 }
615 vim_mut(ed).mode = Mode::Normal;
616 ed.jump_cursor(top, left_col);
617 }
618 Operator::Delete => {
619 ed.push_undo();
620 delete_block_contents(ed, top, bot, cols);
621 if !yank.is_empty() {
622 ed.record_yank_to_host(yank.clone());
623 let target = vim_mut(ed).pending_register.take();
624 ed.record_delete_block(yank, block_width, target);
625 }
626 vim_mut(ed).mode = Mode::Normal;
627 let line_len = buf_line_chars(ed.buffer(), top);
632 ed.jump_cursor(top, left_col.min(line_len.saturating_sub(1)));
633 record_visual_last_change(ed, op, block_extent(top, bot, cols), register, 1);
634 }
635 Operator::Change => {
636 ed.push_undo();
637 record_visual_last_change(ed, op, block_extent(top, bot, cols), register, 1);
641 let top_line = buf_line(ed.buffer(), top).unwrap_or_default();
647 let top_span = block_row(&top_line, cols, ed.settings().tabstop, true);
648 let insert_col = top_span.start + top_span.startspaces;
649 delete_block_contents(ed, top, bot, cols);
650 if !yank.is_empty() {
651 ed.record_yank_to_host(yank.clone());
652 let target = vim_mut(ed).pending_register.take();
653 ed.record_delete_block(yank, block_width, target);
654 }
655 ed.jump_cursor(top, insert_col);
656 begin_insert_noundo(
657 ed,
658 1,
659 InsertReason::BlockChange {
660 top,
661 bot,
662 col: insert_col,
663 },
664 );
665 }
666 Operator::Uppercase | Operator::Lowercase | Operator::ToggleCase | Operator::Rot13 => {
667 ed.push_undo();
668 transform_block_case(ed, op, top, bot, cols);
669 vim_mut(ed).mode = Mode::Normal;
670 ed.jump_cursor(top, left_col);
671 record_visual_last_change(ed, op, block_extent(top, bot, cols), register, 1);
672 }
673 Operator::Indent | Operator::Outdent => {
674 ed.push_undo();
681 if op == Operator::Indent {
682 indent_block(ed, top, bot, cols, count.max(1));
683 } else {
684 outdent_block(ed, top, bot, cols, count.max(1));
685 }
686 vim_mut(ed).mode = Mode::Normal;
687 record_visual_last_change(ed, op, block_extent(top, bot, cols), register, count.max(1));
688 }
689 Operator::Fold => unreachable!("Visual zf takes its own path"),
690 Operator::Reflow => {
691 ed.push_undo();
695 reflow_rows(ed, top, bot);
696 vim_mut(ed).mode = Mode::Normal;
697 }
698 Operator::ReflowKeepCursor => {
699 let saved = ed.cursor();
701 ed.push_undo();
702 let (before, after, opts) = reflow_rows_keep_cursor(ed, top, bot);
703 let (new_row, new_col) =
704 reflow_keep_cursor(top, saved.0, saved.1, &before, &after, opts);
705 buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
706 vim_mut(ed).mode = Mode::Normal;
707 }
708 Operator::AutoIndent => {
709 ed.push_undo();
712 auto_indent_rows(ed, top, bot);
713 vim_mut(ed).mode = Mode::Normal;
714 }
715 Operator::Filter => {}
717 Operator::Comment => {}
719 }
720}
721pub fn transform_block_case<H: hjkl_engine::types::Host>(
725 ed: &mut Editor<hjkl_buffer::View, H>,
726 op: Operator,
727 top: usize,
728 bot: usize,
729 cols: BlockCols,
730) {
731 use hjkl_buffer::{Edit, Position};
732 let tabstop = ed.settings().tabstop;
733 let rope = hjkl_engine::types::Query::rope(ed.buffer());
734 for r in top..=bot.min(rope.len_lines().saturating_sub(1)) {
735 let line = rope_line_to_str(&rope, r);
736 let span = block_row(&line, cols, tabstop, false);
737 if span.start >= span.end {
738 continue;
739 }
740 let mid: String = line
741 .chars()
742 .skip(span.start)
743 .take(span.end - span.start)
744 .collect();
745 let transformed = match op {
746 Operator::Uppercase => mid.to_uppercase(),
747 Operator::Lowercase => mid.to_lowercase(),
748 Operator::ToggleCase => toggle_case_str(&mid),
749 Operator::Rot13 => rot13_str(&mid),
750 _ => mid,
751 };
752 ed.mutate_edit(Edit::Replace {
753 start: Position::new(r, span.start),
754 end: Position::new(r, span.end),
755 with: transformed,
756 });
757 }
758 let saved_yank = ed.yank();
759 let saved_linewise = ed.yank_linewise();
760 let start =
761 block_start_col(ed, top, cols).min(buf_line_chars(ed.buffer(), top).saturating_sub(1));
762 buf_set_cursor_rc(ed.buffer_mut(), top, start);
763 ed.set_yank(saved_yank);
764 ed.set_yank_linewise(saved_linewise);
765}
766pub fn block_start_col<H: hjkl_engine::types::Host>(
770 ed: &Editor<hjkl_buffer::View, H>,
771 row: usize,
772 cols: BlockCols,
773) -> usize {
774 let line = buf_line(ed.buffer(), row).unwrap_or_default();
775 block_row(&line, cols, ed.settings().tabstop, true).start
776}
777pub fn block_cols_at<H: hjkl_engine::types::Host>(
781 ed: &Editor<hjkl_buffer::View, H>,
782 row: usize,
783 col: usize,
784 width: usize,
785 to_eol: bool,
786) -> BlockCols {
787 let line = buf_line(ed.buffer(), row).unwrap_or_default();
788 let left = hjkl_buffer::char_col_to_visual_col(&line, col, ed.settings().tabstop);
789 BlockCols {
790 left,
791 right: if to_eol {
792 usize::MAX
793 } else {
794 left + width.max(1) - 1
795 },
796 }
797}
798pub fn block_yank<H: hjkl_engine::types::Host>(
803 ed: &Editor<hjkl_buffer::View, H>,
804 top: usize,
805 bot: usize,
806 cols: BlockCols,
807) -> String {
808 let tabstop = ed.settings().tabstop;
809 let rope = hjkl_engine::types::Query::rope(ed.buffer());
810 let n = rope.len_lines();
811 let mut rows: Vec<String> = Vec::new();
812 for r in top..=bot {
813 if r >= n {
814 break;
815 }
816 let line = rope_line_to_str(&rope, r);
817 let span = block_row(&line, cols, tabstop, false);
818 let mut text = " ".repeat(span.startspaces);
819 text.extend(line.chars().skip(span.start).take(span.end - span.start));
820 text.push_str(&" ".repeat(span.endspaces));
821 rows.push(text);
822 }
823 rows.join("\n")
824}
825pub fn block_register_width(yank: &str, cols: BlockCols) -> usize {
828 if cols.right == usize::MAX {
829 yank.split('\n')
830 .map(|s| s.chars().count())
831 .max()
832 .unwrap_or(0)
833 } else {
834 cols.right + 1 - cols.left
835 }
836}
837pub fn delete_block_contents<H: hjkl_engine::types::Host>(
842 ed: &mut Editor<hjkl_buffer::View, H>,
843 top: usize,
844 bot: usize,
845 cols: BlockCols,
846) {
847 use hjkl_buffer::{Edit, Position};
848 ed.sync_buffer_content_from_textarea();
849 let tabstop = ed.settings().tabstop;
850 let last_row = bot.min(buf_row_count(ed.buffer()).saturating_sub(1));
851 for row in top..=last_row {
852 let line = buf_line(ed.buffer(), row).unwrap_or_default();
853 let span = block_row(&line, cols, tabstop, true);
854 let spaces = span.startspaces + span.endspaces;
855 if span.start == span.end && spaces == 0 {
856 continue;
857 }
858 ed.mutate_edit(Edit::Replace {
859 start: Position::new(row, span.start),
860 end: Position::new(row, span.end),
861 with: " ".repeat(spaces),
862 });
863 }
864}
865pub fn block_replace<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>, ch: char) {
868 let (top, bot, cols) = block_rect(ed);
869 let to_eol = vim(ed).block_to_eol;
870 if !vim(ed).replaying {
872 vim_mut(ed).last_change = Some(LastChange::VisualBlockReplace {
873 ch,
874 rows: bot - top + 1,
875 cols: if to_eol {
876 1
877 } else {
878 cols.right + 1 - cols.left
879 },
880 to_eol,
881 });
882 }
883 block_replace_bounds(ed, ch, top, bot, cols);
884}
885pub fn block_replace_bounds<H: hjkl_engine::types::Host>(
892 ed: &mut Editor<hjkl_buffer::View, H>,
893 ch: char,
894 top: usize,
895 bot: usize,
896 cols: BlockCols,
897) {
898 ed.push_undo();
899 ed.sync_buffer_content_from_textarea();
900 use hjkl_buffer::{Edit, Position};
901 let tabstop = ed.settings().tabstop;
902 let rope = hjkl_engine::types::Query::rope(ed.buffer());
903 for r in top..=bot.min(rope.len_lines().saturating_sub(1)) {
904 let line = rope_line_to_str(&rope, r);
905 let span = block_row(&line, cols, tabstop, true);
906 let end_vcol = hjkl_buffer::char_col_to_visual_col(&line, span.end, tabstop);
907 let short = cols.right == usize::MAX || end_vcol <= cols.right;
908 let cells = if short {
909 end_vcol.saturating_sub(cols.left)
910 } else {
911 cols.right + 1 - cols.left
912 };
913 if span.start == span.end || cells == 0 {
914 continue;
915 }
916 let mut with = " ".repeat(span.startspaces);
917 with.extend(std::iter::repeat_n(ch, cells));
918 if !short {
919 with.push_str(&" ".repeat(span.endspaces));
920 }
921 ed.mutate_edit(Edit::Replace {
922 start: Position::new(r, span.start),
923 end: Position::new(r, span.end),
924 with,
925 });
926 }
927 vim_mut(ed).mode = Mode::Normal;
928 let start =
929 block_start_col(ed, top, cols).min(buf_line_chars(ed.buffer(), top).saturating_sub(1));
930 ed.jump_cursor(top, start);
931}
932pub fn replicate_block_text<H: hjkl_engine::types::Host>(
944 ed: &mut Editor<hjkl_buffer::View, H>,
945 inserted: &str,
946 top: usize,
947 bot: usize,
948 col: usize,
949 pad: bool,
950 to_eol: bool,
951) {
952 use hjkl_buffer::{Edit, Position};
953 let tabstop = ed.settings().tabstop;
957 let top_line = buf_line(ed.buffer(), top).unwrap_or_default();
958 let vcol = hjkl_buffer::char_col_to_visual_col(&top_line, col, tabstop);
959 for r in (top + 1)..=bot {
960 if !to_eol {
961 let line = buf_line(ed.buffer(), r).unwrap_or_default();
962 if let Some((tab, start, width)) = tab_cut_at(&line, vcol, tabstop) {
963 ed.mutate_edit(Edit::Replace {
964 start: Position::new(r, tab),
965 end: Position::new(r, tab + 1),
966 with: " ".repeat(width),
967 });
968 ed.mutate_edit(Edit::InsertStr {
969 at: Position::new(r, tab + vcol - start),
970 text: inserted.to_string(),
971 });
972 continue;
973 }
974 }
975 let line_len = buf_line_chars(ed.buffer(), r);
976 let line = buf_line(ed.buffer(), r).unwrap_or_default();
977 let row_col = if to_eol {
978 line_len
979 } else {
980 match char_at_vcol(&line, vcol, tabstop) {
981 Ok(c) => c,
982 Err(short) => {
983 if !pad {
984 continue;
987 }
988 ed.mutate_edit(Edit::InsertStr {
989 at: Position::new(r, line_len),
990 text: " ".repeat(short),
991 });
992 line_len + short
993 }
994 }
995 };
996 ed.mutate_edit(Edit::InsertStr {
997 at: Position::new(r, row_col),
998 text: inserted.to_string(),
999 });
1000 }
1001}
1002pub fn replay_block_visual_op<H: hjkl_engine::types::Host>(
1007 ed: &mut Editor<hjkl_buffer::View, H>,
1008 op: Operator,
1009 rows: usize,
1010 cols: usize,
1011 to_eol: bool,
1012 inserted: Option<String>,
1013 levels: usize,
1014) {
1015 let (top, left) = ed.cursor();
1016 let bot = top + rows.saturating_sub(1);
1017 let cols = block_cols_at(ed, top, left, cols, to_eol);
1022 let record_delete = |ed: &mut Editor<hjkl_buffer::View, H>| {
1023 let yank = block_yank(ed, top, bot, cols);
1024 if !yank.is_empty() {
1025 let block_width = block_register_width(&yank, cols);
1026 ed.record_yank_to_host(yank.clone());
1027 let target = vim_mut(ed).pending_register.take();
1031 ed.record_delete_block(yank, block_width, target);
1032 }
1033 };
1034 match op {
1035 Operator::Delete => {
1036 ed.push_undo();
1037 record_delete(ed);
1038 delete_block_contents(ed, top, bot, cols);
1039 let line_len = buf_line_chars(ed.buffer(), top);
1040 ed.jump_cursor(top, left.min(line_len.saturating_sub(1)));
1041 }
1042 Operator::Uppercase | Operator::Lowercase | Operator::ToggleCase | Operator::Rot13 => {
1043 ed.push_undo();
1044 transform_block_case(ed, op, top, bot, cols);
1045 ed.jump_cursor(top, left);
1046 }
1047 Operator::Indent => {
1048 ed.push_undo();
1049 indent_block(ed, top, bot, cols, levels);
1050 }
1051 Operator::Outdent => {
1052 ed.push_undo();
1053 outdent_block(ed, top, bot, cols, levels);
1054 }
1055 Operator::Change => {
1056 ed.push_undo();
1057 record_delete(ed);
1058 let top_line = buf_line(ed.buffer(), top).unwrap_or_default();
1061 let top_span = block_row(&top_line, cols, ed.settings().tabstop, true);
1062 let left = top_span.start + top_span.startspaces;
1063 delete_block_contents(ed, top, bot, cols);
1064 let text = inserted.unwrap_or_default();
1065 if text.is_empty() {
1066 ed.jump_cursor(top, left);
1067 } else {
1068 use hjkl_buffer::{Edit, Position};
1069 ed.mutate_edit(Edit::InsertStr {
1072 at: Position::new(top, left),
1073 text: text.clone(),
1074 });
1075 replicate_block_text(ed, &text, top, bot, left, false, false);
1076 let ins_chars = text.chars().count();
1077 let line_len = buf_line_chars(ed.buffer(), top);
1078 ed.jump_cursor(
1079 top,
1080 (left + ins_chars)
1081 .saturating_sub(1)
1082 .min(line_len.saturating_sub(1)),
1083 );
1084 }
1085 }
1086 _ => {}
1087 }
1088 vim_mut(ed).mode = Mode::Normal;
1089}
1090pub fn replay_block_replace<H: hjkl_engine::types::Host>(
1093 ed: &mut Editor<hjkl_buffer::View, H>,
1094 ch: char,
1095 rows: usize,
1096 cols: usize,
1097 to_eol: bool,
1098) {
1099 let (top, left) = ed.cursor();
1100 let bot = top + rows.saturating_sub(1);
1101 let cols = block_cols_at(ed, top, left, cols, to_eol);
1102 block_replace_bounds(ed, ch, top, bot, cols);
1103}
1104pub fn replay_block_insert<H: hjkl_engine::types::Host>(
1110 ed: &mut Editor<hjkl_buffer::View, H>,
1111 text: &str,
1112 rows: usize,
1113 cols: usize,
1114 to_eol: bool,
1115 append: bool,
1116) {
1117 use hjkl_buffer::{Edit, Position};
1118 let (top, left) = ed.cursor();
1119 let bot = top + rows.saturating_sub(1);
1120 ed.push_undo();
1121 ed.sync_buffer_content_from_textarea();
1122 let line_len = buf_line_chars(ed.buffer(), top);
1127 let top_col = if append {
1128 if to_eol {
1129 line_len
1130 } else {
1131 let line = buf_line(ed.buffer(), top).unwrap_or_default();
1132 let tabstop = ed.settings().tabstop;
1133 let target = hjkl_buffer::char_col_to_visual_col(&line, left, tabstop) + cols;
1134 match char_at_vcol(&line, target, tabstop) {
1135 Ok(c) => c,
1136 Err(short) => {
1137 ed.mutate_edit(Edit::InsertStr {
1138 at: Position::new(top, line_len),
1139 text: " ".repeat(short),
1140 });
1141 line_len + short
1142 }
1143 }
1144 }
1145 } else {
1146 left
1147 };
1148 ed.mutate_edit(Edit::InsertStr {
1151 at: Position::new(top, top_col),
1152 text: text.to_string(),
1153 });
1154 replicate_block_text(ed, text, top, bot, top_col, append, to_eol);
1155 ed.jump_cursor(top, left);
1156 vim_mut(ed).mode = Mode::Normal;
1157}
1158pub fn visual_replace_char<H: hjkl_engine::types::Host>(
1166 ed: &mut Editor<hjkl_buffer::View, H>,
1167 ch: char,
1168) {
1169 let linewise = matches!(vim(ed).mode, Mode::VisualLine);
1170 let (top, bot) = if linewise {
1171 let cursor_row = buf_cursor_pos(ed.buffer()).row;
1172 let anchor_row = vim(ed).visual_line_anchor;
1173 (
1174 (cursor_row.min(anchor_row), 0),
1175 (cursor_row.max(anchor_row), 0),
1176 )
1177 } else {
1178 let anchor = vim(ed).visual_anchor;
1179 let cursor = ed.cursor();
1180 order(anchor, cursor)
1181 };
1182 if !vim(ed).replaying {
1187 let extent = if linewise {
1188 VisualExtent::Line {
1189 lines: bot.0 - top.0 + 1,
1190 }
1191 } else {
1192 charwise_extent(top, bot)
1193 };
1194 vim_mut(ed).last_change = Some(LastChange::VisualReplace { ch, extent });
1195 }
1196 let start = if !linewise {
1199 top
1200 } else if ed.cursor().0 < vim(ed).visual_line_anchor {
1201 ed.cursor()
1202 } else {
1203 top
1204 };
1205 buf_set_cursor_rc(ed.buffer_mut(), start.0, start.1);
1206 replace_range_with_char(ed, linewise, top, bot, ch);
1207}
1208fn replace_range_with_char<H: hjkl_engine::types::Host>(
1214 ed: &mut Editor<hjkl_buffer::View, H>,
1215 linewise: bool,
1216 top: (usize, usize),
1217 bot: (usize, usize),
1218 ch: char,
1219) {
1220 ed.push_undo();
1221 ed.sync_buffer_content_from_textarea();
1222 use hjkl_buffer::{Edit, Position};
1223 let rope = hjkl_engine::types::Query::rope(ed.buffer());
1224 let last_row = bot.0.min(rope.len_lines().saturating_sub(1));
1225 for row in top.0..=last_row {
1226 let chars: Vec<char> = rope_line_to_str(&rope, row).chars().collect();
1227 let lo = if !linewise && row == top.0 { top.1 } else { 0 };
1228 let hi = if !linewise && row == bot.0 {
1229 (bot.1 + 1).min(chars.len())
1230 } else {
1231 chars.len()
1232 };
1233 if lo >= hi {
1234 continue;
1235 }
1236 let middle: String = std::iter::repeat_n(ch, hi - lo).collect();
1238 ed.mutate_edit(Edit::Replace {
1239 start: Position::new(row, lo),
1240 end: Position::new(row, hi),
1241 with: middle,
1242 });
1243 }
1244 vim_mut(ed).mode = Mode::Normal;
1245 ed.jump_cursor(top.0, if linewise { 0 } else { top.1 });
1246}
1247pub fn replay_visual_replace<H: hjkl_engine::types::Host>(
1253 ed: &mut Editor<hjkl_buffer::View, H>,
1254 ch: char,
1255 extent: VisualExtent,
1256) {
1257 let (r, c) = ed.cursor();
1258 match extent {
1259 VisualExtent::Line { lines } => {
1260 let top = (r, 0);
1261 let bot = (r + lines.saturating_sub(1), 0);
1262 replace_range_with_char(ed, true, top, bot, ch);
1263 }
1264 VisualExtent::Char { lines, width } => {
1265 let top = (r, c);
1266 let bot = if lines <= 1 {
1271 (r, c + width.saturating_sub(1))
1272 } else {
1273 (r + lines - 1, width.saturating_sub(1))
1274 };
1275 replace_range_with_char(ed, false, top, bot, ch);
1276 }
1277 VisualExtent::Block { .. } => {}
1278 }
1279}