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