1use super::{
2 DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
3 SelectPhase, Snapshot, MAX_LINE_LEN,
4};
5use clock::ReplicaId;
6use gpui::{
7 color::Color,
8 geometry::{
9 rect::RectF,
10 vector::{vec2f, Vector2F},
11 PathBuilder,
12 },
13 json::{self, ToJson},
14 keymap::Keystroke,
15 text_layout::{self, RunStyle, TextLayoutCache},
16 AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext,
17 MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
18};
19use json::json;
20use language::{Chunk, DiagnosticSeverity};
21use smallvec::SmallVec;
22use std::{
23 cmp::{self, Ordering},
24 collections::{BTreeMap, HashMap},
25 fmt::Write,
26 ops::Range,
27};
28
29pub struct EditorElement {
30 view: WeakViewHandle<Editor>,
31 settings: EditorSettings,
32}
33
34impl EditorElement {
35 pub fn new(view: WeakViewHandle<Editor>, settings: EditorSettings) -> Self {
36 Self { view, settings }
37 }
38
39 fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
40 self.view.upgrade(cx).unwrap().read(cx)
41 }
42
43 fn update_view<F, T>(&self, cx: &mut MutableAppContext, f: F) -> T
44 where
45 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
46 {
47 self.view.upgrade(cx).unwrap().update(cx, f)
48 }
49
50 fn snapshot(&self, cx: &mut MutableAppContext) -> Snapshot {
51 self.update_view(cx, |view, cx| view.snapshot(cx))
52 }
53
54 fn mouse_down(
55 &self,
56 position: Vector2F,
57 cmd: bool,
58 layout: &mut LayoutState,
59 paint: &mut PaintState,
60 cx: &mut EventContext,
61 ) -> bool {
62 if paint.text_bounds.contains_point(position) {
63 let snapshot = self.snapshot(cx.app);
64 let position = paint.point_for_position(&snapshot, layout, position);
65 cx.dispatch_action(Select(SelectPhase::Begin { position, add: cmd }));
66 true
67 } else {
68 false
69 }
70 }
71
72 fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool {
73 if self.view(cx.app.as_ref()).is_selecting() {
74 cx.dispatch_action(Select(SelectPhase::End));
75 true
76 } else {
77 false
78 }
79 }
80
81 fn mouse_dragged(
82 &self,
83 position: Vector2F,
84 layout: &mut LayoutState,
85 paint: &mut PaintState,
86 cx: &mut EventContext,
87 ) -> bool {
88 let view = self.view(cx.app.as_ref());
89
90 if view.is_selecting() {
91 let rect = paint.text_bounds;
92 let mut scroll_delta = Vector2F::zero();
93
94 let vertical_margin = layout.line_height.min(rect.height() / 3.0);
95 let top = rect.origin_y() + vertical_margin;
96 let bottom = rect.lower_left().y() - vertical_margin;
97 if position.y() < top {
98 scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
99 }
100 if position.y() > bottom {
101 scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
102 }
103
104 let horizontal_margin = layout.line_height.min(rect.width() / 3.0);
105 let left = rect.origin_x() + horizontal_margin;
106 let right = rect.upper_right().x() - horizontal_margin;
107 if position.x() < left {
108 scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
109 left - position.x(),
110 ))
111 }
112 if position.x() > right {
113 scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
114 position.x() - right,
115 ))
116 }
117
118 let font_cache = cx.font_cache.clone();
119 let text_layout_cache = cx.text_layout_cache.clone();
120 let snapshot = self.snapshot(cx.app);
121 let position = paint.point_for_position(&snapshot, layout, position);
122
123 cx.dispatch_action(Select(SelectPhase::Update {
124 position,
125 scroll_position: (snapshot.scroll_position() + scroll_delta).clamp(
126 Vector2F::zero(),
127 layout.scroll_max(&font_cache, &text_layout_cache),
128 ),
129 }));
130 true
131 } else {
132 false
133 }
134 }
135
136 fn key_down(&self, chars: &str, keystroke: &Keystroke, cx: &mut EventContext) -> bool {
137 let view = self.view.upgrade(cx.app).unwrap();
138
139 if view.is_focused(cx.app) {
140 if chars.is_empty() {
141 false
142 } else {
143 if chars.chars().any(|c| c.is_control()) || keystroke.cmd || keystroke.ctrl {
144 false
145 } else {
146 cx.dispatch_action(Input(chars.to_string()));
147 true
148 }
149 }
150 } else {
151 false
152 }
153 }
154
155 fn scroll(
156 &self,
157 position: Vector2F,
158 mut delta: Vector2F,
159 precise: bool,
160 layout: &mut LayoutState,
161 paint: &mut PaintState,
162 cx: &mut EventContext,
163 ) -> bool {
164 if !paint.bounds.contains_point(position) {
165 return false;
166 }
167
168 let snapshot = self.snapshot(cx.app);
169 let font_cache = &cx.font_cache;
170 let layout_cache = &cx.text_layout_cache;
171 let max_glyph_width = layout.em_width;
172 if !precise {
173 delta *= vec2f(max_glyph_width, layout.line_height);
174 }
175
176 let scroll_position = snapshot.scroll_position();
177 let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
178 let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height;
179 let scroll_position = vec2f(x, y).clamp(
180 Vector2F::zero(),
181 layout.scroll_max(font_cache, layout_cache),
182 );
183
184 cx.dispatch_action(Scroll(scroll_position));
185
186 true
187 }
188
189 fn paint_background(
190 &self,
191 gutter_bounds: RectF,
192 text_bounds: RectF,
193 layout: &LayoutState,
194 cx: &mut PaintContext,
195 ) {
196 let bounds = gutter_bounds.union_rect(text_bounds);
197 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
198 let editor = self.view(cx.app);
199 let style = &self.settings.style;
200 cx.scene.push_quad(Quad {
201 bounds: gutter_bounds,
202 background: Some(style.gutter_background),
203 border: Border::new(0., Color::transparent_black()),
204 corner_radius: 0.,
205 });
206 cx.scene.push_quad(Quad {
207 bounds: text_bounds,
208 background: Some(style.background),
209 border: Border::new(0., Color::transparent_black()),
210 corner_radius: 0.,
211 });
212
213 if let EditorMode::Full = editor.mode {
214 let mut active_rows = layout.active_rows.iter().peekable();
215 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
216 let mut end_row = *start_row;
217 while active_rows.peek().map_or(false, |r| {
218 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
219 }) {
220 active_rows.next().unwrap();
221 end_row += 1;
222 }
223
224 if !contains_non_empty_selection {
225 let origin = vec2f(
226 bounds.origin_x(),
227 bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top,
228 );
229 let size = vec2f(
230 bounds.width(),
231 layout.line_height * (end_row - start_row + 1) as f32,
232 );
233 cx.scene.push_quad(Quad {
234 bounds: RectF::new(origin, size),
235 background: Some(style.active_line_background),
236 border: Border::default(),
237 corner_radius: 0.,
238 });
239 }
240 }
241 }
242 }
243
244 fn paint_gutter(
245 &mut self,
246 bounds: RectF,
247 visible_bounds: RectF,
248 layout: &LayoutState,
249 cx: &mut PaintContext,
250 ) {
251 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
252 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
253 if let Some(line) = line {
254 let line_origin = bounds.origin()
255 + vec2f(
256 bounds.width() - line.width() - layout.gutter_padding,
257 ix as f32 * layout.line_height - (scroll_top % layout.line_height),
258 );
259 line.paint(line_origin, visible_bounds, layout.line_height, cx);
260 }
261 }
262 }
263
264 fn paint_text(
265 &mut self,
266 bounds: RectF,
267 visible_bounds: RectF,
268 layout: &LayoutState,
269 cx: &mut PaintContext,
270 ) {
271 let view = self.view(cx.app);
272 let style = &self.settings.style;
273 let local_replica_id = view.replica_id(cx);
274 let scroll_position = layout.snapshot.scroll_position();
275 let start_row = scroll_position.y() as u32;
276 let scroll_top = scroll_position.y() * layout.line_height;
277 let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
278 let max_glyph_width = layout.em_width;
279 let scroll_left = scroll_position.x() * max_glyph_width;
280
281 cx.scene.push_layer(Some(bounds));
282
283 // Draw selections
284 let corner_radius = 2.5;
285 let mut cursors = SmallVec::<[Cursor; 32]>::new();
286
287 let content_origin = bounds.origin() + layout.text_offset;
288
289 for (replica_id, selections) in &layout.selections {
290 let style_ix = *replica_id as usize % (style.guest_selections.len() + 1);
291 let style = if style_ix == 0 {
292 &style.selection
293 } else {
294 &style.guest_selections[style_ix - 1]
295 };
296
297 for selection in selections {
298 if selection.start != selection.end {
299 let range_start = cmp::min(selection.start, selection.end);
300 let range_end = cmp::max(selection.start, selection.end);
301 let row_range = if range_end.column() == 0 {
302 cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
303 } else {
304 cmp::max(range_start.row(), start_row)
305 ..cmp::min(range_end.row() + 1, end_row)
306 };
307
308 let selection = Selection {
309 color: style.selection,
310 line_height: layout.line_height,
311 start_y: content_origin.y() + row_range.start as f32 * layout.line_height
312 - scroll_top,
313 lines: row_range
314 .into_iter()
315 .map(|row| {
316 let line_layout = &layout.line_layouts[(row - start_row) as usize];
317 SelectionLine {
318 start_x: if row == range_start.row() {
319 content_origin.x()
320 + line_layout.x_for_index(range_start.column() as usize)
321 - scroll_left
322 } else {
323 content_origin.x() - scroll_left
324 },
325 end_x: if row == range_end.row() {
326 content_origin.x()
327 + line_layout.x_for_index(range_end.column() as usize)
328 - scroll_left
329 } else {
330 content_origin.x()
331 + line_layout.width()
332 + corner_radius * 2.0
333 - scroll_left
334 },
335 }
336 })
337 .collect(),
338 };
339
340 selection.paint(bounds, cx.scene);
341 }
342
343 if view.show_local_cursors() || *replica_id != local_replica_id {
344 let cursor_position = selection.end;
345 if (start_row..end_row).contains(&cursor_position.row()) {
346 let cursor_row_layout =
347 &layout.line_layouts[(selection.end.row() - start_row) as usize];
348 let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
349 - scroll_left;
350 let y = selection.end.row() as f32 * layout.line_height - scroll_top;
351 cursors.push(Cursor {
352 color: style.cursor,
353 origin: content_origin + vec2f(x, y),
354 line_height: layout.line_height,
355 });
356 }
357 }
358 }
359 }
360
361 if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
362 // Draw glyphs
363 for (ix, line) in layout.line_layouts.iter().enumerate() {
364 let row = start_row + ix as u32;
365 line.paint(
366 content_origin
367 + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top),
368 visible_text_bounds,
369 layout.line_height,
370 cx,
371 );
372 }
373 }
374
375 cx.scene.push_layer(Some(bounds));
376 for cursor in cursors {
377 cursor.paint(cx);
378 }
379 cx.scene.pop_layer();
380
381 cx.scene.pop_layer();
382 }
383
384 fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 {
385 let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1;
386 let style = &self.settings.style;
387
388 cx.text_layout_cache
389 .layout_str(
390 "1".repeat(digit_count).as_str(),
391 style.text.font_size,
392 &[(
393 digit_count,
394 RunStyle {
395 font_id: style.text.font_id,
396 color: Color::black(),
397 underline: None,
398 },
399 )],
400 )
401 .width()
402 }
403
404 fn layout_line_numbers(
405 &self,
406 rows: Range<u32>,
407 active_rows: &BTreeMap<u32, bool>,
408 snapshot: &Snapshot,
409 cx: &LayoutContext,
410 ) -> Vec<Option<text_layout::Line>> {
411 let style = &self.settings.style;
412 let mut layouts = Vec::with_capacity(rows.len());
413 let mut line_number = String::new();
414 for (ix, buffer_row) in snapshot
415 .buffer_rows(rows.start)
416 .take((rows.end - rows.start) as usize)
417 .enumerate()
418 {
419 let display_row = rows.start + ix as u32;
420 let color = if active_rows.contains_key(&display_row) {
421 style.line_number_active
422 } else {
423 style.line_number
424 };
425 if let Some(buffer_row) = buffer_row {
426 line_number.clear();
427 write!(&mut line_number, "{}", buffer_row + 1).unwrap();
428 layouts.push(Some(cx.text_layout_cache.layout_str(
429 &line_number,
430 style.text.font_size,
431 &[(
432 line_number.len(),
433 RunStyle {
434 font_id: style.text.font_id,
435 color,
436 underline: None,
437 },
438 )],
439 )));
440 } else {
441 layouts.push(None);
442 }
443 }
444
445 layouts
446 }
447
448 fn layout_lines(
449 &mut self,
450 mut rows: Range<u32>,
451 snapshot: &mut Snapshot,
452 cx: &LayoutContext,
453 ) -> Vec<text_layout::Line> {
454 rows.end = cmp::min(rows.end, snapshot.max_point().row() + 1);
455 if rows.start >= rows.end {
456 return Vec::new();
457 }
458
459 // When the editor is empty and unfocused, then show the placeholder.
460 if snapshot.is_empty() && !snapshot.is_focused() {
461 let placeholder_style = self.settings.style.placeholder_text();
462 let placeholder_text = snapshot.placeholder_text();
463 let placeholder_lines = placeholder_text
464 .as_ref()
465 .map_or("", AsRef::as_ref)
466 .split('\n')
467 .skip(rows.start as usize)
468 .take(rows.len());
469 return placeholder_lines
470 .map(|line| {
471 cx.text_layout_cache.layout_str(
472 line,
473 placeholder_style.font_size,
474 &[(
475 line.len(),
476 RunStyle {
477 font_id: placeholder_style.font_id,
478 color: placeholder_style.color,
479 underline: None,
480 },
481 )],
482 )
483 })
484 .collect();
485 }
486
487 let style = &self.settings.style;
488 let mut prev_font_properties = style.text.font_properties.clone();
489 let mut prev_font_id = style.text.font_id;
490
491 let mut layouts = Vec::with_capacity(rows.len());
492 let mut line = String::new();
493 let mut styles = Vec::new();
494 let mut row = rows.start;
495 let mut line_exceeded_max_len = false;
496 let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax));
497
498 let newline_chunk = Chunk {
499 text: "\n",
500 ..Default::default()
501 };
502 'outer: for chunk in chunks.chain([newline_chunk]) {
503 for (ix, mut line_chunk) in chunk.text.split('\n').enumerate() {
504 if ix > 0 {
505 layouts.push(cx.text_layout_cache.layout_str(
506 &line,
507 style.text.font_size,
508 &styles,
509 ));
510 line.clear();
511 styles.clear();
512 row += 1;
513 line_exceeded_max_len = false;
514 if row == rows.end {
515 break 'outer;
516 }
517 }
518
519 if !line_chunk.is_empty() && !line_exceeded_max_len {
520 let highlight_style =
521 chunk.highlight_style.unwrap_or(style.text.clone().into());
522 // Avoid a lookup if the font properties match the previous ones.
523 let font_id = if highlight_style.font_properties == prev_font_properties {
524 prev_font_id
525 } else {
526 cx.font_cache
527 .select_font(
528 style.text.font_family_id,
529 &highlight_style.font_properties,
530 )
531 .unwrap_or(style.text.font_id)
532 };
533
534 if line.len() + line_chunk.len() > MAX_LINE_LEN {
535 let mut chunk_len = MAX_LINE_LEN - line.len();
536 while !line_chunk.is_char_boundary(chunk_len) {
537 chunk_len -= 1;
538 }
539 line_chunk = &line_chunk[..chunk_len];
540 line_exceeded_max_len = true;
541 }
542
543 let underline = if let Some(severity) = chunk.diagnostic {
544 match severity {
545 DiagnosticSeverity::ERROR => Some(style.error_underline),
546 DiagnosticSeverity::WARNING => Some(style.warning_underline),
547 DiagnosticSeverity::INFORMATION => Some(style.information_underline),
548 DiagnosticSeverity::HINT => Some(style.hint_underline),
549 _ => highlight_style.underline,
550 }
551 } else {
552 highlight_style.underline
553 };
554
555 line.push_str(line_chunk);
556 styles.push((
557 line_chunk.len(),
558 RunStyle {
559 font_id,
560 color: highlight_style.color,
561 underline,
562 },
563 ));
564 prev_font_id = font_id;
565 prev_font_properties = highlight_style.font_properties;
566 }
567 }
568 }
569
570 layouts
571 }
572}
573
574impl Element for EditorElement {
575 type LayoutState = Option<LayoutState>;
576 type PaintState = Option<PaintState>;
577
578 fn layout(
579 &mut self,
580 constraint: SizeConstraint,
581 cx: &mut LayoutContext,
582 ) -> (Vector2F, Self::LayoutState) {
583 let mut size = constraint.max;
584 if size.x().is_infinite() {
585 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
586 }
587
588 let snapshot = self.snapshot(cx.app);
589 let style = self.settings.style.clone();
590 let line_height = style.text.line_height(cx.font_cache);
591
592 let gutter_padding;
593 let gutter_width;
594 if snapshot.mode == EditorMode::Full {
595 gutter_padding = style.text.em_width(cx.font_cache);
596 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
597 } else {
598 gutter_padding = 0.0;
599 gutter_width = 0.0
600 };
601
602 let text_width = size.x() - gutter_width;
603 let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.);
604 let em_width = style.text.em_width(cx.font_cache);
605 let overscroll = vec2f(em_width, 0.);
606 let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
607 let snapshot = self.update_view(cx.app, |view, cx| {
608 if view.set_wrap_width(wrap_width, cx) {
609 view.snapshot(cx)
610 } else {
611 snapshot
612 }
613 });
614
615 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
616 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
617 size.set_y(
618 scroll_height
619 .min(constraint.max_along(Axis::Vertical))
620 .max(constraint.min_along(Axis::Vertical))
621 .min(line_height * max_lines as f32),
622 )
623 } else if size.y().is_infinite() {
624 size.set_y(scroll_height);
625 }
626 let gutter_size = vec2f(gutter_width, size.y());
627 let text_size = vec2f(text_width, size.y());
628
629 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
630 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
631 let snapshot = view.snapshot(cx);
632 (autoscroll_horizontally, snapshot)
633 });
634
635 let scroll_position = snapshot.scroll_position();
636 let start_row = scroll_position.y() as u32;
637 let scroll_top = scroll_position.y() * line_height;
638 let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
639
640 let mut selections = HashMap::new();
641 let mut active_rows = BTreeMap::new();
642 self.update_view(cx.app, |view, cx| {
643 for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
644 let mut set = Vec::new();
645 for selection in view.selections_in_range(
646 selection_set_id,
647 DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
648 cx,
649 ) {
650 set.push(selection.clone());
651 if selection_set_id == view.selection_set_id {
652 let is_empty = selection.start == selection.end;
653 let mut selection_start;
654 let mut selection_end;
655 if selection.start < selection.end {
656 selection_start = selection.start;
657 selection_end = selection.end;
658 } else {
659 selection_start = selection.end;
660 selection_end = selection.start;
661 };
662 selection_start = snapshot.prev_row_boundary(selection_start).0;
663 selection_end = snapshot.next_row_boundary(selection_end).0;
664 for row in cmp::max(selection_start.row(), start_row)
665 ..=cmp::min(selection_end.row(), end_row)
666 {
667 let contains_non_empty_selection =
668 active_rows.entry(row).or_insert(!is_empty);
669 *contains_non_empty_selection |= !is_empty;
670 }
671 }
672 }
673
674 selections.insert(selection_set_id.replica_id, set);
675 }
676 });
677
678 let line_number_layouts = if snapshot.mode == EditorMode::Full {
679 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
680 } else {
681 Vec::new()
682 };
683
684 let mut max_visible_line_width = 0.0;
685 let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
686 for line in &line_layouts {
687 if line.width() > max_visible_line_width {
688 max_visible_line_width = line.width();
689 }
690 }
691
692 let mut layout = LayoutState {
693 size,
694 gutter_size,
695 gutter_padding,
696 text_size,
697 overscroll,
698 text_offset,
699 snapshot,
700 style: self.settings.style.clone(),
701 active_rows,
702 line_layouts,
703 line_number_layouts,
704 line_height,
705 em_width,
706 selections,
707 max_visible_line_width,
708 };
709
710 let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x();
711 let scroll_width = layout.scroll_width(cx.text_layout_cache);
712 let max_glyph_width = style.text.em_width(&cx.font_cache);
713 self.update_view(cx.app, |view, cx| {
714 let clamped = view.clamp_scroll_left(scroll_max);
715 let autoscrolled;
716 if autoscroll_horizontally {
717 autoscrolled = view.autoscroll_horizontally(
718 start_row,
719 layout.text_size.x(),
720 scroll_width,
721 max_glyph_width,
722 &layout.line_layouts,
723 cx,
724 );
725 } else {
726 autoscrolled = false;
727 }
728
729 if clamped || autoscrolled {
730 layout.snapshot = view.snapshot(cx);
731 }
732 });
733
734 (size, Some(layout))
735 }
736
737 fn paint(
738 &mut self,
739 bounds: RectF,
740 visible_bounds: RectF,
741 layout: &mut Self::LayoutState,
742 cx: &mut PaintContext,
743 ) -> Self::PaintState {
744 if let Some(layout) = layout {
745 cx.scene.push_layer(Some(bounds));
746
747 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
748 let text_bounds = RectF::new(
749 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
750 layout.text_size,
751 );
752
753 self.paint_background(gutter_bounds, text_bounds, layout, cx);
754 if layout.gutter_size.x() > 0. {
755 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
756 }
757 self.paint_text(text_bounds, visible_bounds, layout, cx);
758
759 cx.scene.pop_layer();
760
761 Some(PaintState {
762 bounds,
763 text_bounds,
764 })
765 } else {
766 None
767 }
768 }
769
770 fn dispatch_event(
771 &mut self,
772 event: &Event,
773 _: RectF,
774 layout: &mut Self::LayoutState,
775 paint: &mut Self::PaintState,
776 cx: &mut EventContext,
777 ) -> bool {
778 if let (Some(layout), Some(paint)) = (layout, paint) {
779 match event {
780 Event::LeftMouseDown { position, cmd } => {
781 self.mouse_down(*position, *cmd, layout, paint, cx)
782 }
783 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
784 Event::LeftMouseDragged { position } => {
785 self.mouse_dragged(*position, layout, paint, cx)
786 }
787 Event::ScrollWheel {
788 position,
789 delta,
790 precise,
791 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
792 Event::KeyDown {
793 chars, keystroke, ..
794 } => self.key_down(chars, keystroke, cx),
795 _ => false,
796 }
797 } else {
798 false
799 }
800 }
801
802 fn debug(
803 &self,
804 bounds: RectF,
805 _: &Self::LayoutState,
806 _: &Self::PaintState,
807 _: &gpui::DebugContext,
808 ) -> json::Value {
809 json!({
810 "type": "BufferElement",
811 "bounds": bounds.to_json()
812 })
813 }
814}
815
816pub struct LayoutState {
817 size: Vector2F,
818 gutter_size: Vector2F,
819 gutter_padding: f32,
820 text_size: Vector2F,
821 style: EditorStyle,
822 snapshot: Snapshot,
823 active_rows: BTreeMap<u32, bool>,
824 line_layouts: Vec<text_layout::Line>,
825 line_number_layouts: Vec<Option<text_layout::Line>>,
826 line_height: f32,
827 em_width: f32,
828 selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
829 overscroll: Vector2F,
830 text_offset: Vector2F,
831 max_visible_line_width: f32,
832}
833
834impl LayoutState {
835 fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 {
836 let row = self.snapshot.longest_row();
837 let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width();
838 longest_line_width.max(self.max_visible_line_width) + self.overscroll.x()
839 }
840
841 fn scroll_max(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> Vector2F {
842 let text_width = self.text_size.x();
843 let scroll_width = self.scroll_width(layout_cache);
844 let em_width = self.style.text.em_width(font_cache);
845 let max_row = self.snapshot.max_point().row();
846
847 vec2f(
848 ((scroll_width - text_width) / em_width).max(0.0),
849 max_row.saturating_sub(1) as f32,
850 )
851 }
852
853 pub fn layout_line(
854 &self,
855 row: u32,
856 snapshot: &Snapshot,
857 layout_cache: &TextLayoutCache,
858 ) -> text_layout::Line {
859 let mut line = snapshot.line(row);
860
861 if line.len() > MAX_LINE_LEN {
862 let mut len = MAX_LINE_LEN;
863 while !line.is_char_boundary(len) {
864 len -= 1;
865 }
866 line.truncate(len);
867 }
868
869 layout_cache.layout_str(
870 &line,
871 self.style.text.font_size,
872 &[(
873 snapshot.line_len(row) as usize,
874 RunStyle {
875 font_id: self.style.text.font_id,
876 color: Color::black(),
877 underline: None,
878 },
879 )],
880 )
881 }
882}
883
884pub struct PaintState {
885 bounds: RectF,
886 text_bounds: RectF,
887}
888
889impl PaintState {
890 fn point_for_position(
891 &self,
892 snapshot: &Snapshot,
893 layout: &LayoutState,
894 position: Vector2F,
895 ) -> DisplayPoint {
896 let scroll_position = snapshot.scroll_position();
897 let position = position - self.text_bounds.origin();
898 let y = position.y().max(0.0).min(layout.size.y());
899 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
900 let row = cmp::min(row, snapshot.max_point().row());
901 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
902 let x = position.x() + (scroll_position.x() * layout.em_width);
903
904 let column = if x >= 0.0 {
905 line.index_for_x(x)
906 .map(|ix| ix as u32)
907 .unwrap_or(snapshot.line_len(row))
908 } else {
909 0
910 };
911
912 DisplayPoint::new(row, column)
913 }
914}
915
916struct Cursor {
917 origin: Vector2F,
918 line_height: f32,
919 color: Color,
920}
921
922impl Cursor {
923 fn paint(&self, cx: &mut PaintContext) {
924 cx.scene.push_quad(Quad {
925 bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
926 background: Some(self.color),
927 border: Border::new(0., Color::black()),
928 corner_radius: 0.,
929 });
930 }
931}
932
933#[derive(Debug)]
934struct Selection {
935 start_y: f32,
936 line_height: f32,
937 lines: Vec<SelectionLine>,
938 color: Color,
939}
940
941#[derive(Debug)]
942struct SelectionLine {
943 start_x: f32,
944 end_x: f32,
945}
946
947impl Selection {
948 fn paint(&self, bounds: RectF, scene: &mut Scene) {
949 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
950 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
951 self.paint_lines(
952 self.start_y + self.line_height,
953 &self.lines[1..],
954 bounds,
955 scene,
956 );
957 } else {
958 self.paint_lines(self.start_y, &self.lines, bounds, scene);
959 }
960 }
961
962 fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], bounds: RectF, scene: &mut Scene) {
963 if lines.is_empty() {
964 return;
965 }
966
967 let mut path = PathBuilder::new();
968 let corner_radius = 0.15 * self.line_height;
969 let first_line = lines.first().unwrap();
970 let last_line = lines.last().unwrap();
971
972 let first_top_left = vec2f(first_line.start_x, start_y);
973 let first_top_right = vec2f(first_line.end_x, start_y);
974
975 let curve_height = vec2f(0., corner_radius);
976 let curve_width = |start_x: f32, end_x: f32| {
977 let max = (end_x - start_x) / 2.;
978 let width = if max < corner_radius {
979 max
980 } else {
981 corner_radius
982 };
983
984 vec2f(width, 0.)
985 };
986
987 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
988 path.reset(first_top_right - top_curve_width);
989 path.curve_to(first_top_right + curve_height, first_top_right);
990
991 let mut iter = lines.iter().enumerate().peekable();
992 while let Some((ix, line)) = iter.next() {
993 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
994
995 if let Some((_, next_line)) = iter.peek() {
996 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
997
998 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
999 Ordering::Equal => {
1000 path.line_to(bottom_right);
1001 }
1002 Ordering::Less => {
1003 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
1004 path.line_to(bottom_right - curve_height);
1005 path.curve_to(bottom_right - curve_width, bottom_right);
1006 path.line_to(next_top_right + curve_width);
1007 path.curve_to(next_top_right + curve_height, next_top_right);
1008 }
1009 Ordering::Greater => {
1010 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
1011 path.line_to(bottom_right - curve_height);
1012 path.curve_to(bottom_right + curve_width, bottom_right);
1013 path.line_to(next_top_right - curve_width);
1014 path.curve_to(next_top_right + curve_height, next_top_right);
1015 }
1016 }
1017 } else {
1018 let curve_width = curve_width(line.start_x, line.end_x);
1019 path.line_to(bottom_right - curve_height);
1020 path.curve_to(bottom_right - curve_width, bottom_right);
1021
1022 let bottom_left = vec2f(line.start_x, bottom_right.y());
1023 path.line_to(bottom_left + curve_width);
1024 path.curve_to(bottom_left - curve_height, bottom_left);
1025 }
1026 }
1027
1028 if first_line.start_x > last_line.start_x {
1029 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1030 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1031 path.line_to(second_top_left + curve_height);
1032 path.curve_to(second_top_left + curve_width, second_top_left);
1033 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1034 path.line_to(first_bottom_left - curve_width);
1035 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1036 }
1037
1038 path.line_to(first_top_left + curve_height);
1039 path.curve_to(first_top_left + top_curve_width, first_top_left);
1040 path.line_to(first_top_right - top_curve_width);
1041
1042 scene.push_path(path.build(self.color, Some(bounds)));
1043 }
1044}
1045
1046fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1047 delta.powf(1.5) / 100.0
1048}
1049
1050fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1051 delta.powf(1.2) / 300.0
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056 use super::*;
1057 use crate::{
1058 test::sample_text,
1059 {Editor, EditorSettings},
1060 };
1061 use language::Buffer;
1062
1063 #[gpui::test]
1064 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1065 let settings = EditorSettings::test(cx);
1066
1067 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
1068 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1069 Editor::for_buffer(
1070 buffer,
1071 {
1072 let settings = settings.clone();
1073 move |_| settings.clone()
1074 },
1075 cx,
1076 )
1077 });
1078 let element = EditorElement::new(editor.downgrade(), settings);
1079
1080 let layouts = editor.update(cx, |editor, cx| {
1081 let snapshot = editor.snapshot(cx);
1082 let mut presenter = cx.build_presenter(window_id, 30.);
1083 let mut layout_cx = presenter.build_layout_context(false, cx);
1084 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1085 });
1086 assert_eq!(layouts.len(), 6);
1087 }
1088}