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