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