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