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