freya_code_editor/
editor_line.rs

1use std::borrow::Cow;
2
3use freya_core::prelude::*;
4use freya_edit::{
5    EditableEvent,
6    EditorLine,
7    TextEditor,
8};
9use torin::{
10    gaps::Gaps,
11    prelude::Alignment,
12    size::Size,
13};
14
15use crate::{
16    editor_data::CodeEditorData,
17    editor_theme::EditorTheme,
18    syntax::TextNode,
19};
20
21#[derive(Clone, PartialEq)]
22pub struct EditorLineUI {
23    pub(crate) editor: Writable<CodeEditorData>,
24    pub(crate) font_size: f32,
25    pub(crate) line_height: f32,
26    pub(crate) line_index: usize,
27    pub(crate) read_only: bool,
28    pub(crate) gutter: bool,
29    pub(crate) show_whitespace: bool,
30    pub(crate) theme: Readable<EditorTheme>,
31}
32
33impl Component for EditorLineUI {
34    fn render_key(&self) -> DiffKey {
35        DiffKey::from(&self.line_index)
36    }
37    fn render(&self) -> impl IntoElement {
38        let EditorLineUI {
39            mut editor,
40            font_size,
41            line_height,
42            line_index,
43            read_only,
44            gutter,
45            show_whitespace,
46            theme,
47        } = self.clone();
48
49        let holder = use_state(ParagraphHolder::default);
50
51        let editor_data = editor.read();
52        let theme = theme.read();
53
54        let longest_width = editor_data.metrics.longest_width;
55        let line = editor_data.metrics.syntax_blocks.get_line(line_index);
56        let highlights = editor_data.get_visible_selection(EditorLine::Paragraph(line_index));
57        let gutter_width = font_size * 5.0;
58        let is_line_selected = editor_data.cursor_row() == line_index;
59
60        let on_mouse_down = {
61            let mut editor = editor.clone();
62            move |e: Event<MouseEventData>| {
63                editor.write_if(|mut editor_editor| {
64                    editor_editor.process(
65                        font_size,
66                        EditableEvent::Down {
67                            location: e.element_location,
68                            editor_line: EditorLine::Paragraph(line_index),
69                            holder: &holder.read(),
70                        },
71                    )
72                });
73            }
74        };
75
76        let on_mouse_move = move |e: Event<MouseEventData>| {
77            editor.write_if(|mut editor_editor| {
78                editor_editor.process(
79                    font_size,
80                    EditableEvent::Move {
81                        location: e.element_location,
82                        editor_line: EditorLine::Paragraph(line_index),
83                        holder: &holder.read(),
84                    },
85                )
86            });
87        };
88
89        let cursor_index = if read_only {
90            None
91        } else {
92            is_line_selected.then(|| editor_data.cursor_col())
93        };
94        let gutter_color = if is_line_selected {
95            theme.gutter_selected
96        } else {
97            theme.gutter_unselected
98        };
99        let visible_selection = match editor_data.get_selection() {
100            None => false,
101            Some((s, e)) if s != e => true,
102            _ => false,
103        };
104        let line_background = if is_line_selected && !visible_selection {
105            theme.line_selected_background
106        } else {
107            Color::TRANSPARENT
108        };
109
110        rect()
111            .horizontal()
112            .height(Size::px(line_height))
113            .background(line_background)
114            .font_size(font_size)
115            .maybe(gutter, |el| {
116                el.child(
117                    rect()
118                        .width(Size::px(gutter_width))
119                        .height(Size::fill())
120                        .padding(Gaps::new(0., 0., 0., 20.))
121                        .main_align(Alignment::Center)
122                        .child(
123                            label()
124                                .color(gutter_color)
125                                .text(format!("{} ", line_index + 1)),
126                        ),
127                )
128            })
129            .child(
130                paragraph()
131                    .holder(holder.read().clone())
132                    .on_mouse_down(on_mouse_down)
133                    .on_mouse_move(on_mouse_move)
134                    .cursor_color(theme.cursor)
135                    .cursor_style(CursorStyle::Block)
136                    .cursor_index(cursor_index)
137                    .cursor_mode(CursorMode::Expanded)
138                    .vertical_align(VerticalAlign::Center)
139                    .highlights(highlights.map(|h| vec![h]))
140                    .highlight_color(theme.highlight)
141                    .width(Size::px(longest_width))
142                    .min_width(Size::fill())
143                    .height(Size::fill())
144                    .font_family("Jetbrains Mono")
145                    .max_lines(1)
146                    .color(theme.text)
147                    .spans_iter(line.iter().map(|span| {
148                        let text: Cow<str> = match &span.1 {
149                            TextNode::Range(word_pos) => {
150                                editor_data.rope.slice(word_pos.clone()).into()
151                            }
152                            TextNode::LineOfChars { len, char } => {
153                                if show_whitespace {
154                                    Cow::Owned(char.to_string().repeat(*len))
155                                } else {
156                                    Cow::Owned(" ".repeat(*len))
157                                }
158                            }
159                        };
160                        Span::new(Cow::Owned(text.to_string())).color(span.0)
161                    })),
162            )
163    }
164}