1use super::{
2 display_map::{BlockContext, ToDisplayPoint},
3 Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
4 SoftWrap, ToPoint, MAX_LINE_LEN,
5};
6use crate::{display_map::TransformBlock, EditorStyle};
7use clock::ReplicaId;
8use collections::{BTreeMap, HashMap};
9use gpui::{
10 color::Color,
11 elements::*,
12 fonts::{HighlightStyle, Underline},
13 geometry::{
14 rect::RectF,
15 vector::{vec2f, Vector2F},
16 PathBuilder,
17 },
18 json::{self, ToJson},
19 text_layout::{self, Line, RunStyle, TextLayoutCache},
20 AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
21 MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
22};
23use json::json;
24use language::{Bias, DiagnosticSeverity};
25use settings::Settings;
26use smallvec::SmallVec;
27use std::{
28 cmp::{self, Ordering},
29 fmt::Write,
30 iter,
31 ops::Range,
32};
33
34pub struct EditorElement {
35 view: WeakViewHandle<Editor>,
36 style: EditorStyle,
37 cursor_shape: CursorShape,
38}
39
40impl EditorElement {
41 pub fn new(
42 view: WeakViewHandle<Editor>,
43 style: EditorStyle,
44 cursor_shape: CursorShape,
45 ) -> Self {
46 Self {
47 view,
48 style,
49 cursor_shape,
50 }
51 }
52
53 fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
54 self.view.upgrade(cx).unwrap().read(cx)
55 }
56
57 fn update_view<F, T>(&self, cx: &mut MutableAppContext, f: F) -> T
58 where
59 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
60 {
61 self.view.upgrade(cx).unwrap().update(cx, f)
62 }
63
64 fn snapshot(&self, cx: &mut MutableAppContext) -> EditorSnapshot {
65 self.update_view(cx, |view, cx| view.snapshot(cx))
66 }
67
68 fn mouse_down(
69 &self,
70 position: Vector2F,
71 alt: bool,
72 shift: bool,
73 mut click_count: usize,
74 layout: &mut LayoutState,
75 paint: &mut PaintState,
76 cx: &mut EventContext,
77 ) -> bool {
78 if paint.gutter_bounds.contains_point(position) {
79 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
80 } else if !paint.text_bounds.contains_point(position) {
81 return false;
82 }
83
84 let snapshot = self.snapshot(cx.app);
85 let (position, overshoot) = paint.point_for_position(&snapshot, layout, position);
86
87 if shift && alt {
88 cx.dispatch_action(Select(SelectPhase::BeginColumnar {
89 position,
90 overshoot,
91 }));
92 } else if shift {
93 cx.dispatch_action(Select(SelectPhase::Extend {
94 position,
95 click_count,
96 }));
97 } else {
98 cx.dispatch_action(Select(SelectPhase::Begin {
99 position,
100 add: alt,
101 click_count,
102 }));
103 }
104
105 true
106 }
107
108 fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool {
109 if self.view(cx.app.as_ref()).is_selecting() {
110 cx.dispatch_action(Select(SelectPhase::End));
111 true
112 } else {
113 false
114 }
115 }
116
117 fn mouse_dragged(
118 &self,
119 position: Vector2F,
120 layout: &mut LayoutState,
121 paint: &mut PaintState,
122 cx: &mut EventContext,
123 ) -> bool {
124 let view = self.view(cx.app.as_ref());
125
126 if view.is_selecting() {
127 let rect = paint.text_bounds;
128 let mut scroll_delta = Vector2F::zero();
129
130 let vertical_margin = layout.line_height.min(rect.height() / 3.0);
131 let top = rect.origin_y() + vertical_margin;
132 let bottom = rect.lower_left().y() - vertical_margin;
133 if position.y() < top {
134 scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
135 }
136 if position.y() > bottom {
137 scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
138 }
139
140 let horizontal_margin = layout.line_height.min(rect.width() / 3.0);
141 let left = rect.origin_x() + horizontal_margin;
142 let right = rect.upper_right().x() - horizontal_margin;
143 if position.x() < left {
144 scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
145 left - position.x(),
146 ))
147 }
148 if position.x() > right {
149 scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
150 position.x() - right,
151 ))
152 }
153
154 let snapshot = self.snapshot(cx.app);
155 let (position, overshoot) = paint.point_for_position(&snapshot, layout, position);
156
157 cx.dispatch_action(Select(SelectPhase::Update {
158 position,
159 overshoot,
160 scroll_position: (snapshot.scroll_position() + scroll_delta)
161 .clamp(Vector2F::zero(), layout.scroll_max),
162 }));
163 true
164 } else {
165 false
166 }
167 }
168
169 fn key_down(&self, input: Option<&str>, cx: &mut EventContext) -> bool {
170 let view = self.view.upgrade(cx.app).unwrap();
171
172 if view.is_focused(cx.app) {
173 if let Some(input) = input {
174 cx.dispatch_action(Input(input.to_string()));
175 true
176 } else {
177 false
178 }
179 } else {
180 false
181 }
182 }
183
184 fn scroll(
185 &self,
186 position: Vector2F,
187 mut delta: Vector2F,
188 precise: bool,
189 layout: &mut LayoutState,
190 paint: &mut PaintState,
191 cx: &mut EventContext,
192 ) -> bool {
193 if !paint.bounds.contains_point(position) {
194 return false;
195 }
196
197 let snapshot = self.snapshot(cx.app);
198 let max_glyph_width = layout.em_width;
199 if !precise {
200 delta *= vec2f(max_glyph_width, layout.line_height);
201 }
202
203 let scroll_position = snapshot.scroll_position();
204 let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
205 let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height;
206 let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max);
207
208 cx.dispatch_action(Scroll(scroll_position));
209
210 true
211 }
212
213 fn paint_background(
214 &self,
215 gutter_bounds: RectF,
216 text_bounds: RectF,
217 layout: &LayoutState,
218 cx: &mut PaintContext,
219 ) {
220 let bounds = gutter_bounds.union_rect(text_bounds);
221 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
222 let editor = self.view(cx.app);
223 cx.scene.push_quad(Quad {
224 bounds: gutter_bounds,
225 background: Some(self.style.gutter_background),
226 border: Border::new(0., Color::transparent_black()),
227 corner_radius: 0.,
228 });
229 cx.scene.push_quad(Quad {
230 bounds: text_bounds,
231 background: Some(self.style.background),
232 border: Border::new(0., Color::transparent_black()),
233 corner_radius: 0.,
234 });
235
236 if let EditorMode::Full = editor.mode {
237 let mut active_rows = layout.active_rows.iter().peekable();
238 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
239 let mut end_row = *start_row;
240 while active_rows.peek().map_or(false, |r| {
241 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
242 }) {
243 active_rows.next().unwrap();
244 end_row += 1;
245 }
246
247 if !contains_non_empty_selection {
248 let origin = vec2f(
249 bounds.origin_x(),
250 bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top,
251 );
252 let size = vec2f(
253 bounds.width(),
254 layout.line_height * (end_row - start_row + 1) as f32,
255 );
256 cx.scene.push_quad(Quad {
257 bounds: RectF::new(origin, size),
258 background: Some(self.style.active_line_background),
259 border: Border::default(),
260 corner_radius: 0.,
261 });
262 }
263 }
264
265 if let Some(highlighted_rows) = &layout.highlighted_rows {
266 let origin = vec2f(
267 bounds.origin_x(),
268 bounds.origin_y() + (layout.line_height * highlighted_rows.start as f32)
269 - scroll_top,
270 );
271 let size = vec2f(
272 bounds.width(),
273 layout.line_height * highlighted_rows.len() as f32,
274 );
275 cx.scene.push_quad(Quad {
276 bounds: RectF::new(origin, size),
277 background: Some(self.style.highlighted_line_background),
278 border: Border::default(),
279 corner_radius: 0.,
280 });
281 }
282 }
283 }
284
285 fn paint_gutter(
286 &mut self,
287 bounds: RectF,
288 visible_bounds: RectF,
289 layout: &mut LayoutState,
290 cx: &mut PaintContext,
291 ) {
292 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
293 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
294 if let Some(line) = line {
295 let line_origin = bounds.origin()
296 + vec2f(
297 bounds.width() - line.width() - layout.gutter_padding,
298 ix as f32 * layout.line_height - (scroll_top % layout.line_height),
299 );
300 line.paint(line_origin, visible_bounds, layout.line_height, cx);
301 }
302 }
303
304 if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
305 let mut x = bounds.width() - layout.gutter_padding;
306 let mut y = *row as f32 * layout.line_height - scroll_top;
307 x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
308 y += (layout.line_height - indicator.size().y()) / 2.;
309 indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
310 }
311 }
312
313 fn paint_text(
314 &mut self,
315 bounds: RectF,
316 visible_bounds: RectF,
317 layout: &mut LayoutState,
318 cx: &mut PaintContext,
319 ) {
320 let view = self.view(cx.app);
321 let style = &self.style;
322 let local_replica_id = view.replica_id(cx);
323 let scroll_position = layout.snapshot.scroll_position();
324 let start_row = scroll_position.y() as u32;
325 let scroll_top = scroll_position.y() * layout.line_height;
326 let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
327 let max_glyph_width = layout.em_width;
328 let scroll_left = scroll_position.x() * max_glyph_width;
329 let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
330
331 cx.scene.push_layer(Some(bounds));
332
333 for (range, color) in &layout.highlighted_ranges {
334 self.paint_highlighted_range(
335 range.clone(),
336 start_row,
337 end_row,
338 *color,
339 0.,
340 0.15 * layout.line_height,
341 layout,
342 content_origin,
343 scroll_top,
344 scroll_left,
345 bounds,
346 cx,
347 );
348 }
349
350 let mut cursors = SmallVec::<[Cursor; 32]>::new();
351 for (replica_id, selections) in &layout.selections {
352 let selection_style = style.replica_selection_style(*replica_id);
353 let corner_radius = 0.15 * layout.line_height;
354
355 for selection in selections {
356 self.paint_highlighted_range(
357 selection.start..selection.end,
358 start_row,
359 end_row,
360 selection_style.selection,
361 corner_radius,
362 corner_radius * 2.,
363 layout,
364 content_origin,
365 scroll_top,
366 scroll_left,
367 bounds,
368 cx,
369 );
370
371 if view.show_local_cursors() || *replica_id != local_replica_id {
372 let cursor_position = selection.head();
373 if (start_row..end_row).contains(&cursor_position.row()) {
374 let cursor_row_layout =
375 &layout.line_layouts[(cursor_position.row() - start_row) as usize];
376 let cursor_column = cursor_position.column() as usize;
377
378 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
379 let mut block_width =
380 cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
381 if block_width == 0.0 {
382 block_width = layout.em_width;
383 }
384
385 let block_text =
386 if matches!(self.cursor_shape, CursorShape::Block) {
387 layout.snapshot.chars_at(cursor_position).next().and_then(
388 |character| {
389 let font_id =
390 cursor_row_layout.font_for_index(cursor_column)?;
391 let text = character.to_string();
392
393 Some(cx.text_layout_cache.layout_str(
394 &text,
395 cursor_row_layout.font_size(),
396 &[(
397 text.len(),
398 RunStyle {
399 font_id,
400 color: style.background,
401 underline: Default::default(),
402 },
403 )],
404 ))
405 },
406 )
407 } else {
408 None
409 };
410
411 let x = cursor_character_x - scroll_left;
412 let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
413 cursors.push(Cursor {
414 color: selection_style.cursor,
415 block_width,
416 origin: content_origin + vec2f(x, y),
417 line_height: layout.line_height,
418 shape: self.cursor_shape,
419 block_text,
420 });
421 }
422 }
423 }
424 }
425
426 if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
427 // Draw glyphs
428 for (ix, line) in layout.line_layouts.iter().enumerate() {
429 let row = start_row + ix as u32;
430 line.paint(
431 content_origin
432 + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top),
433 visible_text_bounds,
434 layout.line_height,
435 cx,
436 );
437 }
438 }
439
440 cx.scene.push_layer(Some(bounds));
441 for cursor in cursors {
442 cursor.paint(cx);
443 }
444 cx.scene.pop_layer();
445
446 if let Some((position, context_menu)) = layout.context_menu.as_mut() {
447 cx.scene.push_stacking_context(None);
448
449 let cursor_row_layout = &layout.line_layouts[(position.row() - start_row) as usize];
450 let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
451 let y = (position.row() + 1) as f32 * layout.line_height - scroll_top;
452 let mut list_origin = content_origin + vec2f(x, y);
453 let list_height = context_menu.size().y();
454
455 if list_origin.y() + list_height > bounds.lower_left().y() {
456 list_origin.set_y(list_origin.y() - layout.line_height - list_height);
457 }
458
459 context_menu.paint(
460 list_origin,
461 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
462 cx,
463 );
464
465 cx.scene.pop_stacking_context();
466 }
467
468 cx.scene.pop_layer();
469 }
470
471 fn paint_highlighted_range(
472 &self,
473 range: Range<DisplayPoint>,
474 start_row: u32,
475 end_row: u32,
476 color: Color,
477 corner_radius: f32,
478 line_end_overshoot: f32,
479 layout: &LayoutState,
480 content_origin: Vector2F,
481 scroll_top: f32,
482 scroll_left: f32,
483 bounds: RectF,
484 cx: &mut PaintContext,
485 ) {
486 if range.start != range.end {
487 let row_range = if range.end.column() == 0 {
488 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
489 } else {
490 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
491 };
492
493 let highlighted_range = HighlightedRange {
494 color,
495 line_height: layout.line_height,
496 corner_radius,
497 start_y: content_origin.y() + row_range.start as f32 * layout.line_height
498 - scroll_top,
499 lines: row_range
500 .into_iter()
501 .map(|row| {
502 let line_layout = &layout.line_layouts[(row - start_row) as usize];
503 HighlightedRangeLine {
504 start_x: if row == range.start.row() {
505 content_origin.x()
506 + line_layout.x_for_index(range.start.column() as usize)
507 - scroll_left
508 } else {
509 content_origin.x() - scroll_left
510 },
511 end_x: if row == range.end.row() {
512 content_origin.x()
513 + line_layout.x_for_index(range.end.column() as usize)
514 - scroll_left
515 } else {
516 content_origin.x() + line_layout.width() + line_end_overshoot
517 - scroll_left
518 },
519 }
520 })
521 .collect(),
522 };
523
524 highlighted_range.paint(bounds, cx.scene);
525 }
526 }
527
528 fn paint_blocks(
529 &mut self,
530 bounds: RectF,
531 visible_bounds: RectF,
532 layout: &mut LayoutState,
533 cx: &mut PaintContext,
534 ) {
535 let scroll_position = layout.snapshot.scroll_position();
536 let scroll_left = scroll_position.x() * layout.em_width;
537 let scroll_top = scroll_position.y() * layout.line_height;
538
539 for (row, element) in &mut layout.blocks {
540 let origin = bounds.origin()
541 + vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top);
542 element.paint(origin, visible_bounds, cx);
543 }
544 }
545
546 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &LayoutContext) -> f32 {
547 let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
548 let style = &self.style;
549
550 cx.text_layout_cache
551 .layout_str(
552 "1".repeat(digit_count).as_str(),
553 style.text.font_size,
554 &[(
555 digit_count,
556 RunStyle {
557 font_id: style.text.font_id,
558 color: Color::black(),
559 underline: Default::default(),
560 },
561 )],
562 )
563 .width()
564 }
565
566 fn layout_line_numbers(
567 &self,
568 rows: Range<u32>,
569 active_rows: &BTreeMap<u32, bool>,
570 snapshot: &EditorSnapshot,
571 cx: &LayoutContext,
572 ) -> Vec<Option<text_layout::Line>> {
573 let style = &self.style;
574 let include_line_numbers = snapshot.mode == EditorMode::Full;
575 let mut line_number_layouts = Vec::with_capacity(rows.len());
576 let mut line_number = String::new();
577 for (ix, row) in snapshot
578 .buffer_rows(rows.start)
579 .take((rows.end - rows.start) as usize)
580 .enumerate()
581 {
582 let display_row = rows.start + ix as u32;
583 let color = if active_rows.contains_key(&display_row) {
584 style.line_number_active
585 } else {
586 style.line_number
587 };
588 if let Some(buffer_row) = row {
589 if include_line_numbers {
590 line_number.clear();
591 write!(&mut line_number, "{}", buffer_row + 1).unwrap();
592 line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
593 &line_number,
594 style.text.font_size,
595 &[(
596 line_number.len(),
597 RunStyle {
598 font_id: style.text.font_id,
599 color,
600 underline: Default::default(),
601 },
602 )],
603 )));
604 }
605 } else {
606 line_number_layouts.push(None);
607 }
608 }
609
610 line_number_layouts
611 }
612
613 fn layout_lines(
614 &mut self,
615 rows: Range<u32>,
616 snapshot: &EditorSnapshot,
617 cx: &LayoutContext,
618 ) -> Vec<text_layout::Line> {
619 if rows.start >= rows.end {
620 return Vec::new();
621 }
622
623 // When the editor is empty and unfocused, then show the placeholder.
624 if snapshot.is_empty() && !snapshot.is_focused() {
625 let placeholder_style = self
626 .style
627 .placeholder_text
628 .as_ref()
629 .unwrap_or_else(|| &self.style.text);
630 let placeholder_text = snapshot.placeholder_text();
631 let placeholder_lines = placeholder_text
632 .as_ref()
633 .map_or("", AsRef::as_ref)
634 .split('\n')
635 .skip(rows.start as usize)
636 .chain(iter::repeat(""))
637 .take(rows.len());
638 return placeholder_lines
639 .map(|line| {
640 cx.text_layout_cache.layout_str(
641 line,
642 placeholder_style.font_size,
643 &[(
644 line.len(),
645 RunStyle {
646 font_id: placeholder_style.font_id,
647 color: placeholder_style.color,
648 underline: Default::default(),
649 },
650 )],
651 )
652 })
653 .collect();
654 } else {
655 let style = &self.style;
656 let chunks = snapshot.chunks(rows.clone(), true).map(|chunk| {
657 let mut highlight_style = chunk
658 .syntax_highlight_id
659 .and_then(|id| id.style(&style.syntax));
660
661 if let Some(chunk_highlight) = chunk.highlight_style {
662 if let Some(highlight_style) = highlight_style.as_mut() {
663 highlight_style.highlight(chunk_highlight);
664 } else {
665 highlight_style = Some(chunk_highlight);
666 }
667 }
668
669 let mut diagnostic_highlight = HighlightStyle::default();
670
671 if chunk.is_unnecessary {
672 diagnostic_highlight.fade_out = Some(style.unnecessary_code_fade);
673 }
674
675 if let Some(severity) = chunk.diagnostic_severity {
676 // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
677 if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
678 let diagnostic_style = super::diagnostic_style(severity, true, style);
679 diagnostic_highlight.underline = Some(Underline {
680 color: Some(diagnostic_style.message.text.color),
681 thickness: 1.0.into(),
682 squiggly: true,
683 });
684 }
685 }
686
687 if let Some(highlight_style) = highlight_style.as_mut() {
688 highlight_style.highlight(diagnostic_highlight);
689 } else {
690 highlight_style = Some(diagnostic_highlight);
691 }
692
693 (chunk.text, highlight_style)
694 });
695 layout_highlighted_chunks(
696 chunks,
697 &style.text,
698 &cx.text_layout_cache,
699 &cx.font_cache,
700 MAX_LINE_LEN,
701 rows.len() as usize,
702 )
703 }
704 }
705
706 fn layout_blocks(
707 &mut self,
708 rows: Range<u32>,
709 snapshot: &EditorSnapshot,
710 width: f32,
711 gutter_padding: f32,
712 gutter_width: f32,
713 em_width: f32,
714 text_x: f32,
715 line_height: f32,
716 style: &EditorStyle,
717 line_layouts: &[text_layout::Line],
718 cx: &mut LayoutContext,
719 ) -> Vec<(u32, ElementBox)> {
720 let scroll_x = snapshot.scroll_position.x();
721 snapshot
722 .blocks_in_range(rows.clone())
723 .map(|(block_row, block)| {
724 let mut element = match block {
725 TransformBlock::Custom(block) => {
726 let align_to = block
727 .position()
728 .to_point(&snapshot.buffer_snapshot)
729 .to_display_point(snapshot);
730 let anchor_x = text_x
731 + if rows.contains(&align_to.row()) {
732 line_layouts[(align_to.row() - rows.start) as usize]
733 .x_for_index(align_to.column() as usize)
734 } else {
735 layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
736 .x_for_index(align_to.column() as usize)
737 };
738
739 block.render(&BlockContext {
740 cx,
741 anchor_x,
742 gutter_padding,
743 line_height,
744 scroll_x,
745 gutter_width,
746 em_width,
747 })
748 }
749 TransformBlock::ExcerptHeader {
750 buffer,
751 starts_new_buffer,
752 ..
753 } => {
754 if *starts_new_buffer {
755 let style = &self.style.diagnostic_path_header;
756 let font_size =
757 (style.text_scale_factor * self.style.text.font_size).round();
758
759 let mut filename = None;
760 let mut parent_path = None;
761 if let Some(path) = buffer.path() {
762 filename =
763 path.file_name().map(|f| f.to_string_lossy().to_string());
764 parent_path =
765 path.parent().map(|p| p.to_string_lossy().to_string() + "/");
766 }
767
768 Flex::row()
769 .with_child(
770 Label::new(
771 filename.unwrap_or_else(|| "untitled".to_string()),
772 style.filename.text.clone().with_font_size(font_size),
773 )
774 .contained()
775 .with_style(style.filename.container)
776 .boxed(),
777 )
778 .with_children(parent_path.map(|path| {
779 Label::new(
780 path,
781 style.path.text.clone().with_font_size(font_size),
782 )
783 .contained()
784 .with_style(style.path.container)
785 .boxed()
786 }))
787 .aligned()
788 .left()
789 .contained()
790 .with_style(style.container)
791 .with_padding_left(gutter_padding + scroll_x * em_width)
792 .expanded()
793 .named("path header block")
794 } else {
795 let text_style = self.style.text.clone();
796 Label::new("…".to_string(), text_style)
797 .contained()
798 .with_padding_left(gutter_padding + scroll_x * em_width)
799 .named("collapsed context")
800 }
801 }
802 };
803
804 element.layout(
805 SizeConstraint {
806 min: Vector2F::zero(),
807 max: vec2f(width, block.height() as f32 * line_height),
808 },
809 cx,
810 );
811 (block_row, element)
812 })
813 .collect()
814 }
815}
816
817impl Element for EditorElement {
818 type LayoutState = LayoutState;
819 type PaintState = PaintState;
820
821 fn layout(
822 &mut self,
823 constraint: SizeConstraint,
824 cx: &mut LayoutContext,
825 ) -> (Vector2F, Self::LayoutState) {
826 let mut size = constraint.max;
827 if size.x().is_infinite() {
828 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
829 }
830
831 let snapshot = self.snapshot(cx.app);
832 let style = self.style.clone();
833 let line_height = style.text.line_height(cx.font_cache);
834
835 let gutter_padding;
836 let gutter_width;
837 let gutter_margin;
838 if snapshot.mode == EditorMode::Full {
839 gutter_padding = style.text.em_width(cx.font_cache) * style.gutter_padding_factor;
840 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
841 gutter_margin = -style.text.descent(cx.font_cache);
842 } else {
843 gutter_padding = 0.0;
844 gutter_width = 0.0;
845 gutter_margin = 0.0;
846 };
847
848 let text_width = size.x() - gutter_width;
849 let em_width = style.text.em_width(cx.font_cache);
850 let em_advance = style.text.em_advance(cx.font_cache);
851 let overscroll = vec2f(em_width, 0.);
852 let snapshot = self.update_view(cx.app, |view, cx| {
853 let wrap_width = match view.soft_wrap_mode(cx) {
854 SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
855 SoftWrap::EditorWidth => {
856 Some(text_width - gutter_margin - overscroll.x() - em_width)
857 }
858 SoftWrap::Column(column) => Some(column as f32 * em_advance),
859 };
860
861 if view.set_wrap_width(wrap_width, cx) {
862 view.snapshot(cx)
863 } else {
864 snapshot
865 }
866 });
867
868 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
869 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
870 size.set_y(
871 scroll_height
872 .min(constraint.max_along(Axis::Vertical))
873 .max(constraint.min_along(Axis::Vertical))
874 .min(line_height * max_lines as f32),
875 )
876 } else if size.y().is_infinite() {
877 size.set_y(scroll_height);
878 }
879 let gutter_size = vec2f(gutter_width, size.y());
880 let text_size = vec2f(text_width, size.y());
881
882 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
883 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
884 let snapshot = view.snapshot(cx);
885 (autoscroll_horizontally, snapshot)
886 });
887
888 let scroll_position = snapshot.scroll_position();
889 let start_row = scroll_position.y() as u32;
890 let scroll_top = scroll_position.y() * line_height;
891
892 // Add 1 to ensure selections bleed off screen
893 let end_row = 1 + cmp::min(
894 ((scroll_top + size.y()) / line_height).ceil() as u32,
895 snapshot.max_point().row(),
896 );
897
898 let start_anchor = if start_row == 0 {
899 Anchor::min()
900 } else {
901 snapshot
902 .buffer_snapshot
903 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
904 };
905 let end_anchor = if end_row > snapshot.max_point().row() {
906 Anchor::max()
907 } else {
908 snapshot
909 .buffer_snapshot
910 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
911 };
912
913 let mut selections = Vec::new();
914 let mut active_rows = BTreeMap::new();
915 let mut highlighted_rows = None;
916 let mut highlighted_ranges = Vec::new();
917 self.update_view(cx.app, |view, cx| {
918 let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
919
920 highlighted_rows = view.highlighted_rows();
921 let theme = cx.global::<Settings>().theme.as_ref();
922 highlighted_ranges = view.background_highlights_in_range(
923 start_anchor.clone()..end_anchor.clone(),
924 &display_map,
925 theme,
926 );
927
928 let mut remote_selections = HashMap::default();
929 for (replica_id, selection) in display_map
930 .buffer_snapshot
931 .remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone()))
932 {
933 // The local selections match the leader's selections.
934 if Some(replica_id) == view.leader_replica_id {
935 continue;
936 }
937
938 remote_selections
939 .entry(replica_id)
940 .or_insert(Vec::new())
941 .push(crate::Selection {
942 id: selection.id,
943 goal: selection.goal,
944 reversed: selection.reversed,
945 start: selection.start.to_display_point(&display_map),
946 end: selection.end.to_display_point(&display_map),
947 });
948 }
949 selections.extend(remote_selections);
950
951 if view.show_local_selections {
952 let local_selections =
953 view.local_selections_in_range(start_anchor..end_anchor, &display_map);
954 for selection in &local_selections {
955 let is_empty = selection.start == selection.end;
956 let selection_start = snapshot.prev_line_boundary(selection.start).1;
957 let selection_end = snapshot.next_line_boundary(selection.end).1;
958 for row in cmp::max(selection_start.row(), start_row)
959 ..=cmp::min(selection_end.row(), end_row)
960 {
961 let contains_non_empty_selection =
962 active_rows.entry(row).or_insert(!is_empty);
963 *contains_non_empty_selection |= !is_empty;
964 }
965 }
966
967 // Render the local selections in the leader's color when following.
968 let local_replica_id = view.leader_replica_id.unwrap_or(view.replica_id(cx));
969
970 selections.push((
971 local_replica_id,
972 local_selections
973 .into_iter()
974 .map(|selection| crate::Selection {
975 id: selection.id,
976 goal: selection.goal,
977 reversed: selection.reversed,
978 start: selection.start.to_display_point(&display_map),
979 end: selection.end.to_display_point(&display_map),
980 })
981 .collect(),
982 ));
983 }
984 });
985
986 let line_number_layouts =
987 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
988
989 let mut max_visible_line_width = 0.0;
990 let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
991 for line in &line_layouts {
992 if line.width() > max_visible_line_width {
993 max_visible_line_width = line.width();
994 }
995 }
996
997 let style = self.style.clone();
998 let longest_line_width = layout_line(
999 snapshot.longest_row(),
1000 &snapshot,
1001 &style,
1002 cx.text_layout_cache,
1003 )
1004 .width();
1005 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
1006 let em_width = style.text.em_width(cx.font_cache);
1007 let max_row = snapshot.max_point().row();
1008 let scroll_max = vec2f(
1009 ((scroll_width - text_size.x()) / em_width).max(0.0),
1010 max_row.saturating_sub(1) as f32,
1011 );
1012
1013 let mut context_menu = None;
1014 let mut code_actions_indicator = None;
1015 self.update_view(cx.app, |view, cx| {
1016 let clamped = view.clamp_scroll_left(scroll_max.x());
1017 let autoscrolled;
1018 if autoscroll_horizontally {
1019 autoscrolled = view.autoscroll_horizontally(
1020 start_row,
1021 text_size.x(),
1022 scroll_width,
1023 em_width,
1024 &line_layouts,
1025 cx,
1026 );
1027 } else {
1028 autoscrolled = false;
1029 }
1030
1031 if clamped || autoscrolled {
1032 snapshot = view.snapshot(cx);
1033 }
1034
1035 let newest_selection_head = view
1036 .newest_selection_with_snapshot::<usize>(&snapshot.buffer_snapshot)
1037 .head()
1038 .to_display_point(&snapshot);
1039
1040 if (start_row..end_row).contains(&newest_selection_head.row()) {
1041 let style = view.style(cx);
1042 if view.context_menu_visible() {
1043 context_menu =
1044 view.render_context_menu(newest_selection_head, style.clone(), cx);
1045 }
1046
1047 code_actions_indicator = view
1048 .render_code_actions_indicator(&style, cx)
1049 .map(|indicator| (newest_selection_head.row(), indicator));
1050 }
1051 });
1052
1053 if let Some((_, context_menu)) = context_menu.as_mut() {
1054 context_menu.layout(
1055 SizeConstraint {
1056 min: Vector2F::zero(),
1057 max: vec2f(
1058 f32::INFINITY,
1059 (12. * line_height).min((size.y() - line_height) / 2.),
1060 ),
1061 },
1062 cx,
1063 );
1064 }
1065
1066 if let Some((_, indicator)) = code_actions_indicator.as_mut() {
1067 indicator.layout(
1068 SizeConstraint::strict_along(Axis::Vertical, line_height * 0.618),
1069 cx,
1070 );
1071 }
1072
1073 let blocks = self.layout_blocks(
1074 start_row..end_row,
1075 &snapshot,
1076 size.x().max(scroll_width + gutter_width),
1077 gutter_padding,
1078 gutter_width,
1079 em_width,
1080 gutter_width + gutter_margin,
1081 line_height,
1082 &style,
1083 &line_layouts,
1084 cx,
1085 );
1086
1087 (
1088 size,
1089 LayoutState {
1090 size,
1091 scroll_max,
1092 gutter_size,
1093 gutter_padding,
1094 text_size,
1095 gutter_margin,
1096 snapshot,
1097 active_rows,
1098 highlighted_rows,
1099 highlighted_ranges,
1100 line_layouts,
1101 line_number_layouts,
1102 blocks,
1103 line_height,
1104 em_width,
1105 em_advance,
1106 selections,
1107 context_menu,
1108 code_actions_indicator,
1109 },
1110 )
1111 }
1112
1113 fn paint(
1114 &mut self,
1115 bounds: RectF,
1116 visible_bounds: RectF,
1117 layout: &mut Self::LayoutState,
1118 cx: &mut PaintContext,
1119 ) -> Self::PaintState {
1120 cx.scene.push_layer(Some(bounds));
1121
1122 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
1123 let text_bounds = RectF::new(
1124 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
1125 layout.text_size,
1126 );
1127
1128 self.paint_background(gutter_bounds, text_bounds, layout, cx);
1129 if layout.gutter_size.x() > 0. {
1130 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
1131 }
1132 self.paint_text(text_bounds, visible_bounds, layout, cx);
1133
1134 if !layout.blocks.is_empty() {
1135 cx.scene.push_layer(Some(bounds));
1136 self.paint_blocks(bounds, visible_bounds, layout, cx);
1137 cx.scene.pop_layer();
1138 }
1139
1140 cx.scene.pop_layer();
1141
1142 PaintState {
1143 bounds,
1144 gutter_bounds,
1145 text_bounds,
1146 }
1147 }
1148
1149 fn dispatch_event(
1150 &mut self,
1151 event: &Event,
1152 _: RectF,
1153 _: RectF,
1154 layout: &mut LayoutState,
1155 paint: &mut PaintState,
1156 cx: &mut EventContext,
1157 ) -> bool {
1158 if let Some((_, context_menu)) = &mut layout.context_menu {
1159 if context_menu.dispatch_event(event, cx) {
1160 return true;
1161 }
1162 }
1163
1164 if let Some((_, indicator)) = &mut layout.code_actions_indicator {
1165 if indicator.dispatch_event(event, cx) {
1166 return true;
1167 }
1168 }
1169
1170 for (_, block) in &mut layout.blocks {
1171 if block.dispatch_event(event, cx) {
1172 return true;
1173 }
1174 }
1175
1176 match event {
1177 Event::LeftMouseDown {
1178 position,
1179 alt,
1180 shift,
1181 click_count,
1182 ..
1183 } => self.mouse_down(*position, *alt, *shift, *click_count, layout, paint, cx),
1184 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
1185 Event::LeftMouseDragged { position } => {
1186 self.mouse_dragged(*position, layout, paint, cx)
1187 }
1188 Event::ScrollWheel {
1189 position,
1190 delta,
1191 precise,
1192 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
1193 Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
1194 _ => false,
1195 }
1196 }
1197
1198 fn debug(
1199 &self,
1200 bounds: RectF,
1201 _: &Self::LayoutState,
1202 _: &Self::PaintState,
1203 _: &gpui::DebugContext,
1204 ) -> json::Value {
1205 json!({
1206 "type": "BufferElement",
1207 "bounds": bounds.to_json()
1208 })
1209 }
1210}
1211
1212pub struct LayoutState {
1213 size: Vector2F,
1214 scroll_max: Vector2F,
1215 gutter_size: Vector2F,
1216 gutter_padding: f32,
1217 gutter_margin: f32,
1218 text_size: Vector2F,
1219 snapshot: EditorSnapshot,
1220 active_rows: BTreeMap<u32, bool>,
1221 highlighted_rows: Option<Range<u32>>,
1222 line_layouts: Vec<text_layout::Line>,
1223 line_number_layouts: Vec<Option<text_layout::Line>>,
1224 blocks: Vec<(u32, ElementBox)>,
1225 line_height: f32,
1226 em_width: f32,
1227 em_advance: f32,
1228 highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
1229 selections: Vec<(ReplicaId, Vec<text::Selection<DisplayPoint>>)>,
1230 context_menu: Option<(DisplayPoint, ElementBox)>,
1231 code_actions_indicator: Option<(u32, ElementBox)>,
1232}
1233
1234fn layout_line(
1235 row: u32,
1236 snapshot: &EditorSnapshot,
1237 style: &EditorStyle,
1238 layout_cache: &TextLayoutCache,
1239) -> text_layout::Line {
1240 let mut line = snapshot.line(row);
1241
1242 if line.len() > MAX_LINE_LEN {
1243 let mut len = MAX_LINE_LEN;
1244 while !line.is_char_boundary(len) {
1245 len -= 1;
1246 }
1247
1248 line.truncate(len);
1249 }
1250
1251 layout_cache.layout_str(
1252 &line,
1253 style.text.font_size,
1254 &[(
1255 snapshot.line_len(row) as usize,
1256 RunStyle {
1257 font_id: style.text.font_id,
1258 color: Color::black(),
1259 underline: Default::default(),
1260 },
1261 )],
1262 )
1263}
1264
1265pub struct PaintState {
1266 bounds: RectF,
1267 gutter_bounds: RectF,
1268 text_bounds: RectF,
1269}
1270
1271impl PaintState {
1272 fn point_for_position(
1273 &self,
1274 snapshot: &EditorSnapshot,
1275 layout: &LayoutState,
1276 position: Vector2F,
1277 ) -> (DisplayPoint, u32) {
1278 let scroll_position = snapshot.scroll_position();
1279 let position = position - self.text_bounds.origin();
1280 let y = position.y().max(0.0).min(layout.size.y());
1281 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
1282 let row = cmp::min(row, snapshot.max_point().row());
1283 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
1284 let x = position.x() + (scroll_position.x() * layout.em_width);
1285
1286 let column = if x >= 0.0 {
1287 line.index_for_x(x)
1288 .map(|ix| ix as u32)
1289 .unwrap_or_else(|| snapshot.line_len(row))
1290 } else {
1291 0
1292 };
1293 let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
1294
1295 (DisplayPoint::new(row, column), overshoot)
1296 }
1297}
1298
1299#[derive(Copy, Clone, PartialEq, Eq)]
1300pub enum CursorShape {
1301 Bar,
1302 Block,
1303 Underscore,
1304}
1305
1306impl Default for CursorShape {
1307 fn default() -> Self {
1308 CursorShape::Bar
1309 }
1310}
1311
1312struct Cursor {
1313 origin: Vector2F,
1314 block_width: f32,
1315 line_height: f32,
1316 color: Color,
1317 shape: CursorShape,
1318 block_text: Option<Line>,
1319}
1320
1321impl Cursor {
1322 fn paint(&self, cx: &mut PaintContext) {
1323 let bounds = match self.shape {
1324 CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)),
1325 CursorShape::Block => {
1326 RectF::new(self.origin, vec2f(self.block_width, self.line_height))
1327 }
1328 CursorShape::Underscore => RectF::new(
1329 self.origin + Vector2F::new(0.0, self.line_height - 2.0),
1330 vec2f(self.block_width, 2.0),
1331 ),
1332 };
1333
1334 cx.scene.push_quad(Quad {
1335 bounds,
1336 background: Some(self.color),
1337 border: Border::new(0., Color::black()),
1338 corner_radius: 0.,
1339 });
1340
1341 if let Some(block_text) = &self.block_text {
1342 block_text.paint(self.origin, bounds, self.line_height, cx);
1343 }
1344 }
1345}
1346
1347#[derive(Debug)]
1348struct HighlightedRange {
1349 start_y: f32,
1350 line_height: f32,
1351 lines: Vec<HighlightedRangeLine>,
1352 color: Color,
1353 corner_radius: f32,
1354}
1355
1356#[derive(Debug)]
1357struct HighlightedRangeLine {
1358 start_x: f32,
1359 end_x: f32,
1360}
1361
1362impl HighlightedRange {
1363 fn paint(&self, bounds: RectF, scene: &mut Scene) {
1364 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
1365 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
1366 self.paint_lines(
1367 self.start_y + self.line_height,
1368 &self.lines[1..],
1369 bounds,
1370 scene,
1371 );
1372 } else {
1373 self.paint_lines(self.start_y, &self.lines, bounds, scene);
1374 }
1375 }
1376
1377 fn paint_lines(
1378 &self,
1379 start_y: f32,
1380 lines: &[HighlightedRangeLine],
1381 bounds: RectF,
1382 scene: &mut Scene,
1383 ) {
1384 if lines.is_empty() {
1385 return;
1386 }
1387
1388 let mut path = PathBuilder::new();
1389 let first_line = lines.first().unwrap();
1390 let last_line = lines.last().unwrap();
1391
1392 let first_top_left = vec2f(first_line.start_x, start_y);
1393 let first_top_right = vec2f(first_line.end_x, start_y);
1394
1395 let curve_height = vec2f(0., self.corner_radius);
1396 let curve_width = |start_x: f32, end_x: f32| {
1397 let max = (end_x - start_x) / 2.;
1398 let width = if max < self.corner_radius {
1399 max
1400 } else {
1401 self.corner_radius
1402 };
1403
1404 vec2f(width, 0.)
1405 };
1406
1407 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
1408 path.reset(first_top_right - top_curve_width);
1409 path.curve_to(first_top_right + curve_height, first_top_right);
1410
1411 let mut iter = lines.iter().enumerate().peekable();
1412 while let Some((ix, line)) = iter.next() {
1413 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
1414
1415 if let Some((_, next_line)) = iter.peek() {
1416 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
1417
1418 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
1419 Ordering::Equal => {
1420 path.line_to(bottom_right);
1421 }
1422 Ordering::Less => {
1423 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
1424 path.line_to(bottom_right - curve_height);
1425 if self.corner_radius > 0. {
1426 path.curve_to(bottom_right - curve_width, bottom_right);
1427 }
1428 path.line_to(next_top_right + curve_width);
1429 if self.corner_radius > 0. {
1430 path.curve_to(next_top_right + curve_height, next_top_right);
1431 }
1432 }
1433 Ordering::Greater => {
1434 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
1435 path.line_to(bottom_right - curve_height);
1436 if self.corner_radius > 0. {
1437 path.curve_to(bottom_right + curve_width, bottom_right);
1438 }
1439 path.line_to(next_top_right - curve_width);
1440 if self.corner_radius > 0. {
1441 path.curve_to(next_top_right + curve_height, next_top_right);
1442 }
1443 }
1444 }
1445 } else {
1446 let curve_width = curve_width(line.start_x, line.end_x);
1447 path.line_to(bottom_right - curve_height);
1448 if self.corner_radius > 0. {
1449 path.curve_to(bottom_right - curve_width, bottom_right);
1450 }
1451
1452 let bottom_left = vec2f(line.start_x, bottom_right.y());
1453 path.line_to(bottom_left + curve_width);
1454 if self.corner_radius > 0. {
1455 path.curve_to(bottom_left - curve_height, bottom_left);
1456 }
1457 }
1458 }
1459
1460 if first_line.start_x > last_line.start_x {
1461 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1462 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1463 path.line_to(second_top_left + curve_height);
1464 if self.corner_radius > 0. {
1465 path.curve_to(second_top_left + curve_width, second_top_left);
1466 }
1467 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1468 path.line_to(first_bottom_left - curve_width);
1469 if self.corner_radius > 0. {
1470 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1471 }
1472 }
1473
1474 path.line_to(first_top_left + curve_height);
1475 if self.corner_radius > 0. {
1476 path.curve_to(first_top_left + top_curve_width, first_top_left);
1477 }
1478 path.line_to(first_top_right - top_curve_width);
1479
1480 scene.push_path(path.build(self.color, Some(bounds)));
1481 }
1482}
1483
1484fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1485 delta.powf(1.5) / 100.0
1486}
1487
1488fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1489 delta.powf(1.2) / 300.0
1490}
1491
1492#[cfg(test)]
1493mod tests {
1494 use std::sync::Arc;
1495
1496 use super::*;
1497 use crate::{
1498 display_map::{BlockDisposition, BlockProperties},
1499 Editor, MultiBuffer,
1500 };
1501 use settings::Settings;
1502 use util::test::sample_text;
1503
1504 #[gpui::test]
1505 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1506 cx.set_global(Settings::test(cx));
1507 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
1508 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1509 Editor::new(EditorMode::Full, buffer, None, None, cx)
1510 });
1511 let element = EditorElement::new(
1512 editor.downgrade(),
1513 editor.read(cx).style(cx),
1514 CursorShape::Bar,
1515 );
1516
1517 let layouts = editor.update(cx, |editor, cx| {
1518 let snapshot = editor.snapshot(cx);
1519 let mut presenter = cx.build_presenter(window_id, 30.);
1520 let mut layout_cx = presenter.build_layout_context(false, cx);
1521 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1522 });
1523 assert_eq!(layouts.len(), 6);
1524 }
1525
1526 #[gpui::test]
1527 fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::MutableAppContext) {
1528 cx.set_global(Settings::test(cx));
1529 let buffer = MultiBuffer::build_simple("", cx);
1530 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1531 Editor::new(EditorMode::Full, buffer, None, None, cx)
1532 });
1533
1534 editor.update(cx, |editor, cx| {
1535 editor.set_placeholder_text("hello", cx);
1536 editor.insert_blocks(
1537 [BlockProperties {
1538 disposition: BlockDisposition::Above,
1539 height: 3,
1540 position: Anchor::min(),
1541 render: Arc::new(|_| Empty::new().boxed()),
1542 }],
1543 cx,
1544 );
1545
1546 // Blur the editor so that it displays placeholder text.
1547 cx.blur();
1548 });
1549
1550 let mut element = EditorElement::new(
1551 editor.downgrade(),
1552 editor.read(cx).style(cx),
1553 CursorShape::Bar,
1554 );
1555
1556 let mut scene = Scene::new(1.0);
1557 let mut presenter = cx.build_presenter(window_id, 30.);
1558 let mut layout_cx = presenter.build_layout_context(false, cx);
1559 let (size, mut state) = element.layout(
1560 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
1561 &mut layout_cx,
1562 );
1563
1564 assert_eq!(state.line_layouts.len(), 4);
1565 assert_eq!(
1566 state
1567 .line_number_layouts
1568 .iter()
1569 .map(Option::is_some)
1570 .collect::<Vec<_>>(),
1571 &[false, false, false, true]
1572 );
1573
1574 // Don't panic.
1575 let bounds = RectF::new(Default::default(), size);
1576 let mut paint_cx = presenter.build_paint_context(&mut scene, cx);
1577 element.paint(bounds, bounds, &mut state, &mut paint_cx);
1578 }
1579}