floem/views/editor/
text.rs

1use std::{borrow::Cow, fmt::Debug, ops::Range, rc::Rc};
2
3use crate::{
4    peniko::Color,
5    peniko::color::palette,
6    reactive::{RwSignal, Scope},
7    text::{Attrs, AttrsList, FamilyOwned, Stretch, Weight},
8    views::EditorCustomStyle,
9};
10use floem_editor_core::{
11    buffer::rope_text::{RopeText, RopeTextVal},
12    command::EditCommand,
13    cursor::Cursor,
14    editor::EditType,
15    indent::IndentStyle,
16    mode::MotionMode,
17    register::{Clipboard, Register},
18    selection::Selection,
19    word::WordCursor,
20};
21use floem_reactive::SignalGet;
22use lapce_xi_rope::Rope;
23#[cfg(feature = "serde")]
24use serde::{Deserialize, Serialize};
25use ui_events::keyboard::Modifiers;
26
27use super::{
28    Editor, EditorStyle,
29    actions::CommonAction,
30    command::{Command, CommandExecuted},
31    gutter::GutterClass,
32    id::EditorId,
33    layout::TextLayoutLine,
34    normal_compute_screen_lines,
35    phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
36    view::{ScreenLines, ScreenLinesBase},
37};
38
39// TODO(minor): Should we get rid of this now that this is in floem?
40pub struct SystemClipboard;
41
42impl Default for SystemClipboard {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl SystemClipboard {
49    pub fn new() -> Self {
50        Self
51    }
52
53    #[cfg(windows)]
54    pub fn get_file_list() -> Option<Vec<std::path::PathBuf>> {
55        crate::Clipboard::get_file_list().ok()
56    }
57}
58
59impl Clipboard for SystemClipboard {
60    fn get_string(&mut self) -> Option<String> {
61        crate::Clipboard::get_contents().ok()
62    }
63
64    fn put_string(&mut self, s: impl AsRef<str>) {
65        let _ = crate::Clipboard::set_contents(s.as_ref().to_string());
66    }
67}
68
69#[derive(Clone)]
70pub struct Preedit {
71    pub text: String,
72    pub cursor: Option<(usize, usize)>,
73    pub offset: usize,
74}
75
76/// IME Preedit
77///
78/// This is used for IME input, and must be owned by the `Document`.
79#[derive(Debug, Clone)]
80pub struct PreeditData {
81    pub preedit: RwSignal<Option<Preedit>>,
82}
83impl PreeditData {
84    pub fn new(cx: Scope) -> PreeditData {
85        PreeditData {
86            preedit: cx.create_rw_signal(None),
87        }
88    }
89}
90
91/// A document. This holds text.
92pub trait Document: DocumentPhantom + ::std::any::Any {
93    /// Get the text of the document
94    ///
95    /// Note: typically you should call [`Document::rope_text`] as that provides more checks and
96    /// utility functions.
97    fn text(&self) -> Rope;
98
99    fn rope_text(&self) -> RopeTextVal {
100        RopeTextVal::new(self.text())
101    }
102
103    fn cache_rev(&self) -> RwSignal<u64>;
104
105    /// Find the next/previous offset of the match of the given character.
106    ///
107    /// This is intended for use by the [`Movement::NextUnmatched`](floem_editor_core::movement::Movement::NextUnmatched) and
108    /// [`Movement::PreviousUnmatched`](floem_editor_core::movement::Movement::PreviousUnmatched) commands.
109    fn find_unmatched(&self, offset: usize, previous: bool, ch: char) -> usize {
110        let text = self.text();
111        let mut cursor = WordCursor::new(&text, offset);
112        let new_offset = if previous {
113            cursor.previous_unmatched(ch)
114        } else {
115            cursor.next_unmatched(ch)
116        };
117
118        new_offset.unwrap_or(offset)
119    }
120
121    /// Find the offset of the matching pair character.
122    ///
123    /// This is intended for use by the [`Movement::MatchPairs`](floem_editor_core::movement::Movement::MatchPairs) command.
124    fn find_matching_pair(&self, offset: usize) -> usize {
125        WordCursor::new(&self.text(), offset)
126            .match_pairs()
127            .unwrap_or(offset)
128    }
129
130    fn preedit(&self) -> PreeditData;
131
132    // TODO: I don't like passing `under_line` as a parameter but `Document` doesn't have styling
133    // should we just move preedit + phantom text into `Styling`?
134    fn preedit_phantom(&self, under_line: Option<Color>, line: usize) -> Option<PhantomText> {
135        let preedit = self.preedit().preedit.get_untracked()?;
136
137        let rope_text = self.rope_text();
138
139        let (ime_line, col) = rope_text.offset_to_line_col(preedit.offset);
140
141        if line != ime_line {
142            return None;
143        }
144
145        Some(PhantomText {
146            kind: PhantomTextKind::Ime,
147            text: preedit.text,
148            affinity: None,
149            col,
150            font_size: None,
151            fg: None,
152            bg: None,
153            under_line,
154        })
155    }
156
157    /// Compute the visible screen lines.
158    ///
159    /// Note: you should typically *not* need to implement this, unless you have some custom
160    /// behavior. Unfortunately this needs an `&self` to be a trait object. So don't call `.update`
161    /// on `Self`
162    fn compute_screen_lines(
163        &self,
164        editor: &Editor,
165        base: RwSignal<ScreenLinesBase>,
166    ) -> ScreenLines {
167        normal_compute_screen_lines(editor, base)
168    }
169
170    /// Run a command on the document.
171    ///
172    /// The `ed` will contain this document (at some level, if it was wrapped then it may not be
173    /// directly `Rc<Self>`)
174    fn run_command(
175        &self,
176        ed: &Editor,
177        cmd: &Command,
178        count: Option<usize>,
179        modifiers: Modifiers,
180    ) -> CommandExecuted;
181
182    fn receive_char(&self, ed: &Editor, c: &str);
183
184    /// Perform a single edit.
185    ///
186    fn edit_single(&self, selection: Selection, content: &str, edit_type: EditType) {
187        let mut iter = std::iter::once((selection, content));
188        self.edit(&mut iter, edit_type);
189    }
190
191    /// Perform the edit(s) on this document.
192    ///
193    /// This intentionally does not require an `Editor` as this is primarily intended for use by
194    /// code that wants to modify the document from 'outside' the usual keybinding/command logic.
195    ///
196    /// ```rust,ignore
197    /// let editor: TextEditor = text_editor();
198    /// let doc: Rc<dyn Document> = editor.doc();
199    ///
200    /// stack((
201    ///     editor,
202    ///     button(|| "Append 'Hello'").on_click_stop(move |_| {
203    ///         let text = doc.text();
204    ///         doc.edit_single(Selection::caret(text.len()), "Hello", EditType::InsertChars);
205    ///     })
206    /// ))
207    /// ```
208    fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType);
209}
210
211pub trait DocumentPhantom {
212    fn phantom_text(&self, edid: EditorId, styling: &EditorStyle, line: usize) -> PhantomTextLine;
213
214    /// Translate a column position into the position it would be before combining with
215    /// the phantom text.
216    fn before_phantom_col(
217        &self,
218        edid: EditorId,
219        styling: &EditorStyle,
220        line: usize,
221        col: usize,
222    ) -> usize {
223        let phantom = self.phantom_text(edid, styling, line);
224        phantom.before_col(col)
225    }
226
227    fn has_multiline_phantom(&self, _edid: EditorId, _styling: &EditorStyle) -> bool {
228        true
229    }
230}
231
232#[derive(Debug, Default, Clone, Copy, PartialEq)]
233pub enum WrapMethod {
234    None,
235    #[default]
236    EditorWidth,
237    WrapColumn {
238        col: usize,
239    },
240    WrapWidth {
241        width: f32,
242    },
243}
244impl std::fmt::Display for WrapMethod {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        match self {
247            WrapMethod::None => f.write_str("None"),
248            WrapMethod::EditorWidth => f.write_str("Editor Width"),
249            WrapMethod::WrapColumn { col } => f.write_fmt(format_args!("Wrap at Column {col}")),
250            WrapMethod::WrapWidth { width } => f.write_fmt(format_args!("Wrap Width {width}")),
251        }
252    }
253}
254impl WrapMethod {
255    pub fn is_none(&self) -> bool {
256        matches!(self, WrapMethod::None)
257    }
258
259    pub fn is_constant(&self) -> bool {
260        matches!(
261            self,
262            WrapMethod::None | WrapMethod::WrapColumn { .. } | WrapMethod::WrapWidth { .. }
263        )
264    }
265}
266
267#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
268#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
269#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
270pub enum RenderWhitespace {
271    #[default]
272    None,
273    All,
274    Boundary,
275    Trailing,
276}
277impl std::fmt::Display for RenderWhitespace {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        f.write_fmt(format_args!("{self:?}"))
280    }
281}
282
283/// There's currently three stages of styling text:
284///
285/// - `Attrs`: This sets the default values for the text
286///   - Default font size, font family, etc.
287/// - `AttrsList`: This lets you set spans of text to have different styling
288///   - Syntax highlighting, bolding specific words, etc.
289///
290/// Then once the text layout for the line is created from that, we have:
291/// - `Layout Styles`: Where it may depend on the position of text in the line (after wrapping)
292///   - Outline boxes
293///
294/// TODO: We could unify the first two steps if we expose a `.defaults_mut()` on `AttrsList`, and
295/// then `Styling` mostly just applies whatever attributes it wants and defaults at the same time?
296/// but that would complicate pieces of code that need the font size or line height independently.
297pub trait Styling {
298    // TODO: use a more granular system for invalidating styling, because it may simply be that
299    // one line gets different styling.
300    /// The id for caching the styling.
301    fn id(&self) -> u64;
302
303    fn font_size(&self, _edid: EditorId, _line: usize) -> usize {
304        16
305    }
306
307    fn line_height(&self, edid: EditorId, line: usize) -> f32 {
308        let font_size = self.font_size(edid, line) as f32;
309        (1.5 * font_size).round().max(font_size)
310    }
311
312    fn font_family(&self, _edid: EditorId, _line: usize) -> Cow<'_, [FamilyOwned]> {
313        Cow::Borrowed(&[FamilyOwned::SansSerif])
314    }
315
316    fn weight(&self, _edid: EditorId, _line: usize) -> Weight {
317        Weight::NORMAL
318    }
319
320    // TODO(minor): better name?
321    fn italic_style(&self, _edid: EditorId, _line: usize) -> crate::text::Style {
322        crate::text::Style::Normal
323    }
324
325    fn stretch(&self, _edid: EditorId, _line: usize) -> Stretch {
326        Stretch::Normal
327    }
328
329    /// Which line the indentation line should be based off of
330    /// This is used for lining it up under a scope.
331    fn indent_line(&self, _edid: EditorId, line: usize, _line_content: &str) -> usize {
332        line
333    }
334
335    fn tab_width(&self, _edid: EditorId, _line: usize) -> usize {
336        4
337    }
338
339    /// Whether the cursor should treat leading soft tabs as if they are hard tabs
340    fn atomic_soft_tabs(&self, _edid: EditorId, _line: usize) -> bool {
341        false
342    }
343
344    // TODO: get other style information based on EditorColor enum?
345    // TODO: line_style equivalent?
346
347    /// Apply custom attribute styles to the line
348    fn apply_attr_styles(
349        &self,
350        _edid: EditorId,
351        _style: &EditorStyle,
352        _line: usize,
353        _default: Attrs,
354        _attrs: &mut AttrsList,
355    ) {
356    }
357
358    fn apply_layout_styles(
359        &self,
360        _edid: EditorId,
361        _style: &EditorStyle,
362        _line: usize,
363        _layout_line: &mut TextLayoutLine,
364    ) {
365    }
366
367    /// Whether it should draw the cursor caret on the given line.
368    /// Note that these are extra conditions on top of the typical hide cursor &
369    /// the editor being active conditions
370    /// This is called whenever we paint the line.
371    fn paint_caret(&self, _edid: EditorId, _line: usize) -> bool {
372        true
373    }
374}
375
376pub fn default_light_theme(mut style: EditorCustomStyle) -> EditorCustomStyle {
377    let fg = Color::from_rgb8(0x38, 0x3A, 0x42);
378    let bg = Color::from_rgb8(0xFA, 0xFA, 0xFA);
379    let blue = Color::from_rgb8(0x40, 0x78, 0xF2);
380    let grey = Color::from_rgb8(0xE5, 0xE5, 0xE6);
381    let _scroll_bar = Color::from_rgba8(0xB4, 0xB4, 0xB4, 0xBB);
382    let dim = Color::from_rgb8(0xA0, 0xA1, 0xA7);
383    let cursor = Color::from_rgb8(0x52, 0x6F, 0xFF);
384    let current_line = Color::from_rgb8(0xF2, 0xF2, 0xF2);
385    let _dropdown_shadow = Color::from_rgb8(0xB4, 0xB4, 0xB4);
386    let _link = blue;
387    let _sticky_header_background = bg;
388
389    style.0 = style
390        .0
391        .color(fg)
392        .background(bg)
393        .class(GutterClass, |s| s.background(bg));
394
395    style
396        .gutter_dim_color(dim)
397        .cursor_color(cursor)
398        .selection_color(grey)
399        .current_line_color(current_line)
400        .visible_whitespace(grey)
401        .preedit_underline_color(fg)
402        .indent_guide_color(grey)
403        .gutter_current_color(current_line)
404}
405
406pub fn default_dark_color(mut style: EditorCustomStyle) -> EditorCustomStyle {
407    let fg = Color::from_rgb8(0xAB, 0xB2, 0xBF);
408    let bg = Color::from_rgb8(0x28, 0x2C, 0x34);
409    let blue = Color::from_rgb8(0x61, 0xAF, 0xEF);
410    let grey = Color::from_rgb8(0x3E, 0x44, 0x51);
411    let _scroll_bar = Color::from_rgba8(0x3E, 0x44, 0x51, 0xBB);
412    let dim = Color::from_rgb8(0x5C, 0x63, 0x70);
413    let cursor = Color::from_rgb8(0x52, 0x8B, 0xFF);
414    let current_line = Color::from_rgb8(0x2C, 0x31, 0x3c);
415    let _dropdown_shadow = palette::css::BLACK;
416    let _link = blue;
417    let _sticky_header_background = bg;
418
419    style.0 = style
420        .0
421        .color(fg)
422        .background(bg)
423        .class(GutterClass, |s| s.background(bg));
424
425    style
426        .gutter_dim_color(dim)
427        .cursor_color(cursor)
428        .selection_color(grey)
429        .current_line_color(current_line)
430        .visible_whitespace(grey)
431        .preedit_underline_color(fg)
432        .indent_guide_color(grey)
433        .gutter_current_color(current_line)
434}
435
436pub type DocumentRef = Rc<dyn Document>;
437
438/// A document-wrapper for handling commands.
439pub struct ExtCmdDocument<D, F> {
440    pub doc: D,
441    /// Called whenever [`Document::run_command`] is called.
442    ///
443    /// If `handler` returns [`CommandExecuted::Yes`] then the default handler on `doc: D` will not
444    /// be called.
445    pub handler: F,
446}
447impl<D: Document, F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted + 'static>
448    ExtCmdDocument<D, F>
449{
450    pub fn new(doc: D, handler: F) -> ExtCmdDocument<D, F> {
451        ExtCmdDocument { doc, handler }
452    }
453}
454// TODO: it'd be nice if there was some macro to wrap all of the `Document` methods
455// but replace specific ones
456impl<D, F> Document for ExtCmdDocument<D, F>
457where
458    D: Document,
459    F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted + 'static,
460{
461    fn text(&self) -> Rope {
462        self.doc.text()
463    }
464
465    fn rope_text(&self) -> RopeTextVal {
466        self.doc.rope_text()
467    }
468
469    fn cache_rev(&self) -> RwSignal<u64> {
470        self.doc.cache_rev()
471    }
472
473    fn find_unmatched(&self, offset: usize, previous: bool, ch: char) -> usize {
474        self.doc.find_unmatched(offset, previous, ch)
475    }
476
477    fn find_matching_pair(&self, offset: usize) -> usize {
478        self.doc.find_matching_pair(offset)
479    }
480
481    fn preedit(&self) -> PreeditData {
482        self.doc.preedit()
483    }
484
485    fn preedit_phantom(&self, under_line: Option<Color>, line: usize) -> Option<PhantomText> {
486        self.doc.preedit_phantom(under_line, line)
487    }
488
489    fn compute_screen_lines(
490        &self,
491        editor: &Editor,
492        base: RwSignal<ScreenLinesBase>,
493    ) -> ScreenLines {
494        self.doc.compute_screen_lines(editor, base)
495    }
496
497    fn run_command(
498        &self,
499        ed: &Editor,
500        cmd: &Command,
501        count: Option<usize>,
502        modifiers: Modifiers,
503    ) -> CommandExecuted {
504        if (self.handler)(ed, cmd, count, modifiers) == CommandExecuted::Yes {
505            return CommandExecuted::Yes;
506        }
507
508        self.doc.run_command(ed, cmd, count, modifiers)
509    }
510
511    fn receive_char(&self, ed: &Editor, c: &str) {
512        self.doc.receive_char(ed, c)
513    }
514
515    fn edit_single(&self, selection: Selection, content: &str, edit_type: EditType) {
516        self.doc.edit_single(selection, content, edit_type)
517    }
518
519    fn edit(&self, iter: &mut dyn Iterator<Item = (Selection, &str)>, edit_type: EditType) {
520        self.doc.edit(iter, edit_type)
521    }
522}
523impl<D, F> DocumentPhantom for ExtCmdDocument<D, F>
524where
525    D: Document,
526    F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted,
527{
528    fn phantom_text(&self, edid: EditorId, styling: &EditorStyle, line: usize) -> PhantomTextLine {
529        self.doc.phantom_text(edid, styling, line)
530    }
531
532    fn has_multiline_phantom(&self, edid: EditorId, styling: &EditorStyle) -> bool {
533        self.doc.has_multiline_phantom(edid, styling)
534    }
535
536    fn before_phantom_col(
537        &self,
538        edid: EditorId,
539        styling: &EditorStyle,
540        line: usize,
541        col: usize,
542    ) -> usize {
543        self.doc.before_phantom_col(edid, styling, line, col)
544    }
545}
546impl<D, F> CommonAction for ExtCmdDocument<D, F>
547where
548    D: Document + CommonAction,
549    F: Fn(&Editor, &Command, Option<usize>, Modifiers) -> CommandExecuted,
550{
551    fn exec_motion_mode(
552        &self,
553        ed: &Editor,
554        cursor: &mut Cursor,
555        motion_mode: MotionMode,
556        range: Range<usize>,
557        is_vertical: bool,
558        register: &mut Register,
559    ) {
560        self.doc
561            .exec_motion_mode(ed, cursor, motion_mode, range, is_vertical, register)
562    }
563
564    fn do_edit(
565        &self,
566        ed: &Editor,
567        cursor: &mut Cursor,
568        cmd: &EditCommand,
569        modal: bool,
570        register: &mut Register,
571        smart_tab: bool,
572    ) -> bool {
573        self.doc
574            .do_edit(ed, cursor, cmd, modal, register, smart_tab)
575    }
576}
577
578pub const SCALE_OR_SIZE_LIMIT: f32 = 5.0;
579
580#[derive(Debug, Clone)]
581pub struct SimpleStyling {
582    id: u64,
583    font_size: usize,
584    // TODO: should we really have this be a float? Shouldn't it just be a LineHeightValue?
585    /// If less than 5.0, line height will be a multiple of the font size
586    line_height: f32,
587    font_family: Vec<FamilyOwned>,
588    weight: Weight,
589    italic_style: crate::text::Style,
590    stretch: Stretch,
591    tab_width: usize,
592    atomic_soft_tabs: bool,
593}
594impl SimpleStyling {
595    pub fn builder() -> SimpleStylingBuilder {
596        SimpleStylingBuilder::default()
597    }
598
599    pub fn new() -> Self {
600        Self::default()
601    }
602}
603impl SimpleStyling {
604    pub fn increment_id(&mut self) {
605        self.id += 1;
606    }
607
608    pub fn set_font_size(&mut self, font_size: usize) {
609        self.font_size = font_size;
610        self.increment_id();
611    }
612
613    pub fn set_line_height(&mut self, line_height: f32) {
614        self.line_height = line_height;
615        self.increment_id();
616    }
617
618    pub fn set_font_family(&mut self, font_family: Vec<FamilyOwned>) {
619        self.font_family = font_family;
620        self.increment_id();
621    }
622
623    pub fn set_weight(&mut self, weight: Weight) {
624        self.weight = weight;
625        self.increment_id();
626    }
627
628    pub fn set_italic_style(&mut self, italic_style: crate::text::Style) {
629        self.italic_style = italic_style;
630        self.increment_id();
631    }
632
633    pub fn set_stretch(&mut self, stretch: Stretch) {
634        self.stretch = stretch;
635        self.increment_id();
636    }
637
638    pub fn set_tab_width(&mut self, tab_width: usize) {
639        self.tab_width = tab_width;
640        self.increment_id();
641    }
642
643    pub fn set_atomic_soft_tabs(&mut self, atomic_soft_tabs: bool) {
644        self.atomic_soft_tabs = atomic_soft_tabs;
645        self.increment_id();
646    }
647}
648impl Default for SimpleStyling {
649    fn default() -> Self {
650        SimpleStyling {
651            id: 0,
652            font_size: 16,
653            line_height: 1.5,
654            font_family: vec![FamilyOwned::SansSerif],
655            weight: Weight::NORMAL,
656            italic_style: crate::text::Style::Normal,
657            stretch: Stretch::Normal,
658            tab_width: 4,
659            atomic_soft_tabs: false,
660        }
661    }
662}
663impl Styling for SimpleStyling {
664    fn id(&self) -> u64 {
665        0
666    }
667
668    fn font_size(&self, _edid: EditorId, _line: usize) -> usize {
669        self.font_size
670    }
671
672    fn line_height(&self, _edid: EditorId, _line: usize) -> f32 {
673        let line_height = if self.line_height < SCALE_OR_SIZE_LIMIT {
674            self.line_height * self.font_size as f32
675        } else {
676            self.line_height
677        };
678
679        // Prevent overlapping lines
680        (line_height.round() as usize).max(self.font_size) as f32
681    }
682
683    fn font_family(&self, _edid: EditorId, _line: usize) -> Cow<'_, [FamilyOwned]> {
684        Cow::Borrowed(&self.font_family)
685    }
686
687    fn weight(&self, _edid: EditorId, _line: usize) -> Weight {
688        self.weight
689    }
690
691    fn italic_style(&self, _edid: EditorId, _line: usize) -> crate::text::Style {
692        self.italic_style
693    }
694
695    fn stretch(&self, _edid: EditorId, _line: usize) -> Stretch {
696        self.stretch
697    }
698
699    fn tab_width(&self, _edid: EditorId, _line: usize) -> usize {
700        self.tab_width
701    }
702
703    fn atomic_soft_tabs(&self, _edid: EditorId, _line: usize) -> bool {
704        self.atomic_soft_tabs
705    }
706
707    fn apply_attr_styles(
708        &self,
709        _edid: EditorId,
710        _style: &EditorStyle,
711        _line: usize,
712        _default: Attrs,
713        _attrs: &mut AttrsList,
714    ) {
715    }
716
717    fn apply_layout_styles(
718        &self,
719        _edid: EditorId,
720        _style: &EditorStyle,
721        _line: usize,
722        _layout_line: &mut TextLayoutLine,
723    ) {
724    }
725}
726
727#[derive(Default, Clone)]
728pub struct SimpleStylingBuilder {
729    font_size: Option<usize>,
730    line_height: Option<f32>,
731    font_family: Option<Vec<FamilyOwned>>,
732    weight: Option<Weight>,
733    italic_style: Option<crate::text::Style>,
734    stretch: Option<Stretch>,
735    indent_style: Option<IndentStyle>,
736    tab_width: Option<usize>,
737    atomic_soft_tabs: Option<bool>,
738    wrap: Option<WrapMethod>,
739}
740impl SimpleStylingBuilder {
741    /// Set the font size
742    /// Default: 16
743    pub fn font_size(&mut self, font_size: usize) -> &mut Self {
744        self.font_size = Some(font_size);
745        self
746    }
747
748    /// Set the line height
749    /// Default: 1.5
750    pub fn line_height(&mut self, line_height: f32) -> &mut Self {
751        self.line_height = Some(line_height);
752        self
753    }
754
755    /// Set the font families used
756    /// Default: `[FamilyOwned::SansSerif]`
757    pub fn font_family(&mut self, font_family: Vec<FamilyOwned>) -> &mut Self {
758        self.font_family = Some(font_family);
759        self
760    }
761
762    /// Set the font weight (such as boldness or thinness)
763    /// Default: `Weight::NORMAL`
764    pub fn weight(&mut self, weight: Weight) -> &mut Self {
765        self.weight = Some(weight);
766        self
767    }
768
769    /// Set the italic style
770    /// Default: `Style::Normal`
771    pub fn italic_style(&mut self, italic_style: crate::text::Style) -> &mut Self {
772        self.italic_style = Some(italic_style);
773        self
774    }
775
776    /// Set the font stretch
777    /// Default: `Stretch::Normal`
778    pub fn stretch(&mut self, stretch: Stretch) -> &mut Self {
779        self.stretch = Some(stretch);
780        self
781    }
782
783    /// Set the indent style
784    /// Default: `IndentStyle::Spaces(4)`
785    pub fn indent_style(&mut self, indent_style: IndentStyle) -> &mut Self {
786        self.indent_style = Some(indent_style);
787        self
788    }
789
790    /// Set the tab width
791    /// Default: 4
792    pub fn tab_width(&mut self, tab_width: usize) -> &mut Self {
793        self.tab_width = Some(tab_width);
794        self
795    }
796
797    /// Set whether the cursor should treat leading soft tabs as if they are hard tabs
798    /// Default: false
799    pub fn atomic_soft_tabs(&mut self, atomic_soft_tabs: bool) -> &mut Self {
800        self.atomic_soft_tabs = Some(atomic_soft_tabs);
801        self
802    }
803
804    /// Set the wrapping method
805    /// Default: `WrapMethod::EditorWidth`
806    pub fn wrap(&mut self, wrap: WrapMethod) -> &mut Self {
807        self.wrap = Some(wrap);
808        self
809    }
810
811    /// Build the styling with the given color scheme
812    pub fn build(&self) -> SimpleStyling {
813        let default = SimpleStyling::new();
814        SimpleStyling {
815            id: 0,
816            font_size: self.font_size.unwrap_or(default.font_size),
817            line_height: self.line_height.unwrap_or(default.line_height),
818            font_family: self.font_family.clone().unwrap_or(default.font_family),
819            weight: self.weight.unwrap_or(default.weight),
820            italic_style: self.italic_style.unwrap_or(default.italic_style),
821            stretch: self.stretch.unwrap_or(default.stretch),
822            tab_width: self.tab_width.unwrap_or(default.tab_width),
823            atomic_soft_tabs: self.atomic_soft_tabs.unwrap_or(default.atomic_soft_tabs),
824        }
825    }
826}