Skip to main content

cosmic_text/edit/
vi.rs

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            //TODO: make this more efficient
23            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    //TODO: join changes together
37    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
49/// Evaluate if an [`ViEditor`] changed based on its last saved state.
50fn eval_changed(commands: &cosmic_undo_2::Commands<Change>, pivot: Option<usize>) -> bool {
51    // Editors are considered modified if the current change index is unequal to the last
52    // saved index or if `pivot` is None.
53    // The latter case handles a never saved editor with a current command index of None.
54    // Check the unit tests for an example.
55    match (commands.current_command_index(), pivot) {
56        (Some(current), Some(pivot)) => current != pivot,
57        // Edge case for an editor with neither a save point nor any changes.
58        // This could be a new editor or an editor without a save point where undo() is called
59        // until the editor is fresh.
60        (None, None) => false,
61        // Default to true because it's safer to assume a buffer has been modified so as to not
62        // lose changes
63        _ => 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    // Find the largest encompasing object, or if there is none, find the next one.
121    let cursor = editor.cursor();
122    let (start, end) = editor.with_buffer(|buffer| {
123        // Search forwards for isolated end character, counting start and end characters found
124        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        // Search backwards to resolve starts and ends
150        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    /// Modifies the theme of the [`SyntaxEditor`], returning false if the theme is missing
207    pub fn update_theme(&mut self, theme_name: &str) -> bool {
208        self.editor.update_theme(theme_name)
209    }
210
211    /// Load text from a file, and also set syntax to the best option
212    ///
213    /// ## Errors
214    /// Returns an `io::Error` if reading the file fails
215    #[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    /// Get the default background color
226    pub fn background_color(&self) -> Color {
227        self.editor.background_color()
228    }
229
230    /// Get the default foreground (text) color
231    pub fn foreground_color(&self) -> Color {
232        self.editor.foreground_color()
233    }
234
235    /// Get the default cursor color
236    pub fn cursor_color(&self) -> Color {
237        self.editor.cursor_color()
238    }
239
240    /// Get the default selection color
241    pub fn selection_color(&self) -> Color {
242        self.editor.selection_color()
243    }
244
245    /// Get the current syntect theme
246    pub fn theme(&self) -> &SyntaxTheme {
247        self.editor.theme()
248    }
249
250    /// Get changed flag
251    pub fn changed(&self) -> bool {
252        self.changed
253    }
254
255    /// Set changed flag
256    pub fn set_changed(&mut self, changed: bool) {
257        self.changed = changed;
258    }
259
260    /// Set current change as the save (or pivot) point.
261    ///
262    /// A pivot point is the last saved index. Anything before or after the pivot indicates that
263    /// the editor has been changed or is unsaved.
264    ///
265    /// Undoing changes down to the pivot point sets the editor as unchanged.
266    /// Redoing changes up to the pivot point sets the editor as unchanged.
267    ///
268    /// Undoing or redoing changes beyond the pivot point sets the editor to changed.
269    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    /// Set passthrough mode (true will turn off vi features)
275    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    /// Get current vi parser
283    pub fn parser(&self) -> &ViParser {
284        &self.parser
285    }
286
287    /// Redo a change
288    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    /// Undo a change
297    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    /// Draw the editor.
306    ///
307    /// Automatically resolves any pending dirty state before drawing.
308    #[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    /// Render the editor using the provided renderer.
327    ///
328    /// The caller is responsible for calling [`Edit::shape_as_needed`] first
329    /// to ensure layout is up to date.
330    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                    //TODO: better calculation of width
351                    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                                // Guess x offset based on characters
356                                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                // Highlight selection
387                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                            // Guess x offset based on characters
392                            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                            // Highlight all of internal empty lines
424                            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                                // Draw to end of line
430                                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                // Draw cursor
448                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, /*TODO: determine block cursor in other modes*/
457                        }
458                    };
459
460                    let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
461                        Some(glyph) => {
462                            // Start of detected glyph
463                            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                                // End of last glyph
479                                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                                // Start of empty line
490                                (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        // Ensure a change is always started
615        editor.start_change();
616
617        if self.passthrough {
618            editor.action(font_system, action);
619            // Always finish change when passing through (TODO: group changes)
620            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            //TODO: this leaves lots of room for issues in translation, should we directly accept Key?
631            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                // Always finish change when passing through (TODO: group changes)
650                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(&register) {
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                                        // Insert at next line
729                                        cursor.line += 1;
730                                    } else {
731                                        // Previous line will be moved down, so set cursor to next line
732                                        cursor.line += 1;
733                                        editor.set_cursor(cursor);
734                                        cursor.line -= 1;
735                                    }
736                                    // Insert at start of line
737                                    cursor.index = 0;
738
739                                    // Insert text
740                                    editor.insert_at(cursor, "\n", None);
741                                    editor.insert_at(cursor, data, None);
742
743                                    //TODO: Hack to allow immediate up/down
744                                    editor.shape_as_needed(font_system, false);
745
746                                    // Move to inserted line, preserving cursor x position
747                                    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                                    //TODO: traverse lines if necessary
794                                    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                                        //TODO
817                                    }
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                            //TODO: what to do for this psuedo-motion?
855                            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                            //TODO: what to do for this psuedo-motion?
868                            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                            //TODO: what to do for this psuedo-motion?
881                            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                                            // Try again, searching next line
943                                            continue;
944                                        }
945                                    } else if cursor.line + 1 < buffer.lines.len() {
946                                        // Go to next line and rerun loop
947                                        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                                            // Try again, searching next line
969                                            continue;
970                                        }
971                                    } else if cursor.line + 1 < buffer.lines.len() {
972                                        // Go to next line and rerun loop
973                                        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                                            // Try again, searching previous line
1052                                            continue;
1053                                        }
1054                                    } else if cursor.line > 0 {
1055                                        // Go to previous line and rerun loop
1056                                        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                                            // Try again, searching previous line
1079                                            continue;
1080                                        }
1081                                    } else if cursor.line > 0 {
1082                                        // Go to previous line and rerun loop
1083                                        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                            //TODO: is this efficient?
1107                            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                            //TODO: is this efficient?
1117                            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                            //TODO: is this efficient?
1127                            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                            //TODO: what to do for this psuedo-motion?
1144                            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    /// Load text from a file, and also set syntax to the best option
1162    ///
1163    /// ## Errors
1164    /// Returns an `io::Error` if reading the file fails
1165    #[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}