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