1use super::{
2 display_map::{BlockContext, ToDisplayPoint},
3 Anchor, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, Input,
4 Scroll, Select, SelectPhase, SoftWrap, ToPoint, MAX_LINE_LEN,
5};
6use crate::display_map::TransformBlock;
7use clock::ReplicaId;
8use collections::{BTreeMap, HashMap};
9use gpui::{
10 color::Color,
11 elements::*,
12 fonts::{HighlightStyle, Underline},
13 geometry::{
14 rect::RectF,
15 vector::{vec2f, Vector2F},
16 PathBuilder,
17 },
18 json::{self, ToJson},
19 text_layout::{self, RunStyle, TextLayoutCache},
20 AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
21 MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
22};
23use json::json;
24use language::Bias;
25use smallvec::SmallVec;
26use std::{
27 cmp::{self, Ordering},
28 fmt::Write,
29 ops::Range,
30};
31
32pub struct EditorElement {
33 view: WeakViewHandle<Editor>,
34 settings: EditorSettings,
35}
36
37impl EditorElement {
38 pub fn new(view: WeakViewHandle<Editor>, settings: EditorSettings) -> Self {
39 Self { view, settings }
40 }
41
42 fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
43 self.view.upgrade(cx).unwrap().read(cx)
44 }
45
46 fn update_view<F, T>(&self, cx: &mut MutableAppContext, f: F) -> T
47 where
48 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
49 {
50 self.view.upgrade(cx).unwrap().update(cx, f)
51 }
52
53 fn snapshot(&self, cx: &mut MutableAppContext) -> EditorSnapshot {
54 self.update_view(cx, |view, cx| view.snapshot(cx))
55 }
56
57 fn mouse_down(
58 &self,
59 position: Vector2F,
60 alt: bool,
61 shift: bool,
62 mut click_count: usize,
63 layout: &mut LayoutState,
64 paint: &mut PaintState,
65 cx: &mut EventContext,
66 ) -> bool {
67 if paint.gutter_bounds.contains_point(position) {
68 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
69 } else if !paint.text_bounds.contains_point(position) {
70 return false;
71 }
72
73 let snapshot = self.snapshot(cx.app);
74 let (position, overshoot) = paint.point_for_position(&snapshot, layout, position);
75
76 if shift && alt {
77 cx.dispatch_action(Select(SelectPhase::BeginColumnar {
78 position,
79 overshoot,
80 }));
81 } else if shift {
82 cx.dispatch_action(Select(SelectPhase::Extend {
83 position,
84 click_count,
85 }));
86 } else {
87 cx.dispatch_action(Select(SelectPhase::Begin {
88 position,
89 add: alt,
90 click_count,
91 }));
92 }
93
94 true
95 }
96
97 fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool {
98 if self.view(cx.app.as_ref()).is_selecting() {
99 cx.dispatch_action(Select(SelectPhase::End));
100 true
101 } else {
102 false
103 }
104 }
105
106 fn mouse_dragged(
107 &self,
108 position: Vector2F,
109 layout: &mut LayoutState,
110 paint: &mut PaintState,
111 cx: &mut EventContext,
112 ) -> bool {
113 let view = self.view(cx.app.as_ref());
114
115 if view.is_selecting() {
116 let rect = paint.text_bounds;
117 let mut scroll_delta = Vector2F::zero();
118
119 let vertical_margin = layout.line_height.min(rect.height() / 3.0);
120 let top = rect.origin_y() + vertical_margin;
121 let bottom = rect.lower_left().y() - vertical_margin;
122 if position.y() < top {
123 scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
124 }
125 if position.y() > bottom {
126 scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
127 }
128
129 let horizontal_margin = layout.line_height.min(rect.width() / 3.0);
130 let left = rect.origin_x() + horizontal_margin;
131 let right = rect.upper_right().x() - horizontal_margin;
132 if position.x() < left {
133 scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
134 left - position.x(),
135 ))
136 }
137 if position.x() > right {
138 scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
139 position.x() - right,
140 ))
141 }
142
143 let snapshot = self.snapshot(cx.app);
144 let (position, overshoot) = paint.point_for_position(&snapshot, layout, position);
145
146 cx.dispatch_action(Select(SelectPhase::Update {
147 position,
148 overshoot,
149 scroll_position: (snapshot.scroll_position() + scroll_delta)
150 .clamp(Vector2F::zero(), layout.scroll_max),
151 }));
152 true
153 } else {
154 false
155 }
156 }
157
158 fn key_down(&self, input: Option<&str>, cx: &mut EventContext) -> bool {
159 let view = self.view.upgrade(cx.app).unwrap();
160
161 if view.is_focused(cx.app) {
162 if let Some(input) = input {
163 cx.dispatch_action(Input(input.to_string()));
164 true
165 } else {
166 false
167 }
168 } else {
169 false
170 }
171 }
172
173 fn scroll(
174 &self,
175 position: Vector2F,
176 mut delta: Vector2F,
177 precise: bool,
178 layout: &mut LayoutState,
179 paint: &mut PaintState,
180 cx: &mut EventContext,
181 ) -> bool {
182 if !paint.bounds.contains_point(position) {
183 return false;
184 }
185
186 let snapshot = self.snapshot(cx.app);
187 let max_glyph_width = layout.em_width;
188 if !precise {
189 delta *= vec2f(max_glyph_width, layout.line_height);
190 }
191
192 let scroll_position = snapshot.scroll_position();
193 let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
194 let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height;
195 let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max);
196
197 cx.dispatch_action(Scroll(scroll_position));
198
199 true
200 }
201
202 fn paint_background(
203 &self,
204 gutter_bounds: RectF,
205 text_bounds: RectF,
206 layout: &LayoutState,
207 cx: &mut PaintContext,
208 ) {
209 let bounds = gutter_bounds.union_rect(text_bounds);
210 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
211 let editor = self.view(cx.app);
212 let style = &self.settings.style;
213 cx.scene.push_quad(Quad {
214 bounds: gutter_bounds,
215 background: Some(style.gutter_background),
216 border: Border::new(0., Color::transparent_black()),
217 corner_radius: 0.,
218 });
219 cx.scene.push_quad(Quad {
220 bounds: text_bounds,
221 background: Some(style.background),
222 border: Border::new(0., Color::transparent_black()),
223 corner_radius: 0.,
224 });
225
226 if let EditorMode::Full = editor.mode {
227 let mut active_rows = layout.active_rows.iter().peekable();
228 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
229 let mut end_row = *start_row;
230 while active_rows.peek().map_or(false, |r| {
231 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
232 }) {
233 active_rows.next().unwrap();
234 end_row += 1;
235 }
236
237 if !contains_non_empty_selection {
238 let origin = vec2f(
239 bounds.origin_x(),
240 bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top,
241 );
242 let size = vec2f(
243 bounds.width(),
244 layout.line_height * (end_row - start_row + 1) as f32,
245 );
246 cx.scene.push_quad(Quad {
247 bounds: RectF::new(origin, size),
248 background: Some(style.active_line_background),
249 border: Border::default(),
250 corner_radius: 0.,
251 });
252 }
253 }
254
255 if let Some(highlighted_rows) = &layout.highlighted_rows {
256 let origin = vec2f(
257 bounds.origin_x(),
258 bounds.origin_y() + (layout.line_height * highlighted_rows.start as f32)
259 - scroll_top,
260 );
261 let size = vec2f(
262 bounds.width(),
263 layout.line_height * highlighted_rows.len() as f32,
264 );
265 cx.scene.push_quad(Quad {
266 bounds: RectF::new(origin, size),
267 background: Some(style.highlighted_line_background),
268 border: Border::default(),
269 corner_radius: 0.,
270 });
271 }
272 }
273 }
274
275 fn paint_gutter(
276 &mut self,
277 bounds: RectF,
278 visible_bounds: RectF,
279 layout: &mut LayoutState,
280 cx: &mut PaintContext,
281 ) {
282 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
283 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
284 if let Some(line) = line {
285 let line_origin = bounds.origin()
286 + vec2f(
287 bounds.width() - line.width() - layout.gutter_padding,
288 ix as f32 * layout.line_height - (scroll_top % layout.line_height),
289 );
290 line.paint(line_origin, visible_bounds, layout.line_height, cx);
291 }
292 }
293
294 if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
295 let mut x = bounds.width() - layout.gutter_padding;
296 let mut y = *row as f32 * layout.line_height - scroll_top;
297 x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
298 y += (layout.line_height - indicator.size().y()) / 2.;
299 indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
300 }
301 }
302
303 fn paint_text(
304 &mut self,
305 bounds: RectF,
306 visible_bounds: RectF,
307 layout: &mut LayoutState,
308 cx: &mut PaintContext,
309 ) {
310 let view = self.view(cx.app);
311 let style = &self.settings.style;
312 let local_replica_id = view.replica_id(cx);
313 let scroll_position = layout.snapshot.scroll_position();
314 let start_row = scroll_position.y() as u32;
315 let scroll_top = scroll_position.y() * layout.line_height;
316 let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
317 let max_glyph_width = layout.em_width;
318 let scroll_left = scroll_position.x() * max_glyph_width;
319 let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
320
321 cx.scene.push_layer(Some(bounds));
322
323 for (range, color) in &layout.highlighted_ranges {
324 self.paint_highlighted_range(
325 range.clone(),
326 start_row,
327 end_row,
328 *color,
329 0.,
330 0.15 * layout.line_height,
331 layout,
332 content_origin,
333 scroll_top,
334 scroll_left,
335 bounds,
336 cx,
337 );
338 }
339
340 let mut cursors = SmallVec::<[Cursor; 32]>::new();
341 for (replica_id, selections) in &layout.selections {
342 let style = style.replica_selection_style(*replica_id);
343 let corner_radius = 0.15 * layout.line_height;
344
345 for selection in selections {
346 self.paint_highlighted_range(
347 selection.start..selection.end,
348 start_row,
349 end_row,
350 style.selection,
351 corner_radius,
352 corner_radius * 2.,
353 layout,
354 content_origin,
355 scroll_top,
356 scroll_left,
357 bounds,
358 cx,
359 );
360
361 if view.show_local_cursors() || *replica_id != local_replica_id {
362 let cursor_position = selection.head();
363 if (start_row..end_row).contains(&cursor_position.row()) {
364 let cursor_row_layout =
365 &layout.line_layouts[(cursor_position.row() - start_row) as usize];
366 let x = cursor_row_layout.x_for_index(cursor_position.column() as usize)
367 - scroll_left;
368 let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
369 cursors.push(Cursor {
370 color: style.cursor,
371 origin: content_origin + vec2f(x, y),
372 line_height: layout.line_height,
373 });
374 }
375 }
376 }
377 }
378
379 if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
380 // Draw glyphs
381 for (ix, line) in layout.line_layouts.iter().enumerate() {
382 let row = start_row + ix as u32;
383 line.paint(
384 content_origin
385 + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top),
386 visible_text_bounds,
387 layout.line_height,
388 cx,
389 );
390 }
391 }
392
393 cx.scene.push_layer(Some(bounds));
394 for cursor in cursors {
395 cursor.paint(cx);
396 }
397 cx.scene.pop_layer();
398
399 if let Some((position, context_menu)) = layout.context_menu.as_mut() {
400 cx.scene.push_stacking_context(None);
401
402 let cursor_row_layout = &layout.line_layouts[(position.row() - start_row) as usize];
403 let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
404 let y = (position.row() + 1) as f32 * layout.line_height - scroll_top;
405 let mut list_origin = content_origin + vec2f(x, y);
406 let list_height = context_menu.size().y();
407
408 if list_origin.y() + list_height > bounds.lower_left().y() {
409 list_origin.set_y(list_origin.y() - layout.line_height - list_height);
410 }
411
412 context_menu.paint(
413 list_origin,
414 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
415 cx,
416 );
417
418 cx.scene.pop_stacking_context();
419 }
420
421 cx.scene.pop_layer();
422 }
423
424 fn paint_highlighted_range(
425 &self,
426 range: Range<DisplayPoint>,
427 start_row: u32,
428 end_row: u32,
429 color: Color,
430 corner_radius: f32,
431 line_end_overshoot: f32,
432 layout: &LayoutState,
433 content_origin: Vector2F,
434 scroll_top: f32,
435 scroll_left: f32,
436 bounds: RectF,
437 cx: &mut PaintContext,
438 ) {
439 if range.start != range.end {
440 let row_range = if range.end.column() == 0 {
441 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
442 } else {
443 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
444 };
445
446 let highlighted_range = HighlightedRange {
447 color,
448 line_height: layout.line_height,
449 corner_radius,
450 start_y: content_origin.y() + row_range.start as f32 * layout.line_height
451 - scroll_top,
452 lines: row_range
453 .into_iter()
454 .map(|row| {
455 let line_layout = &layout.line_layouts[(row - start_row) as usize];
456 HighlightedRangeLine {
457 start_x: if row == range.start.row() {
458 content_origin.x()
459 + line_layout.x_for_index(range.start.column() as usize)
460 - scroll_left
461 } else {
462 content_origin.x() - scroll_left
463 },
464 end_x: if row == range.end.row() {
465 content_origin.x()
466 + line_layout.x_for_index(range.end.column() as usize)
467 - scroll_left
468 } else {
469 content_origin.x() + line_layout.width() + line_end_overshoot
470 - scroll_left
471 },
472 }
473 })
474 .collect(),
475 };
476
477 highlighted_range.paint(bounds, cx.scene);
478 }
479 }
480
481 fn paint_blocks(
482 &mut self,
483 bounds: RectF,
484 visible_bounds: RectF,
485 layout: &mut LayoutState,
486 cx: &mut PaintContext,
487 ) {
488 let scroll_position = layout.snapshot.scroll_position();
489 let scroll_left = scroll_position.x() * layout.em_width;
490 let scroll_top = scroll_position.y() * layout.line_height;
491
492 for (row, element) in &mut layout.blocks {
493 let origin = bounds.origin()
494 + vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top);
495 element.paint(origin, visible_bounds, cx);
496 }
497 }
498
499 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &LayoutContext) -> f32 {
500 let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
501 let style = &self.settings.style;
502
503 cx.text_layout_cache
504 .layout_str(
505 "1".repeat(digit_count).as_str(),
506 style.text.font_size,
507 &[(
508 digit_count,
509 RunStyle {
510 font_id: style.text.font_id,
511 color: Color::black(),
512 underline: None,
513 },
514 )],
515 )
516 .width()
517 }
518
519 fn layout_line_numbers(
520 &self,
521 rows: Range<u32>,
522 active_rows: &BTreeMap<u32, bool>,
523 snapshot: &EditorSnapshot,
524 cx: &LayoutContext,
525 ) -> Vec<Option<text_layout::Line>> {
526 let style = &self.settings.style;
527 let include_line_numbers = snapshot.mode == EditorMode::Full;
528 let mut line_number_layouts = Vec::with_capacity(rows.len());
529 let mut line_number = String::new();
530 for (ix, row) in snapshot
531 .buffer_rows(rows.start)
532 .take((rows.end - rows.start) as usize)
533 .enumerate()
534 {
535 let display_row = rows.start + ix as u32;
536 let color = if active_rows.contains_key(&display_row) {
537 style.line_number_active
538 } else {
539 style.line_number
540 };
541 if let Some(buffer_row) = row {
542 if include_line_numbers {
543 line_number.clear();
544 write!(&mut line_number, "{}", buffer_row + 1).unwrap();
545 line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
546 &line_number,
547 style.text.font_size,
548 &[(
549 line_number.len(),
550 RunStyle {
551 font_id: style.text.font_id,
552 color,
553 underline: None,
554 },
555 )],
556 )));
557 }
558 } else {
559 line_number_layouts.push(None);
560 }
561 }
562
563 line_number_layouts
564 }
565
566 fn layout_lines(
567 &mut self,
568 mut rows: Range<u32>,
569 snapshot: &mut EditorSnapshot,
570 cx: &LayoutContext,
571 ) -> Vec<text_layout::Line> {
572 rows.end = cmp::min(rows.end, snapshot.max_point().row() + 1);
573 if rows.start >= rows.end {
574 return Vec::new();
575 }
576
577 // When the editor is empty and unfocused, then show the placeholder.
578 if snapshot.is_empty() && !snapshot.is_focused() {
579 let placeholder_style = self.settings.style.placeholder_text();
580 let placeholder_text = snapshot.placeholder_text();
581 let placeholder_lines = placeholder_text
582 .as_ref()
583 .map_or("", AsRef::as_ref)
584 .split('\n')
585 .skip(rows.start as usize)
586 .take(rows.len());
587 return placeholder_lines
588 .map(|line| {
589 cx.text_layout_cache.layout_str(
590 line,
591 placeholder_style.font_size,
592 &[(
593 line.len(),
594 RunStyle {
595 font_id: placeholder_style.font_id,
596 color: placeholder_style.color,
597 underline: None,
598 },
599 )],
600 )
601 })
602 .collect();
603 } else {
604 let style = &self.settings.style;
605 let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| {
606 let highlight_style = chunk
607 .highlight_id
608 .and_then(|highlight_id| highlight_id.style(&style.syntax));
609 let highlight = if let Some(severity) = chunk.diagnostic {
610 let diagnostic_style = super::diagnostic_style(severity, true, style);
611 let underline = Some(Underline {
612 color: diagnostic_style.message.text.color,
613 thickness: 1.0.into(),
614 squiggly: true,
615 });
616 if let Some(mut highlight) = highlight_style {
617 highlight.underline = underline;
618 Some(highlight)
619 } else {
620 Some(HighlightStyle {
621 underline,
622 color: style.text.color,
623 font_properties: style.text.font_properties,
624 })
625 }
626 } else {
627 highlight_style
628 };
629 (chunk.text, highlight)
630 });
631 layout_highlighted_chunks(
632 chunks,
633 &style.text,
634 &cx.text_layout_cache,
635 &cx.font_cache,
636 MAX_LINE_LEN,
637 rows.len() as usize,
638 )
639 }
640 }
641
642 fn layout_blocks(
643 &mut self,
644 rows: Range<u32>,
645 snapshot: &EditorSnapshot,
646 width: f32,
647 gutter_padding: f32,
648 gutter_width: f32,
649 em_width: f32,
650 text_x: f32,
651 line_height: f32,
652 style: &EditorStyle,
653 line_layouts: &[text_layout::Line],
654 cx: &mut LayoutContext,
655 ) -> Vec<(u32, ElementBox)> {
656 let scroll_x = snapshot.scroll_position.x();
657 snapshot
658 .blocks_in_range(rows.clone())
659 .map(|(block_row, block)| {
660 let mut element = match block {
661 TransformBlock::Custom(block) => {
662 let align_to = block
663 .position()
664 .to_point(&snapshot.buffer_snapshot)
665 .to_display_point(snapshot);
666 let anchor_x = text_x
667 + if rows.contains(&align_to.row()) {
668 line_layouts[(align_to.row() - rows.start) as usize]
669 .x_for_index(align_to.column() as usize)
670 } else {
671 layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
672 .x_for_index(align_to.column() as usize)
673 };
674
675 block.render(&BlockContext {
676 cx,
677 anchor_x,
678 gutter_padding,
679 line_height,
680 scroll_x,
681 gutter_width,
682 em_width,
683 })
684 }
685 TransformBlock::ExcerptHeader {
686 buffer,
687 starts_new_buffer,
688 ..
689 } => {
690 if *starts_new_buffer {
691 let style = &self.settings.style.diagnostic_path_header;
692 let font_size = (style.text_scale_factor
693 * self.settings.style.text.font_size)
694 .round();
695
696 let mut filename = None;
697 let mut parent_path = None;
698 if let Some(path) = buffer.path() {
699 filename =
700 path.file_name().map(|f| f.to_string_lossy().to_string());
701 parent_path =
702 path.parent().map(|p| p.to_string_lossy().to_string() + "/");
703 }
704
705 Flex::row()
706 .with_child(
707 Label::new(
708 filename.unwrap_or_else(|| "untitled".to_string()),
709 style.filename.text.clone().with_font_size(font_size),
710 )
711 .contained()
712 .with_style(style.filename.container)
713 .boxed(),
714 )
715 .with_children(parent_path.map(|path| {
716 Label::new(
717 path,
718 style.path.text.clone().with_font_size(font_size),
719 )
720 .contained()
721 .with_style(style.path.container)
722 .boxed()
723 }))
724 .aligned()
725 .left()
726 .contained()
727 .with_style(style.container)
728 .with_padding_left(gutter_padding + scroll_x * em_width)
729 .expanded()
730 .named("path header block")
731 } else {
732 let text_style = self.settings.style.text.clone();
733 Label::new("…".to_string(), text_style)
734 .contained()
735 .with_padding_left(gutter_padding + scroll_x * em_width)
736 .named("collapsed context")
737 }
738 }
739 };
740
741 element.layout(
742 SizeConstraint {
743 min: Vector2F::zero(),
744 max: vec2f(width, block.height() as f32 * line_height),
745 },
746 cx,
747 );
748 (block_row, element)
749 })
750 .collect()
751 }
752}
753
754impl Element for EditorElement {
755 type LayoutState = LayoutState;
756 type PaintState = PaintState;
757
758 fn layout(
759 &mut self,
760 constraint: SizeConstraint,
761 cx: &mut LayoutContext,
762 ) -> (Vector2F, Self::LayoutState) {
763 let mut size = constraint.max;
764 if size.x().is_infinite() {
765 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
766 }
767
768 let snapshot = self.snapshot(cx.app);
769 let style = self.settings.style.clone();
770 let line_height = style.text.line_height(cx.font_cache);
771
772 let gutter_padding;
773 let gutter_width;
774 let gutter_margin;
775 if snapshot.mode == EditorMode::Full {
776 gutter_padding = style.text.em_width(cx.font_cache) * style.gutter_padding_factor;
777 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
778 gutter_margin = -style.text.descent(cx.font_cache);
779 } else {
780 gutter_padding = 0.0;
781 gutter_width = 0.0;
782 gutter_margin = 0.0;
783 };
784
785 let text_width = size.x() - gutter_width;
786 let em_width = style.text.em_width(cx.font_cache);
787 let em_advance = style.text.em_advance(cx.font_cache);
788 let overscroll = vec2f(em_width, 0.);
789 let wrap_width = match self.settings.soft_wrap {
790 SoftWrap::None => None,
791 SoftWrap::EditorWidth => Some(text_width - gutter_margin - overscroll.x() - em_width),
792 SoftWrap::Column(column) => Some(column as f32 * em_advance),
793 };
794 let snapshot = self.update_view(cx.app, |view, cx| {
795 if view.set_wrap_width(wrap_width, cx) {
796 view.snapshot(cx)
797 } else {
798 snapshot
799 }
800 });
801
802 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
803 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
804 size.set_y(
805 scroll_height
806 .min(constraint.max_along(Axis::Vertical))
807 .max(constraint.min_along(Axis::Vertical))
808 .min(line_height * max_lines as f32),
809 )
810 } else if size.y().is_infinite() {
811 size.set_y(scroll_height);
812 }
813 let gutter_size = vec2f(gutter_width, size.y());
814 let text_size = vec2f(text_width, size.y());
815
816 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
817 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
818 let snapshot = view.snapshot(cx);
819 (autoscroll_horizontally, snapshot)
820 });
821
822 let scroll_position = snapshot.scroll_position();
823 let start_row = scroll_position.y() as u32;
824 let scroll_top = scroll_position.y() * line_height;
825 let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
826
827 let start_anchor = if start_row == 0 {
828 Anchor::min()
829 } else {
830 snapshot
831 .buffer_snapshot
832 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
833 };
834 let end_anchor = if end_row > snapshot.max_point().row() {
835 Anchor::max()
836 } else {
837 snapshot
838 .buffer_snapshot
839 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
840 };
841
842 let mut selections = HashMap::default();
843 let mut active_rows = BTreeMap::new();
844 let mut highlighted_rows = None;
845 let mut highlighted_ranges = Vec::new();
846 self.update_view(cx.app, |view, cx| {
847 let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
848
849 highlighted_rows = view.highlighted_rows();
850 highlighted_ranges = view.highlighted_ranges_in_range(
851 start_anchor.clone()..end_anchor.clone(),
852 &display_map,
853 );
854
855 let local_selections = view
856 .local_selections_in_range(start_anchor.clone()..end_anchor.clone(), &display_map);
857 for selection in &local_selections {
858 let is_empty = selection.start == selection.end;
859 let selection_start = snapshot.prev_line_boundary(selection.start).1;
860 let selection_end = snapshot.next_line_boundary(selection.end).1;
861 for row in cmp::max(selection_start.row(), start_row)
862 ..=cmp::min(selection_end.row(), end_row)
863 {
864 let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
865 *contains_non_empty_selection |= !is_empty;
866 }
867 }
868 selections.insert(
869 view.replica_id(cx),
870 local_selections
871 .into_iter()
872 .map(|selection| crate::Selection {
873 id: selection.id,
874 goal: selection.goal,
875 reversed: selection.reversed,
876 start: selection.start.to_display_point(&display_map),
877 end: selection.end.to_display_point(&display_map),
878 })
879 .collect(),
880 );
881
882 for (replica_id, selection) in display_map
883 .buffer_snapshot
884 .remote_selections_in_range(&(start_anchor..end_anchor))
885 {
886 selections
887 .entry(replica_id)
888 .or_insert(Vec::new())
889 .push(crate::Selection {
890 id: selection.id,
891 goal: selection.goal,
892 reversed: selection.reversed,
893 start: selection.start.to_display_point(&display_map),
894 end: selection.end.to_display_point(&display_map),
895 });
896 }
897 });
898
899 let line_number_layouts =
900 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
901
902 let mut max_visible_line_width = 0.0;
903 let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
904 for line in &line_layouts {
905 if line.width() > max_visible_line_width {
906 max_visible_line_width = line.width();
907 }
908 }
909
910 let style = self.settings.style.clone();
911 let longest_line_width = layout_line(
912 snapshot.longest_row(),
913 &snapshot,
914 &style,
915 cx.text_layout_cache,
916 )
917 .width();
918 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
919 let em_width = style.text.em_width(cx.font_cache);
920 let max_row = snapshot.max_point().row();
921 let scroll_max = vec2f(
922 ((scroll_width - text_size.x()) / em_width).max(0.0),
923 max_row.saturating_sub(1) as f32,
924 );
925
926 let mut context_menu = None;
927 let mut code_actions_indicator = None;
928 self.update_view(cx.app, |view, cx| {
929 let clamped = view.clamp_scroll_left(scroll_max.x());
930 let autoscrolled;
931 if autoscroll_horizontally {
932 autoscrolled = view.autoscroll_horizontally(
933 start_row,
934 text_size.x(),
935 scroll_width,
936 em_width,
937 &line_layouts,
938 cx,
939 );
940 } else {
941 autoscrolled = false;
942 }
943
944 if clamped || autoscrolled {
945 snapshot = view.snapshot(cx);
946 }
947
948 let newest_selection_head = view
949 .newest_selection::<usize>(&snapshot.buffer_snapshot)
950 .head()
951 .to_display_point(&snapshot);
952
953 if (start_row..end_row).contains(&newest_selection_head.row()) {
954 if view.context_menu_visible() {
955 context_menu = view.render_context_menu(newest_selection_head, cx);
956 }
957
958 code_actions_indicator = view
959 .render_code_actions_indicator(cx)
960 .map(|indicator| (newest_selection_head.row(), indicator));
961 }
962 });
963
964 if let Some((_, context_menu)) = context_menu.as_mut() {
965 context_menu.layout(
966 SizeConstraint {
967 min: Vector2F::zero(),
968 max: vec2f(
969 f32::INFINITY,
970 (12. * line_height).min((size.y() - line_height) / 2.),
971 ),
972 },
973 cx,
974 );
975 }
976
977 if let Some((_, indicator)) = code_actions_indicator.as_mut() {
978 indicator.layout(
979 SizeConstraint::strict_along(Axis::Vertical, line_height * 0.618),
980 cx,
981 );
982 }
983
984 let blocks = self.layout_blocks(
985 start_row..end_row,
986 &snapshot,
987 size.x().max(scroll_width + gutter_width),
988 gutter_padding,
989 gutter_width,
990 em_width,
991 gutter_width + gutter_margin,
992 line_height,
993 &style,
994 &line_layouts,
995 cx,
996 );
997
998 (
999 size,
1000 LayoutState {
1001 size,
1002 scroll_max,
1003 gutter_size,
1004 gutter_padding,
1005 text_size,
1006 gutter_margin,
1007 snapshot,
1008 active_rows,
1009 highlighted_rows,
1010 highlighted_ranges,
1011 line_layouts,
1012 line_number_layouts,
1013 blocks,
1014 line_height,
1015 em_width,
1016 em_advance,
1017 selections,
1018 context_menu,
1019 code_actions_indicator,
1020 },
1021 )
1022 }
1023
1024 fn paint(
1025 &mut self,
1026 bounds: RectF,
1027 visible_bounds: RectF,
1028 layout: &mut Self::LayoutState,
1029 cx: &mut PaintContext,
1030 ) -> Self::PaintState {
1031 cx.scene.push_layer(Some(bounds));
1032
1033 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
1034 let text_bounds = RectF::new(
1035 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
1036 layout.text_size,
1037 );
1038
1039 self.paint_background(gutter_bounds, text_bounds, layout, cx);
1040 if layout.gutter_size.x() > 0. {
1041 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
1042 }
1043 self.paint_text(text_bounds, visible_bounds, layout, cx);
1044
1045 if !layout.blocks.is_empty() {
1046 cx.scene.push_layer(Some(bounds));
1047 self.paint_blocks(bounds, visible_bounds, layout, cx);
1048 cx.scene.pop_layer();
1049 }
1050
1051 cx.scene.pop_layer();
1052
1053 PaintState {
1054 bounds,
1055 gutter_bounds,
1056 text_bounds,
1057 }
1058 }
1059
1060 fn dispatch_event(
1061 &mut self,
1062 event: &Event,
1063 _: RectF,
1064 layout: &mut LayoutState,
1065 paint: &mut PaintState,
1066 cx: &mut EventContext,
1067 ) -> bool {
1068 if let Some((_, context_menu)) = &mut layout.context_menu {
1069 if context_menu.dispatch_event(event, cx) {
1070 return true;
1071 }
1072 }
1073
1074 if let Some((_, indicator)) = &mut layout.code_actions_indicator {
1075 if indicator.dispatch_event(event, cx) {
1076 return true;
1077 }
1078 }
1079
1080 for (_, block) in &mut layout.blocks {
1081 if block.dispatch_event(event, cx) {
1082 return true;
1083 }
1084 }
1085
1086 match event {
1087 Event::LeftMouseDown {
1088 position,
1089 alt,
1090 shift,
1091 click_count,
1092 ..
1093 } => self.mouse_down(*position, *alt, *shift, *click_count, layout, paint, cx),
1094 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
1095 Event::LeftMouseDragged { position } => {
1096 self.mouse_dragged(*position, layout, paint, cx)
1097 }
1098 Event::ScrollWheel {
1099 position,
1100 delta,
1101 precise,
1102 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
1103 Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
1104 _ => false,
1105 }
1106 }
1107
1108 fn debug(
1109 &self,
1110 bounds: RectF,
1111 _: &Self::LayoutState,
1112 _: &Self::PaintState,
1113 _: &gpui::DebugContext,
1114 ) -> json::Value {
1115 json!({
1116 "type": "BufferElement",
1117 "bounds": bounds.to_json()
1118 })
1119 }
1120}
1121
1122pub struct LayoutState {
1123 size: Vector2F,
1124 scroll_max: Vector2F,
1125 gutter_size: Vector2F,
1126 gutter_padding: f32,
1127 gutter_margin: f32,
1128 text_size: Vector2F,
1129 snapshot: EditorSnapshot,
1130 active_rows: BTreeMap<u32, bool>,
1131 highlighted_rows: Option<Range<u32>>,
1132 line_layouts: Vec<text_layout::Line>,
1133 line_number_layouts: Vec<Option<text_layout::Line>>,
1134 blocks: Vec<(u32, ElementBox)>,
1135 line_height: f32,
1136 em_width: f32,
1137 em_advance: f32,
1138 highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
1139 selections: HashMap<ReplicaId, Vec<text::Selection<DisplayPoint>>>,
1140 context_menu: Option<(DisplayPoint, ElementBox)>,
1141 code_actions_indicator: Option<(u32, ElementBox)>,
1142}
1143
1144fn layout_line(
1145 row: u32,
1146 snapshot: &EditorSnapshot,
1147 style: &EditorStyle,
1148 layout_cache: &TextLayoutCache,
1149) -> text_layout::Line {
1150 let mut line = snapshot.line(row);
1151
1152 if line.len() > MAX_LINE_LEN {
1153 let mut len = MAX_LINE_LEN;
1154 while !line.is_char_boundary(len) {
1155 len -= 1;
1156 }
1157 line.truncate(len);
1158 }
1159
1160 layout_cache.layout_str(
1161 &line,
1162 style.text.font_size,
1163 &[(
1164 snapshot.line_len(row) as usize,
1165 RunStyle {
1166 font_id: style.text.font_id,
1167 color: Color::black(),
1168 underline: None,
1169 },
1170 )],
1171 )
1172}
1173
1174pub struct PaintState {
1175 bounds: RectF,
1176 gutter_bounds: RectF,
1177 text_bounds: RectF,
1178}
1179
1180impl PaintState {
1181 fn point_for_position(
1182 &self,
1183 snapshot: &EditorSnapshot,
1184 layout: &LayoutState,
1185 position: Vector2F,
1186 ) -> (DisplayPoint, u32) {
1187 let scroll_position = snapshot.scroll_position();
1188 let position = position - self.text_bounds.origin();
1189 let y = position.y().max(0.0).min(layout.size.y());
1190 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
1191 let row = cmp::min(row, snapshot.max_point().row());
1192 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
1193 let x = position.x() + (scroll_position.x() * layout.em_width);
1194
1195 let column = if x >= 0.0 {
1196 line.index_for_x(x)
1197 .map(|ix| ix as u32)
1198 .unwrap_or_else(|| snapshot.line_len(row))
1199 } else {
1200 0
1201 };
1202 let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
1203
1204 (DisplayPoint::new(row, column), overshoot)
1205 }
1206}
1207
1208struct Cursor {
1209 origin: Vector2F,
1210 line_height: f32,
1211 color: Color,
1212}
1213
1214impl Cursor {
1215 fn paint(&self, cx: &mut PaintContext) {
1216 cx.scene.push_quad(Quad {
1217 bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
1218 background: Some(self.color),
1219 border: Border::new(0., Color::black()),
1220 corner_radius: 0.,
1221 });
1222 }
1223}
1224
1225#[derive(Debug)]
1226struct HighlightedRange {
1227 start_y: f32,
1228 line_height: f32,
1229 lines: Vec<HighlightedRangeLine>,
1230 color: Color,
1231 corner_radius: f32,
1232}
1233
1234#[derive(Debug)]
1235struct HighlightedRangeLine {
1236 start_x: f32,
1237 end_x: f32,
1238}
1239
1240impl HighlightedRange {
1241 fn paint(&self, bounds: RectF, scene: &mut Scene) {
1242 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
1243 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
1244 self.paint_lines(
1245 self.start_y + self.line_height,
1246 &self.lines[1..],
1247 bounds,
1248 scene,
1249 );
1250 } else {
1251 self.paint_lines(self.start_y, &self.lines, bounds, scene);
1252 }
1253 }
1254
1255 fn paint_lines(
1256 &self,
1257 start_y: f32,
1258 lines: &[HighlightedRangeLine],
1259 bounds: RectF,
1260 scene: &mut Scene,
1261 ) {
1262 if lines.is_empty() {
1263 return;
1264 }
1265
1266 let mut path = PathBuilder::new();
1267 let first_line = lines.first().unwrap();
1268 let last_line = lines.last().unwrap();
1269
1270 let first_top_left = vec2f(first_line.start_x, start_y);
1271 let first_top_right = vec2f(first_line.end_x, start_y);
1272
1273 let curve_height = vec2f(0., self.corner_radius);
1274 let curve_width = |start_x: f32, end_x: f32| {
1275 let max = (end_x - start_x) / 2.;
1276 let width = if max < self.corner_radius {
1277 max
1278 } else {
1279 self.corner_radius
1280 };
1281
1282 vec2f(width, 0.)
1283 };
1284
1285 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
1286 path.reset(first_top_right - top_curve_width);
1287 path.curve_to(first_top_right + curve_height, first_top_right);
1288
1289 let mut iter = lines.iter().enumerate().peekable();
1290 while let Some((ix, line)) = iter.next() {
1291 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
1292
1293 if let Some((_, next_line)) = iter.peek() {
1294 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
1295
1296 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
1297 Ordering::Equal => {
1298 path.line_to(bottom_right);
1299 }
1300 Ordering::Less => {
1301 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
1302 path.line_to(bottom_right - curve_height);
1303 if self.corner_radius > 0. {
1304 path.curve_to(bottom_right - curve_width, bottom_right);
1305 }
1306 path.line_to(next_top_right + curve_width);
1307 if self.corner_radius > 0. {
1308 path.curve_to(next_top_right + curve_height, next_top_right);
1309 }
1310 }
1311 Ordering::Greater => {
1312 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
1313 path.line_to(bottom_right - curve_height);
1314 if self.corner_radius > 0. {
1315 path.curve_to(bottom_right + curve_width, bottom_right);
1316 }
1317 path.line_to(next_top_right - curve_width);
1318 if self.corner_radius > 0. {
1319 path.curve_to(next_top_right + curve_height, next_top_right);
1320 }
1321 }
1322 }
1323 } else {
1324 let curve_width = curve_width(line.start_x, line.end_x);
1325 path.line_to(bottom_right - curve_height);
1326 if self.corner_radius > 0. {
1327 path.curve_to(bottom_right - curve_width, bottom_right);
1328 }
1329
1330 let bottom_left = vec2f(line.start_x, bottom_right.y());
1331 path.line_to(bottom_left + curve_width);
1332 if self.corner_radius > 0. {
1333 path.curve_to(bottom_left - curve_height, bottom_left);
1334 }
1335 }
1336 }
1337
1338 if first_line.start_x > last_line.start_x {
1339 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1340 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1341 path.line_to(second_top_left + curve_height);
1342 if self.corner_radius > 0. {
1343 path.curve_to(second_top_left + curve_width, second_top_left);
1344 }
1345 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1346 path.line_to(first_bottom_left - curve_width);
1347 if self.corner_radius > 0. {
1348 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1349 }
1350 }
1351
1352 path.line_to(first_top_left + curve_height);
1353 if self.corner_radius > 0. {
1354 path.curve_to(first_top_left + top_curve_width, first_top_left);
1355 }
1356 path.line_to(first_top_right - top_curve_width);
1357
1358 scene.push_path(path.build(self.color, Some(bounds)));
1359 }
1360}
1361
1362fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1363 delta.powf(1.5) / 100.0
1364}
1365
1366fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1367 delta.powf(1.2) / 300.0
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372 use super::*;
1373 use crate::{Editor, EditorSettings, MultiBuffer};
1374 use std::sync::Arc;
1375 use util::test::sample_text;
1376
1377 #[gpui::test]
1378 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1379 let settings = EditorSettings::test(cx);
1380 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
1381 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1382 Editor::for_buffer(
1383 buffer,
1384 {
1385 let settings = settings.clone();
1386 Arc::new(move |_| settings.clone())
1387 },
1388 None,
1389 cx,
1390 )
1391 });
1392 let element = EditorElement::new(editor.downgrade(), settings);
1393
1394 let layouts = editor.update(cx, |editor, cx| {
1395 let snapshot = editor.snapshot(cx);
1396 let mut presenter = cx.build_presenter(window_id, 30.);
1397 let mut layout_cx = presenter.build_layout_context(false, cx);
1398 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1399 });
1400 assert_eq!(layouts.len(), 6);
1401 }
1402}