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