1use super::{
2 display_map::{BlockContext, ToDisplayPoint},
3 Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Scroll, Select, SelectPhase,
4 SoftWrap, ToPoint, MAX_LINE_LEN,
5};
6use crate::{
7 display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
8 hover_popover::{
9 HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
10 },
11 link_go_to_definition::{
12 GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
13 },
14 mouse_context_menu::DeployMouseContextMenu,
15 AnchorRangeExt, EditorStyle,
16};
17use clock::ReplicaId;
18use collections::{BTreeMap, HashMap};
19use git::diff::DiffHunkStatus;
20use gpui::{
21 color::Color,
22 elements::*,
23 fonts::{HighlightStyle, Underline},
24 geometry::{
25 rect::RectF,
26 vector::{vec2f, Vector2F},
27 PathBuilder,
28 },
29 json::{self, ToJson},
30 platform::CursorStyle,
31 text_layout::{self, Line, RunStyle, TextLayoutCache},
32 AppContext, Axis, Border, CursorRegion, Element, ElementBox, EventContext, LayoutContext,
33 Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MutableAppContext,
34 PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle,
35};
36use json::json;
37use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Point, Selection};
38use project::ProjectPath;
39use settings::{GitGutter, Settings};
40use smallvec::SmallVec;
41use std::{
42 cmp::{self, Ordering},
43 fmt::Write,
44 iter,
45 ops::{DerefMut, Range},
46 sync::Arc,
47};
48
49#[derive(Debug)]
50struct DiffHunkLayout {
51 visual_range: Range<u32>,
52 status: DiffHunkStatus,
53 is_folded: bool,
54}
55
56struct SelectionLayout {
57 head: DisplayPoint,
58 cursor_shape: CursorShape,
59 range: Range<DisplayPoint>,
60}
61
62impl SelectionLayout {
63 fn new<T: ToPoint + ToDisplayPoint + Clone>(
64 selection: Selection<T>,
65 line_mode: bool,
66 cursor_shape: CursorShape,
67 map: &DisplaySnapshot,
68 ) -> Self {
69 if line_mode {
70 let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
71 let point_range = map.expand_to_line(selection.range());
72 Self {
73 head: selection.head().to_display_point(map),
74 cursor_shape,
75 range: point_range.start.to_display_point(map)
76 ..point_range.end.to_display_point(map),
77 }
78 } else {
79 let selection = selection.map(|p| p.to_display_point(map));
80 Self {
81 head: selection.head(),
82 cursor_shape,
83 range: selection.range(),
84 }
85 }
86 }
87}
88
89#[derive(Clone)]
90pub struct EditorElement {
91 view: WeakViewHandle<Editor>,
92 style: Arc<EditorStyle>,
93}
94
95impl EditorElement {
96 pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
97 Self {
98 view,
99 style: Arc::new(style),
100 }
101 }
102
103 fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
104 self.view.upgrade(cx).unwrap().read(cx)
105 }
106
107 fn update_view<F, T>(&self, cx: &mut MutableAppContext, f: F) -> T
108 where
109 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
110 {
111 self.view.upgrade(cx).unwrap().update(cx, f)
112 }
113
114 fn snapshot(&self, cx: &mut MutableAppContext) -> EditorSnapshot {
115 self.update_view(cx, |view, cx| view.snapshot(cx))
116 }
117
118 fn attach_mouse_handlers(
119 view: &WeakViewHandle<Editor>,
120 position_map: &Arc<PositionMap>,
121 visible_bounds: RectF,
122 text_bounds: RectF,
123 gutter_bounds: RectF,
124 bounds: RectF,
125 cx: &mut PaintContext,
126 ) {
127 enum EditorElementMouseHandlers {}
128 cx.scene.push_mouse_region(
129 MouseRegion::new::<EditorElementMouseHandlers>(view.id(), view.id(), visible_bounds)
130 .on_down(MouseButton::Left, {
131 let position_map = position_map.clone();
132 move |e, cx| {
133 if !Self::mouse_down(
134 e.platform_event,
135 position_map.as_ref(),
136 text_bounds,
137 gutter_bounds,
138 cx,
139 ) {
140 cx.propagate_event();
141 }
142 }
143 })
144 .on_down(MouseButton::Right, {
145 let position_map = position_map.clone();
146 move |e, cx| {
147 if !Self::mouse_right_down(
148 e.position,
149 position_map.as_ref(),
150 text_bounds,
151 cx,
152 ) {
153 cx.propagate_event();
154 }
155 }
156 })
157 .on_up(MouseButton::Left, {
158 let view = view.clone();
159 let position_map = position_map.clone();
160 move |e, cx| {
161 if !Self::mouse_up(
162 view.clone(),
163 e.position,
164 e.cmd,
165 e.shift,
166 position_map.as_ref(),
167 text_bounds,
168 cx,
169 ) {
170 cx.propagate_event()
171 }
172 }
173 })
174 .on_drag(MouseButton::Left, {
175 let view = view.clone();
176 let position_map = position_map.clone();
177 move |e, cx| {
178 if !Self::mouse_dragged(
179 view.clone(),
180 e.platform_event,
181 position_map.as_ref(),
182 text_bounds,
183 cx,
184 ) {
185 cx.propagate_event()
186 }
187 }
188 })
189 .on_move({
190 let position_map = position_map.clone();
191 move |e, cx| {
192 if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
193 cx.propagate_event()
194 }
195 }
196 })
197 .on_scroll({
198 let position_map = position_map.clone();
199 move |e, cx| {
200 if !Self::scroll(e.position, e.delta, e.precise, &position_map, bounds, cx)
201 {
202 cx.propagate_event()
203 }
204 }
205 }),
206 );
207 }
208
209 fn mouse_down(
210 MouseButtonEvent {
211 position,
212 modifiers:
213 Modifiers {
214 shift,
215 ctrl,
216 alt,
217 cmd,
218 ..
219 },
220 mut click_count,
221 ..
222 }: MouseButtonEvent,
223 position_map: &PositionMap,
224 text_bounds: RectF,
225 gutter_bounds: RectF,
226 cx: &mut EventContext,
227 ) -> bool {
228 if gutter_bounds.contains_point(position) {
229 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
230 } else if !text_bounds.contains_point(position) {
231 return false;
232 }
233
234 let (position, target_position) = position_map.point_for_position(text_bounds, position);
235
236 if shift && alt {
237 cx.dispatch_action(Select(SelectPhase::BeginColumnar {
238 position,
239 goal_column: target_position.column(),
240 }));
241 } else if shift && !ctrl && !alt && !cmd {
242 cx.dispatch_action(Select(SelectPhase::Extend {
243 position,
244 click_count,
245 }));
246 } else {
247 cx.dispatch_action(Select(SelectPhase::Begin {
248 position,
249 add: alt,
250 click_count,
251 }));
252 }
253
254 true
255 }
256
257 fn mouse_right_down(
258 position: Vector2F,
259 position_map: &PositionMap,
260 text_bounds: RectF,
261 cx: &mut EventContext,
262 ) -> bool {
263 if !text_bounds.contains_point(position) {
264 return false;
265 }
266
267 let (point, _) = position_map.point_for_position(text_bounds, position);
268
269 cx.dispatch_action(DeployMouseContextMenu { position, point });
270 true
271 }
272
273 fn mouse_up(
274 view: WeakViewHandle<Editor>,
275 position: Vector2F,
276 cmd: bool,
277 shift: bool,
278 position_map: &PositionMap,
279 text_bounds: RectF,
280 cx: &mut EventContext,
281 ) -> bool {
282 let view = view.upgrade(cx.app).unwrap().read(cx.app);
283 let end_selection = view.has_pending_selection();
284 let pending_nonempty_selections = view.has_pending_nonempty_selection();
285
286 if end_selection {
287 cx.dispatch_action(Select(SelectPhase::End));
288 }
289
290 if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
291 let (point, target_point) = position_map.point_for_position(text_bounds, position);
292
293 if point == target_point {
294 if shift {
295 cx.dispatch_action(GoToFetchedTypeDefinition { point });
296 } else {
297 cx.dispatch_action(GoToFetchedDefinition { point });
298 }
299
300 return true;
301 }
302 }
303
304 end_selection
305 }
306
307 fn mouse_dragged(
308 view: WeakViewHandle<Editor>,
309 MouseMovedEvent {
310 modifiers: Modifiers { cmd, shift, .. },
311 position,
312 ..
313 }: MouseMovedEvent,
314 position_map: &PositionMap,
315 text_bounds: RectF,
316 cx: &mut EventContext,
317 ) -> bool {
318 // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
319 // Don't trigger hover popover if mouse is hovering over context menu
320 let point = if text_bounds.contains_point(position) {
321 let (point, target_point) = position_map.point_for_position(text_bounds, position);
322 if point == target_point {
323 Some(point)
324 } else {
325 None
326 }
327 } else {
328 None
329 };
330
331 cx.dispatch_action(UpdateGoToDefinitionLink {
332 point,
333 cmd_held: cmd,
334 shift_held: shift,
335 });
336
337 let view = view.upgrade(cx.app).unwrap().read(cx.app);
338 if view.has_pending_selection() {
339 let mut scroll_delta = Vector2F::zero();
340
341 let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
342 let top = text_bounds.origin_y() + vertical_margin;
343 let bottom = text_bounds.lower_left().y() - vertical_margin;
344 if position.y() < top {
345 scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
346 }
347 if position.y() > bottom {
348 scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
349 }
350
351 let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
352 let left = text_bounds.origin_x() + horizontal_margin;
353 let right = text_bounds.upper_right().x() - horizontal_margin;
354 if position.x() < left {
355 scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
356 left - position.x(),
357 ))
358 }
359 if position.x() > right {
360 scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
361 position.x() - right,
362 ))
363 }
364
365 let (position, target_position) =
366 position_map.point_for_position(text_bounds, position);
367
368 cx.dispatch_action(Select(SelectPhase::Update {
369 position,
370 goal_column: target_position.column(),
371 scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
372 .clamp(Vector2F::zero(), position_map.scroll_max),
373 }));
374
375 cx.dispatch_action(HoverAt { point });
376 true
377 } else {
378 cx.dispatch_action(HoverAt { point });
379 false
380 }
381 }
382
383 fn mouse_moved(
384 MouseMovedEvent {
385 modifiers: Modifiers { shift, cmd, .. },
386 position,
387 ..
388 }: MouseMovedEvent,
389 position_map: &PositionMap,
390 text_bounds: RectF,
391 cx: &mut EventContext,
392 ) -> bool {
393 // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
394 // Don't trigger hover popover if mouse is hovering over context menu
395 let point = if text_bounds.contains_point(position) {
396 let (point, target_point) = position_map.point_for_position(text_bounds, position);
397 if point == target_point {
398 Some(point)
399 } else {
400 None
401 }
402 } else {
403 None
404 };
405
406 cx.dispatch_action(UpdateGoToDefinitionLink {
407 point,
408 cmd_held: cmd,
409 shift_held: shift,
410 });
411
412 cx.dispatch_action(HoverAt { point });
413 true
414 }
415
416 fn scroll(
417 position: Vector2F,
418 mut delta: Vector2F,
419 precise: bool,
420 position_map: &PositionMap,
421 bounds: RectF,
422 cx: &mut EventContext,
423 ) -> bool {
424 if !bounds.contains_point(position) {
425 return false;
426 }
427
428 let line_height = position_map.line_height;
429 let max_glyph_width = position_map.em_width;
430
431 let axis = if precise {
432 //Trackpad
433 position_map.snapshot.ongoing_scroll.filter(&mut delta)
434 } else {
435 //Not trackpad
436 delta *= vec2f(max_glyph_width, line_height);
437 None //Resets ongoing scroll
438 };
439
440 let scroll_position = position_map.snapshot.scroll_position();
441 let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
442 let y = (scroll_position.y() * line_height - delta.y()) / line_height;
443 let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), position_map.scroll_max);
444
445 cx.dispatch_action(Scroll {
446 scroll_position,
447 axis,
448 });
449
450 true
451 }
452
453 fn paint_background(
454 &self,
455 gutter_bounds: RectF,
456 text_bounds: RectF,
457 layout: &LayoutState,
458 cx: &mut PaintContext,
459 ) {
460 let bounds = gutter_bounds.union_rect(text_bounds);
461 let scroll_top =
462 layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
463 cx.scene.push_quad(Quad {
464 bounds: gutter_bounds,
465 background: Some(self.style.gutter_background),
466 border: Border::new(0., Color::transparent_black()),
467 corner_radius: 0.,
468 });
469 cx.scene.push_quad(Quad {
470 bounds: text_bounds,
471 background: Some(self.style.background),
472 border: Border::new(0., Color::transparent_black()),
473 corner_radius: 0.,
474 });
475
476 if let EditorMode::Full = layout.mode {
477 let mut active_rows = layout.active_rows.iter().peekable();
478 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
479 let mut end_row = *start_row;
480 while active_rows.peek().map_or(false, |r| {
481 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
482 }) {
483 active_rows.next().unwrap();
484 end_row += 1;
485 }
486
487 if !contains_non_empty_selection {
488 let origin = vec2f(
489 bounds.origin_x(),
490 bounds.origin_y() + (layout.position_map.line_height * *start_row as f32)
491 - scroll_top,
492 );
493 let size = vec2f(
494 bounds.width(),
495 layout.position_map.line_height * (end_row - start_row + 1) as f32,
496 );
497 cx.scene.push_quad(Quad {
498 bounds: RectF::new(origin, size),
499 background: Some(self.style.active_line_background),
500 border: Border::default(),
501 corner_radius: 0.,
502 });
503 }
504 }
505
506 if let Some(highlighted_rows) = &layout.highlighted_rows {
507 let origin = vec2f(
508 bounds.origin_x(),
509 bounds.origin_y()
510 + (layout.position_map.line_height * highlighted_rows.start as f32)
511 - scroll_top,
512 );
513 let size = vec2f(
514 bounds.width(),
515 layout.position_map.line_height * highlighted_rows.len() as f32,
516 );
517 cx.scene.push_quad(Quad {
518 bounds: RectF::new(origin, size),
519 background: Some(self.style.highlighted_line_background),
520 border: Border::default(),
521 corner_radius: 0.,
522 });
523 }
524 }
525 }
526
527 fn paint_gutter(
528 &mut self,
529 bounds: RectF,
530 visible_bounds: RectF,
531 layout: &mut LayoutState,
532 cx: &mut PaintContext,
533 ) {
534 let line_height = layout.position_map.line_height;
535
536 let scroll_position = layout.position_map.snapshot.scroll_position();
537 let scroll_top = scroll_position.y() * line_height;
538
539 let show_gutter = matches!(
540 &cx.global::<Settings>()
541 .git_overrides
542 .git_gutter
543 .unwrap_or_default(),
544 GitGutter::TrackedFiles
545 );
546
547 if show_gutter {
548 Self::paint_diff_hunks(bounds, layout, cx);
549 }
550
551 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
552 if let Some(line) = line {
553 let line_origin = bounds.origin()
554 + vec2f(
555 bounds.width() - line.width() - layout.gutter_padding,
556 ix as f32 * line_height - (scroll_top % line_height),
557 );
558
559 line.paint(line_origin, visible_bounds, line_height, cx);
560 }
561 }
562
563 if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
564 let mut x = bounds.width() - layout.gutter_padding;
565 let mut y = *row as f32 * line_height - scroll_top;
566 x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
567 y += (line_height - indicator.size().y()) / 2.;
568 indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
569 }
570 }
571
572 fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
573 let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
574 let line_height = layout.position_map.line_height;
575
576 let scroll_position = layout.position_map.snapshot.scroll_position();
577 let scroll_top = scroll_position.y() * line_height;
578
579 for hunk in &layout.hunk_layouts {
580 let color = match (hunk.status, hunk.is_folded) {
581 (DiffHunkStatus::Added, false) => diff_style.inserted,
582 (DiffHunkStatus::Modified, false) => diff_style.modified,
583
584 //TODO: This rendering is entirely a horrible hack
585 (DiffHunkStatus::Removed, false) => {
586 let row = hunk.visual_range.start;
587
588 let offset = line_height / 2.;
589 let start_y = row as f32 * line_height - offset - scroll_top;
590 let end_y = start_y + line_height;
591
592 let width = diff_style.removed_width_em * line_height;
593 let highlight_origin = bounds.origin() + vec2f(-width, start_y);
594 let highlight_size = vec2f(width * 2., end_y - start_y);
595 let highlight_bounds = RectF::new(highlight_origin, highlight_size);
596
597 cx.scene.push_quad(Quad {
598 bounds: highlight_bounds,
599 background: Some(diff_style.deleted),
600 border: Border::new(0., Color::transparent_black()),
601 corner_radius: 1. * line_height,
602 });
603
604 continue;
605 }
606
607 (_, true) => {
608 let row = hunk.visual_range.start;
609 let start_y = row as f32 * line_height - scroll_top;
610 let end_y = start_y + line_height;
611
612 let width = diff_style.removed_width_em * line_height;
613 let highlight_origin = bounds.origin() + vec2f(-width, start_y);
614 let highlight_size = vec2f(width * 2., end_y - start_y);
615 let highlight_bounds = RectF::new(highlight_origin, highlight_size);
616
617 cx.scene.push_quad(Quad {
618 bounds: highlight_bounds,
619 background: Some(diff_style.modified),
620 border: Border::new(0., Color::transparent_black()),
621 corner_radius: 1. * line_height,
622 });
623
624 continue;
625 }
626 };
627
628 let start_row = hunk.visual_range.start;
629 let end_row = hunk.visual_range.end;
630
631 let start_y = start_row as f32 * line_height - scroll_top;
632 let end_y = end_row as f32 * line_height - scroll_top;
633
634 let width = diff_style.width_em * line_height;
635 let highlight_origin = bounds.origin() + vec2f(-width, start_y);
636 let highlight_size = vec2f(width * 2., end_y - start_y);
637 let highlight_bounds = RectF::new(highlight_origin, highlight_size);
638
639 cx.scene.push_quad(Quad {
640 bounds: highlight_bounds,
641 background: Some(color),
642 border: Border::new(0., Color::transparent_black()),
643 corner_radius: diff_style.corner_radius * line_height,
644 });
645 }
646 }
647
648 fn paint_text(
649 &mut self,
650 bounds: RectF,
651 visible_bounds: RectF,
652 layout: &mut LayoutState,
653 cx: &mut PaintContext,
654 ) {
655 let view = self.view(cx.app);
656 let style = &self.style;
657 let local_replica_id = view.replica_id(cx);
658 let scroll_position = layout.position_map.snapshot.scroll_position();
659 let start_row = layout.visible_display_row_range.start;
660 let scroll_top = scroll_position.y() * layout.position_map.line_height;
661 let max_glyph_width = layout.position_map.em_width;
662 let scroll_left = scroll_position.x() * max_glyph_width;
663 let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
664
665 cx.scene.push_layer(Some(bounds));
666
667 cx.scene.push_cursor_region(CursorRegion {
668 bounds,
669 style: if !view.link_go_to_definition_state.definitions.is_empty() {
670 CursorStyle::PointingHand
671 } else {
672 CursorStyle::IBeam
673 },
674 });
675
676 for (range, color) in &layout.highlighted_ranges {
677 self.paint_highlighted_range(
678 range.clone(),
679 *color,
680 0.,
681 0.15 * layout.position_map.line_height,
682 layout,
683 content_origin,
684 scroll_top,
685 scroll_left,
686 bounds,
687 cx,
688 );
689 }
690
691 let mut cursors = SmallVec::<[Cursor; 32]>::new();
692 for (replica_id, selections) in &layout.selections {
693 let selection_style = style.replica_selection_style(*replica_id);
694 let corner_radius = 0.15 * layout.position_map.line_height;
695
696 for selection in selections {
697 self.paint_highlighted_range(
698 selection.range.clone(),
699 selection_style.selection,
700 corner_radius,
701 corner_radius * 2.,
702 layout,
703 content_origin,
704 scroll_top,
705 scroll_left,
706 bounds,
707 cx,
708 );
709
710 if view.show_local_cursors(cx) || *replica_id != local_replica_id {
711 let cursor_position = selection.head;
712 if layout
713 .visible_display_row_range
714 .contains(&cursor_position.row())
715 {
716 let cursor_row_layout = &layout.position_map.line_layouts
717 [(cursor_position.row() - start_row) as usize];
718 let cursor_column = cursor_position.column() as usize;
719
720 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
721 let mut block_width =
722 cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
723 if block_width == 0.0 {
724 block_width = layout.position_map.em_width;
725 }
726 let block_text = if let CursorShape::Block = selection.cursor_shape {
727 layout
728 .position_map
729 .snapshot
730 .chars_at(cursor_position)
731 .next()
732 .and_then(|(character, _)| {
733 let font_id =
734 cursor_row_layout.font_for_index(cursor_column)?;
735 let text = character.to_string();
736
737 Some(cx.text_layout_cache.layout_str(
738 &text,
739 cursor_row_layout.font_size(),
740 &[(
741 text.len(),
742 RunStyle {
743 font_id,
744 color: style.background,
745 underline: Default::default(),
746 },
747 )],
748 ))
749 })
750 } else {
751 None
752 };
753
754 let x = cursor_character_x - scroll_left;
755 let y = cursor_position.row() as f32 * layout.position_map.line_height
756 - scroll_top;
757 cursors.push(Cursor {
758 color: selection_style.cursor,
759 block_width,
760 origin: vec2f(x, y),
761 line_height: layout.position_map.line_height,
762 shape: selection.cursor_shape,
763 block_text,
764 });
765 }
766 }
767 }
768 }
769
770 if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
771 // Draw glyphs
772 for (ix, line) in layout.position_map.line_layouts.iter().enumerate() {
773 let row = start_row + ix as u32;
774 line.paint(
775 content_origin
776 + vec2f(
777 -scroll_left,
778 row as f32 * layout.position_map.line_height - scroll_top,
779 ),
780 visible_text_bounds,
781 layout.position_map.line_height,
782 cx,
783 );
784 }
785 }
786
787 cx.scene.push_layer(Some(bounds));
788 for cursor in cursors {
789 cursor.paint(content_origin, cx);
790 }
791 cx.scene.pop_layer();
792
793 if let Some((position, context_menu)) = layout.context_menu.as_mut() {
794 cx.scene.push_stacking_context(None, None);
795 let cursor_row_layout =
796 &layout.position_map.line_layouts[(position.row() - start_row) as usize];
797 let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
798 let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
799 let mut list_origin = content_origin + vec2f(x, y);
800 let list_width = context_menu.size().x();
801 let list_height = context_menu.size().y();
802
803 // Snap the right edge of the list to the right edge of the window if
804 // its horizontal bounds overflow.
805 if list_origin.x() + list_width > cx.window_size.x() {
806 list_origin.set_x((cx.window_size.x() - list_width).max(0.));
807 }
808
809 if list_origin.y() + list_height > bounds.max_y() {
810 list_origin.set_y(list_origin.y() - layout.position_map.line_height - list_height);
811 }
812
813 context_menu.paint(
814 list_origin,
815 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
816 cx,
817 );
818
819 cx.scene.pop_stacking_context();
820 }
821
822 if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
823 cx.scene.push_stacking_context(None, None);
824
825 // This is safe because we check on layout whether the required row is available
826 let hovered_row_layout =
827 &layout.position_map.line_layouts[(position.row() - start_row) as usize];
828
829 // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
830 // height. This is the size we will use to decide whether to render popovers above or below
831 // the hovered line.
832 let first_size = hover_popovers[0].size();
833 let height_to_reserve = first_size.y()
834 + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;
835
836 // Compute Hovered Point
837 let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
838 let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
839 let hovered_point = content_origin + vec2f(x, y);
840
841 if hovered_point.y() - height_to_reserve > 0.0 {
842 // There is enough space above. Render popovers above the hovered point
843 let mut current_y = hovered_point.y();
844 for hover_popover in hover_popovers {
845 let size = hover_popover.size();
846 let mut popover_origin = vec2f(hovered_point.x(), current_y - size.y());
847
848 let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
849 if x_out_of_bounds < 0.0 {
850 popover_origin.set_x(popover_origin.x() + x_out_of_bounds);
851 }
852
853 hover_popover.paint(
854 popover_origin,
855 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
856 cx,
857 );
858
859 current_y = popover_origin.y() - HOVER_POPOVER_GAP;
860 }
861 } else {
862 // There is not enough space above. Render popovers below the hovered point
863 let mut current_y = hovered_point.y() + layout.position_map.line_height;
864 for hover_popover in hover_popovers {
865 let size = hover_popover.size();
866 let mut popover_origin = vec2f(hovered_point.x(), current_y);
867
868 let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
869 if x_out_of_bounds < 0.0 {
870 popover_origin.set_x(popover_origin.x() + x_out_of_bounds);
871 }
872
873 hover_popover.paint(
874 popover_origin,
875 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
876 cx,
877 );
878
879 current_y = popover_origin.y() + size.y() + HOVER_POPOVER_GAP;
880 }
881 }
882
883 cx.scene.pop_stacking_context();
884 }
885
886 cx.scene.pop_layer();
887 }
888
889 fn paint_scrollbar(&mut self, bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
890 enum ScrollbarMouseHandlers {}
891 if layout.mode != EditorMode::Full {
892 return;
893 }
894
895 let view = self.view.clone();
896 let style = &self.style.theme.scrollbar;
897
898 let top = bounds.min_y();
899 let bottom = bounds.max_y();
900 let right = bounds.max_x();
901 let left = right - style.width;
902 let row_range = &layout.scrollbar_row_range;
903 let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
904
905 let mut height = bounds.height();
906 let mut first_row_y_offset = 0.0;
907
908 // Impose a minimum height on the scrollbar thumb
909 let min_thumb_height =
910 style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
911 let thumb_height = (row_range.end - row_range.start) * height / max_row;
912 if thumb_height < min_thumb_height {
913 first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
914 height -= min_thumb_height - thumb_height;
915 }
916
917 let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * height / max_row };
918
919 let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
920 let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
921 let track_bounds = RectF::from_points(vec2f(left, top), vec2f(right, bottom));
922 let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
923
924 if layout.show_scrollbars {
925 cx.scene.push_quad(Quad {
926 bounds: track_bounds,
927 border: style.track.border,
928 background: style.track.background_color,
929 ..Default::default()
930 });
931 cx.scene.push_quad(Quad {
932 bounds: thumb_bounds,
933 border: style.thumb.border,
934 background: style.thumb.background_color,
935 corner_radius: style.thumb.corner_radius,
936 });
937 }
938
939 cx.scene.push_cursor_region(CursorRegion {
940 bounds: track_bounds,
941 style: CursorStyle::Arrow,
942 });
943 cx.scene.push_mouse_region(
944 MouseRegion::new::<ScrollbarMouseHandlers>(view.id(), view.id(), track_bounds)
945 .on_move({
946 let view = view.clone();
947 move |_, cx| {
948 if let Some(view) = view.upgrade(cx.deref_mut()) {
949 view.update(cx.deref_mut(), |view, cx| {
950 view.make_scrollbar_visible(cx);
951 });
952 }
953 }
954 })
955 .on_down(MouseButton::Left, {
956 let view = view.clone();
957 let row_range = row_range.clone();
958 move |e, cx| {
959 let y = e.position.y();
960 if let Some(view) = view.upgrade(cx.deref_mut()) {
961 view.update(cx.deref_mut(), |view, cx| {
962 if y < thumb_top || thumb_bottom < y {
963 let center_row =
964 ((y - top) * max_row as f32 / height).round() as u32;
965 let top_row = center_row.saturating_sub(
966 (row_range.end - row_range.start) as u32 / 2,
967 );
968 let mut position = view.scroll_position(cx);
969 position.set_y(top_row as f32);
970 view.set_scroll_position(position, cx);
971 } else {
972 view.make_scrollbar_visible(cx);
973 }
974 });
975 }
976 }
977 })
978 .on_drag(MouseButton::Left, {
979 let view = view.clone();
980 move |e, cx| {
981 let y = e.prev_mouse_position.y();
982 let new_y = e.position.y();
983 if thumb_top < y && y < thumb_bottom {
984 if let Some(view) = view.upgrade(cx.deref_mut()) {
985 view.update(cx.deref_mut(), |view, cx| {
986 let mut position = view.scroll_position(cx);
987 position.set_y(
988 position.y() + (new_y - y) * (max_row as f32) / height,
989 );
990 if position.y() < 0.0 {
991 position.set_y(0.);
992 }
993 view.set_scroll_position(position, cx);
994 });
995 }
996 }
997 }
998 }),
999 );
1000 }
1001
1002 #[allow(clippy::too_many_arguments)]
1003 fn paint_highlighted_range(
1004 &self,
1005 range: Range<DisplayPoint>,
1006 color: Color,
1007 corner_radius: f32,
1008 line_end_overshoot: f32,
1009 layout: &LayoutState,
1010 content_origin: Vector2F,
1011 scroll_top: f32,
1012 scroll_left: f32,
1013 bounds: RectF,
1014 cx: &mut PaintContext,
1015 ) {
1016 let start_row = layout.visible_display_row_range.start;
1017 let end_row = layout.visible_display_row_range.end;
1018 if range.start != range.end {
1019 let row_range = if range.end.column() == 0 {
1020 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
1021 } else {
1022 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
1023 };
1024
1025 let highlighted_range = HighlightedRange {
1026 color,
1027 line_height: layout.position_map.line_height,
1028 corner_radius,
1029 start_y: content_origin.y()
1030 + row_range.start as f32 * layout.position_map.line_height
1031 - scroll_top,
1032 lines: row_range
1033 .into_iter()
1034 .map(|row| {
1035 let line_layout =
1036 &layout.position_map.line_layouts[(row - start_row) as usize];
1037 HighlightedRangeLine {
1038 start_x: if row == range.start.row() {
1039 content_origin.x()
1040 + line_layout.x_for_index(range.start.column() as usize)
1041 - scroll_left
1042 } else {
1043 content_origin.x() - scroll_left
1044 },
1045 end_x: if row == range.end.row() {
1046 content_origin.x()
1047 + line_layout.x_for_index(range.end.column() as usize)
1048 - scroll_left
1049 } else {
1050 content_origin.x() + line_layout.width() + line_end_overshoot
1051 - scroll_left
1052 },
1053 }
1054 })
1055 .collect(),
1056 };
1057
1058 highlighted_range.paint(bounds, cx.scene);
1059 }
1060 }
1061
1062 fn paint_blocks(
1063 &mut self,
1064 bounds: RectF,
1065 visible_bounds: RectF,
1066 layout: &mut LayoutState,
1067 cx: &mut PaintContext,
1068 ) {
1069 let scroll_position = layout.position_map.snapshot.scroll_position();
1070 let scroll_left = scroll_position.x() * layout.position_map.em_width;
1071 let scroll_top = scroll_position.y() * layout.position_map.line_height;
1072
1073 for block in &mut layout.blocks {
1074 let mut origin = bounds.origin()
1075 + vec2f(
1076 0.,
1077 block.row as f32 * layout.position_map.line_height - scroll_top,
1078 );
1079 if !matches!(block.style, BlockStyle::Sticky) {
1080 origin += vec2f(-scroll_left, 0.);
1081 }
1082 block.element.paint(origin, visible_bounds, cx);
1083 }
1084 }
1085
1086 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &LayoutContext) -> f32 {
1087 let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
1088 let style = &self.style;
1089
1090 cx.text_layout_cache
1091 .layout_str(
1092 "1".repeat(digit_count).as_str(),
1093 style.text.font_size,
1094 &[(
1095 digit_count,
1096 RunStyle {
1097 font_id: style.text.font_id,
1098 color: Color::black(),
1099 underline: Default::default(),
1100 },
1101 )],
1102 )
1103 .width()
1104 }
1105
1106 //Folds contained in a hunk are ignored apart from shrinking visual size
1107 //If a fold contains any hunks then that fold line is marked as modified
1108 fn layout_git_gutters(
1109 &self,
1110 rows: Range<u32>,
1111 snapshot: &EditorSnapshot,
1112 ) -> Vec<DiffHunkLayout> {
1113 let buffer_snapshot = &snapshot.buffer_snapshot;
1114 let visual_start = DisplayPoint::new(rows.start, 0).to_point(snapshot).row;
1115 let visual_end = DisplayPoint::new(rows.end, 0).to_point(snapshot).row;
1116 let hunks = buffer_snapshot.git_diff_hunks_in_range(visual_start..visual_end);
1117
1118 let mut layouts = Vec::<DiffHunkLayout>::new();
1119
1120 for hunk in hunks {
1121 let hunk_start_point = Point::new(hunk.buffer_range.start, 0);
1122 let hunk_end_point = Point::new(hunk.buffer_range.end, 0);
1123 let hunk_start_point_sub = Point::new(hunk.buffer_range.start.saturating_sub(1), 0);
1124 let hunk_end_point_sub = Point::new(
1125 hunk.buffer_range
1126 .end
1127 .saturating_sub(1)
1128 .max(hunk.buffer_range.start),
1129 0,
1130 );
1131
1132 let is_removal = hunk.status() == DiffHunkStatus::Removed;
1133
1134 let folds_start = Point::new(hunk.buffer_range.start.saturating_sub(1), 0);
1135 let folds_end = Point::new(hunk.buffer_range.end + 1, 0);
1136 let folds_range = folds_start..folds_end;
1137
1138 let containing_fold = snapshot.folds_in_range(folds_range).find(|fold_range| {
1139 let fold_point_range = fold_range.to_point(buffer_snapshot);
1140 let fold_point_range = fold_point_range.start..=fold_point_range.end;
1141
1142 let folded_start = fold_point_range.contains(&hunk_start_point);
1143 let folded_end = fold_point_range.contains(&hunk_end_point_sub);
1144 let folded_start_sub = fold_point_range.contains(&hunk_start_point_sub);
1145
1146 (folded_start && folded_end) || (is_removal && folded_start_sub)
1147 });
1148
1149 let visual_range = if let Some(fold) = containing_fold {
1150 let row = fold.start.to_display_point(snapshot).row();
1151 row..row
1152 } else {
1153 let start = hunk_start_point.to_display_point(snapshot).row();
1154 let end = hunk_end_point.to_display_point(snapshot).row();
1155 start..end
1156 };
1157
1158 let has_existing_layout = match layouts.last() {
1159 Some(e) => visual_range == e.visual_range && e.status == hunk.status(),
1160 None => false,
1161 };
1162
1163 if !has_existing_layout {
1164 layouts.push(DiffHunkLayout {
1165 visual_range,
1166 status: hunk.status(),
1167 is_folded: containing_fold.is_some(),
1168 });
1169 }
1170 }
1171
1172 layouts
1173 }
1174
1175 fn layout_line_numbers(
1176 &self,
1177 rows: Range<u32>,
1178 active_rows: &BTreeMap<u32, bool>,
1179 snapshot: &EditorSnapshot,
1180 cx: &LayoutContext,
1181 ) -> Vec<Option<text_layout::Line>> {
1182 let style = &self.style;
1183 let include_line_numbers = snapshot.mode == EditorMode::Full;
1184 let mut line_number_layouts = Vec::with_capacity(rows.len());
1185 let mut line_number = String::new();
1186 for (ix, row) in snapshot
1187 .buffer_rows(rows.start)
1188 .take((rows.end - rows.start) as usize)
1189 .enumerate()
1190 {
1191 let display_row = rows.start + ix as u32;
1192 let color = if active_rows.contains_key(&display_row) {
1193 style.line_number_active
1194 } else {
1195 style.line_number
1196 };
1197 if let Some(buffer_row) = row {
1198 if include_line_numbers {
1199 line_number.clear();
1200 write!(&mut line_number, "{}", buffer_row + 1).unwrap();
1201 line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
1202 &line_number,
1203 style.text.font_size,
1204 &[(
1205 line_number.len(),
1206 RunStyle {
1207 font_id: style.text.font_id,
1208 color,
1209 underline: Default::default(),
1210 },
1211 )],
1212 )));
1213 }
1214 } else {
1215 line_number_layouts.push(None);
1216 }
1217 }
1218
1219 line_number_layouts
1220 }
1221
1222 fn layout_lines(
1223 &mut self,
1224 rows: Range<u32>,
1225 snapshot: &EditorSnapshot,
1226 cx: &LayoutContext,
1227 ) -> Vec<text_layout::Line> {
1228 if rows.start >= rows.end {
1229 return Vec::new();
1230 }
1231
1232 // When the editor is empty and unfocused, then show the placeholder.
1233 if snapshot.is_empty() && !snapshot.is_focused() {
1234 let placeholder_style = self
1235 .style
1236 .placeholder_text
1237 .as_ref()
1238 .unwrap_or(&self.style.text);
1239 let placeholder_text = snapshot.placeholder_text();
1240 let placeholder_lines = placeholder_text
1241 .as_ref()
1242 .map_or("", AsRef::as_ref)
1243 .split('\n')
1244 .skip(rows.start as usize)
1245 .chain(iter::repeat(""))
1246 .take(rows.len());
1247 placeholder_lines
1248 .map(|line| {
1249 cx.text_layout_cache.layout_str(
1250 line,
1251 placeholder_style.font_size,
1252 &[(
1253 line.len(),
1254 RunStyle {
1255 font_id: placeholder_style.font_id,
1256 color: placeholder_style.color,
1257 underline: Default::default(),
1258 },
1259 )],
1260 )
1261 })
1262 .collect()
1263 } else {
1264 let style = &self.style;
1265 let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| {
1266 let mut highlight_style = chunk
1267 .syntax_highlight_id
1268 .and_then(|id| id.style(&style.syntax));
1269
1270 if let Some(chunk_highlight) = chunk.highlight_style {
1271 if let Some(highlight_style) = highlight_style.as_mut() {
1272 highlight_style.highlight(chunk_highlight);
1273 } else {
1274 highlight_style = Some(chunk_highlight);
1275 }
1276 }
1277
1278 let mut diagnostic_highlight = HighlightStyle::default();
1279
1280 if chunk.is_unnecessary {
1281 diagnostic_highlight.fade_out = Some(style.unnecessary_code_fade);
1282 }
1283
1284 if let Some(severity) = chunk.diagnostic_severity {
1285 // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
1286 if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
1287 let diagnostic_style = super::diagnostic_style(severity, true, style);
1288 diagnostic_highlight.underline = Some(Underline {
1289 color: Some(diagnostic_style.message.text.color),
1290 thickness: 1.0.into(),
1291 squiggly: true,
1292 });
1293 }
1294 }
1295
1296 if let Some(highlight_style) = highlight_style.as_mut() {
1297 highlight_style.highlight(diagnostic_highlight);
1298 } else {
1299 highlight_style = Some(diagnostic_highlight);
1300 }
1301
1302 (chunk.text, highlight_style)
1303 });
1304 layout_highlighted_chunks(
1305 chunks,
1306 &style.text,
1307 cx.text_layout_cache,
1308 cx.font_cache,
1309 MAX_LINE_LEN,
1310 rows.len() as usize,
1311 )
1312 }
1313 }
1314
1315 #[allow(clippy::too_many_arguments)]
1316 fn layout_blocks(
1317 &mut self,
1318 rows: Range<u32>,
1319 snapshot: &EditorSnapshot,
1320 editor_width: f32,
1321 scroll_width: f32,
1322 gutter_padding: f32,
1323 gutter_width: f32,
1324 em_width: f32,
1325 text_x: f32,
1326 line_height: f32,
1327 style: &EditorStyle,
1328 line_layouts: &[text_layout::Line],
1329 include_root: bool,
1330 cx: &mut LayoutContext,
1331 ) -> (f32, Vec<BlockLayout>) {
1332 let editor = if let Some(editor) = self.view.upgrade(cx) {
1333 editor
1334 } else {
1335 return Default::default();
1336 };
1337
1338 let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
1339 let scroll_x = snapshot.scroll_position.x();
1340 let (fixed_blocks, non_fixed_blocks) = snapshot
1341 .blocks_in_range(rows.clone())
1342 .partition::<Vec<_>, _>(|(_, block)| match block {
1343 TransformBlock::ExcerptHeader { .. } => false,
1344 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
1345 });
1346 let mut render_block = |block: &TransformBlock, width: f32| {
1347 let mut element = match block {
1348 TransformBlock::Custom(block) => {
1349 let align_to = block
1350 .position()
1351 .to_point(&snapshot.buffer_snapshot)
1352 .to_display_point(snapshot);
1353 let anchor_x = text_x
1354 + if rows.contains(&align_to.row()) {
1355 line_layouts[(align_to.row() - rows.start) as usize]
1356 .x_for_index(align_to.column() as usize)
1357 } else {
1358 layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
1359 .x_for_index(align_to.column() as usize)
1360 };
1361
1362 cx.render(&editor, |_, cx| {
1363 block.render(&mut BlockContext {
1364 cx,
1365 anchor_x,
1366 gutter_padding,
1367 line_height,
1368 scroll_x,
1369 gutter_width,
1370 em_width,
1371 })
1372 })
1373 }
1374 TransformBlock::ExcerptHeader {
1375 key,
1376 buffer,
1377 range,
1378 starts_new_buffer,
1379 ..
1380 } => {
1381 let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
1382 let jump_position = range
1383 .primary
1384 .as_ref()
1385 .map_or(range.context.start, |primary| primary.start);
1386 let jump_action = crate::Jump {
1387 path: ProjectPath {
1388 worktree_id: file.worktree_id(cx),
1389 path: file.path.clone(),
1390 },
1391 position: language::ToPoint::to_point(&jump_position, buffer),
1392 anchor: jump_position,
1393 };
1394
1395 enum JumpIcon {}
1396 cx.render(&editor, |_, cx| {
1397 MouseEventHandler::<JumpIcon>::new(*key, cx, |state, _| {
1398 let style = style.jump_icon.style_for(state, false);
1399 Svg::new("icons/arrow_up_right_8.svg")
1400 .with_color(style.color)
1401 .constrained()
1402 .with_width(style.icon_width)
1403 .aligned()
1404 .contained()
1405 .with_style(style.container)
1406 .constrained()
1407 .with_width(style.button_width)
1408 .with_height(style.button_width)
1409 .boxed()
1410 })
1411 .with_cursor_style(CursorStyle::PointingHand)
1412 .on_click(MouseButton::Left, move |_, cx| {
1413 cx.dispatch_action(jump_action.clone())
1414 })
1415 .with_tooltip::<JumpIcon, _>(
1416 *key,
1417 "Jump to Buffer".to_string(),
1418 Some(Box::new(crate::OpenExcerpts)),
1419 tooltip_style.clone(),
1420 cx,
1421 )
1422 .aligned()
1423 .flex_float()
1424 .boxed()
1425 })
1426 });
1427
1428 if *starts_new_buffer {
1429 let style = &self.style.diagnostic_path_header;
1430 let font_size =
1431 (style.text_scale_factor * self.style.text.font_size).round();
1432
1433 let path = buffer.resolve_file_path(cx, include_root);
1434 let mut filename = None;
1435 let mut parent_path = None;
1436 // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
1437 if let Some(path) = path {
1438 filename = path.file_name().map(|f| f.to_string_lossy().to_string());
1439 parent_path =
1440 path.parent().map(|p| p.to_string_lossy().to_string() + "/");
1441 }
1442
1443 Flex::row()
1444 .with_child(
1445 Label::new(
1446 filename.unwrap_or_else(|| "untitled".to_string()),
1447 style.filename.text.clone().with_font_size(font_size),
1448 )
1449 .contained()
1450 .with_style(style.filename.container)
1451 .aligned()
1452 .boxed(),
1453 )
1454 .with_children(parent_path.map(|path| {
1455 Label::new(path, style.path.text.clone().with_font_size(font_size))
1456 .contained()
1457 .with_style(style.path.container)
1458 .aligned()
1459 .boxed()
1460 }))
1461 .with_children(jump_icon)
1462 .contained()
1463 .with_style(style.container)
1464 .with_padding_left(gutter_padding)
1465 .with_padding_right(gutter_padding)
1466 .expanded()
1467 .named("path header block")
1468 } else {
1469 let text_style = self.style.text.clone();
1470 Flex::row()
1471 .with_child(Label::new("…".to_string(), text_style).boxed())
1472 .with_children(jump_icon)
1473 .contained()
1474 .with_padding_left(gutter_padding)
1475 .with_padding_right(gutter_padding)
1476 .expanded()
1477 .named("collapsed context")
1478 }
1479 }
1480 };
1481
1482 element.layout(
1483 SizeConstraint {
1484 min: Vector2F::zero(),
1485 max: vec2f(width, block.height() as f32 * line_height),
1486 },
1487 cx,
1488 );
1489 element
1490 };
1491
1492 let mut fixed_block_max_width = 0f32;
1493 let mut blocks = Vec::new();
1494 for (row, block) in fixed_blocks {
1495 let element = render_block(block, f32::INFINITY);
1496 fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
1497 blocks.push(BlockLayout {
1498 row,
1499 element,
1500 style: BlockStyle::Fixed,
1501 });
1502 }
1503 for (row, block) in non_fixed_blocks {
1504 let style = match block {
1505 TransformBlock::Custom(block) => block.style(),
1506 TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
1507 };
1508 let width = match style {
1509 BlockStyle::Sticky => editor_width,
1510 BlockStyle::Flex => editor_width
1511 .max(fixed_block_max_width)
1512 .max(gutter_width + scroll_width),
1513 BlockStyle::Fixed => unreachable!(),
1514 };
1515 let element = render_block(block, width);
1516 blocks.push(BlockLayout {
1517 row,
1518 element,
1519 style,
1520 });
1521 }
1522 (
1523 scroll_width.max(fixed_block_max_width - gutter_width),
1524 blocks,
1525 )
1526 }
1527}
1528
1529impl Element for EditorElement {
1530 type LayoutState = LayoutState;
1531 type PaintState = ();
1532
1533 fn layout(
1534 &mut self,
1535 constraint: SizeConstraint,
1536 cx: &mut LayoutContext,
1537 ) -> (Vector2F, Self::LayoutState) {
1538 let mut size = constraint.max;
1539 if size.x().is_infinite() {
1540 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
1541 }
1542
1543 let snapshot = self.snapshot(cx.app);
1544 let style = self.style.clone();
1545 let line_height = style.text.line_height(cx.font_cache);
1546
1547 let gutter_padding;
1548 let gutter_width;
1549 let gutter_margin;
1550 if snapshot.mode == EditorMode::Full {
1551 gutter_padding = style.text.em_width(cx.font_cache) * style.gutter_padding_factor;
1552 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
1553 gutter_margin = -style.text.descent(cx.font_cache);
1554 } else {
1555 gutter_padding = 0.0;
1556 gutter_width = 0.0;
1557 gutter_margin = 0.0;
1558 };
1559
1560 let text_width = size.x() - gutter_width;
1561 let em_width = style.text.em_width(cx.font_cache);
1562 let em_advance = style.text.em_advance(cx.font_cache);
1563 let overscroll = vec2f(em_width, 0.);
1564 let snapshot = self.update_view(cx.app, |view, cx| {
1565 view.set_visible_line_count(size.y() / line_height);
1566
1567 let wrap_width = match view.soft_wrap_mode(cx) {
1568 SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
1569 SoftWrap::EditorWidth => {
1570 Some(text_width - gutter_margin - overscroll.x() - em_width)
1571 }
1572 SoftWrap::Column(column) => Some(column as f32 * em_advance),
1573 };
1574
1575 if view.set_wrap_width(wrap_width, cx) {
1576 view.snapshot(cx)
1577 } else {
1578 snapshot
1579 }
1580 });
1581
1582 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
1583 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
1584 size.set_y(
1585 scroll_height
1586 .min(constraint.max_along(Axis::Vertical))
1587 .max(constraint.min_along(Axis::Vertical))
1588 .min(line_height * max_lines as f32),
1589 )
1590 } else if let EditorMode::SingleLine = snapshot.mode {
1591 size.set_y(
1592 line_height
1593 .min(constraint.max_along(Axis::Vertical))
1594 .max(constraint.min_along(Axis::Vertical)),
1595 )
1596 } else if size.y().is_infinite() {
1597 size.set_y(scroll_height);
1598 }
1599 let gutter_size = vec2f(gutter_width, size.y());
1600 let text_size = vec2f(text_width, size.y());
1601
1602 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
1603 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
1604 let snapshot = view.snapshot(cx);
1605 (autoscroll_horizontally, snapshot)
1606 });
1607
1608 let scroll_position = snapshot.scroll_position();
1609 // The scroll position is a fractional point, the whole number of which represents
1610 // the top of the window in terms of display rows.
1611 let start_row = scroll_position.y() as u32;
1612 let height_in_lines = size.y() / line_height;
1613 let max_row = snapshot.max_point().row();
1614
1615 // Add 1 to ensure selections bleed off screen
1616 let end_row = 1 + cmp::min(
1617 (scroll_position.y() + height_in_lines).ceil() as u32,
1618 max_row,
1619 );
1620
1621 let start_anchor = if start_row == 0 {
1622 Anchor::min()
1623 } else {
1624 snapshot
1625 .buffer_snapshot
1626 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
1627 };
1628 let end_anchor = if end_row > max_row {
1629 Anchor::max()
1630 } else {
1631 snapshot
1632 .buffer_snapshot
1633 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
1634 };
1635
1636 let mut selections: Vec<(ReplicaId, Vec<SelectionLayout>)> = Vec::new();
1637 let mut active_rows = BTreeMap::new();
1638 let mut highlighted_rows = None;
1639 let mut highlighted_ranges = Vec::new();
1640 let mut show_scrollbars = false;
1641 let mut include_root = false;
1642 self.update_view(cx.app, |view, cx| {
1643 let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
1644
1645 highlighted_rows = view.highlighted_rows();
1646 let theme = cx.global::<Settings>().theme.as_ref();
1647 highlighted_ranges = view.background_highlights_in_range(
1648 start_anchor.clone()..end_anchor.clone(),
1649 &display_map,
1650 theme,
1651 );
1652
1653 let mut remote_selections = HashMap::default();
1654 for (replica_id, line_mode, cursor_shape, selection) in display_map
1655 .buffer_snapshot
1656 .remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone()))
1657 {
1658 // The local selections match the leader's selections.
1659 if Some(replica_id) == view.leader_replica_id {
1660 continue;
1661 }
1662 remote_selections
1663 .entry(replica_id)
1664 .or_insert(Vec::new())
1665 .push(SelectionLayout::new(
1666 selection,
1667 line_mode,
1668 cursor_shape,
1669 &display_map,
1670 ));
1671 }
1672 selections.extend(remote_selections);
1673
1674 if view.show_local_selections {
1675 let mut local_selections = view
1676 .selections
1677 .disjoint_in_range(start_anchor..end_anchor, cx);
1678 local_selections.extend(view.selections.pending(cx));
1679 for selection in &local_selections {
1680 let is_empty = selection.start == selection.end;
1681 let selection_start = snapshot.prev_line_boundary(selection.start).1;
1682 let selection_end = snapshot.next_line_boundary(selection.end).1;
1683 for row in cmp::max(selection_start.row(), start_row)
1684 ..=cmp::min(selection_end.row(), end_row)
1685 {
1686 let contains_non_empty_selection =
1687 active_rows.entry(row).or_insert(!is_empty);
1688 *contains_non_empty_selection |= !is_empty;
1689 }
1690 }
1691
1692 // Render the local selections in the leader's color when following.
1693 let local_replica_id = view
1694 .leader_replica_id
1695 .unwrap_or_else(|| view.replica_id(cx));
1696
1697 selections.push((
1698 local_replica_id,
1699 local_selections
1700 .into_iter()
1701 .map(|selection| {
1702 SelectionLayout::new(
1703 selection,
1704 view.selections.line_mode,
1705 view.cursor_shape,
1706 &display_map,
1707 )
1708 })
1709 .collect(),
1710 ));
1711 }
1712
1713 show_scrollbars = view.show_scrollbars();
1714 include_root = view
1715 .project
1716 .as_ref()
1717 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
1718 .unwrap_or_default()
1719 });
1720
1721 let line_number_layouts =
1722 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
1723
1724 let hunk_layouts = self.layout_git_gutters(start_row..end_row, &snapshot);
1725
1726 let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines);
1727
1728 let mut max_visible_line_width = 0.0;
1729 let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
1730 for line in &line_layouts {
1731 if line.width() > max_visible_line_width {
1732 max_visible_line_width = line.width();
1733 }
1734 }
1735
1736 let style = self.style.clone();
1737 let longest_line_width = layout_line(
1738 snapshot.longest_row(),
1739 &snapshot,
1740 &style,
1741 cx.text_layout_cache,
1742 )
1743 .width();
1744 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
1745 let em_width = style.text.em_width(cx.font_cache);
1746 let (scroll_width, blocks) = self.layout_blocks(
1747 start_row..end_row,
1748 &snapshot,
1749 size.x(),
1750 scroll_width,
1751 gutter_padding,
1752 gutter_width,
1753 em_width,
1754 gutter_width + gutter_margin,
1755 line_height,
1756 &style,
1757 &line_layouts,
1758 include_root,
1759 cx,
1760 );
1761
1762 let scroll_max = vec2f(
1763 ((scroll_width - text_size.x()) / em_width).max(0.0),
1764 max_row as f32,
1765 );
1766
1767 self.update_view(cx.app, |view, cx| {
1768 let clamped = view.clamp_scroll_left(scroll_max.x());
1769
1770 let autoscrolled = if autoscroll_horizontally {
1771 view.autoscroll_horizontally(
1772 start_row,
1773 text_size.x(),
1774 scroll_width,
1775 em_width,
1776 &line_layouts,
1777 cx,
1778 )
1779 } else {
1780 false
1781 };
1782
1783 if clamped || autoscrolled {
1784 snapshot = view.snapshot(cx);
1785 }
1786 });
1787
1788 let mut context_menu = None;
1789 let mut code_actions_indicator = None;
1790 let mut hover = None;
1791 let mut mode = EditorMode::Full;
1792 cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
1793 let newest_selection_head = view
1794 .selections
1795 .newest::<usize>(cx)
1796 .head()
1797 .to_display_point(&snapshot);
1798
1799 let style = view.style(cx);
1800 if (start_row..end_row).contains(&newest_selection_head.row()) {
1801 if view.context_menu_visible() {
1802 context_menu =
1803 view.render_context_menu(newest_selection_head, style.clone(), cx);
1804 }
1805
1806 code_actions_indicator = view
1807 .render_code_actions_indicator(&style, cx)
1808 .map(|indicator| (newest_selection_head.row(), indicator));
1809 }
1810
1811 let visible_rows = start_row..start_row + line_layouts.len() as u32;
1812 hover = view.hover_state.render(&snapshot, &style, visible_rows, cx);
1813 mode = view.mode;
1814 });
1815
1816 if let Some((_, context_menu)) = context_menu.as_mut() {
1817 context_menu.layout(
1818 SizeConstraint {
1819 min: Vector2F::zero(),
1820 max: vec2f(
1821 cx.window_size.x() * 0.7,
1822 (12. * line_height).min((size.y() - line_height) / 2.),
1823 ),
1824 },
1825 cx,
1826 );
1827 }
1828
1829 if let Some((_, indicator)) = code_actions_indicator.as_mut() {
1830 indicator.layout(
1831 SizeConstraint::strict_along(
1832 Axis::Vertical,
1833 line_height * style.code_actions.vertical_scale,
1834 ),
1835 cx,
1836 );
1837 }
1838
1839 if let Some((_, hover_popovers)) = hover.as_mut() {
1840 for hover_popover in hover_popovers.iter_mut() {
1841 hover_popover.layout(
1842 SizeConstraint {
1843 min: Vector2F::zero(),
1844 max: vec2f(
1845 (120. * em_width) // Default size
1846 .min(size.x() / 2.) // Shrink to half of the editor width
1847 .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
1848 (16. * line_height) // Default size
1849 .min(size.y() / 2.) // Shrink to half of the editor height
1850 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
1851 ),
1852 },
1853 cx,
1854 );
1855 }
1856 }
1857
1858 (
1859 size,
1860 LayoutState {
1861 mode,
1862 position_map: Arc::new(PositionMap {
1863 size,
1864 scroll_max,
1865 line_layouts,
1866 line_height,
1867 em_width,
1868 em_advance,
1869 snapshot,
1870 }),
1871 visible_display_row_range: start_row..end_row,
1872 gutter_size,
1873 gutter_padding,
1874 text_size,
1875 scrollbar_row_range,
1876 show_scrollbars,
1877 max_row,
1878 gutter_margin,
1879 active_rows,
1880 highlighted_rows,
1881 highlighted_ranges,
1882 line_number_layouts,
1883 hunk_layouts,
1884 blocks,
1885 selections,
1886 context_menu,
1887 code_actions_indicator,
1888 hover_popovers: hover,
1889 },
1890 )
1891 }
1892
1893 fn paint(
1894 &mut self,
1895 bounds: RectF,
1896 visible_bounds: RectF,
1897 layout: &mut Self::LayoutState,
1898 cx: &mut PaintContext,
1899 ) -> Self::PaintState {
1900 let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
1901 cx.scene.push_layer(Some(visible_bounds));
1902
1903 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
1904 let text_bounds = RectF::new(
1905 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
1906 layout.text_size,
1907 );
1908
1909 Self::attach_mouse_handlers(
1910 &self.view,
1911 &layout.position_map,
1912 visible_bounds,
1913 text_bounds,
1914 gutter_bounds,
1915 bounds,
1916 cx,
1917 );
1918
1919 self.paint_background(gutter_bounds, text_bounds, layout, cx);
1920 if layout.gutter_size.x() > 0. {
1921 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
1922 }
1923 self.paint_text(text_bounds, visible_bounds, layout, cx);
1924
1925 cx.scene.push_layer(Some(bounds));
1926 if !layout.blocks.is_empty() {
1927 self.paint_blocks(bounds, visible_bounds, layout, cx);
1928 }
1929 self.paint_scrollbar(bounds, layout, cx);
1930 cx.scene.pop_layer();
1931
1932 cx.scene.pop_layer();
1933 }
1934
1935 fn rect_for_text_range(
1936 &self,
1937 range_utf16: Range<usize>,
1938 bounds: RectF,
1939 _: RectF,
1940 layout: &Self::LayoutState,
1941 _: &Self::PaintState,
1942 _: &gpui::MeasurementContext,
1943 ) -> Option<RectF> {
1944 let text_bounds = RectF::new(
1945 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
1946 layout.text_size,
1947 );
1948 let content_origin = text_bounds.origin() + vec2f(layout.gutter_margin, 0.);
1949 let scroll_position = layout.position_map.snapshot.scroll_position();
1950 let start_row = scroll_position.y() as u32;
1951 let scroll_top = scroll_position.y() * layout.position_map.line_height;
1952 let scroll_left = scroll_position.x() * layout.position_map.em_width;
1953
1954 let range_start = OffsetUtf16(range_utf16.start)
1955 .to_display_point(&layout.position_map.snapshot.display_snapshot);
1956 if range_start.row() < start_row {
1957 return None;
1958 }
1959
1960 let line = layout
1961 .position_map
1962 .line_layouts
1963 .get((range_start.row() - start_row) as usize)?;
1964 let range_start_x = line.x_for_index(range_start.column() as usize);
1965 let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
1966 Some(RectF::new(
1967 content_origin
1968 + vec2f(
1969 range_start_x,
1970 range_start_y + layout.position_map.line_height,
1971 )
1972 - vec2f(scroll_left, scroll_top),
1973 vec2f(
1974 layout.position_map.em_width,
1975 layout.position_map.line_height,
1976 ),
1977 ))
1978 }
1979
1980 fn debug(
1981 &self,
1982 bounds: RectF,
1983 _: &Self::LayoutState,
1984 _: &Self::PaintState,
1985 _: &gpui::DebugContext,
1986 ) -> json::Value {
1987 json!({
1988 "type": "BufferElement",
1989 "bounds": bounds.to_json()
1990 })
1991 }
1992}
1993
1994pub struct LayoutState {
1995 position_map: Arc<PositionMap>,
1996 gutter_size: Vector2F,
1997 gutter_padding: f32,
1998 gutter_margin: f32,
1999 text_size: Vector2F,
2000 mode: EditorMode,
2001 visible_display_row_range: Range<u32>,
2002 active_rows: BTreeMap<u32, bool>,
2003 highlighted_rows: Option<Range<u32>>,
2004 line_number_layouts: Vec<Option<text_layout::Line>>,
2005 hunk_layouts: Vec<DiffHunkLayout>,
2006 blocks: Vec<BlockLayout>,
2007 highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
2008 selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
2009 scrollbar_row_range: Range<f32>,
2010 show_scrollbars: bool,
2011 max_row: u32,
2012 context_menu: Option<(DisplayPoint, ElementBox)>,
2013 code_actions_indicator: Option<(u32, ElementBox)>,
2014 hover_popovers: Option<(DisplayPoint, Vec<ElementBox>)>,
2015}
2016
2017pub struct PositionMap {
2018 size: Vector2F,
2019 line_height: f32,
2020 scroll_max: Vector2F,
2021 em_width: f32,
2022 em_advance: f32,
2023 line_layouts: Vec<text_layout::Line>,
2024 snapshot: EditorSnapshot,
2025}
2026
2027impl PositionMap {
2028 /// Returns two display points:
2029 /// 1. The nearest *valid* position in the editor
2030 /// 2. An unclipped, potentially *invalid* position that maps directly to
2031 /// the given pixel position.
2032 fn point_for_position(
2033 &self,
2034 text_bounds: RectF,
2035 position: Vector2F,
2036 ) -> (DisplayPoint, DisplayPoint) {
2037 let scroll_position = self.snapshot.scroll_position();
2038 let position = position - text_bounds.origin();
2039 let y = position.y().max(0.0).min(self.size.y());
2040 let x = position.x() + (scroll_position.x() * self.em_width);
2041 let row = (y / self.line_height + scroll_position.y()) as u32;
2042 let (column, x_overshoot) = if let Some(line) = self
2043 .line_layouts
2044 .get(row as usize - scroll_position.y() as usize)
2045 {
2046 if let Some(ix) = line.index_for_x(x) {
2047 (ix as u32, 0.0)
2048 } else {
2049 (line.len() as u32, 0f32.max(x - line.width()))
2050 }
2051 } else {
2052 (0, x)
2053 };
2054
2055 let mut target_point = DisplayPoint::new(row, column);
2056 let point = self.snapshot.clip_point(target_point, Bias::Left);
2057 *target_point.column_mut() += (x_overshoot / self.em_advance) as u32;
2058
2059 (point, target_point)
2060 }
2061}
2062
2063struct BlockLayout {
2064 row: u32,
2065 element: ElementBox,
2066 style: BlockStyle,
2067}
2068
2069fn layout_line(
2070 row: u32,
2071 snapshot: &EditorSnapshot,
2072 style: &EditorStyle,
2073 layout_cache: &TextLayoutCache,
2074) -> text_layout::Line {
2075 let mut line = snapshot.line(row);
2076
2077 if line.len() > MAX_LINE_LEN {
2078 let mut len = MAX_LINE_LEN;
2079 while !line.is_char_boundary(len) {
2080 len -= 1;
2081 }
2082
2083 line.truncate(len);
2084 }
2085
2086 layout_cache.layout_str(
2087 &line,
2088 style.text.font_size,
2089 &[(
2090 snapshot.line_len(row) as usize,
2091 RunStyle {
2092 font_id: style.text.font_id,
2093 color: Color::black(),
2094 underline: Default::default(),
2095 },
2096 )],
2097 )
2098}
2099
2100#[derive(Debug)]
2101pub struct Cursor {
2102 origin: Vector2F,
2103 block_width: f32,
2104 line_height: f32,
2105 color: Color,
2106 shape: CursorShape,
2107 block_text: Option<Line>,
2108}
2109
2110impl Cursor {
2111 pub fn new(
2112 origin: Vector2F,
2113 block_width: f32,
2114 line_height: f32,
2115 color: Color,
2116 shape: CursorShape,
2117 block_text: Option<Line>,
2118 ) -> Cursor {
2119 Cursor {
2120 origin,
2121 block_width,
2122 line_height,
2123 color,
2124 shape,
2125 block_text,
2126 }
2127 }
2128
2129 pub fn bounding_rect(&self, origin: Vector2F) -> RectF {
2130 RectF::new(
2131 self.origin + origin,
2132 vec2f(self.block_width, self.line_height),
2133 )
2134 }
2135
2136 pub fn paint(&self, origin: Vector2F, cx: &mut PaintContext) {
2137 let bounds = match self.shape {
2138 CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
2139 CursorShape::Block | CursorShape::Hollow => RectF::new(
2140 self.origin + origin,
2141 vec2f(self.block_width, self.line_height),
2142 ),
2143 CursorShape::Underscore => RectF::new(
2144 self.origin + origin + Vector2F::new(0.0, self.line_height - 2.0),
2145 vec2f(self.block_width, 2.0),
2146 ),
2147 };
2148
2149 //Draw background or border quad
2150 if matches!(self.shape, CursorShape::Hollow) {
2151 cx.scene.push_quad(Quad {
2152 bounds,
2153 background: None,
2154 border: Border::all(1., self.color),
2155 corner_radius: 0.,
2156 });
2157 } else {
2158 cx.scene.push_quad(Quad {
2159 bounds,
2160 background: Some(self.color),
2161 border: Default::default(),
2162 corner_radius: 0.,
2163 });
2164 }
2165
2166 if let Some(block_text) = &self.block_text {
2167 block_text.paint(self.origin + origin, bounds, self.line_height, cx);
2168 }
2169 }
2170
2171 pub fn shape(&self) -> CursorShape {
2172 self.shape
2173 }
2174}
2175
2176#[derive(Debug)]
2177pub struct HighlightedRange {
2178 pub start_y: f32,
2179 pub line_height: f32,
2180 pub lines: Vec<HighlightedRangeLine>,
2181 pub color: Color,
2182 pub corner_radius: f32,
2183}
2184
2185#[derive(Debug)]
2186pub struct HighlightedRangeLine {
2187 pub start_x: f32,
2188 pub end_x: f32,
2189}
2190
2191impl HighlightedRange {
2192 pub fn paint(&self, bounds: RectF, scene: &mut SceneBuilder) {
2193 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
2194 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
2195 self.paint_lines(
2196 self.start_y + self.line_height,
2197 &self.lines[1..],
2198 bounds,
2199 scene,
2200 );
2201 } else {
2202 self.paint_lines(self.start_y, &self.lines, bounds, scene);
2203 }
2204 }
2205
2206 fn paint_lines(
2207 &self,
2208 start_y: f32,
2209 lines: &[HighlightedRangeLine],
2210 bounds: RectF,
2211 scene: &mut SceneBuilder,
2212 ) {
2213 if lines.is_empty() {
2214 return;
2215 }
2216
2217 let mut path = PathBuilder::new();
2218 let first_line = lines.first().unwrap();
2219 let last_line = lines.last().unwrap();
2220
2221 let first_top_left = vec2f(first_line.start_x, start_y);
2222 let first_top_right = vec2f(first_line.end_x, start_y);
2223
2224 let curve_height = vec2f(0., self.corner_radius);
2225 let curve_width = |start_x: f32, end_x: f32| {
2226 let max = (end_x - start_x) / 2.;
2227 let width = if max < self.corner_radius {
2228 max
2229 } else {
2230 self.corner_radius
2231 };
2232
2233 vec2f(width, 0.)
2234 };
2235
2236 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
2237 path.reset(first_top_right - top_curve_width);
2238 path.curve_to(first_top_right + curve_height, first_top_right);
2239
2240 let mut iter = lines.iter().enumerate().peekable();
2241 while let Some((ix, line)) = iter.next() {
2242 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
2243
2244 if let Some((_, next_line)) = iter.peek() {
2245 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
2246
2247 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
2248 Ordering::Equal => {
2249 path.line_to(bottom_right);
2250 }
2251 Ordering::Less => {
2252 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
2253 path.line_to(bottom_right - curve_height);
2254 if self.corner_radius > 0. {
2255 path.curve_to(bottom_right - curve_width, bottom_right);
2256 }
2257 path.line_to(next_top_right + curve_width);
2258 if self.corner_radius > 0. {
2259 path.curve_to(next_top_right + curve_height, next_top_right);
2260 }
2261 }
2262 Ordering::Greater => {
2263 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
2264 path.line_to(bottom_right - curve_height);
2265 if self.corner_radius > 0. {
2266 path.curve_to(bottom_right + curve_width, bottom_right);
2267 }
2268 path.line_to(next_top_right - curve_width);
2269 if self.corner_radius > 0. {
2270 path.curve_to(next_top_right + curve_height, next_top_right);
2271 }
2272 }
2273 }
2274 } else {
2275 let curve_width = curve_width(line.start_x, line.end_x);
2276 path.line_to(bottom_right - curve_height);
2277 if self.corner_radius > 0. {
2278 path.curve_to(bottom_right - curve_width, bottom_right);
2279 }
2280
2281 let bottom_left = vec2f(line.start_x, bottom_right.y());
2282 path.line_to(bottom_left + curve_width);
2283 if self.corner_radius > 0. {
2284 path.curve_to(bottom_left - curve_height, bottom_left);
2285 }
2286 }
2287 }
2288
2289 if first_line.start_x > last_line.start_x {
2290 let curve_width = curve_width(last_line.start_x, first_line.start_x);
2291 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
2292 path.line_to(second_top_left + curve_height);
2293 if self.corner_radius > 0. {
2294 path.curve_to(second_top_left + curve_width, second_top_left);
2295 }
2296 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
2297 path.line_to(first_bottom_left - curve_width);
2298 if self.corner_radius > 0. {
2299 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
2300 }
2301 }
2302
2303 path.line_to(first_top_left + curve_height);
2304 if self.corner_radius > 0. {
2305 path.curve_to(first_top_left + top_curve_width, first_top_left);
2306 }
2307 path.line_to(first_top_right - top_curve_width);
2308
2309 scene.push_path(path.build(self.color, Some(bounds)));
2310 }
2311}
2312
2313pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
2314 delta.powf(1.5) / 100.0
2315}
2316
2317fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
2318 delta.powf(1.2) / 300.0
2319}
2320
2321#[cfg(test)]
2322mod tests {
2323 use std::sync::Arc;
2324
2325 use super::*;
2326 use crate::{
2327 display_map::{BlockDisposition, BlockProperties},
2328 Editor, MultiBuffer,
2329 };
2330 use settings::Settings;
2331 use util::test::sample_text;
2332
2333 #[gpui::test]
2334 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
2335 cx.set_global(Settings::test(cx));
2336 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
2337 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
2338 Editor::new(EditorMode::Full, buffer, None, None, cx)
2339 });
2340 let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
2341
2342 let layouts = editor.update(cx, |editor, cx| {
2343 let snapshot = editor.snapshot(cx);
2344 let mut presenter = cx.build_presenter(window_id, 30., Default::default());
2345 let layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
2346 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &layout_cx)
2347 });
2348 assert_eq!(layouts.len(), 6);
2349 }
2350
2351 #[gpui::test]
2352 fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::MutableAppContext) {
2353 cx.set_global(Settings::test(cx));
2354 let buffer = MultiBuffer::build_simple("", cx);
2355 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
2356 Editor::new(EditorMode::Full, buffer, None, None, cx)
2357 });
2358
2359 editor.update(cx, |editor, cx| {
2360 editor.set_placeholder_text("hello", cx);
2361 editor.insert_blocks(
2362 [BlockProperties {
2363 style: BlockStyle::Fixed,
2364 disposition: BlockDisposition::Above,
2365 height: 3,
2366 position: Anchor::min(),
2367 render: Arc::new(|_| Empty::new().boxed()),
2368 }],
2369 cx,
2370 );
2371
2372 // Blur the editor so that it displays placeholder text.
2373 cx.blur();
2374 });
2375
2376 let mut element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
2377
2378 let mut scene = SceneBuilder::new(1.0);
2379 let mut presenter = cx.build_presenter(window_id, 30., Default::default());
2380 let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
2381 let (size, mut state) = element.layout(
2382 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
2383 &mut layout_cx,
2384 );
2385
2386 assert_eq!(state.position_map.line_layouts.len(), 4);
2387 assert_eq!(
2388 state
2389 .line_number_layouts
2390 .iter()
2391 .map(Option::is_some)
2392 .collect::<Vec<_>>(),
2393 &[false, false, false, true]
2394 );
2395
2396 // Don't panic.
2397 let bounds = RectF::new(Default::default(), size);
2398 let mut paint_cx = presenter.build_paint_context(&mut scene, bounds.size(), cx);
2399 element.paint(bounds, bounds, &mut state, &mut paint_cx);
2400 }
2401}