freya_code_editor/
editor_line.rs1use 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}