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