1use alloc::{collections::BTreeMap, string::String};
2use core::cmp;
3use modit::{Event, Key, Parser, TextObject, WordIter};
4use unicode_segmentation::UnicodeSegmentation;
5
6use crate::{
7 Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, FontSystem,
8 Motion, Selection, SyntaxEditor, SyntaxTheme,
9};
10
11pub use modit::{ViMode, ViParser};
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")]
306 pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
307 where
308 F: FnMut(i32, i32, u32, u32, Color),
309 {
310 let background_color = self.background_color();
311 let foreground_color = self.foreground_color();
312 let cursor_color = self.cursor_color();
313 let selection_color = self.selection_color();
314 self.with_buffer(|buffer| {
315 let size = buffer.size();
316 if let Some(width) = size.0 {
317 if let Some(height) = size.1 {
318 f(0, 0, width as u32, height as u32, background_color);
319 }
320 }
321 let font_size = buffer.metrics().font_size;
322 for run in buffer.layout_runs() {
323 let line_i = run.line_i;
324 let line_y = run.line_y;
325 let line_top = run.line_top;
326 let line_height = run.line_height;
327
328 let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> {
329 let default_width = font_size / 2.0;
331 if cursor.line == line_i {
332 for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
333 if cursor.index >= glyph.start && cursor.index < glyph.end {
334 let mut before = 0;
336 let mut total = 0;
337
338 let cluster = &run.text[glyph.start..glyph.end];
339 for (i, _) in cluster.grapheme_indices(true) {
340 if glyph.start + i < cursor.index {
341 before += 1;
342 }
343 total += 1;
344 }
345
346 let width = glyph.w / (total as f32);
347 let offset = (before as f32) * width;
348 return Some((glyph_i, offset, width));
349 }
350 }
351 match run.glyphs.last() {
352 Some(glyph) => {
353 if cursor.index == glyph.end {
354 return Some((run.glyphs.len(), 0.0, default_width));
355 }
356 }
357 None => {
358 return Some((0, 0.0, default_width));
359 }
360 }
361 }
362 None
363 };
364
365 if let Some((start, end)) = self.selection_bounds() {
367 if line_i >= start.line && line_i <= end.line {
368 let mut range_opt = None;
369 for glyph in run.glyphs.iter() {
370 let cluster = &run.text[glyph.start..glyph.end];
372 let total = cluster.grapheme_indices(true).count();
373 let mut c_x = glyph.x;
374 let c_w = glyph.w / total as f32;
375 for (i, c) in cluster.grapheme_indices(true) {
376 let c_start = glyph.start + i;
377 let c_end = glyph.start + i + c.len();
378 if (start.line != line_i || c_end > start.index)
379 && (end.line != line_i || c_start < end.index)
380 {
381 range_opt = match range_opt.take() {
382 Some((min, max)) => Some((
383 cmp::min(min, c_x as i32),
384 cmp::max(max, (c_x + c_w) as i32),
385 )),
386 None => Some((c_x as i32, (c_x + c_w) as i32)),
387 };
388 } else if let Some((min, max)) = range_opt.take() {
389 f(
390 min,
391 line_top as i32,
392 cmp::max(0, max - min) as u32,
393 line_height as u32,
394 selection_color,
395 );
396 }
397 c_x += c_w;
398 }
399 }
400
401 if run.glyphs.is_empty() && end.line > line_i {
402 range_opt = Some((0, buffer.size().0.unwrap_or(0.0) as i32));
404 }
405
406 if let Some((mut min, mut max)) = range_opt.take() {
407 if end.line > line_i {
408 if run.rtl {
410 min = 0;
411 } else {
412 max = buffer.size().0.unwrap_or(0.0) as i32;
413 }
414 }
415 f(
416 min,
417 line_top as i32,
418 cmp::max(0, max - min) as u32,
419 line_height as u32,
420 selection_color,
421 );
422 }
423 }
424 }
425
426 if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
428 cursor_glyph_opt(&self.cursor())
429 {
430 let block_cursor = if self.passthrough {
431 false
432 } else {
433 match self.parser.mode {
434 ViMode::Insert | ViMode::Replace => false,
435 _ => true, }
437 };
438
439 let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
440 Some(glyph) => {
441 if glyph.level.is_rtl() {
443 (
444 (glyph.x + glyph.w - cursor_glyph_offset) as i32,
445 (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
446 as i32,
447 )
448 } else {
449 (
450 (glyph.x + cursor_glyph_offset) as i32,
451 (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
452 )
453 }
454 }
455 None => match run.glyphs.last() {
456 Some(glyph) => {
457 if glyph.level.is_rtl() {
459 (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
460 } else {
461 (
462 (glyph.x + glyph.w) as i32,
463 (glyph.x + glyph.w + cursor_glyph_width) as i32,
464 )
465 }
466 }
467 None => {
468 (0, cursor_glyph_width as i32)
470 }
471 },
472 };
473
474 if block_cursor {
475 let left_x = cmp::min(start_x, end_x);
476 let right_x = cmp::max(start_x, end_x);
477 f(
478 left_x,
479 line_top as i32,
480 (right_x - left_x) as u32,
481 line_height as u32,
482 selection_color,
483 );
484 } else {
485 f(
486 start_x,
487 line_top as i32,
488 1,
489 line_height as u32,
490 cursor_color,
491 );
492 }
493 }
494
495 for glyph in run.glyphs.iter() {
496 let physical_glyph = glyph.physical((0., 0.), 1.0);
497
498 let glyph_color = match glyph.color_opt {
499 Some(some) => some,
500 None => foreground_color,
501 };
502
503 cache.with_pixels(
504 font_system,
505 physical_glyph.cache_key,
506 glyph_color,
507 |x, y, color| {
508 f(
509 physical_glyph.x + x,
510 line_y as i32 + physical_glyph.y + y,
511 1,
512 1,
513 color,
514 );
515 },
516 );
517 }
518 }
519 });
520 }
521}
522
523impl<'buffer> Edit<'buffer> for ViEditor<'_, 'buffer> {
524 fn buffer_ref(&self) -> &BufferRef<'buffer> {
525 self.editor.buffer_ref()
526 }
527
528 fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer> {
529 self.editor.buffer_ref_mut()
530 }
531
532 fn cursor(&self) -> Cursor {
533 self.editor.cursor()
534 }
535
536 fn set_cursor(&mut self, cursor: Cursor) {
537 self.editor.set_cursor(cursor);
538 }
539
540 fn selection(&self) -> Selection {
541 self.editor.selection()
542 }
543
544 fn set_selection(&mut self, selection: Selection) {
545 self.editor.set_selection(selection);
546 }
547
548 fn auto_indent(&self) -> bool {
549 self.editor.auto_indent()
550 }
551
552 fn set_auto_indent(&mut self, auto_indent: bool) {
553 self.editor.set_auto_indent(auto_indent);
554 }
555
556 fn tab_width(&self) -> u16 {
557 self.editor.tab_width()
558 }
559
560 fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
561 self.editor.set_tab_width(font_system, tab_width);
562 }
563
564 fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
565 self.editor.shape_as_needed(font_system, prune);
566 }
567
568 fn delete_range(&mut self, start: Cursor, end: Cursor) {
569 self.editor.delete_range(start, end);
570 }
571
572 fn insert_at(&mut self, cursor: Cursor, data: &str, attrs_list: Option<AttrsList>) -> Cursor {
573 self.editor.insert_at(cursor, data, attrs_list)
574 }
575
576 fn copy_selection(&self) -> Option<String> {
577 self.editor.copy_selection()
578 }
579
580 fn delete_selection(&mut self) -> bool {
581 self.editor.delete_selection()
582 }
583
584 fn apply_change(&mut self, change: &Change) -> bool {
585 self.editor.apply_change(change)
586 }
587
588 fn start_change(&mut self) {
589 self.editor.start_change();
590 }
591
592 fn finish_change(&mut self) -> Option<Change> {
593 finish_change(
594 &mut self.editor,
595 &mut self.commands,
596 &mut self.changed,
597 self.save_pivot,
598 )
599 }
600
601 fn action(&mut self, font_system: &mut FontSystem, action: Action) {
602 log::debug!("Action {:?}", action);
603
604 let editor = &mut self.editor;
605
606 editor.start_change();
608
609 if self.passthrough {
610 editor.action(font_system, action);
611 finish_change(
613 editor,
614 &mut self.commands,
615 &mut self.changed,
616 self.save_pivot,
617 );
618 return;
619 }
620
621 let key = match action {
622 Action::Backspace => Key::Backspace,
624 Action::Delete => Key::Delete,
625 Action::Motion(Motion::Down) => Key::Down,
626 Action::Motion(Motion::End) => Key::End,
627 Action::Enter => Key::Enter,
628 Action::Escape => Key::Escape,
629 Action::Motion(Motion::Home) => Key::Home,
630 Action::Indent => Key::Tab,
631 Action::Insert(c) => Key::Char(c),
632 Action::Motion(Motion::Left) => Key::Left,
633 Action::Motion(Motion::PageDown) => Key::PageDown,
634 Action::Motion(Motion::PageUp) => Key::PageUp,
635 Action::Motion(Motion::Right) => Key::Right,
636 Action::Unindent => Key::Backtab,
637 Action::Motion(Motion::Up) => Key::Up,
638 _ => {
639 log::debug!("Pass through action {:?}", action);
640 editor.action(font_system, action);
641 finish_change(
643 editor,
644 &mut self.commands,
645 &mut self.changed,
646 self.save_pivot,
647 );
648 return;
649 }
650 };
651
652 let has_selection = !matches!(editor.selection(), Selection::None);
653
654 self.parser.parse(key, has_selection, |event| {
655 log::debug!(" Event {:?}", event);
656 let action = match event {
657 Event::AutoIndent => {
658 log::info!("TODO: AutoIndent");
659 return;
660 }
661 Event::Backspace => Action::Backspace,
662 Event::BackspaceInLine => {
663 let cursor = editor.cursor();
664 if cursor.index > 0 {
665 Action::Backspace
666 } else {
667 return;
668 }
669 }
670 Event::ChangeStart => {
671 editor.start_change();
672 return;
673 }
674 Event::ChangeFinish => {
675 finish_change(
676 editor,
677 &mut self.commands,
678 &mut self.changed,
679 self.save_pivot,
680 );
681 return;
682 }
683 Event::Delete => Action::Delete,
684 Event::DeleteInLine => {
685 let cursor = editor.cursor();
686 if cursor.index
687 < editor.with_buffer(|buffer| buffer.lines[cursor.line].text().len())
688 {
689 Action::Delete
690 } else {
691 return;
692 }
693 }
694 Event::Escape => Action::Escape,
695 Event::Insert(c) => Action::Insert(c),
696 Event::NewLine => Action::Enter,
697 Event::Put { register, after } => {
698 if let Some((selection, data)) = self.registers.get(®ister) {
699 editor.start_change();
700 if editor.delete_selection() {
701 editor.insert_string(data, None);
702 } else {
703 match selection {
704 Selection::None | Selection::Normal(_) | Selection::Word(_) => {
705 let mut cursor = editor.cursor();
706 if after {
707 editor.with_buffer(|buffer| {
708 let text = buffer.lines[cursor.line].text();
709 if let Some(c) = text[cursor.index..].chars().next() {
710 cursor.index += c.len_utf8();
711 }
712 });
713 editor.set_cursor(cursor);
714 }
715 editor.insert_at(cursor, data, None);
716 }
717 Selection::Line(_) => {
718 let mut cursor = editor.cursor();
719 if after {
720 cursor.line += 1;
722 } else {
723 cursor.line += 1;
725 editor.set_cursor(cursor);
726 cursor.line -= 1;
727 }
728 cursor.index = 0;
730
731 editor.insert_at(cursor, "\n", None);
733 editor.insert_at(cursor, data, None);
734
735 editor.shape_as_needed(font_system, false);
737
738 if after {
740 editor.action(font_system, Action::Motion(Motion::Down));
741 } else {
742 editor.action(font_system, Action::Motion(Motion::Up));
743 }
744 }
745 }
746 }
747 finish_change(
748 editor,
749 &mut self.commands,
750 &mut self.changed,
751 self.save_pivot,
752 );
753 }
754 return;
755 }
756 Event::Redraw => {
757 editor.with_buffer_mut(|buffer| buffer.set_redraw(true));
758 return;
759 }
760 Event::SelectClear => {
761 editor.set_selection(Selection::None);
762 return;
763 }
764 Event::SelectStart => {
765 let cursor = editor.cursor();
766 editor.set_selection(Selection::Normal(cursor));
767 return;
768 }
769 Event::SelectLineStart => {
770 let cursor = editor.cursor();
771 editor.set_selection(Selection::Line(cursor));
772 return;
773 }
774 Event::SelectTextObject(text_object, include) => {
775 match text_object {
776 TextObject::AngleBrackets => select_in(editor, '<', '>', include),
777 TextObject::CurlyBrackets => select_in(editor, '{', '}', include),
778 TextObject::DoubleQuotes => select_in(editor, '"', '"', include),
779 TextObject::Parentheses => select_in(editor, '(', ')', include),
780 TextObject::Search { forwards } => {
781 if let Some((value, _)) = &self.search_opt {
782 if search(editor, value, forwards) {
783 let mut cursor = editor.cursor();
784 editor.set_selection(Selection::Normal(cursor));
785 cursor.index += value.len();
787 editor.set_cursor(cursor);
788 }
789 }
790 }
791 TextObject::SingleQuotes => select_in(editor, '\'', '\'', include),
792 TextObject::SquareBrackets => select_in(editor, '[', ']', include),
793 TextObject::Ticks => select_in(editor, '`', '`', include),
794 TextObject::Word(word) => {
795 let mut cursor = editor.cursor();
796 let mut selection = editor.selection();
797 editor.with_buffer(|buffer| {
798 let text = buffer.lines[cursor.line].text();
799 match WordIter::new(text, word)
800 .find(|&(i, w)| i <= cursor.index && i + w.len() > cursor.index)
801 {
802 Some((i, w)) => {
803 cursor.index = i;
804 selection = Selection::Normal(cursor);
805 cursor.index += w.len();
806 }
807 None => {
808 }
810 }
811 });
812 editor.set_selection(selection);
813 editor.set_cursor(cursor);
814 }
815 _ => {
816 log::info!("TODO: {:?}", text_object);
817 }
818 }
819 return;
820 }
821 Event::SetSearch(value, forwards) => {
822 self.search_opt = Some((value, forwards));
823 return;
824 }
825 Event::ShiftLeft => Action::Unindent,
826 Event::ShiftRight => Action::Indent,
827 Event::SwapCase => {
828 log::info!("TODO: SwapCase");
829 return;
830 }
831 Event::Undo => {
832 for action in self.commands.undo() {
833 undo_2_action(editor, action);
834 }
835 return;
836 }
837 Event::Yank { register } => {
838 if let Some(data) = editor.copy_selection() {
839 self.registers.insert(register, (editor.selection(), data));
840 }
841 return;
842 }
843 Event::Motion(motion) => {
844 match motion {
845 modit::Motion::Around => {
846 return;
848 }
849 modit::Motion::Down => Action::Motion(Motion::Down),
850 modit::Motion::End => Action::Motion(Motion::End),
851 modit::Motion::GotoLine(line) => {
852 Action::Motion(Motion::GotoLine(line.saturating_sub(1)))
853 }
854 modit::Motion::GotoEof => Action::Motion(Motion::GotoLine(
855 editor.with_buffer(|buffer| buffer.lines.len().saturating_sub(1)),
856 )),
857 modit::Motion::Home => Action::Motion(Motion::Home),
858 modit::Motion::Inside => {
859 return;
861 }
862 modit::Motion::Left => Action::Motion(Motion::Left),
863 modit::Motion::LeftInLine => {
864 let cursor = editor.cursor();
865 if cursor.index > 0 {
866 Action::Motion(Motion::Left)
867 } else {
868 return;
869 }
870 }
871 modit::Motion::Line => {
872 return;
874 }
875 modit::Motion::NextChar(find_c) => {
876 let mut cursor = editor.cursor();
877 editor.with_buffer(|buffer| {
878 let text = buffer.lines[cursor.line].text();
879 if cursor.index < text.len() {
880 if let Some((i, _)) = text[cursor.index..]
881 .char_indices()
882 .find(|&(i, c)| i > 0 && c == find_c)
883 {
884 cursor.index += i;
885 }
886 }
887 });
888 editor.set_cursor(cursor);
889 return;
890 }
891 modit::Motion::NextCharTill(find_c) => {
892 let mut cursor = editor.cursor();
893 editor.with_buffer(|buffer| {
894 let text = buffer.lines[cursor.line].text();
895 if cursor.index < text.len() {
896 let mut last_i = 0;
897 for (i, c) in text[cursor.index..].char_indices() {
898 if last_i > 0 && c == find_c {
899 cursor.index += last_i;
900 break;
901 } else {
902 last_i = i;
903 }
904 }
905 }
906 });
907 editor.set_cursor(cursor);
908 return;
909 }
910 modit::Motion::NextSearch => match &self.search_opt {
911 Some((value, forwards)) => {
912 search(editor, value, *forwards);
913 return;
914 }
915 None => return,
916 },
917 modit::Motion::NextWordEnd(word) => {
918 let mut cursor = editor.cursor();
919 editor.with_buffer(|buffer| {
920 loop {
921 let text = buffer.lines[cursor.line].text();
922 if cursor.index < text.len() {
923 cursor.index = WordIter::new(text, word)
924 .map(|(i, w)| {
925 i + w
926 .char_indices()
927 .last()
928 .map(|(i, _)| i)
929 .unwrap_or(0)
930 })
931 .find(|&i| i > cursor.index)
932 .unwrap_or(text.len());
933 if cursor.index == text.len() {
934 continue;
936 }
937 } else if cursor.line + 1 < buffer.lines.len() {
938 cursor.line += 1;
940 cursor.index = 0;
941 continue;
942 }
943 break;
944 }
945 });
946 editor.set_cursor(cursor);
947 return;
948 }
949 modit::Motion::NextWordStart(word) => {
950 let mut cursor = editor.cursor();
951 editor.with_buffer(|buffer| {
952 loop {
953 let text = buffer.lines[cursor.line].text();
954 if cursor.index < text.len() {
955 cursor.index = WordIter::new(text, word)
956 .map(|(i, _)| i)
957 .find(|&i| i > cursor.index)
958 .unwrap_or(text.len());
959 if cursor.index == text.len() {
960 continue;
962 }
963 } else if cursor.line + 1 < buffer.lines.len() {
964 cursor.line += 1;
966 cursor.index = 0;
967 continue;
968 }
969 break;
970 }
971 });
972 editor.set_cursor(cursor);
973 return;
974 }
975 modit::Motion::PageDown => Action::Motion(Motion::PageDown),
976 modit::Motion::PageUp => Action::Motion(Motion::PageUp),
977 modit::Motion::PreviousChar(find_c) => {
978 let mut cursor = editor.cursor();
979 editor.with_buffer(|buffer| {
980 let text = buffer.lines[cursor.line].text();
981 if cursor.index > 0 {
982 if let Some((i, _)) = text[..cursor.index]
983 .char_indices()
984 .rfind(|&(_, c)| c == find_c)
985 {
986 cursor.index = i;
987 }
988 }
989 });
990 editor.set_cursor(cursor);
991 return;
992 }
993 modit::Motion::PreviousCharTill(find_c) => {
994 let mut cursor = editor.cursor();
995 editor.with_buffer(|buffer| {
996 let text = buffer.lines[cursor.line].text();
997 if cursor.index > 0 {
998 if let Some(i) = text[..cursor.index]
999 .char_indices()
1000 .filter_map(|(i, c)| {
1001 if c == find_c {
1002 let end = i + c.len_utf8();
1003 if end < cursor.index {
1004 return Some(end);
1005 }
1006 }
1007 None
1008 })
1009 .last()
1010 {
1011 cursor.index = i;
1012 }
1013 }
1014 });
1015 editor.set_cursor(cursor);
1016 return;
1017 }
1018 modit::Motion::PreviousSearch => match &self.search_opt {
1019 Some((value, forwards)) => {
1020 search(editor, value, !*forwards);
1021 return;
1022 }
1023 None => return,
1024 },
1025 modit::Motion::PreviousWordEnd(word) => {
1026 let mut cursor = editor.cursor();
1027 editor.with_buffer(|buffer| {
1028 loop {
1029 let text = buffer.lines[cursor.line].text();
1030 if cursor.index > 0 {
1031 cursor.index = WordIter::new(text, word)
1032 .map(|(i, w)| {
1033 i + w
1034 .char_indices()
1035 .last()
1036 .map(|(i, _)| i)
1037 .unwrap_or(0)
1038 })
1039 .filter(|&i| i < cursor.index)
1040 .last()
1041 .unwrap_or(0);
1042 if cursor.index == 0 {
1043 continue;
1045 }
1046 } else if cursor.line > 0 {
1047 cursor.line -= 1;
1049 cursor.index = buffer.lines[cursor.line].text().len();
1050 continue;
1051 }
1052 break;
1053 }
1054 });
1055 editor.set_cursor(cursor);
1056 return;
1057 }
1058 modit::Motion::PreviousWordStart(word) => {
1059 let mut cursor = editor.cursor();
1060 editor.with_buffer(|buffer| {
1061 loop {
1062 let text = buffer.lines[cursor.line].text();
1063 if cursor.index > 0 {
1064 cursor.index = WordIter::new(text, word)
1065 .map(|(i, _)| i)
1066 .filter(|&i| i < cursor.index)
1067 .last()
1068 .unwrap_or(0);
1069 if cursor.index == 0 {
1070 continue;
1072 }
1073 } else if cursor.line > 0 {
1074 cursor.line -= 1;
1076 cursor.index = buffer.lines[cursor.line].text().len();
1077 continue;
1078 }
1079 break;
1080 }
1081 });
1082 editor.set_cursor(cursor);
1083 return;
1084 }
1085 modit::Motion::Right => Action::Motion(Motion::Right),
1086 modit::Motion::RightInLine => {
1087 let cursor = editor.cursor();
1088 if cursor.index
1089 < editor
1090 .with_buffer(|buffer| buffer.lines[cursor.line].text().len())
1091 {
1092 Action::Motion(Motion::Right)
1093 } else {
1094 return;
1095 }
1096 }
1097 modit::Motion::ScreenHigh => {
1098 if let Some(line_i) = editor.with_buffer(|buffer| {
1100 buffer.layout_runs().next().map(|first| first.line_i)
1101 }) {
1102 Action::Motion(Motion::GotoLine(line_i))
1103 } else {
1104 return;
1105 }
1106 }
1107 modit::Motion::ScreenLow => {
1108 if let Some(line_i) = editor.with_buffer(|buffer| {
1110 buffer.layout_runs().last().map(|last| last.line_i)
1111 }) {
1112 Action::Motion(Motion::GotoLine(line_i))
1113 } else {
1114 return;
1115 }
1116 }
1117 modit::Motion::ScreenMiddle => {
1118 let action_opt = editor.with_buffer(|buffer| {
1120 let mut layout_runs = buffer.layout_runs();
1121
1122 match (layout_runs.next(), layout_runs.last()) {
1123 (Some(first), Some(last)) => Some(Action::Motion(
1124 Motion::GotoLine((last.line_i + first.line_i) / 2),
1125 )),
1126 _ => None,
1127 }
1128 });
1129 match action_opt {
1130 Some(action) => action,
1131 None => return,
1132 }
1133 }
1134 modit::Motion::Selection => {
1135 return;
1137 }
1138 modit::Motion::SoftHome => Action::Motion(Motion::SoftHome),
1139 modit::Motion::Up => Action::Motion(Motion::Up),
1140 }
1141 }
1142 };
1143 editor.action(font_system, action);
1144 });
1145 }
1146
1147 fn cursor_position(&self) -> Option<(i32, i32)> {
1148 self.editor.cursor_position()
1149 }
1150}
1151
1152impl BorrowedWithFontSystem<'_, ViEditor<'_, '_>> {
1153 #[cfg(feature = "std")]
1158 pub fn load_text<P: AsRef<std::path::Path>>(
1159 &mut self,
1160 path: P,
1161 attrs: crate::Attrs,
1162 ) -> std::io::Result<()> {
1163 self.inner.load_text(self.font_system, path, attrs)
1164 }
1165
1166 #[cfg(feature = "swash")]
1167 pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, f: F)
1168 where
1169 F: FnMut(i32, i32, u32, u32, Color),
1170 {
1171 self.inner.draw(self.font_system, cache, f);
1172 }
1173}