floem/views/editor/
text.rs

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