floem/views/editor/
text_document.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::HashMap,
4    ops::Range,
5    rc::Rc,
6};
7
8use floem_editor_core::{
9    buffer::{Buffer, InvalLines, rope_text::RopeText},
10    command::EditCommand,
11    cursor::Cursor,
12    editor::{Action, EditConf, EditType},
13    mode::{Mode, MotionMode},
14    register::Register,
15    selection::Selection,
16    word::WordCursor,
17};
18use floem_reactive::{
19    RwSignal, Scope, SignalGet, SignalTrack, SignalUpdate, SignalWith, create_effect,
20};
21use lapce_xi_rope::{Rope, RopeDelta};
22use smallvec::{SmallVec, smallvec};
23use ui_events::keyboard::Modifiers;
24
25use super::{
26    Editor, EditorStyle,
27    actions::{CommonAction, handle_command_default},
28    command::{Command, CommandExecuted},
29    id::EditorId,
30    phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
31    text::{Document, DocumentPhantom, PreeditData, SystemClipboard},
32};
33
34type PreCommandFn = Box<dyn Fn(PreCommand) -> CommandExecuted>;
35#[derive(Debug, Clone)]
36pub struct PreCommand<'a> {
37    pub editor: &'a Editor,
38    pub cmd: &'a Command,
39    pub count: Option<usize>,
40    pub mods: Modifiers,
41}
42
43type OnUpdateFn = Box<dyn Fn(OnUpdate)>;
44#[derive(Debug, Clone)]
45pub struct OnUpdate<'a> {
46    /// Optional because the document can be edited from outside any editor views
47    pub editor: Option<&'a Editor>,
48    deltas: &'a [(Rope, RopeDelta, InvalLines)],
49}
50impl<'a> OnUpdate<'a> {
51    pub fn deltas(&self) -> impl Iterator<Item = &'a RopeDelta> {
52        self.deltas.iter().map(|(_, delta, _)| delta)
53    }
54}
55
56/// A simple text document that holds content in a rope.
57///
58/// This can be used as a base structure for common operations.
59#[derive(Clone)]
60pub struct TextDocument {
61    buffer: RwSignal<Buffer>,
62    cache_rev: RwSignal<u64>,
63    preedit: PreeditData,
64
65    /// Whether to keep the indent of the previous line when inserting a new line
66    pub keep_indent: Cell<bool>,
67    /// Whether to automatically indent the new line via heuristics
68    pub auto_indent: Cell<bool>,
69
70    pub placeholders: RwSignal<HashMap<EditorId, String>>,
71
72    // (cmd: &Command, count: Option<usize>, modifiers: ModifierState)
73    /// Ran before a command is executed. If it says that it executed the command, then handlers
74    /// after it will not be called.
75    pre_command: Rc<RefCell<HashMap<EditorId, SmallVec<[PreCommandFn; 1]>>>>,
76
77    on_updates: Rc<RefCell<SmallVec<[OnUpdateFn; 1]>>>,
78}
79impl TextDocument {
80    pub fn new(cx: Scope, text: impl Into<Rope>) -> TextDocument {
81        let text = text.into();
82        let buffer = Buffer::new(text);
83        let preedit = PreeditData {
84            preedit: cx.create_rw_signal(None),
85        };
86        let cache_rev = cx.create_rw_signal(0);
87
88        let placeholders = cx.create_rw_signal(HashMap::new());
89
90        // Whenever the placeholders change, update the cache rev
91        create_effect(move |_| {
92            placeholders.track();
93            cache_rev.try_update(|cache_rev| {
94                *cache_rev += 1;
95            });
96        });
97
98        TextDocument {
99            buffer: cx.create_rw_signal(buffer),
100            cache_rev,
101            preedit,
102            keep_indent: Cell::new(true),
103            auto_indent: Cell::new(false),
104            placeholders,
105            pre_command: Rc::new(RefCell::new(HashMap::new())),
106            on_updates: Rc::new(RefCell::new(SmallVec::new())),
107        }
108    }
109
110    fn update_cache_rev(&self) {
111        self.cache_rev.try_update(|cache_rev| {
112            *cache_rev += 1;
113        });
114    }
115
116    fn on_update(&self, ed: Option<&Editor>, deltas: &[(Rope, RopeDelta, InvalLines)]) {
117        let on_updates = self.on_updates.borrow();
118        let data = OnUpdate { editor: ed, deltas };
119        for on_update in on_updates.iter() {
120            on_update(data.clone());
121        }
122    }
123
124    pub fn add_pre_command(
125        &self,
126        id: EditorId,
127        pre_command: impl Fn(PreCommand) -> CommandExecuted + 'static,
128    ) {
129        let pre_command: PreCommandFn = Box::new(pre_command);
130        self.pre_command
131            .borrow_mut()
132            .insert(id, smallvec![pre_command]);
133    }
134
135    pub fn clear_pre_commands(&self) {
136        self.pre_command.borrow_mut().clear();
137    }
138
139    pub fn add_on_update(&self, on_update: impl Fn(OnUpdate) + 'static) {
140        self.on_updates.borrow_mut().push(Box::new(on_update));
141    }
142
143    pub fn clear_on_updates(&self) {
144        self.on_updates.borrow_mut().clear();
145    }
146
147    pub fn add_placeholder(&self, editor_id: EditorId, placeholder: String) {
148        self.placeholders.update(|placeholders| {
149            placeholders.insert(editor_id, placeholder);
150        });
151    }
152
153    fn placeholder(&self, editor_id: EditorId) -> Option<String> {
154        self.placeholders
155            .with_untracked(|placeholders| placeholders.get(&editor_id).cloned())
156    }
157}
158impl Document for TextDocument {
159    fn text(&self) -> Rope {
160        self.buffer.with_untracked(|buffer| buffer.text().clone())
161    }
162
163    fn cache_rev(&self) -> RwSignal<u64> {
164        self.cache_rev
165    }
166
167    fn preedit(&self) -> PreeditData {
168        self.preedit.clone()
169    }
170
171    fn run_command(
172        &self,
173        ed: &Editor,
174        cmd: &Command,
175        count: Option<usize>,
176        modifiers: Modifiers,
177    ) -> CommandExecuted {
178        let pre_commands = self.pre_command.borrow();
179        let pre_commands = pre_commands.get(&ed.id());
180        let pre_commands = pre_commands.iter().flat_map(|c| c.iter());
181        let data = PreCommand {
182            editor: ed,
183            cmd,
184            count,
185            mods: modifiers,
186        };
187
188        for pre_command in pre_commands {
189            if pre_command(data.clone()) == CommandExecuted::Yes {
190                return CommandExecuted::Yes;
191            }
192        }
193
194        handle_command_default(ed, self, cmd, count, modifiers)
195    }
196
197    fn receive_char(&self, ed: &Editor, c: &str) {
198        if ed.read_only.get_untracked() {
199            return;
200        }
201
202        let mode = ed.cursor.with_untracked(|c| c.get_mode());
203        if mode == Mode::Insert {
204            let mut cursor = ed.cursor.get_untracked();
205            {
206                let old_cursor_mode = cursor.mode.clone();
207                let deltas = self
208                    .buffer
209                    .try_update(|buffer| {
210                        Action::insert(
211                            &mut cursor,
212                            buffer,
213                            c,
214                            &|_, c, offset| {
215                                WordCursor::new(&self.text(), offset).previous_unmatched(c)
216                            },
217                            // TODO: ?
218                            false,
219                            false,
220                        )
221                    })
222                    .unwrap();
223                self.buffer.update(|buffer| {
224                    buffer.set_cursor_before(old_cursor_mode);
225                    buffer.set_cursor_after(cursor.mode.clone());
226                });
227                // TODO: line specific invalidation
228                self.update_cache_rev();
229                self.on_update(Some(ed), &deltas);
230            }
231            ed.cursor.set(cursor);
232        }
233    }
234
235    fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType) {
236        let deltas = self
237            .buffer
238            .try_update(|buffer| buffer.edit(iter, edit_type));
239        let deltas = deltas.map(|x| [x]);
240        let deltas = deltas.as_ref().map(|x| x as &[_]).unwrap_or(&[]);
241
242        self.update_cache_rev();
243        self.on_update(None, deltas);
244    }
245}
246impl DocumentPhantom for TextDocument {
247    fn phantom_text(&self, edid: EditorId, styling: &EditorStyle, line: usize) -> PhantomTextLine {
248        let mut text = SmallVec::new();
249
250        if self.buffer.with_untracked(Buffer::is_empty)
251            && self.preedit.preedit.with_untracked(|p| p.is_none())
252        {
253            if let Some(placeholder) = self.placeholder(edid) {
254                text.push(PhantomText {
255                    kind: PhantomTextKind::Placeholder,
256                    col: 0,
257                    affinity: None,
258                    text: placeholder,
259                    font_size: None,
260                    fg: Some(styling.placeholder_color()),
261                    bg: None,
262                    under_line: None,
263                });
264            }
265        }
266
267        if let Some(preedit) = self.preedit_phantom(Some(styling.preedit_underline_color()), line) {
268            text.push(preedit);
269        }
270
271        PhantomTextLine { text }
272    }
273
274    fn has_multiline_phantom(&self, edid: EditorId, _styling: &EditorStyle) -> bool {
275        if !self.buffer.with_untracked(Buffer::is_empty) {
276            return false;
277        }
278
279        let placeholder_ml = self.placeholders.with_untracked(|placeholder| {
280            let Some(placeholder) = placeholder.get(&edid) else {
281                return false;
282            };
283
284            placeholder.lines().count() > 1
285        });
286
287        if placeholder_ml {
288            return true;
289        }
290
291        self.preedit.preedit.with_untracked(|preedit| {
292            let Some(preedit) = preedit else {
293                return false;
294            };
295
296            preedit.text.lines().count() > 1
297        })
298    }
299}
300impl CommonAction for TextDocument {
301    fn exec_motion_mode(
302        &self,
303        _ed: &Editor,
304        cursor: &mut Cursor,
305        motion_mode: MotionMode,
306        range: Range<usize>,
307        is_vertical: bool,
308        register: &mut Register,
309    ) {
310        self.buffer.try_update(move |buffer| {
311            Action::execute_motion_mode(cursor, buffer, motion_mode, range, is_vertical, register)
312        });
313    }
314
315    fn do_edit(
316        &self,
317        ed: &Editor,
318        cursor: &mut Cursor,
319        cmd: &EditCommand,
320        modal: bool,
321        register: &mut Register,
322        smart_tab: bool,
323    ) -> bool {
324        if ed.read_only.get_untracked() && !cmd.not_changing_buffer() {
325            return false;
326        }
327
328        let mut clipboard = SystemClipboard::new();
329        let old_cursor = cursor.mode.clone();
330        // TODO: configurable comment token
331        let deltas = self
332            .buffer
333            .try_update(|buffer| {
334                Action::do_edit(
335                    cursor,
336                    buffer,
337                    cmd,
338                    &mut clipboard,
339                    register,
340                    EditConf {
341                        modal,
342                        comment_token: "",
343                        smart_tab,
344                        keep_indent: self.keep_indent.get(),
345                        auto_indent: self.auto_indent.get(),
346                    },
347                )
348            })
349            .unwrap();
350
351        if !deltas.is_empty() {
352            self.buffer.update(|buffer| {
353                buffer.set_cursor_before(old_cursor);
354                buffer.set_cursor_after(cursor.mode.clone());
355            });
356
357            self.update_cache_rev();
358            self.on_update(Some(ed), &deltas);
359        }
360
361        !deltas.is_empty()
362    }
363}
364
365impl std::fmt::Debug for TextDocument {
366    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367        let mut s = f.debug_struct("TextDocument");
368        s.field("text", &self.text());
369        s.finish()
370    }
371}