floem/views/
text_editor.rs

1use std::rc::Rc;
2
3use floem_editor_core::{buffer::rope_text::RopeTextVal, indent::IndentStyle};
4use floem_reactive::{RwSignal, Scope, SignalUpdate, SignalWith, UpdaterEffect};
5use peniko::Color;
6
7use lapce_xi_rope::Rope;
8
9use crate::{
10    id::ViewId,
11    style::{CursorColor, Style},
12    view::{IntoView, View},
13    views::editor::{
14        Editor,
15        command::CommandExecuted,
16        id::EditorId,
17        keypress::{KeypressKey, default_key_handler},
18        text::{Document, SimpleStyling, Styling},
19        text_document::{OnUpdate, PreCommand, TextDocument},
20        view::editor_container_view,
21    },
22};
23
24use super::editor::{
25    CurrentLineColor, CursorSurroundingLines, IndentGuideColor, IndentStyleProp, Modal,
26    ModalRelativeLine, PhantomColor, PlaceholderColor, PreeditUnderlineColor, RenderWhitespaceProp,
27    ScrollBeyondLastLine, SelectionColor, ShowIndentGuide, SmartTab, VisibleWhitespaceColor,
28    WrapProp,
29    gutter::{DimColor, GutterClass, LeftOfCenterPadding, RightOfCenterPadding},
30    text::{RenderWhitespace, WrapMethod},
31    view::EditorViewClass,
32};
33
34/// A text editor view built on top of [Editor](super::editor::Editor). See [`text_editor`].
35///
36/// Note: this requires that the document underlying it is a [`TextDocument`] for the use of some
37/// logic.
38pub struct TextEditor {
39    id: ViewId,
40    child: ViewId,
41    // /// The scope this view was created in, used for creating the final view
42    cx: Scope,
43    editor: Editor,
44}
45
46// Note: this should typically be kept in sync with Lapce's
47// `defaults/keymaps-common.toml`
48//
49/// A text editor view built on top of [Editor](super::editor::Editor). This is the main editor view used in the
50/// [Lapce](https://lap.dev/lapce/) code editor. The default keymap includes the standard editing keys, for using your
51/// own keymap use [`text_editor_keys`].
52///
53/// ## Default Keymaps
54/// ### Basic Editing
55/// Up + ALT => Move Line Up
56/// Down + ALT => Move Line Down
57///
58/// Delete => Delete Forward
59/// Backspace => Delete Backward
60/// Backspace + Shift => Delete Forward
61///
62/// Home => Move to the start of the file
63/// End => Move to the end of the file
64///
65/// PageUp => Scroll up by a page
66/// PageDown => Scroll down by a page
67///
68/// PageUp + CTRL => Scroll up
69/// PageDown + CTRL => Scroll down
70///
71/// Enter => Insert New Line
72/// Tab => Insert Tab
73///
74/// Up + ALT, Up + SHIFT => Duplicate line up
75/// Down + ALT, Down + SHIFT => Duplicate line down
76///
77/// ### Multi Cursor
78/// i + ALT, i + SHIFT => Insert Cursor at the end of the line
79pub fn text_editor(text: impl Into<Rope>) -> TextEditor {
80    let id = ViewId::new();
81    let cx = Scope::current();
82
83    let doc = Rc::new(TextDocument::new(cx, text));
84    let style = Rc::new(SimpleStyling::new());
85    let editor = Editor::new(cx, doc, style, false);
86
87    let editor_sig = cx.create_rw_signal(editor.clone());
88    let child = cx
89        .enter(|| editor_container_view(editor_sig, |_| true, default_key_handler(editor_sig)))
90        .into_view();
91
92    let child_id = child.id();
93    id.set_children([child]);
94
95    TextEditor {
96        id,
97        child: child_id,
98        cx,
99        editor,
100    }
101}
102
103/// A text editor view built on top of [Editor](super::editor::Editor) that allows providing your own keymap callback.
104///
105/// See [`text_editor`] for a list of the default keymaps that you will need to handle yourself if using this function.
106pub fn text_editor_keys(
107    text: impl Into<Rope>,
108    handle_key_event: impl Fn(RwSignal<Editor>, &KeypressKey) -> CommandExecuted + 'static,
109) -> TextEditor {
110    let id = ViewId::new();
111    let cx = Scope::current();
112
113    let doc = Rc::new(TextDocument::new(cx, text));
114    let style = Rc::new(SimpleStyling::new());
115    let editor = Editor::new(cx, doc, style, false);
116
117    let editor_sig = cx.create_rw_signal(editor.clone());
118    let child = cx
119        .enter(|| {
120            editor_container_view(
121                editor_sig,
122                |_| true,
123                move |kp| handle_key_event(editor_sig, &kp),
124            )
125        })
126        .into_view();
127
128    let child_id = child.id();
129    id.set_children([child]);
130
131    TextEditor {
132        id,
133        cx,
134        editor,
135        child: child_id,
136    }
137}
138
139impl View for TextEditor {
140    fn id(&self) -> ViewId {
141        self.id
142    }
143
144    fn view_style(&self) -> Option<Style> {
145        Some(Style::new().min_width(25).min_height(10))
146    }
147
148    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
149        "Text Editor".into()
150    }
151
152    fn paint(&mut self, cx: &mut crate::context::PaintCx) {
153        cx.save();
154        let size = self
155            .id
156            .get_layout()
157            .map(|layout| {
158                peniko::kurbo::Size::new(layout.size.width as f64, layout.size.height as f64)
159            })
160            .unwrap_or_default();
161        let border_radii =
162            crate::view::border_to_radii(&self.id.state().borrow().combined_style, size);
163
164        if crate::view::radii_max(border_radii) > 0.0 {
165            let rect = size.to_rect().to_rounded_rect(border_radii);
166            cx.clip(&rect);
167        } else {
168            cx.clip(&size.to_rect());
169        }
170        cx.paint_view(self.child);
171        cx.restore();
172    }
173}
174
175/// The custom style elements that are specific to an [Editor].
176pub struct EditorCustomStyle(pub(crate) Style);
177
178impl EditorCustomStyle {
179    /// Sets whether the gutter should be hidden.
180    pub fn hide_gutter(mut self, hide: bool) -> Self {
181        self.0 = self
182            .0
183            .class(GutterClass, |s| s.apply_if(hide, |s| s.hide()));
184        self
185    }
186
187    /// Sets the text accent color of the gutter.
188    ///
189    /// This is the color of the line number for the current line.
190    /// It will default to the current Text Color
191    pub fn gutter_accent_color(mut self, color: Color) -> Self {
192        self.0 = self.0.class(GutterClass, |s| s.color(color));
193        self
194    }
195
196    /// Sets the text dim color of the gutter.
197    ///
198    /// This is the color of the line number for all lines except the current line.
199    /// If this is not specified it will default to the gutter accent color.
200    pub fn gutter_dim_color(mut self, color: Color) -> Self {
201        self.0 = self.0.class(GutterClass, |s| s.set(DimColor, color));
202        self
203    }
204
205    /// Sets the padding to the left of the numbers in the gutter.
206    pub fn gutter_left_padding(mut self, padding: f64) -> Self {
207        self.0 = self
208            .0
209            .class(GutterClass, |s| s.set(LeftOfCenterPadding, padding));
210        self
211    }
212
213    /// Sets the padding to the right of the numbers in the gutter.
214    pub fn gutter_right_padding(mut self, padding: f64) -> Self {
215        self.0 = self
216            .0
217            .class(GutterClass, |s| s.set(RightOfCenterPadding, padding));
218        self
219    }
220
221    /// Sets the background color of the current line in the gutter
222    pub fn gutter_current_color(mut self, color: Color) -> Self {
223        self.0 = self
224            .0
225            .class(GutterClass, |s| s.set(CurrentLineColor, color));
226        self
227    }
228
229    /// Sets the background color to be applied around selected text.
230    pub fn selection_color(mut self, color: Color) -> Self {
231        self.0 = self
232            .0
233            .class(EditorViewClass, |s| s.set(SelectionColor, color));
234        self
235    }
236
237    /// Sets the indent style.
238    pub fn indent_style(mut self, indent_style: IndentStyle) -> Self {
239        self.0 = self
240            .0
241            .class(EditorViewClass, |s| s.set(IndentStyleProp, indent_style));
242        self
243    }
244
245    /// Sets the color of the indent guide.
246    pub fn indent_guide_color(mut self, color: Color) -> Self {
247        self.0 = self
248            .0
249            .class(EditorViewClass, |s| s.set(IndentGuideColor, color));
250        self
251    }
252
253    /// Sets the method for wrapping lines.
254    pub fn wrap_method(mut self, wrap: WrapMethod) -> Self {
255        self.0 = self.0.class(EditorViewClass, |s| s.set(WrapProp, wrap));
256        self
257    }
258
259    /// Sets the color of the cursor.
260    pub fn cursor_color(mut self, cursor: Color) -> Self {
261        self.0 = self
262            .0
263            .class(EditorViewClass, |s| s.set(CursorColor, cursor));
264        self
265    }
266
267    /// Allow scrolling beyond the last line of the document.
268    pub fn scroll_beyond_last_line(mut self, scroll_beyond: bool) -> Self {
269        self.0 = self.0.class(EditorViewClass, |s| {
270            s.set(ScrollBeyondLastLine, scroll_beyond)
271        });
272        self
273    }
274
275    /// Sets the background color of the current line.
276    pub fn current_line_color(mut self, color: Color) -> Self {
277        self.0 = self
278            .0
279            .class(EditorViewClass, |s| s.set(CurrentLineColor, color));
280        self
281    }
282
283    /// Sets the color of visible whitespace characters.
284    pub fn visible_whitespace(mut self, color: Color) -> Self {
285        self.0 = self
286            .0
287            .class(EditorViewClass, |s| s.set(VisibleWhitespaceColor, color));
288        self
289    }
290
291    /// Sets which white space characters should be rendered.
292    pub fn render_white_space(mut self, render_white_space: RenderWhitespace) -> Self {
293        self.0 = self.0.class(EditorViewClass, |s| {
294            s.set(RenderWhitespaceProp, render_white_space)
295        });
296        self
297    }
298
299    /// Set the number of lines to keep visible above and below the cursor.
300    /// Default: `1`
301    pub fn cursor_surrounding_lines(mut self, lines: usize) -> Self {
302        self.0 = self
303            .0
304            .class(EditorViewClass, |s| s.set(CursorSurroundingLines, lines));
305        self
306    }
307
308    /// Sets whether the indent guides should be displayed.
309    pub fn indent_guide(mut self, show: bool) -> Self {
310        self.0 = self
311            .0
312            .class(EditorViewClass, |s| s.set(ShowIndentGuide, show));
313        self
314    }
315
316    /// Sets the editor's mode to modal or non-modal.
317    pub fn modal(mut self, modal: bool) -> Self {
318        self.0 = self.0.class(EditorViewClass, |s| s.set(Modal, modal));
319        self
320    }
321
322    /// Determines if line numbers are relative in modal mode.
323    pub fn modal_relative_line(mut self, modal_relative_line: bool) -> Self {
324        self.0 = self.0.class(EditorViewClass, |s| {
325            s.set(ModalRelativeLine, modal_relative_line)
326        });
327        self
328    }
329
330    /// Enables or disables smart tab behavior, which inserts the indent style detected in the file when the tab key is pressed.
331    pub fn smart_tab(mut self, smart_tab: bool) -> Self {
332        self.0 = self
333            .0
334            .class(EditorViewClass, |s| s.set(SmartTab, smart_tab));
335        self
336    }
337
338    /// Sets the color of phantom text
339    pub fn phantom_color(mut self, color: Color) -> Self {
340        self.0 = self
341            .0
342            .class(EditorViewClass, |s| s.set(PhantomColor, color));
343        self
344    }
345
346    /// Sets the color of the placeholder text.
347    pub fn placeholder_color(mut self, color: Color) -> Self {
348        self.0 = self
349            .0
350            .class(EditorViewClass, |s| s.set(PlaceholderColor, color));
351        self
352    }
353
354    /// Sets the color of the underline for preedit text.
355    pub fn preedit_underline_color(mut self, color: Color) -> Self {
356        self.0 = self
357            .0
358            .class(EditorViewClass, |s| s.set(PreeditUnderlineColor, color));
359        self
360    }
361}
362
363impl TextEditor {
364    /// Sets the custom style properties of the `TextEditor`.
365    pub fn editor_style(
366        self,
367        style: impl Fn(EditorCustomStyle) -> EditorCustomStyle + 'static,
368    ) -> Self {
369        let id = self.id();
370        let view_state = id.state();
371        let offset = view_state.borrow_mut().style.next_offset();
372        let style = UpdaterEffect::new(
373            move || style(EditorCustomStyle(Style::new())),
374            move |style| id.update_style(offset, style.0),
375        );
376        view_state.borrow_mut().style.push(style.0);
377        self
378    }
379
380    /// Return a reference to the underlying [Editor].
381    pub fn editor(&self) -> &Editor {
382        &self.editor
383    }
384
385    /// Allows for creation of a [TextEditor] with an existing [Editor].
386    pub fn with_editor(self, f: impl FnOnce(&Editor)) -> Self {
387        f(&self.editor);
388        self
389    }
390
391    /// Allows for creation of a [TextEditor] with an existing mutable [Editor].
392    pub fn with_editor_mut(mut self, f: impl FnOnce(&mut Editor)) -> Self {
393        f(&mut self.editor);
394        self
395    }
396
397    /// Returns the [EditorId] of the underlying [Editor].
398    pub fn editor_id(&self) -> EditorId {
399        self.editor.id()
400    }
401
402    /// Opens the `TextEditor` with the provided [`Document`].
403    /// You should usually not swap this out without good reason.
404    pub fn with_doc(self, f: impl FnOnce(&dyn Document)) -> Self {
405        self.editor.doc.with_untracked(|doc| {
406            f(doc.as_ref());
407        });
408        self
409    }
410
411    /// Returns a reference to the underlying [Document]. This should usually be a [TextDocument].
412    pub fn doc(&self) -> Rc<dyn Document> {
413        self.editor.doc()
414    }
415
416    /// Try downcasting the document to a [`TextDocument`].
417    /// Returns `None` if the document is not a [`TextDocument`].
418    fn text_doc(&self) -> Option<Rc<TextDocument>> {
419        (self.doc() as Rc<dyn ::std::any::Any>).downcast().ok()
420    }
421
422    // TODO(minor): should this be named `text`? Ideally most users should use the rope text version
423    pub fn rope_text(&self) -> RopeTextVal {
424        self.editor.rope_text()
425    }
426
427    /// Use a different document in the text editor
428    pub fn use_doc(self, doc: Rc<dyn Document>) -> Self {
429        self.editor.update_doc(doc, None);
430        self
431    }
432
433    /// Use the same document as another text editor view.
434    ///
435    /// ```rust,ignore
436    /// let primary = text_editor();
437    /// let secondary = text_editor().share_document(&primary);
438    ///
439    /// stack((
440    ///     primary,
441    ///     secondary,
442    /// ))
443    /// ```
444    ///
445    /// If you wish for it to also share the styling, consider using [`TextEditor::shared_editor`]
446    /// instead.
447    pub fn share_doc(self, other: &TextEditor) -> Self {
448        self.use_doc(other.editor.doc())
449    }
450
451    /// Create a new [`TextEditor`] instance from this instance, sharing the document and styling.
452    ///
453    /// ```rust,ignore
454    /// let primary = text_editor();
455    /// let secondary = primary.shared_editor();
456    /// ```
457    ///
458    /// Also see the [Editor example](https://github.com/lapce/floem/tree/main/examples/editor).
459    pub fn shared_editor(&self) -> TextEditor {
460        let id = ViewId::new();
461
462        let doc = self.editor.doc();
463        let style = self.editor.style();
464        let editor = Editor::new(self.cx, doc, style, false);
465
466        let editor_sig = self.cx.create_rw_signal(editor.clone());
467        let child = self
468            .cx
469            .enter(|| editor_container_view(editor_sig, |_| true, default_key_handler(editor_sig)))
470            .into_view();
471
472        let child_id = child.id();
473        id.set_children([child]);
474
475        TextEditor {
476            id,
477            cx: self.cx,
478            editor,
479            child: child_id,
480        }
481    }
482
483    /// Change the [`Styling`] used for the editor.
484    ///
485    /// ```rust,ignore
486    /// let styling = SimpleStyling::builder()
487    ///     .font_size(12)
488    ///     .weight(Weight::BOLD);
489    /// text_editor().styling(styling);
490    /// ```
491    pub fn styling(self, styling: impl Styling + 'static) -> Self {
492        self.styling_rc(Rc::new(styling))
493    }
494
495    /// Use an `Rc<dyn Styling>` to share between different editors.
496    pub fn styling_rc(self, styling: Rc<dyn Styling>) -> Self {
497        self.editor.update_styling(styling);
498        self
499    }
500
501    /// Set the text editor to read only.
502    /// Equivalent to setting [`Editor::read_only`]
503    /// Default: `false`
504    pub fn read_only(self) -> Self {
505        self.editor.read_only.set(true);
506        self
507    }
508
509    /// Set the placeholder text that is displayed when the document is empty.
510    /// Can span multiple lines.
511    /// This is per-editor, not per-document.
512    /// Equivalent to calling [`TextDocument::add_placeholder`]
513    /// Default: `None`
514    ///
515    /// Note: only works for the default backing [`TextDocument`] doc
516    pub fn placeholder(self, text: impl Into<String>) -> Self {
517        if let Some(doc) = self.text_doc() {
518            doc.add_placeholder(self.editor_id(), text.into());
519        }
520
521        self
522    }
523
524    /// When commands are run on the document, this function is called.
525    /// If it returns [`CommandExecuted::Yes`] then further handlers after it, including the
526    /// default handler, are not executed.
527    ///
528    /// ```rust
529    /// use floem::views::editor::command::{Command, CommandExecuted};
530    /// use floem::views::text_editor::text_editor;
531    /// use floem_editor_core::command::EditCommand;
532    /// text_editor("Hello")
533    ///     .pre_command(|ev| {
534    ///         if matches!(ev.cmd, Command::Edit(EditCommand::Undo)) {
535    ///             // Sorry, no undoing allowed
536    ///             CommandExecuted::Yes
537    ///         } else {
538    ///             CommandExecuted::No
539    ///         }
540    ///     })
541    ///     .pre_command(|_| {
542    ///         // This will never be called if command was an undo
543    ///         CommandExecuted::Yes
544    ///     })
545    ///     .pre_command(|_| {
546    ///         // This will never be called
547    ///         CommandExecuted::No
548    ///     });
549    /// ```
550    ///
551    /// Note that these are specific to each text editor view.
552    ///
553    /// Note: only works for the default backing [`TextDocument`] doc
554    pub fn pre_command(self, f: impl Fn(PreCommand) -> CommandExecuted + 'static) -> Self {
555        if let Some(doc) = self.text_doc() {
556            doc.add_pre_command(self.editor.id(), f);
557        }
558        self
559    }
560
561    /// Listen for deltas applied to the editor.
562    ///
563    /// Useful for anything that has positions based in the editor that can be updated after
564    /// typing, such as syntax highlighting.
565    ///
566    /// Note: only works for the default backing [`TextDocument`] doc
567    pub fn update(self, f: impl Fn(OnUpdate) + 'static) -> Self {
568        if let Some(doc) = self.text_doc() {
569            doc.add_on_update(f);
570        }
571        self
572    }
573}