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