1use alloc::{collections::BTreeMap, string::String};
2use core::cmp;
3use modit::{Event, Key, Parser, TextObject, WordIter};
4
5use crate::{
6 Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, FontSystem,
7 Motion, Renderer, Selection, SyntaxEditor, SyntaxTheme,
8};
9
10pub use modit::{ViMode, ViParser};
11use unicode_segmentation::UnicodeSegmentation;
12
13fn undo_2_action<'buffer, E: Edit<'buffer>>(
14 editor: &mut E,
15 action: cosmic_undo_2::Action<&Change>,
16) {
17 match action {
18 cosmic_undo_2::Action::Do(change) => {
19 editor.apply_change(change);
20 }
21 cosmic_undo_2::Action::Undo(change) => {
22 let mut reversed = change.clone();
24 reversed.reverse();
25 editor.apply_change(&reversed);
26 }
27 }
28}
29
30fn finish_change<'buffer, E: Edit<'buffer>>(
31 editor: &mut E,
32 commands: &mut cosmic_undo_2::Commands<Change>,
33 changed: &mut bool,
34 pivot: Option<usize>,
35) -> Option<Change> {
36 match editor.finish_change() {
38 Some(change) => {
39 if !change.items.is_empty() {
40 commands.push(change.clone());
41 *changed = eval_changed(commands, pivot);
42 }
43 Some(change)
44 }
45 None => None,
46 }
47}
48
49fn eval_changed(commands: &cosmic_undo_2::Commands<Change>, pivot: Option<usize>) -> bool {
51 match (commands.current_command_index(), pivot) {
56 (Some(current), Some(pivot)) => current != pivot,
57 (None, None) => false,
61 _ => true,
64 }
65}
66
67fn search<'buffer, E: Edit<'buffer>>(editor: &mut E, value: &str, forwards: bool) -> bool {
68 let mut cursor = editor.cursor();
69 let start_line = cursor.line;
70 if forwards {
71 while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) {
72 if let Some(index) = editor.with_buffer(|buffer| {
73 buffer.lines[cursor.line]
74 .text()
75 .match_indices(value)
76 .filter_map(|(i, _)| {
77 if cursor.line != start_line || i > cursor.index {
78 Some(i)
79 } else {
80 None
81 }
82 })
83 .next()
84 }) {
85 cursor.index = index;
86 editor.set_cursor(cursor);
87 return true;
88 }
89
90 cursor.line += 1;
91 }
92 } else {
93 cursor.line += 1;
94 while cursor.line > 0 {
95 cursor.line -= 1;
96
97 if let Some(index) = editor.with_buffer(|buffer| {
98 buffer.lines[cursor.line]
99 .text()
100 .rmatch_indices(value)
101 .filter_map(|(i, _)| {
102 if cursor.line != start_line || i < cursor.index {
103 Some(i)
104 } else {
105 None
106 }
107 })
108 .next()
109 }) {
110 cursor.index = index;
111 editor.set_cursor(cursor);
112 return true;
113 }
114 }
115 }
116 false
117}
118
119fn select_in<'buffer, E: Edit<'buffer>>(editor: &mut E, start_c: char, end_c: char, include: bool) {
120 let cursor = editor.cursor();
122 let (start, end) = editor.with_buffer(|buffer| {
123 let mut end = cursor;
125 let mut starts = 0;
126 let mut ends = 0;
127 'find_end: loop {
128 let line = &buffer.lines[end.line];
129 let text = line.text();
130 for (i, c) in text[end.index..].char_indices() {
131 if c == end_c {
132 ends += 1;
133 } else if c == start_c {
134 starts += 1;
135 }
136 if ends > starts {
137 end.index += if include { i + c.len_utf8() } else { i };
138 break 'find_end;
139 }
140 }
141 if end.line + 1 < buffer.lines.len() {
142 end.line += 1;
143 end.index = 0;
144 } else {
145 break 'find_end;
146 }
147 }
148
149 let mut start = cursor;
151 'find_start: loop {
152 let line = &buffer.lines[start.line];
153 let text = line.text();
154 for (i, c) in text[..start.index].char_indices().rev() {
155 if c == start_c {
156 starts += 1;
157 } else if c == end_c {
158 ends += 1;
159 }
160 if starts >= ends {
161 start.index = if include { i } else { i + c.len_utf8() };
162 break 'find_start;
163 }
164 }
165 if start.line > 0 {
166 start.line -= 1;
167 start.index = buffer.lines[start.line].text().len();
168 } else {
169 break 'find_start;
170 }
171 }
172
173 (start, end)
174 });
175
176 editor.set_selection(Selection::Normal(start));
177 editor.set_cursor(end);
178}
179
180#[derive(Debug)]
181pub struct ViEditor<'syntax_system, 'buffer> {
182 editor: SyntaxEditor<'syntax_system, 'buffer>,
183 parser: ViParser,
184 passthrough: bool,
185 registers: BTreeMap<char, (Selection, String)>,
186 search_opt: Option<(String, bool)>,
187 commands: cosmic_undo_2::Commands<Change>,
188 changed: bool,
189 save_pivot: Option<usize>,
190}
191
192impl<'syntax_system, 'buffer> ViEditor<'syntax_system, 'buffer> {
193 pub fn new(editor: SyntaxEditor<'syntax_system, 'buffer>) -> Self {
194 Self {
195 editor,
196 parser: ViParser::new(),
197 passthrough: false,
198 registers: BTreeMap::new(),
199 search_opt: None,
200 commands: cosmic_undo_2::Commands::new(),
201 changed: false,
202 save_pivot: None,
203 }
204 }
205
206 pub fn update_theme(&mut self, theme_name: &str) -> bool {
208 self.editor.update_theme(theme_name)
209 }
210
211 #[cfg(feature = "std")]
216 pub fn load_text<P: AsRef<std::path::Path>>(
217 &mut self,
218 font_system: &mut FontSystem,
219 path: P,
220 attrs: crate::Attrs,
221 ) -> std::io::Result<()> {
222 self.editor.load_text(font_system, path, attrs)
223 }
224
225 pub fn background_color(&self) -> Color {
227 self.editor.background_color()
228 }
229
230 pub fn foreground_color(&self) -> Color {
232 self.editor.foreground_color()
233 }
234
235 pub fn cursor_color(&self) -> Color {
237 self.editor.cursor_color()
238 }
239
240 pub fn selection_color(&self) -> Color {
242 self.editor.selection_color()
243 }
244
245 pub fn theme(&self) -> &SyntaxTheme {
247 self.editor.theme()
248 }
249
250 pub fn changed(&self) -> bool {
252 self.changed
253 }
254
255 pub fn set_changed(&mut self, changed: bool) {
257 self.changed = changed;
258 }
259
260 pub fn save_point(&mut self) {
270 self.save_pivot = Some(self.commands.current_command_index().unwrap_or_default());
271 self.changed = false;
272 }
273
274 pub fn set_passthrough(&mut self, passthrough: bool) {
276 if passthrough != self.passthrough {
277 self.passthrough = passthrough;
278 self.with_buffer_mut(|buffer| buffer.set_redraw(true));
279 }
280 }
281
282 pub fn parser(&self) -> &ViParser {
284 &self.parser
285 }
286
287 pub fn redo(&mut self) {
289 log::debug!("Redo");
290 for action in self.commands.redo() {
291 undo_2_action(&mut self.editor, action);
292 }
293 self.changed = eval_changed(&self.commands, self.save_pivot);
294 }
295
296 pub fn undo(&mut self) {
298 log::debug!("Undo");
299 for action in self.commands.undo() {
300 undo_2_action(&mut self.editor, action);
301 }
302 self.changed = eval_changed(&self.commands, self.save_pivot);
303 }
304
305 #[cfg(feature = "swash")]
309 pub fn draw<F>(
310 &mut self,
311 font_system: &mut FontSystem,
312 cache: &mut crate::SwashCache,
313 callback: F,
314 ) where
315 F: FnMut(i32, i32, u32, u32, Color),
316 {
317 self.with_buffer_mut(|buffer| buffer.shape_until_scroll(font_system, false));
318 let mut renderer = crate::LegacyRenderer {
319 font_system,
320 cache,
321 callback,
322 };
323 self.render(&mut renderer);
324 }
325
326 pub fn render<R: Renderer>(&self, renderer: &mut R) {
331 let background_color = self.background_color();
332 let foreground_color = self.foreground_color();
333 let cursor_color = self.cursor_color();
334 let selection_color = self.selection_color();
335 self.with_buffer(|buffer| {
336 let size = buffer.size();
337 if let Some(width) = size.0 {
338 if let Some(height) = size.1 {
339 renderer.rectangle(0, 0, width as u32, height as u32, background_color);
340 }
341 }
342 let font_size = buffer.metrics().font_size;
343 for run in buffer.layout_runs() {
344 let line_i = run.line_i;
345 let line_y = run.line_y;
346 let line_top = run.line_top;
347 let line_height = run.line_height;
348
349 let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> {
350 let default_width = font_size / 2.0;
352 if cursor.line == line_i {
353 for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
354 if cursor.index >= glyph.start && cursor.index < glyph.end {
355 let mut before = 0;
357 let mut total = 0;
358
359 let cluster = &run.text[glyph.start..glyph.end];
360 for (i, _) in cluster.grapheme_indices(true) {
361 if glyph.start + i < cursor.index {
362 before += 1;
363 }
364 total += 1;
365 }
366
367 let width = glyph.w / (total as f32);
368 let offset = (before as f32) * width;
369 return Some((glyph_i, offset, width));
370 }
371 }
372 match run.glyphs.last() {
373 Some(glyph) => {
374 if cursor.index == glyph.end {
375 return Some((run.glyphs.len(), 0.0, default_width));
376 }
377 }
378 None => {
379 return Some((0, 0.0, default_width));
380 }
381 }
382 }
383 None
384 };
385
386 if let Some((start, end)) = self.selection_bounds() {
388 if line_i >= start.line && line_i <= end.line {
389 let mut range_opt = None;
390 for glyph in run.glyphs.iter() {
391 let cluster = &run.text[glyph.start..glyph.end];
393 let total = cluster.grapheme_indices(true).count();
394 let mut c_x = glyph.x;
395 let c_w = glyph.w / total as f32;
396 for (i, c) in cluster.grapheme_indices(true) {
397 let c_start = glyph.start + i;
398 let c_end = glyph.start + i + c.len();
399 if (start.line != line_i || c_end > start.index)
400 && (end.line != line_i || c_start < end.index)
401 {
402 range_opt = match range_opt.take() {
403 Some((min, max)) => Some((
404 cmp::min(min, c_x as i32),
405 cmp::max(max, (c_x + c_w) as i32),
406 )),
407 None => Some((c_x as i32, (c_x + c_w) as i32)),
408 };
409 } else if let Some((min, max)) = range_opt.take() {
410 renderer.rectangle(
411 min,
412 line_top as i32,
413 cmp::max(0, max - min) as u32,
414 line_height as u32,
415 selection_color,
416 );
417 }
418 c_x += c_w;
419 }
420 }
421
422 if run.glyphs.is_empty() && end.line > line_i {
423 range_opt = Some((0, buffer.size().0.unwrap_or(0.0) as i32));
425 }
426
427 if let Some((mut min, mut max)) = range_opt.take() {
428 if end.line > line_i {
429 if run.rtl {
431 min = 0;
432 } else {
433 max = buffer.size().0.unwrap_or(0.0) as i32;
434 }
435 }
436 renderer.rectangle(
437 min,
438 line_top as i32,
439 cmp::max(0, max - min) as u32,
440 line_height as u32,
441 selection_color,
442 );
443 }
444 }
445 }
446
447 if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
449 cursor_glyph_opt(&self.cursor())
450 {
451 let block_cursor = if self.passthrough {
452 false
453 } else {
454 match self.parser.mode {
455 ViMode::Insert | ViMode::Replace => false,
456 _ => true, }
458 };
459
460 let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
461 Some(glyph) => {
462 if glyph.level.is_rtl() {
464 (
465 (glyph.x + glyph.w - cursor_glyph_offset) as i32,
466 (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
467 as i32,
468 )
469 } else {
470 (
471 (glyph.x + cursor_glyph_offset) as i32,
472 (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
473 )
474 }
475 }
476 None => match run.glyphs.last() {
477 Some(glyph) => {
478 if glyph.level.is_rtl() {
480 (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
481 } else {
482 (
483 (glyph.x + glyph.w) as i32,
484 (glyph.x + glyph.w + cursor_glyph_width) as i32,
485 )
486 }
487 }
488 None => {
489 (0, cursor_glyph_width as i32)
491 }
492 },
493 };
494
495 if block_cursor {
496 let left_x = cmp::min(start_x, end_x);
497 let right_x = cmp::max(start_x, end_x);
498 renderer.rectangle(
499 left_x,
500 line_top as i32,
501 (right_x - left_x) as u32,
502 line_height as u32,
503 selection_color,
504 );
505 } else {
506 renderer.rectangle(
507 start_x,
508 line_top as i32,
509 1,
510 line_height as u32,
511 cursor_color,
512 );
513 }
514 }
515
516 for glyph in run.glyphs.iter() {
517 let physical_glyph = glyph.physical((0., line_y), 1.0);
518
519 let glyph_color = match glyph.color_opt {
520 Some(some) => some,
521 None => foreground_color,
522 };
523
524 renderer.glyph(physical_glyph, glyph_color);
525 }
526 }
527 });
528 }
529}
530
531impl<'buffer> Edit<'buffer> for ViEditor<'_, 'buffer> {
532 fn buffer_ref(&self) -> &BufferRef<'buffer> {
533 self.editor.buffer_ref()
534 }
535
536 fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer> {
537 self.editor.buffer_ref_mut()
538 }
539
540 fn cursor(&self) -> Cursor {
541 self.editor.cursor()
542 }
543
544 fn set_cursor(&mut self, cursor: Cursor) {
545 self.editor.set_cursor(cursor);
546 }
547
548 fn selection(&self) -> Selection {
549 self.editor.selection()
550 }
551
552 fn set_selection(&mut self, selection: Selection) {
553 self.editor.set_selection(selection);
554 }
555
556 fn auto_indent(&self) -> bool {
557 self.editor.auto_indent()
558 }
559
560 fn set_auto_indent(&mut self, auto_indent: bool) {
561 self.editor.set_auto_indent(auto_indent);
562 }
563
564 fn tab_width(&self) -> u16 {
565 self.editor.tab_width()
566 }
567
568 fn set_tab_width(&mut self, tab_width: u16) {
569 self.editor.set_tab_width(tab_width);
570 }
571
572 fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
573 self.editor.shape_as_needed(font_system, prune);
574 }
575
576 fn delete_range(&mut self, start: Cursor, end: Cursor) {
577 self.editor.delete_range(start, end);
578 }
579
580 fn insert_at(&mut self, cursor: Cursor, data: &str, attrs_list: Option<AttrsList>) -> Cursor {
581 self.editor.insert_at(cursor, data, attrs_list)
582 }
583
584 fn copy_selection(&self) -> Option<String> {
585 self.editor.copy_selection()
586 }
587
588 fn delete_selection(&mut self) -> bool {
589 self.editor.delete_selection()
590 }
591
592 fn apply_change(&mut self, change: &Change) -> bool {
593 self.editor.apply_change(change)
594 }
595
596 fn start_change(&mut self) {
597 self.editor.start_change();
598 }
599
600 fn finish_change(&mut self) -> Option<Change> {
601 finish_change(
602 &mut self.editor,
603 &mut self.commands,
604 &mut self.changed,
605 self.save_pivot,
606 )
607 }
608
609 fn action(&mut self, font_system: &mut FontSystem, action: Action) {
610 log::debug!("Action {action:?}");
611
612 let editor = &mut self.editor;
613
614 editor.start_change();
616
617 if self.passthrough {
618 editor.action(font_system, action);
619 finish_change(
621 editor,
622 &mut self.commands,
623 &mut self.changed,
624 self.save_pivot,
625 );
626 return;
627 }
628
629 let key = match action {
630 Action::Backspace => Key::Backspace,
632 Action::Delete => Key::Delete,
633 Action::Motion(Motion::Down) => Key::Down,
634 Action::Motion(Motion::End) => Key::End,
635 Action::Enter => Key::Enter,
636 Action::Escape => Key::Escape,
637 Action::Motion(Motion::Home) => Key::Home,
638 Action::Indent => Key::Tab,
639 Action::Insert(c) => Key::Char(c),
640 Action::Motion(Motion::Left) => Key::Left,
641 Action::Motion(Motion::PageDown) => Key::PageDown,
642 Action::Motion(Motion::PageUp) => Key::PageUp,
643 Action::Motion(Motion::Right) => Key::Right,
644 Action::Unindent => Key::Backtab,
645 Action::Motion(Motion::Up) => Key::Up,
646 _ => {
647 log::debug!("Pass through action {action:?}");
648 editor.action(font_system, action);
649 finish_change(
651 editor,
652 &mut self.commands,
653 &mut self.changed,
654 self.save_pivot,
655 );
656 return;
657 }
658 };
659
660 let has_selection = !matches!(editor.selection(), Selection::None);
661
662 self.parser.parse(key, has_selection, |event| {
663 log::debug!(" Event {event:?}");
664 let action = match event {
665 Event::AutoIndent => {
666 log::info!("TODO: AutoIndent");
667 return;
668 }
669 Event::Backspace => Action::Backspace,
670 Event::BackspaceInLine => {
671 let cursor = editor.cursor();
672 if cursor.index > 0 {
673 Action::Backspace
674 } else {
675 return;
676 }
677 }
678 Event::ChangeStart => {
679 editor.start_change();
680 return;
681 }
682 Event::ChangeFinish => {
683 finish_change(
684 editor,
685 &mut self.commands,
686 &mut self.changed,
687 self.save_pivot,
688 );
689 return;
690 }
691 Event::Delete => Action::Delete,
692 Event::DeleteInLine => {
693 let cursor = editor.cursor();
694 if cursor.index
695 < editor.with_buffer(|buffer| buffer.lines[cursor.line].text().len())
696 {
697 Action::Delete
698 } else {
699 return;
700 }
701 }
702 Event::Escape => Action::Escape,
703 Event::Insert(c) => Action::Insert(c),
704 Event::NewLine => Action::Enter,
705 Event::Put { register, after } => {
706 if let Some((selection, data)) = self.registers.get(®ister) {
707 editor.start_change();
708 if editor.delete_selection() {
709 editor.insert_string(data, None);
710 } else {
711 match selection {
712 Selection::None | Selection::Normal(_) | Selection::Word(_) => {
713 let mut cursor = editor.cursor();
714 if after {
715 editor.with_buffer(|buffer| {
716 let text = buffer.lines[cursor.line].text();
717 if let Some(c) = text[cursor.index..].chars().next() {
718 cursor.index += c.len_utf8();
719 }
720 });
721 editor.set_cursor(cursor);
722 }
723 editor.insert_at(cursor, data, None);
724 }
725 Selection::Line(_) => {
726 let mut cursor = editor.cursor();
727 if after {
728 cursor.line += 1;
730 } else {
731 cursor.line += 1;
733 editor.set_cursor(cursor);
734 cursor.line -= 1;
735 }
736 cursor.index = 0;
738
739 editor.insert_at(cursor, "\n", None);
741 editor.insert_at(cursor, data, None);
742
743 editor.shape_as_needed(font_system, false);
745
746 if after {
748 editor.action(font_system, Action::Motion(Motion::Down));
749 } else {
750 editor.action(font_system, Action::Motion(Motion::Up));
751 }
752 }
753 }
754 }
755 finish_change(
756 editor,
757 &mut self.commands,
758 &mut self.changed,
759 self.save_pivot,
760 );
761 }
762 return;
763 }
764 Event::Redraw => {
765 editor.with_buffer_mut(|buffer| buffer.set_redraw(true));
766 return;
767 }
768 Event::SelectClear => {
769 editor.set_selection(Selection::None);
770 return;
771 }
772 Event::SelectStart => {
773 let cursor = editor.cursor();
774 editor.set_selection(Selection::Normal(cursor));
775 return;
776 }
777 Event::SelectLineStart => {
778 let cursor = editor.cursor();
779 editor.set_selection(Selection::Line(cursor));
780 return;
781 }
782 Event::SelectTextObject(text_object, include) => {
783 match text_object {
784 TextObject::AngleBrackets => select_in(editor, '<', '>', include),
785 TextObject::CurlyBrackets => select_in(editor, '{', '}', include),
786 TextObject::DoubleQuotes => select_in(editor, '"', '"', include),
787 TextObject::Parentheses => select_in(editor, '(', ')', include),
788 TextObject::Search { forwards } => {
789 if let Some((value, _)) = &self.search_opt {
790 if search(editor, value, forwards) {
791 let mut cursor = editor.cursor();
792 editor.set_selection(Selection::Normal(cursor));
793 cursor.index += value.len();
795 editor.set_cursor(cursor);
796 }
797 }
798 }
799 TextObject::SingleQuotes => select_in(editor, '\'', '\'', include),
800 TextObject::SquareBrackets => select_in(editor, '[', ']', include),
801 TextObject::Ticks => select_in(editor, '`', '`', include),
802 TextObject::Word(word) => {
803 let mut cursor = editor.cursor();
804 let mut selection = editor.selection();
805 editor.with_buffer(|buffer| {
806 let text = buffer.lines[cursor.line].text();
807 match WordIter::new(text, word)
808 .find(|&(i, w)| i <= cursor.index && i + w.len() > cursor.index)
809 {
810 Some((i, w)) => {
811 cursor.index = i;
812 selection = Selection::Normal(cursor);
813 cursor.index += w.len();
814 }
815 None => {
816 }
818 }
819 });
820 editor.set_selection(selection);
821 editor.set_cursor(cursor);
822 }
823 _ => {
824 log::info!("TODO: {text_object:?}");
825 }
826 }
827 return;
828 }
829 Event::SetSearch(value, forwards) => {
830 self.search_opt = Some((value, forwards));
831 return;
832 }
833 Event::ShiftLeft => Action::Unindent,
834 Event::ShiftRight => Action::Indent,
835 Event::SwapCase => {
836 log::info!("TODO: SwapCase");
837 return;
838 }
839 Event::Undo => {
840 for action in self.commands.undo() {
841 undo_2_action(editor, action);
842 }
843 return;
844 }
845 Event::Yank { register } => {
846 if let Some(data) = editor.copy_selection() {
847 self.registers.insert(register, (editor.selection(), data));
848 }
849 return;
850 }
851 Event::Motion(motion) => {
852 match motion {
853 modit::Motion::Around => {
854 return;
856 }
857 modit::Motion::Down => Action::Motion(Motion::Down),
858 modit::Motion::End => Action::Motion(Motion::End),
859 modit::Motion::GotoLine(line) => {
860 Action::Motion(Motion::GotoLine(line.saturating_sub(1)))
861 }
862 modit::Motion::GotoEof => Action::Motion(Motion::GotoLine(
863 editor.with_buffer(|buffer| buffer.lines.len().saturating_sub(1)),
864 )),
865 modit::Motion::Home => Action::Motion(Motion::Home),
866 modit::Motion::Inside => {
867 return;
869 }
870 modit::Motion::Left => Action::Motion(Motion::Left),
871 modit::Motion::LeftInLine => {
872 let cursor = editor.cursor();
873 if cursor.index > 0 {
874 Action::Motion(Motion::Left)
875 } else {
876 return;
877 }
878 }
879 modit::Motion::Line => {
880 return;
882 }
883 modit::Motion::NextChar(find_c) => {
884 let mut cursor = editor.cursor();
885 editor.with_buffer(|buffer| {
886 let text = buffer.lines[cursor.line].text();
887 if cursor.index < text.len() {
888 if let Some((i, _)) = text[cursor.index..]
889 .char_indices()
890 .find(|&(i, c)| i > 0 && c == find_c)
891 {
892 cursor.index += i;
893 }
894 }
895 });
896 editor.set_cursor(cursor);
897 return;
898 }
899 modit::Motion::NextCharTill(find_c) => {
900 let mut cursor = editor.cursor();
901 editor.with_buffer(|buffer| {
902 let text = buffer.lines[cursor.line].text();
903 if cursor.index < text.len() {
904 let mut last_i = 0;
905 for (i, c) in text[cursor.index..].char_indices() {
906 if last_i > 0 && c == find_c {
907 cursor.index += last_i;
908 break;
909 } else {
910 last_i = i;
911 }
912 }
913 }
914 });
915 editor.set_cursor(cursor);
916 return;
917 }
918 modit::Motion::NextSearch => match &self.search_opt {
919 Some((value, forwards)) => {
920 search(editor, value, *forwards);
921 return;
922 }
923 None => return,
924 },
925 modit::Motion::NextWordEnd(word) => {
926 let mut cursor = editor.cursor();
927 editor.with_buffer(|buffer| {
928 loop {
929 let text = buffer.lines[cursor.line].text();
930 if cursor.index < text.len() {
931 cursor.index = WordIter::new(text, word)
932 .map(|(i, w)| {
933 i + w
934 .char_indices()
935 .last()
936 .map(|(i, _)| i)
937 .unwrap_or(0)
938 })
939 .find(|&i| i > cursor.index)
940 .unwrap_or(text.len());
941 if cursor.index == text.len() {
942 continue;
944 }
945 } else if cursor.line + 1 < buffer.lines.len() {
946 cursor.line += 1;
948 cursor.index = 0;
949 continue;
950 }
951 break;
952 }
953 });
954 editor.set_cursor(cursor);
955 return;
956 }
957 modit::Motion::NextWordStart(word) => {
958 let mut cursor = editor.cursor();
959 editor.with_buffer(|buffer| {
960 loop {
961 let text = buffer.lines[cursor.line].text();
962 if cursor.index < text.len() {
963 cursor.index = WordIter::new(text, word)
964 .map(|(i, _)| i)
965 .find(|&i| i > cursor.index)
966 .unwrap_or(text.len());
967 if cursor.index == text.len() {
968 continue;
970 }
971 } else if cursor.line + 1 < buffer.lines.len() {
972 cursor.line += 1;
974 cursor.index = 0;
975 continue;
976 }
977 break;
978 }
979 });
980 editor.set_cursor(cursor);
981 return;
982 }
983 modit::Motion::PageDown => Action::Motion(Motion::PageDown),
984 modit::Motion::PageUp => Action::Motion(Motion::PageUp),
985 modit::Motion::PreviousChar(find_c) => {
986 let mut cursor = editor.cursor();
987 editor.with_buffer(|buffer| {
988 let text = buffer.lines[cursor.line].text();
989 if cursor.index > 0 {
990 if let Some((i, _)) = text[..cursor.index]
991 .char_indices()
992 .rfind(|&(_, c)| c == find_c)
993 {
994 cursor.index = i;
995 }
996 }
997 });
998 editor.set_cursor(cursor);
999 return;
1000 }
1001 modit::Motion::PreviousCharTill(find_c) => {
1002 let mut cursor = editor.cursor();
1003 editor.with_buffer(|buffer| {
1004 let text = buffer.lines[cursor.line].text();
1005 if cursor.index > 0 {
1006 if let Some(i) = text[..cursor.index]
1007 .char_indices()
1008 .filter_map(|(i, c)| {
1009 if c == find_c {
1010 let end = i + c.len_utf8();
1011 if end < cursor.index {
1012 return Some(end);
1013 }
1014 }
1015 None
1016 })
1017 .next_back()
1018 {
1019 cursor.index = i;
1020 }
1021 }
1022 });
1023 editor.set_cursor(cursor);
1024 return;
1025 }
1026 modit::Motion::PreviousSearch => match &self.search_opt {
1027 Some((value, forwards)) => {
1028 search(editor, value, !*forwards);
1029 return;
1030 }
1031 None => return,
1032 },
1033 modit::Motion::PreviousWordEnd(word) => {
1034 let mut cursor = editor.cursor();
1035 editor.with_buffer(|buffer| {
1036 loop {
1037 let text = buffer.lines[cursor.line].text();
1038 if cursor.index > 0 {
1039 cursor.index = WordIter::new(text, word)
1040 .map(|(i, w)| {
1041 i + w
1042 .char_indices()
1043 .last()
1044 .map(|(i, _)| i)
1045 .unwrap_or(0)
1046 })
1047 .filter(|&i| i < cursor.index)
1048 .last()
1049 .unwrap_or(0);
1050 if cursor.index == 0 {
1051 continue;
1053 }
1054 } else if cursor.line > 0 {
1055 cursor.line -= 1;
1057 cursor.index = buffer.lines[cursor.line].text().len();
1058 continue;
1059 }
1060 break;
1061 }
1062 });
1063 editor.set_cursor(cursor);
1064 return;
1065 }
1066 modit::Motion::PreviousWordStart(word) => {
1067 let mut cursor = editor.cursor();
1068 editor.with_buffer(|buffer| {
1069 loop {
1070 let text = buffer.lines[cursor.line].text();
1071 if cursor.index > 0 {
1072 cursor.index = WordIter::new(text, word)
1073 .map(|(i, _)| i)
1074 .filter(|&i| i < cursor.index)
1075 .last()
1076 .unwrap_or(0);
1077 if cursor.index == 0 {
1078 continue;
1080 }
1081 } else if cursor.line > 0 {
1082 cursor.line -= 1;
1084 cursor.index = buffer.lines[cursor.line].text().len();
1085 continue;
1086 }
1087 break;
1088 }
1089 });
1090 editor.set_cursor(cursor);
1091 return;
1092 }
1093 modit::Motion::Right => Action::Motion(Motion::Right),
1094 modit::Motion::RightInLine => {
1095 let cursor = editor.cursor();
1096 if cursor.index
1097 < editor
1098 .with_buffer(|buffer| buffer.lines[cursor.line].text().len())
1099 {
1100 Action::Motion(Motion::Right)
1101 } else {
1102 return;
1103 }
1104 }
1105 modit::Motion::ScreenHigh => {
1106 if let Some(line_i) = editor.with_buffer(|buffer| {
1108 buffer.layout_runs().next().map(|first| first.line_i)
1109 }) {
1110 Action::Motion(Motion::GotoLine(line_i))
1111 } else {
1112 return;
1113 }
1114 }
1115 modit::Motion::ScreenLow => {
1116 if let Some(line_i) = editor.with_buffer(|buffer| {
1118 buffer.layout_runs().last().map(|last| last.line_i)
1119 }) {
1120 Action::Motion(Motion::GotoLine(line_i))
1121 } else {
1122 return;
1123 }
1124 }
1125 modit::Motion::ScreenMiddle => {
1126 let action_opt = editor.with_buffer(|buffer| {
1128 let mut layout_runs = buffer.layout_runs();
1129
1130 match (layout_runs.next(), layout_runs.last()) {
1131 (Some(first), Some(last)) => Some(Action::Motion(
1132 Motion::GotoLine((last.line_i + first.line_i) / 2),
1133 )),
1134 _ => None,
1135 }
1136 });
1137 match action_opt {
1138 Some(action) => action,
1139 None => return,
1140 }
1141 }
1142 modit::Motion::Selection => {
1143 return;
1145 }
1146 modit::Motion::SoftHome => Action::Motion(Motion::SoftHome),
1147 modit::Motion::Up => Action::Motion(Motion::Up),
1148 }
1149 }
1150 };
1151 editor.action(font_system, action);
1152 });
1153 }
1154
1155 fn cursor_position(&self) -> Option<(i32, i32)> {
1156 self.editor.cursor_position()
1157 }
1158}
1159
1160impl BorrowedWithFontSystem<'_, ViEditor<'_, '_>> {
1161 #[cfg(feature = "std")]
1166 pub fn load_text<P: AsRef<std::path::Path>>(
1167 &mut self,
1168 path: P,
1169 attrs: crate::Attrs,
1170 ) -> std::io::Result<()> {
1171 self.inner.load_text(self.font_system, path, attrs)
1172 }
1173
1174 #[cfg(feature = "swash")]
1175 pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, f: F)
1176 where
1177 F: FnMut(i32, i32, u32, u32, Color),
1178 {
1179 self.inner.draw(self.font_system, cache, f);
1180 }
1181}