salvation_cosmic_text/edit/
mod.rs

1use alloc::sync::Arc;
2use core::ops::Range;
3
4#[cfg(not(feature = "std"))]
5use alloc::{string::String, vec::Vec};
6use core::cmp;
7use unicode_segmentation::UnicodeSegmentation;
8
9use crate::{AttrsList, AttrsOwned, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};
10
11pub use self::editor::*;
12mod editor;
13
14#[cfg(feature = "syntect")]
15pub use self::syntect::*;
16#[cfg(feature = "syntect")]
17mod syntect;
18
19#[cfg(feature = "vi")]
20pub use self::vi::*;
21#[cfg(feature = "vi")]
22mod vi;
23
24/// An action to perform on an [`Editor`]
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum Action {
27    /// Move the cursor with some motion
28    Motion {
29        motion: Motion,
30        select: bool,
31    },
32    /// Escape, clears selection
33    Escape,
34    /// Select text from start to end
35    SelectAll,
36    /// Insert character at cursor
37    Insert(char),
38    /// Create new line
39    Enter,
40    /// Delete text behind cursor
41    Backspace,
42    /// Delete text behind cursor to next word boundary
43    DeleteStartOfWord,
44    /// Delete text in front of cursor
45    Delete,
46    /// Delete text in front of cursor to next word boundary
47    DeleteEndOfWord,
48    // Indent text (typically Tab)
49    Indent,
50    // Unindent text (typically Shift+Tab)
51    Unindent,
52    /// Mouse click at specified position
53    Click {
54        x: i32,
55        y: i32,
56        select: bool,
57    },
58    /// Mouse double click at specified position
59    DoubleClick {
60        x: i32,
61        y: i32,
62    },
63    /// Mouse triple click at specified position
64    TripleClick {
65        x: i32,
66        y: i32,
67    },
68    /// Mouse drag to specified position
69    Drag {
70        x: i32,
71        y: i32,
72    },
73    /// Scroll specified number of lines
74    Scroll {
75        lines: i32,
76    },
77    /// Set preedit text, replacing any previous preedit text
78    ///
79    /// If `cursor` is specified, it contains a start and end cursor byte positions
80    /// within the preedit. If no cursor is specified for a non-empty preedit,
81    /// the cursor should be hidden.
82    ///
83    /// If `attrs` is specified, these attributes will be assigned to the preedit's span.
84    /// However, regardless of `attrs` setting, the preedit's span will always have
85    /// `is_preedit` set to `true`.
86    SetPreedit {
87        preedit: String,
88        cursor: Option<(usize, usize)>,
89        attrs: Option<AttrsOwned>,
90    },
91}
92
93#[derive(Debug)]
94pub enum BufferRef<'buffer> {
95    Owned(Buffer),
96    Borrowed(&'buffer mut Buffer),
97    Arc(Arc<Buffer>),
98}
99
100impl<'buffer> From<Buffer> for BufferRef<'buffer> {
101    fn from(buffer: Buffer) -> Self {
102        Self::Owned(buffer)
103    }
104}
105
106impl<'buffer> From<&'buffer mut Buffer> for BufferRef<'buffer> {
107    fn from(buffer: &'buffer mut Buffer) -> Self {
108        Self::Borrowed(buffer)
109    }
110}
111
112impl<'buffer> From<Arc<Buffer>> for BufferRef<'buffer> {
113    fn from(arc: Arc<Buffer>) -> Self {
114        Self::Arc(arc)
115    }
116}
117
118/// A unique change to an editor
119#[derive(Clone, Debug)]
120pub struct ChangeItem {
121    /// Cursor indicating start of change
122    pub start: Cursor,
123    /// Cursor indicating end of change
124    pub end: Cursor,
125    /// Text to be inserted or deleted
126    pub text: String,
127    /// Insert if true, delete if false
128    pub insert: bool,
129}
130
131impl ChangeItem {
132    // Reverse change item (in place)
133    pub fn reverse(&mut self) {
134        self.insert = !self.insert;
135    }
136}
137
138/// A set of change items grouped into one logical change
139#[derive(Clone, Debug, Default)]
140pub struct Change {
141    /// Change items grouped into one change
142    pub items: Vec<ChangeItem>,
143}
144
145impl Change {
146    // Reverse change (in place)
147    pub fn reverse(&mut self) {
148        self.items.reverse();
149        for item in self.items.iter_mut() {
150            item.reverse();
151        }
152    }
153}
154
155/// Selection mode
156#[derive(Clone, Copy, Debug, Eq, PartialEq)]
157pub enum Selection {
158    /// No selection
159    None,
160    /// Normal selection
161    Normal(Cursor),
162    /// Select by lines
163    Line(Cursor),
164    /// Select by words
165    Word(Cursor),
166    //TODO: Select block
167}
168
169/// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor`
170pub trait Edit<'buffer> {
171    /// Mutably borrows `self` together with an [`FontSystem`] for more convenient methods
172    fn borrow_with<'font_system>(
173        &'font_system mut self,
174        font_system: &'font_system mut FontSystem,
175    ) -> BorrowedWithFontSystem<'font_system, Self>
176    where
177        Self: Sized,
178    {
179        BorrowedWithFontSystem {
180            inner: self,
181            font_system,
182        }
183    }
184
185    /// Get the internal [`BufferRef`]
186    fn buffer_ref(&self) -> &BufferRef<'buffer>;
187
188    /// Get the internal [`BufferRef`]
189    fn buffer_ref_mut(&mut self) -> &mut BufferRef<'buffer>;
190
191    /// Get the internal [`Buffer`]
192    fn with_buffer<F: FnOnce(&Buffer) -> T, T>(&self, f: F) -> T {
193        match self.buffer_ref() {
194            BufferRef::Owned(buffer) => f(buffer),
195            BufferRef::Borrowed(buffer) => f(buffer),
196            BufferRef::Arc(buffer) => f(buffer),
197        }
198    }
199
200    /// Get the internal [`Buffer`], mutably
201    fn with_buffer_mut<F: FnOnce(&mut Buffer) -> T, T>(&mut self, f: F) -> T {
202        match self.buffer_ref_mut() {
203            BufferRef::Owned(buffer) => f(buffer),
204            BufferRef::Borrowed(buffer) => f(buffer),
205            BufferRef::Arc(buffer) => f(Arc::make_mut(buffer)),
206        }
207    }
208
209    /// Get the [`Buffer`] redraw flag
210    fn redraw(&self) -> bool {
211        self.with_buffer(|buffer| buffer.redraw())
212    }
213
214    /// Set the [`Buffer`] redraw flag
215    fn set_redraw(&mut self, redraw: bool) {
216        self.with_buffer_mut(|buffer| buffer.set_redraw(redraw))
217    }
218
219    /// Get the current cursor
220    fn cursor(&self) -> Cursor;
221
222    /// Hide or show the cursor
223    ///
224    /// This should be used to hide the cursor, for example,
225    /// when the editor is unfocused, when the text is not editable,
226    /// or to implement cursor blinking.
227    ///
228    /// Note that even after `set_cursor_hidden(false)`, the editor may
229    /// choose to hide the cursor based on internal state, for example,
230    /// when there is a selection or when there is a preedit without a cursor.
231    fn set_cursor_hidden(&mut self, hidden: bool);
232
233    /// Set the current cursor
234    fn set_cursor(&mut self, cursor: Cursor);
235
236    /// Returns true if some text is selected
237    fn has_selection(&self) -> bool {
238        match self.selection() {
239            Selection::None => false,
240            Selection::Normal(selection) => {
241                let cursor = self.cursor();
242                selection.line != cursor.line || selection.index != cursor.index
243            }
244            Selection::Line(selection) => selection.line != self.cursor().line,
245            Selection::Word(_) => true,
246        }
247    }
248
249    /// Get the current selection position
250    fn selection(&self) -> Selection;
251
252    /// Set the current selection position
253    fn set_selection(&mut self, selection: Selection);
254
255    /// Get the bounds of the current selection
256    //TODO: will not work with Block select
257    fn selection_bounds(&self) -> Option<(Cursor, Cursor)> {
258        self.with_buffer(|buffer| {
259            let cursor = self.cursor();
260            match self.selection() {
261                Selection::None => None,
262                Selection::Normal(select) => match select.line.cmp(&cursor.line) {
263                    cmp::Ordering::Greater => Some((cursor, select)),
264                    cmp::Ordering::Less => Some((select, cursor)),
265                    cmp::Ordering::Equal => {
266                        /* select.line == cursor.line */
267                        if select.index < cursor.index {
268                            Some((select, cursor))
269                        } else {
270                            /* select.index >= cursor.index */
271                            Some((cursor, select))
272                        }
273                    }
274                },
275                Selection::Line(select) => {
276                    let start_line = cmp::min(select.line, cursor.line);
277                    let end_line = cmp::max(select.line, cursor.line);
278                    let end_index = buffer.lines[end_line].text().len();
279                    Some((Cursor::new(start_line, 0), Cursor::new(end_line, end_index)))
280                }
281                Selection::Word(select) => {
282                    let (mut start, mut end) = match select.line.cmp(&cursor.line) {
283                        cmp::Ordering::Greater => (cursor, select),
284                        cmp::Ordering::Less => (select, cursor),
285                        cmp::Ordering::Equal => {
286                            /* select.line == cursor.line */
287                            if select.index < cursor.index {
288                                (select, cursor)
289                            } else {
290                                /* select.index >= cursor.index */
291                                (cursor, select)
292                            }
293                        }
294                    };
295
296                    // Move start to beginning of word
297                    {
298                        let line = &buffer.lines[start.line];
299                        start.index = line
300                            .text()
301                            .unicode_word_indices()
302                            .rev()
303                            .map(|(i, _)| i)
304                            .find(|&i| i < start.index)
305                            .unwrap_or(0);
306                    }
307
308                    // Move end to end of word
309                    {
310                        let line = &buffer.lines[end.line];
311                        end.index = line
312                            .text()
313                            .unicode_word_indices()
314                            .map(|(i, word)| i + word.len())
315                            .find(|&i| i > end.index)
316                            .unwrap_or(line.text().len());
317                    }
318
319                    Some((start, end))
320                }
321            }
322        })
323    }
324
325    /// Get the current automatic indentation setting
326    fn auto_indent(&self) -> bool;
327
328    /// Enable or disable automatic indentation
329    fn set_auto_indent(&mut self, auto_indent: bool);
330
331    /// Get the current tab width
332    fn tab_width(&self) -> u16;
333
334    /// Set the current tab width. A `tab_width` of 0 is not allowed, and will be ignored
335    fn set_tab_width(&mut self, tab_width: u16);
336
337    /// Shape lines until scroll, after adjusting scroll if the cursor moved
338    fn shape_as_needed(&mut self, font_system: &mut FontSystem, prune: bool);
339
340    /// Delete text starting at start Cursor and ending at end Cursor
341    fn delete_range(&mut self, start: Cursor, end: Cursor);
342
343    /// Insert text at specified cursor with specified attrs_list
344    fn insert_at(&mut self, cursor: Cursor, data: &str, attrs_list: Option<AttrsList>) -> Cursor;
345
346    /// Copy selection
347    fn copy_selection(&self) -> Option<String>;
348
349    /// Delete selection, adjusting cursor and returning true if there was a selection
350    // Also used by backspace, delete, insert, and enter when there is a selection
351    fn delete_selection(&mut self) -> bool;
352
353    /// Insert a string at the current cursor or replacing the current selection with the given
354    /// attributes, or with the previous character's attributes if None is given.
355    fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>) {
356        self.delete_selection();
357        let new_cursor = self.insert_at(self.cursor(), data, attrs_list);
358        self.set_cursor(new_cursor);
359    }
360
361    /// Apply a change
362    fn apply_change(&mut self, change: &Change) -> bool;
363
364    /// Start collecting change
365    fn start_change(&mut self);
366
367    /// Get completed change
368    fn finish_change(&mut self) -> Option<Change>;
369
370    /// Returns the range of byte indices of the text corresponding
371    /// to the preedit
372    fn preedit_range(&self) -> Option<Range<usize>>;
373
374    /// Get current preedit text
375    fn preedit_text(&self) -> Option<String>;
376
377    /// Perform an [Action] on the editor
378    fn action(&mut self, font_system: &mut FontSystem, action: Action);
379
380    /// Get X and Y position of the top left corner of the cursor
381    fn cursor_position(&self) -> Option<(i32, i32)>;
382}
383
384impl<'font_system, 'buffer, E: Edit<'buffer>> BorrowedWithFontSystem<'font_system, E> {
385    /// Get the internal [`Buffer`], mutably
386    pub fn with_buffer_mut<F: FnOnce(&mut BorrowedWithFontSystem<Buffer>) -> T, T>(
387        &mut self,
388        f: F,
389    ) -> T {
390        self.inner.with_buffer_mut(|buffer| {
391            let mut borrowed = BorrowedWithFontSystem {
392                inner: buffer,
393                font_system: self.font_system,
394            };
395            f(&mut borrowed)
396        })
397    }
398
399    /// Shape lines until scroll, after adjusting scroll if the cursor moved
400    pub fn shape_as_needed(&mut self, prune: bool) {
401        self.inner.shape_as_needed(self.font_system, prune);
402    }
403
404    /// Perform an [Action] on the editor
405    pub fn action(&mut self, action: Action) {
406        self.inner.action(self.font_system, action);
407    }
408}