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