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