salvation_cosmic_text/edit/
vi.rs

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            //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    #[cfg(feature = "std")]
213    pub fn load_text<P: AsRef<std::path::Path>>(
214        &mut self,
215        font_system: &mut FontSystem,
216        path: P,
217        attrs: crate::Attrs,
218    ) -> std::io::Result<()> {
219        self.editor.load_text(font_system, path, attrs)
220    }
221
222    /// Get the default background color
223    pub fn background_color(&self) -> Color {
224        self.editor.background_color()
225    }
226
227    /// Get the default foreground (text) color
228    pub fn foreground_color(&self) -> Color {
229        self.editor.foreground_color()
230    }
231
232    /// Get the default cursor color
233    pub fn cursor_color(&self) -> Color {
234        self.editor.cursor_color()
235    }
236
237    /// Get the default selection color
238    pub fn selection_color(&self) -> Color {
239        self.editor.selection_color()
240    }
241
242    /// Get the current syntect theme
243    pub fn theme(&self) -> &SyntaxTheme {
244        self.editor.theme()
245    }
246
247    /// Get changed flag
248    pub fn changed(&self) -> bool {
249        self.changed
250    }
251
252    /// Set changed flag
253    pub fn set_changed(&mut self, changed: bool) {
254        self.changed = changed;
255    }
256
257    /// Set current change as the save (or pivot) point.
258    ///
259    /// A pivot point is the last saved index. Anything before or after the pivot indicates that
260    /// the editor has been changed or is unsaved.
261    ///
262    /// Undoing changes down to the pivot point sets the editor as unchanged.
263    /// Redoing changes up to the pivot point sets the editor as unchanged.
264    ///
265    /// Undoing or redoing changes beyond the pivot point sets the editor to changed.
266    pub fn save_point(&mut self) {
267        self.save_pivot = Some(self.commands.current_command_index().unwrap_or_default());
268        self.changed = false;
269    }
270
271    /// Set passthrough mode (true will turn off vi features)
272    pub fn set_passthrough(&mut self, passthrough: bool) {
273        if passthrough != self.passthrough {
274            self.passthrough = passthrough;
275            self.with_buffer_mut(|buffer| buffer.set_redraw(true));
276        }
277    }
278
279    /// Get current vi parser
280    pub fn parser(&self) -> &ViParser {
281        &self.parser
282    }
283
284    /// Redo a change
285    pub fn redo(&mut self) {
286        log::debug!("Redo");
287        for action in self.commands.redo() {
288            undo_2_action(&mut self.editor, action);
289        }
290        self.changed = eval_changed(&self.commands, self.save_pivot);
291    }
292
293    /// Undo a change
294    pub fn undo(&mut self) {
295        log::debug!("Undo");
296        for action in self.commands.undo() {
297            undo_2_action(&mut self.editor, action);
298        }
299        self.changed = eval_changed(&self.commands, self.save_pivot);
300    }
301
302    #[cfg(feature = "swash")]
303    pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
304    where
305        F: FnMut(i32, i32, u32, u32, Color),
306    {
307        let background_color = self.background_color();
308        let foreground_color = self.foreground_color();
309        let cursor_color = self.cursor_color();
310        let selection_color = self.selection_color();
311        self.with_buffer(|buffer| {
312            let size = buffer.size();
313            f(0, 0, size.0 as u32, size.1 as u32, background_color);
314            let font_size = buffer.metrics().font_size;
315            let line_height = buffer.metrics().line_height;
316            for run in buffer.layout_runs() {
317                let line_i = run.line_i;
318                let line_y = run.line_y;
319                let line_top = run.line_top;
320
321                let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> {
322                    //TODO: better calculation of width
323                    let default_width = font_size / 2.0;
324                    if cursor.line == line_i {
325                        for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
326                            if cursor.index >= glyph.start && cursor.index < glyph.end {
327                                // Guess x offset based on characters
328                                let mut before = 0;
329                                let mut total = 0;
330
331                                let cluster = &run.text[glyph.start..glyph.end];
332                                for (i, _) in cluster.grapheme_indices(true) {
333                                    if glyph.start + i < cursor.index {
334                                        before += 1;
335                                    }
336                                    total += 1;
337                                }
338
339                                let width = glyph.w / (total as f32);
340                                let offset = (before as f32) * width;
341                                return Some((glyph_i, offset, width));
342                            }
343                        }
344                        match run.glyphs.last() {
345                            Some(glyph) => {
346                                if cursor.index == glyph.end {
347                                    return Some((run.glyphs.len(), 0.0, default_width));
348                                }
349                            }
350                            None => {
351                                return Some((0, 0.0, default_width));
352                            }
353                        }
354                    }
355                    None
356                };
357
358                // Highlight selection
359                if let Some((start, end)) = self.selection_bounds() {
360                    if line_i >= start.line && line_i <= end.line {
361                        let mut range_opt = None;
362                        for glyph in run.glyphs.iter() {
363                            // Guess x offset based on characters
364                            let cluster = &run.text[glyph.start..glyph.end];
365                            let total = cluster.grapheme_indices(true).count();
366                            let mut c_x = glyph.x;
367                            let c_w = glyph.w / total as f32;
368                            for (i, c) in cluster.grapheme_indices(true) {
369                                let c_start = glyph.start + i;
370                                let c_end = glyph.start + i + c.len();
371                                if (start.line != line_i || c_end > start.index)
372                                    && (end.line != line_i || c_start < end.index)
373                                {
374                                    range_opt = match range_opt.take() {
375                                        Some((min, max)) => Some((
376                                            cmp::min(min, c_x as i32),
377                                            cmp::max(max, (c_x + c_w) as i32),
378                                        )),
379                                        None => Some((c_x as i32, (c_x + c_w) as i32)),
380                                    };
381                                } else if let Some((min, max)) = range_opt.take() {
382                                    f(
383                                        min,
384                                        line_top as i32,
385                                        cmp::max(0, max - min) as u32,
386                                        line_height as u32,
387                                        selection_color,
388                                    );
389                                }
390                                c_x += c_w;
391                            }
392                        }
393
394                        if run.glyphs.is_empty() && end.line > line_i {
395                            // Highlight all of internal empty lines
396                            range_opt = Some((0, buffer.size().0 as i32));
397                        }
398
399                        if let Some((mut min, mut max)) = range_opt.take() {
400                            if end.line > line_i {
401                                // Draw to end of line
402                                if run.rtl {
403                                    min = 0;
404                                } else {
405                                    max = buffer.size().0 as i32;
406                                }
407                            }
408                            f(
409                                min,
410                                line_top as i32,
411                                cmp::max(0, max - min) as u32,
412                                line_height as u32,
413                                selection_color,
414                            );
415                        }
416                    }
417                }
418
419                // Draw cursor
420                if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
421                    cursor_glyph_opt(&self.cursor())
422                {
423                    let block_cursor = if self.passthrough {
424                        false
425                    } else {
426                        match self.parser.mode {
427                            ViMode::Insert | ViMode::Replace => false,
428                            _ => true, /*TODO: determine block cursor in other modes*/
429                        }
430                    };
431
432                    let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
433                        Some(glyph) => {
434                            // Start of detected glyph
435                            if glyph.level.is_rtl() {
436                                (
437                                    (glyph.x + glyph.w - cursor_glyph_offset) as i32,
438                                    (glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
439                                        as i32,
440                                )
441                            } else {
442                                (
443                                    (glyph.x + cursor_glyph_offset) as i32,
444                                    (glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
445                                )
446                            }
447                        }
448                        None => match run.glyphs.last() {
449                            Some(glyph) => {
450                                // End of last glyph
451                                if glyph.level.is_rtl() {
452                                    (glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
453                                } else {
454                                    (
455                                        (glyph.x + glyph.w) as i32,
456                                        (glyph.x + glyph.w + cursor_glyph_width) as i32,
457                                    )
458                                }
459                            }
460                            None => {
461                                // Start of empty line
462                                (0, cursor_glyph_width as i32)
463                            }
464                        },
465                    };
466
467                    if block_cursor {
468                        let left_x = cmp::min(start_x, end_x);
469                        let right_x = cmp::max(start_x, end_x);
470                        f(
471                            left_x,
472                            line_top as i32,
473                            (right_x - left_x) as u32,
474                            line_height as u32,
475                            selection_color,
476                        );
477                    } else {
478                        f(
479                            start_x,
480                            line_top as i32,
481                            1,
482                            line_height as u32,
483                            cursor_color,
484                        );
485                    }
486                }
487
488                for glyph in run.glyphs.iter() {
489                    let physical_glyph = glyph.physical((0., 0.), 1.0);
490
491                    let glyph_color = match glyph.color_opt {
492                        Some(some) => some,
493                        None => foreground_color,
494                    };
495
496                    cache.with_pixels(
497                        font_system,
498                        physical_glyph.cache_key,
499                        glyph_color,
500                        |x, y, color| {
501                            f(
502                                physical_glyph.x + x,
503                                line_y as i32 + physical_glyph.y + y,
504                                1,
505                                1,
506                                color,
507                            );
508                        },
509                    );
510                }
511            }
512        });
513    }
514}
515
516impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer> {
517    fn buffer_ref(&self) -> &BufferRef<'buffer> {
518        self.editor.buffer_ref()
519    }
520
521    fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer> {
522        self.editor.buffer_ref_mut()
523    }
524
525    fn cursor(&self) -> Cursor {
526        self.editor.cursor()
527    }
528
529    fn set_cursor(&mut self, cursor: Cursor) {
530        self.editor.set_cursor(cursor);
531    }
532
533    fn selection(&self) -> Selection {
534        self.editor.selection()
535    }
536
537    fn set_selection(&mut self, selection: Selection) {
538        self.editor.set_selection(selection);
539    }
540
541    fn auto_indent(&self) -> bool {
542        self.editor.auto_indent()
543    }
544
545    fn set_auto_indent(&mut self, auto_indent: bool) {
546        self.editor.set_auto_indent(auto_indent);
547    }
548
549    fn tab_width(&self) -> u16 {
550        self.editor.tab_width()
551    }
552
553    fn set_tab_width(&mut self, tab_width: u16) {
554        self.editor.set_tab_width(tab_width);
555    }
556
557    fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool) {
558        self.editor.shape_as_needed(font_system, prune);
559    }
560
561    fn delete_range(&mut self, start: Cursor, end: Cursor) {
562        self.editor.delete_range(start, end);
563    }
564
565    fn insert_at(&mut self, cursor: Cursor, data: &str, attrs_list: Option<AttrsList>) -> Cursor {
566        self.editor.insert_at(cursor, data, attrs_list)
567    }
568
569    fn copy_selection(&self) -> Option<String> {
570        self.editor.copy_selection()
571    }
572
573    fn delete_selection(&mut self) -> bool {
574        self.editor.delete_selection()
575    }
576
577    fn apply_change(&mut self, change: &Change) -> bool {
578        self.editor.apply_change(change)
579    }
580
581    fn start_change(&mut self) {
582        self.editor.start_change();
583    }
584
585    fn finish_change(&mut self) -> Option<Change> {
586        finish_change(
587            &mut self.editor,
588            &mut self.commands,
589            &mut self.changed,
590            self.save_pivot,
591        )
592    }
593
594    fn action(&mut self, font_system: &mut FontSystem, action: Action) {
595        log::debug!("Action {:?}", action);
596
597        let editor = &mut self.editor;
598
599        // Ensure a change is always started
600        editor.start_change();
601
602        if self.passthrough {
603            editor.action(font_system, action);
604            // Always finish change when passing through (TODO: group changes)
605            finish_change(
606                editor,
607                &mut self.commands,
608                &mut self.changed,
609                self.save_pivot,
610            );
611            return;
612        }
613
614        let key = match action {
615            //TODO: this leaves lots of room for issues in translation, should we directly accept Key?
616            Action::Backspace => Key::Backspace,
617            Action::Delete => Key::Delete,
618            Action::Motion {
619                motion: Motion::Down,
620                ..
621            } => Key::Down,
622            Action::Motion {
623                motion: Motion::End,
624                ..
625            } => Key::End,
626            Action::Enter => Key::Enter,
627            Action::Escape => Key::Escape,
628            Action::Motion {
629                motion: Motion::Home,
630                ..
631            } => Key::Home,
632            Action::Indent => Key::Tab,
633            Action::Insert(c) => Key::Char(c),
634            Action::Motion {
635                motion: Motion::Left,
636                ..
637            } => Key::Left,
638            Action::Motion {
639                motion: Motion::PageDown,
640                ..
641            } => Key::PageDown,
642            Action::Motion {
643                motion: Motion::PageUp,
644                ..
645            } => Key::PageUp,
646            Action::Motion {
647                motion: Motion::Right,
648                ..
649            } => Key::Right,
650            Action::Unindent => Key::Backtab,
651            Action::Motion {
652                motion: Motion::Up, ..
653            } => Key::Up,
654            _ => {
655                log::debug!("Pass through action {:?}", action);
656                editor.action(font_system, action);
657                // Always finish change when passing through (TODO: group changes)
658                finish_change(
659                    editor,
660                    &mut self.commands,
661                    &mut self.changed,
662                    self.save_pivot,
663                );
664                return;
665            }
666        };
667
668        let has_selection = match editor.selection() {
669            Selection::None => false,
670            _ => true,
671        };
672
673        self.parser.parse(key, has_selection, |event| {
674            log::debug!("  Event {:?}", event);
675            let action = match event {
676                Event::AutoIndent => {
677                    log::info!("TODO: AutoIndent");
678                    return;
679                }
680                Event::Backspace => Action::Backspace,
681                Event::BackspaceInLine => {
682                    let cursor = editor.cursor();
683                    if cursor.index > 0 {
684                        Action::Backspace
685                    } else {
686                        return;
687                    }
688                }
689                Event::ChangeStart => {
690                    editor.start_change();
691                    return;
692                }
693                Event::ChangeFinish => {
694                    finish_change(
695                        editor,
696                        &mut self.commands,
697                        &mut self.changed,
698                        self.save_pivot,
699                    );
700                    return;
701                }
702                Event::Delete => Action::Delete,
703                Event::DeleteInLine => {
704                    let cursor = editor.cursor();
705                    if cursor.index
706                        < editor.with_buffer(|buffer| buffer.lines[cursor.line].text().len())
707                    {
708                        Action::Delete
709                    } else {
710                        return;
711                    }
712                }
713                Event::Escape => Action::Escape,
714                Event::Insert(c) => Action::Insert(c),
715                Event::NewLine => Action::Enter,
716                Event::Put { register, after } => {
717                    if let Some((selection, data)) = self.registers.get(&register) {
718                        editor.start_change();
719                        if editor.delete_selection() {
720                            editor.insert_string(data, None);
721                        } else {
722                            match selection {
723                                Selection::None | Selection::Normal(_) | Selection::Word(_) => {
724                                    let mut cursor = editor.cursor();
725                                    if after {
726                                        editor.with_buffer(|buffer| {
727                                            let text = buffer.lines[cursor.line].text();
728                                            if let Some(c) = text[cursor.index..].chars().next() {
729                                                cursor.index += c.len_utf8();
730                                            }
731                                        });
732                                        editor.set_cursor(cursor);
733                                    }
734                                    editor.insert_at(cursor, data, None);
735                                }
736                                Selection::Line(_) => {
737                                    let mut cursor = editor.cursor();
738                                    if after {
739                                        // Insert at next line
740                                        cursor.line += 1;
741                                    } else {
742                                        // Previous line will be moved down, so set cursor to next line
743                                        cursor.line += 1;
744                                        editor.set_cursor(cursor);
745                                        cursor.line -= 1;
746                                    }
747                                    // Insert at start of line
748                                    cursor.index = 0;
749
750                                    // Insert text
751                                    editor.insert_at(cursor, "\n", None);
752                                    editor.insert_at(cursor, data, None);
753
754                                    //TODO: Hack to allow immediate up/down
755                                    editor.shape_as_needed(font_system, false);
756
757                                    // Move to inserted line, preserving cursor x position
758                                    if after {
759                                        editor.action(
760                                            font_system,
761                                            Action::Motion {
762                                                motion: Motion::Down,
763                                                select: false,
764                                            },
765                                        );
766                                    } else {
767                                        editor.action(
768                                            font_system,
769                                            Action::Motion {
770                                                motion: Motion::Up,
771                                                select: false,
772                                            },
773                                        );
774                                    }
775                                }
776                            }
777                        }
778                        finish_change(
779                            editor,
780                            &mut self.commands,
781                            &mut self.changed,
782                            self.save_pivot,
783                        );
784                    }
785                    return;
786                }
787                Event::Redraw => {
788                    editor.with_buffer_mut(|buffer| buffer.set_redraw(true));
789                    return;
790                }
791                Event::SelectClear => {
792                    editor.set_selection(Selection::None);
793                    return;
794                }
795                Event::SelectStart => {
796                    let cursor = editor.cursor();
797                    editor.set_selection(Selection::Normal(cursor));
798                    return;
799                }
800                Event::SelectLineStart => {
801                    let cursor = editor.cursor();
802                    editor.set_selection(Selection::Line(cursor));
803                    return;
804                }
805                Event::SelectTextObject(text_object, include) => {
806                    match text_object {
807                        TextObject::AngleBrackets => select_in(editor, '<', '>', include),
808                        TextObject::CurlyBrackets => select_in(editor, '{', '}', include),
809                        TextObject::DoubleQuotes => select_in(editor, '"', '"', include),
810                        TextObject::Parentheses => select_in(editor, '(', ')', include),
811                        TextObject::Search { forwards } => {
812                            match &self.search_opt {
813                                Some((value, _)) => {
814                                    if search(editor, value, forwards) {
815                                        let mut cursor = editor.cursor();
816                                        editor.set_selection(Selection::Normal(cursor));
817                                        //TODO: traverse lines if necessary
818                                        cursor.index += value.len();
819                                        editor.set_cursor(cursor);
820                                    }
821                                }
822                                None => {}
823                            }
824                        }
825                        TextObject::SingleQuotes => select_in(editor, '\'', '\'', include),
826                        TextObject::SquareBrackets => select_in(editor, '[', ']', include),
827                        TextObject::Ticks => select_in(editor, '`', '`', include),
828                        TextObject::Word(word) => {
829                            let mut cursor = editor.cursor();
830                            let mut selection = editor.selection();
831                            editor.with_buffer(|buffer| {
832                                let text = buffer.lines[cursor.line].text();
833                                match WordIter::new(text, word)
834                                    .find(|&(i, w)| i <= cursor.index && i + w.len() > cursor.index)
835                                {
836                                    Some((i, w)) => {
837                                        cursor.index = i;
838                                        selection = Selection::Normal(cursor);
839                                        cursor.index += w.len();
840                                    }
841                                    None => {
842                                        //TODO
843                                    }
844                                }
845                            });
846                            editor.set_selection(selection);
847                            editor.set_cursor(cursor);
848                        }
849                        _ => {
850                            log::info!("TODO: {:?}", text_object);
851                        }
852                    }
853                    return;
854                }
855                Event::SetSearch(value, forwards) => {
856                    self.search_opt = Some((value, forwards));
857                    return;
858                }
859                Event::ShiftLeft => Action::Unindent,
860                Event::ShiftRight => Action::Indent,
861                Event::SwapCase => {
862                    log::info!("TODO: SwapCase");
863                    return;
864                }
865                Event::Undo => {
866                    for action in self.commands.undo() {
867                        undo_2_action(editor, action);
868                    }
869                    return;
870                }
871                Event::Yank { register } => {
872                    if let Some(data) = editor.copy_selection() {
873                        self.registers.insert(register, (editor.selection(), data));
874                    }
875                    return;
876                }
877                Event::Motion(motion) => {
878                    match motion {
879                        modit::Motion::Around => {
880                            //TODO: what to do for this psuedo-motion?
881                            return;
882                        }
883                        modit::Motion::Down => Action::Motion {
884                            motion: Motion::Down,
885                            select: false,
886                        },
887                        modit::Motion::End => Action::Motion {
888                            motion: Motion::End,
889                            select: false,
890                        },
891                        modit::Motion::GotoLine(line) => Action::Motion {
892                            motion: Motion::GotoLine(line.saturating_sub(1)),
893                            select: false,
894                        },
895                        modit::Motion::GotoEof => Action::Motion {
896                            motion: Motion::GotoLine(
897                                editor.with_buffer(|buffer| buffer.lines.len().saturating_sub(1)),
898                            ),
899                            select: false,
900                        },
901                        modit::Motion::Home => Action::Motion {
902                            motion: Motion::Home,
903                            select: false,
904                        },
905                        modit::Motion::Inside => {
906                            //TODO: what to do for this psuedo-motion?
907                            return;
908                        }
909                        modit::Motion::Left => Action::Motion {
910                            motion: Motion::Left,
911                            select: false,
912                        },
913                        modit::Motion::LeftInLine => {
914                            let cursor = editor.cursor();
915                            if cursor.index > 0 {
916                                Action::Motion {
917                                    motion: Motion::Left,
918                                    select: false,
919                                }
920                            } else {
921                                return;
922                            }
923                        }
924                        modit::Motion::Line => {
925                            //TODO: what to do for this psuedo-motion?
926                            return;
927                        }
928                        modit::Motion::NextChar(find_c) => {
929                            let mut cursor = editor.cursor();
930                            editor.with_buffer(|buffer| {
931                                let text = buffer.lines[cursor.line].text();
932                                if cursor.index < text.len() {
933                                    match text[cursor.index..]
934                                        .char_indices()
935                                        .filter(|&(i, c)| i > 0 && c == find_c)
936                                        .next()
937                                    {
938                                        Some((i, _)) => {
939                                            cursor.index += i;
940                                        }
941                                        None => {}
942                                    }
943                                }
944                            });
945                            editor.set_cursor(cursor);
946                            return;
947                        }
948                        modit::Motion::NextCharTill(find_c) => {
949                            let mut cursor = editor.cursor();
950                            editor.with_buffer(|buffer| {
951                                let text = buffer.lines[cursor.line].text();
952                                if cursor.index < text.len() {
953                                    let mut last_i = 0;
954                                    for (i, c) in text[cursor.index..].char_indices() {
955                                        if last_i > 0 && c == find_c {
956                                            cursor.index += last_i;
957                                            break;
958                                        } else {
959                                            last_i = i;
960                                        }
961                                    }
962                                }
963                            });
964                            editor.set_cursor(cursor);
965                            return;
966                        }
967                        modit::Motion::NextSearch => match &self.search_opt {
968                            Some((value, forwards)) => {
969                                search(editor, value, *forwards);
970                                return;
971                            }
972                            None => return,
973                        },
974                        modit::Motion::NextWordEnd(word) => {
975                            let mut cursor = editor.cursor();
976                            editor.with_buffer(|buffer| {
977                                loop {
978                                    let text = buffer.lines[cursor.line].text();
979                                    if cursor.index < text.len() {
980                                        cursor.index = WordIter::new(text, word)
981                                            .map(|(i, w)| {
982                                                i + w
983                                                    .char_indices()
984                                                    .last()
985                                                    .map(|(i, _)| i)
986                                                    .unwrap_or(0)
987                                            })
988                                            .find(|&i| i > cursor.index)
989                                            .unwrap_or(text.len());
990                                        if cursor.index == text.len() {
991                                            // Try again, searching next line
992                                            continue;
993                                        }
994                                    } else if cursor.line + 1 < buffer.lines.len() {
995                                        // Go to next line and rerun loop
996                                        cursor.line += 1;
997                                        cursor.index = 0;
998                                        continue;
999                                    }
1000                                    break;
1001                                }
1002                            });
1003                            editor.set_cursor(cursor);
1004                            return;
1005                        }
1006                        modit::Motion::NextWordStart(word) => {
1007                            let mut cursor = editor.cursor();
1008                            editor.with_buffer(|buffer| {
1009                                loop {
1010                                    let text = buffer.lines[cursor.line].text();
1011                                    if cursor.index < text.len() {
1012                                        cursor.index = WordIter::new(text, word)
1013                                            .map(|(i, _)| i)
1014                                            .find(|&i| i > cursor.index)
1015                                            .unwrap_or(text.len());
1016                                        if cursor.index == text.len() {
1017                                            // Try again, searching next line
1018                                            continue;
1019                                        }
1020                                    } else if cursor.line + 1 < buffer.lines.len() {
1021                                        // Go to next line and rerun loop
1022                                        cursor.line += 1;
1023                                        cursor.index = 0;
1024                                        continue;
1025                                    }
1026                                    break;
1027                                }
1028                            });
1029                            editor.set_cursor(cursor);
1030                            return;
1031                        }
1032                        modit::Motion::PageDown => Action::Motion {
1033                            motion: Motion::PageDown,
1034                            select: false,
1035                        },
1036                        modit::Motion::PageUp => Action::Motion {
1037                            motion: Motion::PageUp,
1038                            select: false,
1039                        },
1040                        modit::Motion::PreviousChar(find_c) => {
1041                            let mut cursor = editor.cursor();
1042                            editor.with_buffer(|buffer| {
1043                                let text = buffer.lines[cursor.line].text();
1044                                if cursor.index > 0 {
1045                                    match text[..cursor.index]
1046                                        .char_indices()
1047                                        .filter(|&(_, c)| c == find_c)
1048                                        .last()
1049                                    {
1050                                        Some((i, _)) => {
1051                                            cursor.index = i;
1052                                        }
1053                                        None => {}
1054                                    }
1055                                }
1056                            });
1057                            editor.set_cursor(cursor);
1058                            return;
1059                        }
1060                        modit::Motion::PreviousCharTill(find_c) => {
1061                            let mut cursor = editor.cursor();
1062                            editor.with_buffer(|buffer| {
1063                                let text = buffer.lines[cursor.line].text();
1064                                if cursor.index > 0 {
1065                                    match text[..cursor.index]
1066                                        .char_indices()
1067                                        .filter_map(|(i, c)| {
1068                                            if c == find_c {
1069                                                let end = i + c.len_utf8();
1070                                                if end < cursor.index {
1071                                                    return Some(end);
1072                                                }
1073                                            }
1074                                            None
1075                                        })
1076                                        .last()
1077                                    {
1078                                        Some(i) => {
1079                                            cursor.index = i;
1080                                        }
1081                                        None => {}
1082                                    }
1083                                }
1084                            });
1085                            editor.set_cursor(cursor);
1086                            return;
1087                        }
1088                        modit::Motion::PreviousSearch => match &self.search_opt {
1089                            Some((value, forwards)) => {
1090                                search(editor, value, !*forwards);
1091                                return;
1092                            }
1093                            None => return,
1094                        },
1095                        modit::Motion::PreviousWordEnd(word) => {
1096                            let mut cursor = editor.cursor();
1097                            editor.with_buffer(|buffer| {
1098                                loop {
1099                                    let text = buffer.lines[cursor.line].text();
1100                                    if cursor.index > 0 {
1101                                        cursor.index = WordIter::new(text, word)
1102                                            .map(|(i, w)| {
1103                                                i + w
1104                                                    .char_indices()
1105                                                    .last()
1106                                                    .map(|(i, _)| i)
1107                                                    .unwrap_or(0)
1108                                            })
1109                                            .filter(|&i| i < cursor.index)
1110                                            .last()
1111                                            .unwrap_or(0);
1112                                        if cursor.index == 0 {
1113                                            // Try again, searching previous line
1114                                            continue;
1115                                        }
1116                                    } else if cursor.line > 0 {
1117                                        // Go to previous line and rerun loop
1118                                        cursor.line -= 1;
1119                                        cursor.index = buffer.lines[cursor.line].text().len();
1120                                        continue;
1121                                    }
1122                                    break;
1123                                }
1124                            });
1125                            editor.set_cursor(cursor);
1126                            return;
1127                        }
1128                        modit::Motion::PreviousWordStart(word) => {
1129                            let mut cursor = editor.cursor();
1130                            editor.with_buffer(|buffer| {
1131                                loop {
1132                                    let text = buffer.lines[cursor.line].text();
1133                                    if cursor.index > 0 {
1134                                        cursor.index = WordIter::new(text, word)
1135                                            .map(|(i, _)| i)
1136                                            .filter(|&i| i < cursor.index)
1137                                            .last()
1138                                            .unwrap_or(0);
1139                                        if cursor.index == 0 {
1140                                            // Try again, searching previous line
1141                                            continue;
1142                                        }
1143                                    } else if cursor.line > 0 {
1144                                        // Go to previous line and rerun loop
1145                                        cursor.line -= 1;
1146                                        cursor.index = buffer.lines[cursor.line].text().len();
1147                                        continue;
1148                                    }
1149                                    break;
1150                                }
1151                            });
1152                            editor.set_cursor(cursor);
1153                            return;
1154                        }
1155                        modit::Motion::Right => Action::Motion {
1156                            motion: Motion::Right,
1157                            select: false,
1158                        },
1159                        modit::Motion::RightInLine => {
1160                            let cursor = editor.cursor();
1161                            if cursor.index
1162                                < editor
1163                                    .with_buffer(|buffer| buffer.lines[cursor.line].text().len())
1164                            {
1165                                Action::Motion {
1166                                    motion: Motion::Right,
1167                                    select: false,
1168                                }
1169                            } else {
1170                                return;
1171                            }
1172                        }
1173                        modit::Motion::ScreenHigh => {
1174                            //TODO: is this efficient?
1175                            if let Some(line_i) = editor.with_buffer(|buffer| {
1176                                buffer.layout_runs().next().map(|first| first.line_i)
1177                            }) {
1178                                Action::Motion {
1179                                    motion: Motion::GotoLine(line_i),
1180                                    select: false,
1181                                }
1182                            } else {
1183                                return;
1184                            }
1185                        }
1186                        modit::Motion::ScreenLow => {
1187                            //TODO: is this efficient?
1188                            if let Some(line_i) = editor.with_buffer(|buffer| {
1189                                buffer.layout_runs().last().map(|last| last.line_i)
1190                            }) {
1191                                Action::Motion {
1192                                    motion: Motion::GotoLine(line_i),
1193                                    select: false,
1194                                }
1195                            } else {
1196                                return;
1197                            }
1198                        }
1199                        modit::Motion::ScreenMiddle => {
1200                            //TODO: is this efficient?
1201                            let action_opt = editor.with_buffer(|buffer| {
1202                                let mut layout_runs = buffer.layout_runs();
1203                                if let Some(first) = layout_runs.next() {
1204                                    if let Some(last) = layout_runs.last() {
1205                                        Some(Action::Motion {
1206                                            motion: Motion::GotoLine(
1207                                                (last.line_i + first.line_i) / 2,
1208                                            ),
1209                                            select: false,
1210                                        })
1211                                    } else {
1212                                        None
1213                                    }
1214                                } else {
1215                                    None
1216                                }
1217                            });
1218                            match action_opt {
1219                                Some(action) => action,
1220                                None => return,
1221                            }
1222                        }
1223                        modit::Motion::Selection => {
1224                            //TODO: what to do for this psuedo-motion?
1225                            return;
1226                        }
1227                        modit::Motion::SoftHome => Action::Motion {
1228                            motion: Motion::SoftHome,
1229                            select: false,
1230                        },
1231                        modit::Motion::Up => Action::Motion {
1232                            motion: Motion::Up,
1233                            select: false,
1234                        },
1235                    }
1236                }
1237            };
1238            editor.action(font_system, action);
1239        });
1240    }
1241
1242    fn preedit_range(&self) -> Option<core::ops::Range<usize>> {
1243        self.editor.preedit_range()
1244    }
1245
1246    fn preedit_text(&self) -> Option<String> {
1247        self.editor.preedit_text()
1248    }
1249
1250    fn cursor_position(&self) -> Option<(i32, i32)> {
1251        self.editor.cursor_position()
1252    }
1253
1254    fn set_cursor_hidden(&mut self, hidden: bool) {
1255        self.editor.set_cursor_hidden(hidden);
1256    }
1257}
1258
1259impl<'font_system, 'syntax_system, 'buffer>
1260    BorrowedWithFontSystem<'font_system, ViEditor<'syntax_system, 'buffer>>
1261{
1262    /// Load text from a file, and also set syntax to the best option
1263    #[cfg(feature = "std")]
1264    pub fn load_text<P: AsRef<std::path::Path>>(
1265        &mut self,
1266        path: P,
1267        attrs: crate::Attrs,
1268    ) -> std::io::Result<()> {
1269        self.inner.load_text(self.font_system, path, attrs)
1270    }
1271
1272    #[cfg(feature = "swash")]
1273    pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, f: F)
1274    where
1275        F: FnMut(i32, i32, u32, u32, Color),
1276    {
1277        self.inner.draw(self.font_system, cache, f);
1278    }
1279}