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 new<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::new(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::new(selection, view.selections.line_mode, &display_map)
1011 })
1012 .collect(),
1013 ));
1014 }
1015 });
1016
1017 let line_number_layouts =
1018 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
1019
1020 let mut max_visible_line_width = 0.0;
1021 let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
1022 for line in &line_layouts {
1023 if line.width() > max_visible_line_width {
1024 max_visible_line_width = line.width();
1025 }
1026 }
1027
1028 let style = self.style.clone();
1029 let longest_line_width = layout_line(
1030 snapshot.longest_row(),
1031 &snapshot,
1032 &style,
1033 cx.text_layout_cache,
1034 )
1035 .width();
1036 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
1037 let em_width = style.text.em_width(cx.font_cache);
1038 let max_row = snapshot.max_point().row();
1039 let scroll_max = vec2f(
1040 ((scroll_width - text_size.x()) / em_width).max(0.0),
1041 max_row.saturating_sub(1) as f32,
1042 );
1043
1044 let mut context_menu = None;
1045 let mut code_actions_indicator = None;
1046 self.update_view(cx.app, |view, cx| {
1047 let clamped = view.clamp_scroll_left(scroll_max.x());
1048 let autoscrolled;
1049 if autoscroll_horizontally {
1050 autoscrolled = view.autoscroll_horizontally(
1051 start_row,
1052 text_size.x(),
1053 scroll_width,
1054 em_width,
1055 &line_layouts,
1056 cx,
1057 );
1058 } else {
1059 autoscrolled = false;
1060 }
1061
1062 if clamped || autoscrolled {
1063 snapshot = view.snapshot(cx);
1064 }
1065
1066 let newest_selection_head = view
1067 .selections
1068 .newest::<usize>(cx)
1069 .head()
1070 .to_display_point(&snapshot);
1071
1072 if (start_row..end_row).contains(&newest_selection_head.row()) {
1073 let style = view.style(cx);
1074 if view.context_menu_visible() {
1075 context_menu =
1076 view.render_context_menu(newest_selection_head, style.clone(), cx);
1077 }
1078
1079 code_actions_indicator = view
1080 .render_code_actions_indicator(&style, cx)
1081 .map(|indicator| (newest_selection_head.row(), indicator));
1082 }
1083 });
1084
1085 if let Some((_, context_menu)) = context_menu.as_mut() {
1086 context_menu.layout(
1087 SizeConstraint {
1088 min: Vector2F::zero(),
1089 max: vec2f(
1090 f32::INFINITY,
1091 (12. * line_height).min((size.y() - line_height) / 2.),
1092 ),
1093 },
1094 cx,
1095 );
1096 }
1097
1098 if let Some((_, indicator)) = code_actions_indicator.as_mut() {
1099 indicator.layout(
1100 SizeConstraint::strict_along(Axis::Vertical, line_height * 0.618),
1101 cx,
1102 );
1103 }
1104
1105 let blocks = self.layout_blocks(
1106 start_row..end_row,
1107 &snapshot,
1108 size.x().max(scroll_width + gutter_width),
1109 gutter_padding,
1110 gutter_width,
1111 em_width,
1112 gutter_width + gutter_margin,
1113 line_height,
1114 &style,
1115 &line_layouts,
1116 cx,
1117 );
1118
1119 (
1120 size,
1121 LayoutState {
1122 size,
1123 scroll_max,
1124 gutter_size,
1125 gutter_padding,
1126 text_size,
1127 gutter_margin,
1128 snapshot,
1129 active_rows,
1130 highlighted_rows,
1131 highlighted_ranges,
1132 line_layouts,
1133 line_number_layouts,
1134 blocks,
1135 line_height,
1136 em_width,
1137 em_advance,
1138 selections,
1139 context_menu,
1140 code_actions_indicator,
1141 },
1142 )
1143 }
1144
1145 fn paint(
1146 &mut self,
1147 bounds: RectF,
1148 visible_bounds: RectF,
1149 layout: &mut Self::LayoutState,
1150 cx: &mut PaintContext,
1151 ) -> Self::PaintState {
1152 cx.scene.push_layer(Some(bounds));
1153
1154 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
1155 let text_bounds = RectF::new(
1156 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
1157 layout.text_size,
1158 );
1159
1160 self.paint_background(gutter_bounds, text_bounds, layout, cx);
1161 if layout.gutter_size.x() > 0. {
1162 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
1163 }
1164 self.paint_text(text_bounds, visible_bounds, layout, cx);
1165
1166 if !layout.blocks.is_empty() {
1167 cx.scene.push_layer(Some(bounds));
1168 self.paint_blocks(bounds, visible_bounds, layout, cx);
1169 cx.scene.pop_layer();
1170 }
1171
1172 cx.scene.pop_layer();
1173
1174 PaintState {
1175 bounds,
1176 gutter_bounds,
1177 text_bounds,
1178 }
1179 }
1180
1181 fn dispatch_event(
1182 &mut self,
1183 event: &Event,
1184 _: RectF,
1185 _: RectF,
1186 layout: &mut LayoutState,
1187 paint: &mut PaintState,
1188 cx: &mut EventContext,
1189 ) -> bool {
1190 if let Some((_, context_menu)) = &mut layout.context_menu {
1191 if context_menu.dispatch_event(event, cx) {
1192 return true;
1193 }
1194 }
1195
1196 if let Some((_, indicator)) = &mut layout.code_actions_indicator {
1197 if indicator.dispatch_event(event, cx) {
1198 return true;
1199 }
1200 }
1201
1202 for (_, block) in &mut layout.blocks {
1203 if block.dispatch_event(event, cx) {
1204 return true;
1205 }
1206 }
1207
1208 match event {
1209 Event::LeftMouseDown {
1210 position,
1211 alt,
1212 shift,
1213 click_count,
1214 ..
1215 } => self.mouse_down(*position, *alt, *shift, *click_count, layout, paint, cx),
1216 Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx),
1217 Event::LeftMouseDragged { position } => {
1218 self.mouse_dragged(*position, layout, paint, cx)
1219 }
1220 Event::ScrollWheel {
1221 position,
1222 delta,
1223 precise,
1224 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
1225 Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
1226 _ => false,
1227 }
1228 }
1229
1230 fn debug(
1231 &self,
1232 bounds: RectF,
1233 _: &Self::LayoutState,
1234 _: &Self::PaintState,
1235 _: &gpui::DebugContext,
1236 ) -> json::Value {
1237 json!({
1238 "type": "BufferElement",
1239 "bounds": bounds.to_json()
1240 })
1241 }
1242}
1243
1244pub struct LayoutState {
1245 size: Vector2F,
1246 scroll_max: Vector2F,
1247 gutter_size: Vector2F,
1248 gutter_padding: f32,
1249 gutter_margin: f32,
1250 text_size: Vector2F,
1251 snapshot: EditorSnapshot,
1252 active_rows: BTreeMap<u32, bool>,
1253 highlighted_rows: Option<Range<u32>>,
1254 line_layouts: Vec<text_layout::Line>,
1255 line_number_layouts: Vec<Option<text_layout::Line>>,
1256 blocks: Vec<(u32, ElementBox)>,
1257 line_height: f32,
1258 em_width: f32,
1259 em_advance: f32,
1260 highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
1261 selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
1262 context_menu: Option<(DisplayPoint, ElementBox)>,
1263 code_actions_indicator: Option<(u32, ElementBox)>,
1264}
1265
1266fn layout_line(
1267 row: u32,
1268 snapshot: &EditorSnapshot,
1269 style: &EditorStyle,
1270 layout_cache: &TextLayoutCache,
1271) -> text_layout::Line {
1272 let mut line = snapshot.line(row);
1273
1274 if line.len() > MAX_LINE_LEN {
1275 let mut len = MAX_LINE_LEN;
1276 while !line.is_char_boundary(len) {
1277 len -= 1;
1278 }
1279
1280 line.truncate(len);
1281 }
1282
1283 layout_cache.layout_str(
1284 &line,
1285 style.text.font_size,
1286 &[(
1287 snapshot.line_len(row) as usize,
1288 RunStyle {
1289 font_id: style.text.font_id,
1290 color: Color::black(),
1291 underline: Default::default(),
1292 },
1293 )],
1294 )
1295}
1296
1297pub struct PaintState {
1298 bounds: RectF,
1299 gutter_bounds: RectF,
1300 text_bounds: RectF,
1301}
1302
1303impl PaintState {
1304 fn point_for_position(
1305 &self,
1306 snapshot: &EditorSnapshot,
1307 layout: &LayoutState,
1308 position: Vector2F,
1309 ) -> (DisplayPoint, u32) {
1310 let scroll_position = snapshot.scroll_position();
1311 let position = position - self.text_bounds.origin();
1312 let y = position.y().max(0.0).min(layout.size.y());
1313 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
1314 let row = cmp::min(row, snapshot.max_point().row());
1315 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
1316 let x = position.x() + (scroll_position.x() * layout.em_width);
1317
1318 let column = if x >= 0.0 {
1319 line.index_for_x(x)
1320 .map(|ix| ix as u32)
1321 .unwrap_or_else(|| snapshot.line_len(row))
1322 } else {
1323 0
1324 };
1325 let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
1326
1327 (DisplayPoint::new(row, column), overshoot)
1328 }
1329}
1330
1331#[derive(Copy, Clone, PartialEq, Eq)]
1332pub enum CursorShape {
1333 Bar,
1334 Block,
1335 Underscore,
1336}
1337
1338impl Default for CursorShape {
1339 fn default() -> Self {
1340 CursorShape::Bar
1341 }
1342}
1343
1344struct Cursor {
1345 origin: Vector2F,
1346 block_width: f32,
1347 line_height: f32,
1348 color: Color,
1349 shape: CursorShape,
1350 block_text: Option<Line>,
1351}
1352
1353impl Cursor {
1354 fn paint(&self, cx: &mut PaintContext) {
1355 let bounds = match self.shape {
1356 CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)),
1357 CursorShape::Block => {
1358 RectF::new(self.origin, vec2f(self.block_width, self.line_height))
1359 }
1360 CursorShape::Underscore => RectF::new(
1361 self.origin + Vector2F::new(0.0, self.line_height - 2.0),
1362 vec2f(self.block_width, 2.0),
1363 ),
1364 };
1365
1366 cx.scene.push_quad(Quad {
1367 bounds,
1368 background: Some(self.color),
1369 border: Border::new(0., Color::black()),
1370 corner_radius: 0.,
1371 });
1372
1373 if let Some(block_text) = &self.block_text {
1374 block_text.paint(self.origin, bounds, self.line_height, cx);
1375 }
1376 }
1377}
1378
1379#[derive(Debug)]
1380struct HighlightedRange {
1381 start_y: f32,
1382 line_height: f32,
1383 lines: Vec<HighlightedRangeLine>,
1384 color: Color,
1385 corner_radius: f32,
1386}
1387
1388#[derive(Debug)]
1389struct HighlightedRangeLine {
1390 start_x: f32,
1391 end_x: f32,
1392}
1393
1394impl HighlightedRange {
1395 fn paint(&self, bounds: RectF, scene: &mut Scene) {
1396 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
1397 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
1398 self.paint_lines(
1399 self.start_y + self.line_height,
1400 &self.lines[1..],
1401 bounds,
1402 scene,
1403 );
1404 } else {
1405 self.paint_lines(self.start_y, &self.lines, bounds, scene);
1406 }
1407 }
1408
1409 fn paint_lines(
1410 &self,
1411 start_y: f32,
1412 lines: &[HighlightedRangeLine],
1413 bounds: RectF,
1414 scene: &mut Scene,
1415 ) {
1416 if lines.is_empty() {
1417 return;
1418 }
1419
1420 let mut path = PathBuilder::new();
1421 let first_line = lines.first().unwrap();
1422 let last_line = lines.last().unwrap();
1423
1424 let first_top_left = vec2f(first_line.start_x, start_y);
1425 let first_top_right = vec2f(first_line.end_x, start_y);
1426
1427 let curve_height = vec2f(0., self.corner_radius);
1428 let curve_width = |start_x: f32, end_x: f32| {
1429 let max = (end_x - start_x) / 2.;
1430 let width = if max < self.corner_radius {
1431 max
1432 } else {
1433 self.corner_radius
1434 };
1435
1436 vec2f(width, 0.)
1437 };
1438
1439 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
1440 path.reset(first_top_right - top_curve_width);
1441 path.curve_to(first_top_right + curve_height, first_top_right);
1442
1443 let mut iter = lines.iter().enumerate().peekable();
1444 while let Some((ix, line)) = iter.next() {
1445 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
1446
1447 if let Some((_, next_line)) = iter.peek() {
1448 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
1449
1450 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
1451 Ordering::Equal => {
1452 path.line_to(bottom_right);
1453 }
1454 Ordering::Less => {
1455 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
1456 path.line_to(bottom_right - curve_height);
1457 if self.corner_radius > 0. {
1458 path.curve_to(bottom_right - curve_width, bottom_right);
1459 }
1460 path.line_to(next_top_right + curve_width);
1461 if self.corner_radius > 0. {
1462 path.curve_to(next_top_right + curve_height, next_top_right);
1463 }
1464 }
1465 Ordering::Greater => {
1466 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
1467 path.line_to(bottom_right - curve_height);
1468 if self.corner_radius > 0. {
1469 path.curve_to(bottom_right + curve_width, bottom_right);
1470 }
1471 path.line_to(next_top_right - curve_width);
1472 if self.corner_radius > 0. {
1473 path.curve_to(next_top_right + curve_height, next_top_right);
1474 }
1475 }
1476 }
1477 } else {
1478 let curve_width = curve_width(line.start_x, line.end_x);
1479 path.line_to(bottom_right - curve_height);
1480 if self.corner_radius > 0. {
1481 path.curve_to(bottom_right - curve_width, bottom_right);
1482 }
1483
1484 let bottom_left = vec2f(line.start_x, bottom_right.y());
1485 path.line_to(bottom_left + curve_width);
1486 if self.corner_radius > 0. {
1487 path.curve_to(bottom_left - curve_height, bottom_left);
1488 }
1489 }
1490 }
1491
1492 if first_line.start_x > last_line.start_x {
1493 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1494 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1495 path.line_to(second_top_left + curve_height);
1496 if self.corner_radius > 0. {
1497 path.curve_to(second_top_left + curve_width, second_top_left);
1498 }
1499 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1500 path.line_to(first_bottom_left - curve_width);
1501 if self.corner_radius > 0. {
1502 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1503 }
1504 }
1505
1506 path.line_to(first_top_left + curve_height);
1507 if self.corner_radius > 0. {
1508 path.curve_to(first_top_left + top_curve_width, first_top_left);
1509 }
1510 path.line_to(first_top_right - top_curve_width);
1511
1512 scene.push_path(path.build(self.color, Some(bounds)));
1513 }
1514}
1515
1516fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1517 delta.powf(1.5) / 100.0
1518}
1519
1520fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1521 delta.powf(1.2) / 300.0
1522}
1523
1524#[cfg(test)]
1525mod tests {
1526 use std::sync::Arc;
1527
1528 use super::*;
1529 use crate::{
1530 display_map::{BlockDisposition, BlockProperties},
1531 Editor, MultiBuffer,
1532 };
1533 use settings::Settings;
1534 use util::test::sample_text;
1535
1536 #[gpui::test]
1537 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1538 cx.set_global(Settings::test(cx));
1539 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
1540 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1541 Editor::new(EditorMode::Full, buffer, None, None, None, cx)
1542 });
1543 let element = EditorElement::new(
1544 editor.downgrade(),
1545 editor.read(cx).style(cx),
1546 CursorShape::Bar,
1547 );
1548
1549 let layouts = editor.update(cx, |editor, cx| {
1550 let snapshot = editor.snapshot(cx);
1551 let mut presenter = cx.build_presenter(window_id, 30.);
1552 let mut layout_cx = presenter.build_layout_context(false, cx);
1553 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1554 });
1555 assert_eq!(layouts.len(), 6);
1556 }
1557
1558 #[gpui::test]
1559 fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::MutableAppContext) {
1560 cx.set_global(Settings::test(cx));
1561 let buffer = MultiBuffer::build_simple("", cx);
1562 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1563 Editor::new(EditorMode::Full, buffer, None, None, None, cx)
1564 });
1565
1566 editor.update(cx, |editor, cx| {
1567 editor.set_placeholder_text("hello", cx);
1568 editor.insert_blocks(
1569 [BlockProperties {
1570 disposition: BlockDisposition::Above,
1571 height: 3,
1572 position: Anchor::min(),
1573 render: Arc::new(|_| Empty::new().boxed()),
1574 }],
1575 cx,
1576 );
1577
1578 // Blur the editor so that it displays placeholder text.
1579 cx.blur();
1580 });
1581
1582 let mut element = EditorElement::new(
1583 editor.downgrade(),
1584 editor.read(cx).style(cx),
1585 CursorShape::Bar,
1586 );
1587
1588 let mut scene = Scene::new(1.0);
1589 let mut presenter = cx.build_presenter(window_id, 30.);
1590 let mut layout_cx = presenter.build_layout_context(false, cx);
1591 let (size, mut state) = element.layout(
1592 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
1593 &mut layout_cx,
1594 );
1595
1596 assert_eq!(state.line_layouts.len(), 4);
1597 assert_eq!(
1598 state
1599 .line_number_layouts
1600 .iter()
1601 .map(Option::is_some)
1602 .collect::<Vec<_>>(),
1603 &[false, false, false, true]
1604 );
1605
1606 // Don't panic.
1607 let bounds = RectF::new(Default::default(), size);
1608 let mut paint_cx = presenter.build_paint_context(&mut scene, cx);
1609 element.paint(bounds, bounds, &mut state, &mut paint_cx);
1610 }
1611}