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