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