1use crate::buffer::TextBuffer;
4use crate::editor::Editor;
5use gpui::*;
6
7pub struct EditorElement {
9 editor: Editor,
10}
11
12impl EditorElement {
13 pub fn new(editor: Editor) -> Self {
15 Self { editor }
16 }
17
18 pub fn editor(&self) -> &Editor {
20 &self.editor
21 }
22
23 pub fn editor_mut(&mut self) -> &mut Editor {
25 &mut self.editor
26 }
27
28 fn line_bounds(&self, row: usize, bounds: Bounds<Pixels>) -> Bounds<Pixels> {
29 let config = self.editor.config();
30 Bounds {
31 origin: point(
32 bounds.origin.x + config.gutter_width,
33 bounds.origin.y + config.line_height * row as f32,
34 ),
35 size: size(bounds.size.width - config.gutter_width, config.line_height),
36 }
37 }
38
39 fn cursor_position_px(&self, bounds: Bounds<Pixels>, window: &mut Window) -> Point<Pixels> {
40 let config = self.editor.config();
41 let cursor_pos = self.editor.get_cursor_position();
42 let line = self
43 .editor
44 .get_buffer()
45 .get_line(cursor_pos.row)
46 .unwrap_or_else(|| String::new());
47
48 let text_before_cursor = &line[..cursor_pos.col.min(line.len())];
49 let text_x = bounds.origin.x + config.gutter_width + config.gutter_padding;
50
51 let offset_x = if !text_before_cursor.is_empty() {
52 let shaped = window.text_system().shape_line(
53 SharedString::from(text_before_cursor.to_string()),
54 config.font_size,
55 &[TextRun {
56 len: text_before_cursor.len(),
57 font: Font {
58 family: config.font_family.clone(),
59 features: Default::default(),
60 weight: FontWeight::NORMAL,
61 style: FontStyle::Normal,
62 fallbacks: Default::default(),
63 },
64 color: config.text_color.into(),
65 background_color: None,
66 underline: None,
67 strikethrough: None,
68 }],
69 None,
70 );
71 shaped.width
72 } else {
73 px(0.0)
74 };
75
76 point(
77 text_x + offset_x,
78 bounds.origin.y + config.line_height * cursor_pos.row as f32,
79 )
80 }
81
82 fn paint_editor_background(&self, window: &mut Window, bounds: Bounds<Pixels>) {
83 let config = self.editor.config();
84 let bg_color: Hsla = config.editor_bg_color.into();
85
86 if bg_color.is_opaque() {
87 let editor_bounds = Bounds {
88 origin: point(bounds.origin.x + config.gutter_width, bounds.origin.y),
89 size: size(bounds.size.width - config.gutter_width, bounds.size.height),
90 };
91 window.paint_quad(PaintQuad {
92 bounds: editor_bounds,
93 corner_radii: (0.0).into(),
94 background: config.editor_bg_color.into(),
95 border_color: transparent_black(),
96 border_widths: (0.0).into(),
97 border_style: BorderStyle::Solid,
98 });
99 }
100 }
101
102 fn paint_gutter_background(&self, window: &mut Window, bounds: Bounds<Pixels>) {
103 let config = self.editor.config();
104 let bg_color: Hsla = config.gutter_bg_color.into();
105
106 if bg_color.is_opaque() {
107 let gutter_bounds = Bounds {
108 origin: bounds.origin,
109 size: size(config.gutter_width, bounds.size.height),
110 };
111 window.paint_quad(PaintQuad {
112 bounds: gutter_bounds,
113 corner_radii: (0.0).into(),
114 background: config.gutter_bg_color.into(),
115 border_color: transparent_black(),
116 border_widths: (0.0).into(),
117 border_style: BorderStyle::Solid,
118 });
119 }
120 }
121
122 fn paint_active_line_background(&self, window: &mut Window, bounds: Bounds<Pixels>) {
123 let config = self.editor.config();
124 let cursor_pos = self.editor.get_cursor_position();
125 let bg_color: Hsla = config.active_line_bg_color.into();
126
127 if bg_color.is_opaque() {
128 let active_line_bounds = self.line_bounds(cursor_pos.row, bounds);
129 window.paint_quad(PaintQuad {
130 bounds: active_line_bounds,
131 corner_radii: (0.0).into(),
132 background: config.active_line_bg_color.into(),
133 border_color: transparent_black(),
134 border_widths: (0.0).into(),
135 border_style: BorderStyle::Solid,
136 });
137 }
138 }
139
140 fn paint_selection(&self, window: &mut Window, bounds: Bounds<Pixels>) {
141 let config = self.editor.config();
142
143 if let Some((start, end)) = self.editor.get_selection_range() {
144 let selection_color = rgba(0x264f78ff);
145
146 for row in start.row..=end.row {
147 if let Some(line) = self.editor.get_buffer().get_line(row) {
148 let line_bounds = self.line_bounds(row, bounds);
149
150 let start_col = if row == start.row { start.col } else { 0 };
151 let end_col = if row == end.row { end.col } else { line.len() };
152
153 let text_x_start = line_bounds.origin.x + config.gutter_padding;
154
155 let start_x = if start_col > 0 {
156 let text_before =
157 SharedString::from(line[..start_col.min(line.len())].to_string());
158 let shaped = window.text_system().shape_line(
159 text_before.clone(),
160 config.font_size,
161 &[TextRun {
162 len: text_before.len(),
163 font: Font {
164 family: config.font_family.clone(),
165 features: Default::default(),
166 weight: FontWeight::NORMAL,
167 style: FontStyle::Normal,
168 fallbacks: Default::default(),
169 },
170 color: config.text_color.into(),
171 background_color: None,
172 underline: None,
173 strikethrough: None,
174 }],
175 None,
176 );
177 shaped.width
178 } else {
179 px(0.0)
180 };
181
182 let end_x = if end_col > 0 {
183 let text_to_end =
184 SharedString::from(line[..end_col.min(line.len())].to_string());
185 let shaped = window.text_system().shape_line(
186 text_to_end.clone(),
187 config.font_size,
188 &[TextRun {
189 len: text_to_end.len(),
190 font: Font {
191 family: config.font_family.clone(),
192 features: Default::default(),
193 weight: FontWeight::NORMAL,
194 style: FontStyle::Normal,
195 fallbacks: Default::default(),
196 },
197 color: config.text_color.into(),
198 background_color: None,
199 underline: None,
200 strikethrough: None,
201 }],
202 None,
203 );
204 shaped.width
205 } else {
206 px(0.0)
207 };
208
209 let selection_bounds = Bounds {
210 origin: point(text_x_start + start_x, line_bounds.origin.y),
211 size: size(end_x - start_x, config.line_height),
212 };
213
214 window.paint_quad(PaintQuad {
215 bounds: selection_bounds,
216 corner_radii: (0.0).into(),
217 background: selection_color.into(),
218 border_color: transparent_black(),
219 border_widths: (0.0).into(),
220 border_style: BorderStyle::Solid,
221 });
222 }
223 }
224 }
225 }
226
227 fn paint_lines(&mut self, cx: &mut App, window: &mut Window, bounds: Bounds<Pixels>) {
228 let _config = self.editor.config();
229 let lines = self.editor.get_buffer().all_lines();
230
231 for (i, line) in lines.iter().enumerate() {
232 let line_bounds = self.line_bounds(i, bounds);
233 self.paint_line_number(cx, window, i + 1, line_bounds, bounds);
234 self.paint_line(cx, window, line, i, line_bounds);
235 }
236 }
237
238 fn paint_line_number(
239 &self,
240 cx: &mut App,
241 window: &mut Window,
242 line_number: usize,
243 line_bounds: Bounds<Pixels>,
244 editor_bounds: Bounds<Pixels>,
245 ) {
246 let config = self.editor.config();
247 let line_number_str = SharedString::new(line_number.to_string());
248 let line_number_len = line_number_str.len();
249 let gutter_padding = px(10.0);
250 let line_number_x =
251 editor_bounds.origin.x + config.gutter_width - gutter_padding - px(20.0);
252
253 let shaped_line_number = window.text_system().shape_line(
254 line_number_str,
255 config.font_size,
256 &[TextRun {
257 len: line_number_len,
258 font: Font {
259 family: config.font_family.clone(),
260 features: Default::default(),
261 weight: FontWeight::NORMAL,
262 style: FontStyle::Normal,
263 fallbacks: Default::default(),
264 },
265 color: config.line_number_color.into(),
266 background_color: None,
267 underline: None,
268 strikethrough: None,
269 }],
270 None,
271 );
272
273 let _ = shaped_line_number.paint(
274 point(line_number_x, line_bounds.origin.y),
275 config.line_height,
276 window,
277 cx,
278 );
279 }
280
281 fn paint_line(
282 &mut self,
283 cx: &mut App,
284 window: &mut Window,
285 line: impl Into<SharedString>,
286 line_index: usize,
287 line_bounds: Bounds<Pixels>,
288 ) {
289 let gutter_padding = px(10.0);
290 let text_x = line_bounds.origin.x + gutter_padding;
291 let line = line.into();
292
293 let config = self.editor.config();
295 let font_family = config.font_family.clone();
296 let font_size = config.font_size;
297 let line_height = config.line_height;
298 let font_size_f32: f32 = font_size.into();
299 let text_runs = self
300 .editor
301 .highlight_line(&line, line_index, font_family, font_size_f32);
302
303 let shaped_line =
304 window
305 .text_system()
306 .shape_line(line.clone(), font_size, &text_runs, None);
307
308 let _ = shaped_line.paint(point(text_x, line_bounds.origin.y), line_height, window, cx);
309 }
310
311 fn paint_cursor(&self, window: &mut Window, bounds: Bounds<Pixels>) {
312 let config = self.editor.config();
313 let cursor_pos = self.cursor_position_px(bounds, window);
314 let cursor_bounds = Bounds {
315 origin: cursor_pos,
316 size: size(px(2.0), config.line_height),
317 };
318
319 window.paint_quad(PaintQuad {
320 bounds: cursor_bounds,
321 corner_radii: (0.0).into(),
322 background: rgb(0xffffff).into(),
323 border_color: transparent_black(),
324 border_widths: (0.0).into(),
325 border_style: BorderStyle::Solid,
326 });
327 }
328}
329
330impl IntoElement for EditorElement {
331 type Element = Self;
332
333 fn into_element(self) -> Self::Element {
334 self
335 }
336}
337
338impl Element for EditorElement {
339 type RequestLayoutState = ();
340 type PrepaintState = ();
341
342 fn id(&self) -> Option<ElementId> {
343 Some(self.editor.id().clone())
344 }
345
346 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
347 None
348 }
349
350 fn request_layout(
351 &mut self,
352 _: Option<&GlobalElementId>,
353 _: Option<&gpui::InspectorElementId>,
354 window: &mut Window,
355 cx: &mut App,
356 ) -> (LayoutId, Self::RequestLayoutState) {
357 let mut style = Style::default();
358 style.flex_grow = 1.0;
359 style.size.width = relative(1.0).into();
360 style.size.height = relative(1.0).into();
361 let layout_id = window.request_layout(style, None, cx);
362 (layout_id, ())
363 }
364
365 fn prepaint(
366 &mut self,
367 _: Option<&GlobalElementId>,
368 _: Option<&gpui::InspectorElementId>,
369 _: Bounds<Pixels>,
370 _: &mut Self::RequestLayoutState,
371 _window: &mut Window,
372 _cx: &mut App,
373 ) -> Self::PrepaintState {
374 ()
375 }
376
377 fn paint(
378 &mut self,
379 _: Option<&GlobalElementId>,
380 _: Option<&gpui::InspectorElementId>,
381 bounds: Bounds<Pixels>,
382 _: &mut Self::RequestLayoutState,
383 _: &mut Self::PrepaintState,
384 window: &mut Window,
385 cx: &mut App,
386 ) {
387 self.paint_gutter_background(window, bounds);
388 self.paint_editor_background(window, bounds);
389 self.paint_active_line_background(window, bounds);
390 self.paint_selection(window, bounds);
391 self.paint_lines(cx, window, bounds);
392 self.paint_cursor(window, bounds);
393 }
394}