Skip to main content

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) font_family: Cow<'static, str>,
31    pub(crate) theme: EditorTheme,
32    pub(crate) a11y_id: AccessibilityId,
33}
34
35impl Component for EditorLineUI {
36    fn render_key(&self) -> DiffKey {
37        DiffKey::from(&self.line_index)
38    }
39    fn render(&self) -> impl IntoElement {
40        let EditorLineUI {
41            mut editor,
42            font_size,
43            line_height,
44            line_index,
45            read_only,
46            gutter,
47            show_whitespace,
48            font_family,
49            theme,
50            a11y_id,
51        } = self.clone();
52
53        let holder = use_state(ParagraphHolder::default);
54
55        let editor_data = editor.read();
56
57        let longest_width = editor_data.metrics.longest_width;
58        let line = editor_data.metrics.syntax_blocks.get_line(line_index);
59        let highlights = editor_data.get_visible_selection(EditorLine::Paragraph(line_index));
60        let gutter_width = font_size * 5.0;
61        let is_line_selected = editor_data.cursor_row() == line_index;
62
63        let on_tap = {
64            let mut editor = editor.clone();
65            let font_family = font_family.clone();
66            move |e: Event<FocusPressEventData>| {
67                let processed = editor.write_if(|mut editor_editor| {
68                    editor_editor.process(
69                        font_size,
70                        &font_family,
71                        EditableEvent::Down {
72                            location: e.element_location(),
73                            editor_line: EditorLine::Paragraph(line_index),
74                            holder: &holder.read(),
75                        },
76                    )
77                });
78                if processed {
79                    a11y_id.request_focus();
80                }
81            }
82        };
83
84        let on_pointer_move = {
85            let font_family = font_family.clone();
86            move |e: Event<PointerEventData>| {
87                editor.write_if(|mut editor_editor| {
88                    editor_editor.process(
89                        font_size,
90                        &font_family,
91                        EditableEvent::Move {
92                            location: e.element_location(),
93                            editor_line: EditorLine::Paragraph(line_index),
94                            holder: &holder.read(),
95                        },
96                    )
97                });
98            }
99        };
100
101        let cursor_index = if read_only {
102            None
103        } else {
104            is_line_selected.then(|| editor_data.cursor_col())
105        };
106        let gutter_color = if is_line_selected {
107            theme.gutter_selected
108        } else {
109            theme.gutter_unselected
110        };
111        let visible_selection = match editor_data.get_selection() {
112            None => false,
113            Some((s, e)) if s != e => true,
114            _ => false,
115        };
116        let line_background = if is_line_selected && !visible_selection {
117            theme.line_selected_background
118        } else {
119            Color::TRANSPARENT
120        };
121
122        rect()
123            .horizontal()
124            .height(Size::px(line_height))
125            .background(line_background)
126            .font_size(font_size)
127            .maybe(gutter, |el| {
128                el.child(
129                    rect()
130                        .width(Size::px(gutter_width))
131                        .height(Size::fill())
132                        .padding(Gaps::new(0., 0., 0., 20.))
133                        .main_align(Alignment::Center)
134                        .child(
135                            label()
136                                .color(gutter_color)
137                                .text(format!("{} ", line_index + 1)),
138                        ),
139                )
140            })
141            .child(
142                paragraph()
143                    .holder(holder.read().clone())
144                    .on_pointer_move(on_pointer_move)
145                    .on_focus_press(on_tap)
146                    .cursor_color(theme.cursor)
147                    .cursor_style(CursorStyle::Block)
148                    .cursor_index(cursor_index)
149                    .cursor_mode(CursorMode::Expanded)
150                    .vertical_align(VerticalAlign::Center)
151                    .highlights(highlights.map(|h| vec![h]))
152                    .highlight_color(theme.highlight)
153                    .width(Size::px(longest_width))
154                    .min_width(Size::fill())
155                    .height(Size::fill())
156                    .font_family(font_family)
157                    .max_lines(1)
158                    .color(theme.text)
159                    .spans_iter(line.iter().map(|span| {
160                        let text: Cow<str> = match &span.1 {
161                            TextNode::Range(word_pos) => {
162                                editor_data.rope.slice(word_pos.clone()).into()
163                            }
164                            TextNode::LineOfChars { len, char } => {
165                                if show_whitespace {
166                                    Cow::Owned(char.to_string().repeat(*len))
167                                } else {
168                                    Cow::Owned(" ".repeat(*len))
169                                }
170                            }
171                        };
172                        Span::new(Cow::Owned(text.to_string())).color(span.0)
173                    })),
174            )
175    }
176}