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