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