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 scroll_x = snapshot.scroll_position.x();
759 snapshot
760 .blocks_in_range(rows.clone())
761 .map(|(block_row, block)| {
762 let mut element = match block {
763 TransformBlock::Custom(block) => {
764 let align_to = block
765 .position()
766 .to_point(&snapshot.buffer_snapshot)
767 .to_display_point(snapshot);
768 let anchor_x = text_x
769 + if rows.contains(&align_to.row()) {
770 line_layouts[(align_to.row() - rows.start) as usize]
771 .x_for_index(align_to.column() as usize)
772 } else {
773 layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
774 .x_for_index(align_to.column() as usize)
775 };
776
777 block.render(&BlockContext {
778 cx,
779 anchor_x,
780 gutter_padding,
781 line_height,
782 scroll_x,
783 gutter_width,
784 em_width,
785 })
786 }
787 TransformBlock::ExcerptHeader {
788 buffer,
789 starts_new_buffer,
790 ..
791 } => {
792 if *starts_new_buffer {
793 let style = &self.style.diagnostic_path_header;
794 let font_size =
795 (style.text_scale_factor * self.style.text.font_size).round();
796
797 let mut filename = None;
798 let mut parent_path = None;
799 if let Some(path) = buffer.path() {
800 filename =
801 path.file_name().map(|f| f.to_string_lossy().to_string());
802 parent_path =
803 path.parent().map(|p| p.to_string_lossy().to_string() + "/");
804 }
805
806 Flex::row()
807 .with_child(
808 Label::new(
809 filename.unwrap_or_else(|| "untitled".to_string()),
810 style.filename.text.clone().with_font_size(font_size),
811 )
812 .contained()
813 .with_style(style.filename.container)
814 .boxed(),
815 )
816 .with_children(parent_path.map(|path| {
817 Label::new(
818 path,
819 style.path.text.clone().with_font_size(font_size),
820 )
821 .contained()
822 .with_style(style.path.container)
823 .boxed()
824 }))
825 .aligned()
826 .left()
827 .contained()
828 .with_style(style.container)
829 .with_padding_left(gutter_padding + scroll_x * em_width)
830 .expanded()
831 .named("path header block")
832 } else {
833 let text_style = self.style.text.clone();
834 Label::new("…".to_string(), text_style)
835 .contained()
836 .with_padding_left(gutter_padding + scroll_x * em_width)
837 .named("collapsed context")
838 }
839 }
840 };
841
842 element.layout(
843 SizeConstraint {
844 min: Vector2F::zero(),
845 max: vec2f(width, block.height() as f32 * line_height),
846 },
847 cx,
848 );
849 (block_row, element)
850 })
851 .collect()
852 }
853}
854
855impl Element for EditorElement {
856 type LayoutState = LayoutState;
857 type PaintState = PaintState;
858
859 fn layout(
860 &mut self,
861 constraint: SizeConstraint,
862 cx: &mut LayoutContext,
863 ) -> (Vector2F, Self::LayoutState) {
864 let mut size = constraint.max;
865 if size.x().is_infinite() {
866 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
867 }
868
869 let snapshot = self.snapshot(cx.app);
870 let style = self.style.clone();
871 let line_height = style.text.line_height(cx.font_cache);
872
873 let gutter_padding;
874 let gutter_width;
875 let gutter_margin;
876 if snapshot.mode == EditorMode::Full {
877 gutter_padding = style.text.em_width(cx.font_cache) * style.gutter_padding_factor;
878 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
879 gutter_margin = -style.text.descent(cx.font_cache);
880 } else {
881 gutter_padding = 0.0;
882 gutter_width = 0.0;
883 gutter_margin = 0.0;
884 };
885
886 let text_width = size.x() - gutter_width;
887 let em_width = style.text.em_width(cx.font_cache);
888 let em_advance = style.text.em_advance(cx.font_cache);
889 let overscroll = vec2f(em_width, 0.);
890 let snapshot = self.update_view(cx.app, |view, cx| {
891 let wrap_width = match view.soft_wrap_mode(cx) {
892 SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
893 SoftWrap::EditorWidth => {
894 Some(text_width - gutter_margin - overscroll.x() - em_width)
895 }
896 SoftWrap::Column(column) => Some(column as f32 * em_advance),
897 };
898
899 if view.set_wrap_width(wrap_width, cx) {
900 view.snapshot(cx)
901 } else {
902 snapshot
903 }
904 });
905
906 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
907 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
908 size.set_y(
909 scroll_height
910 .min(constraint.max_along(Axis::Vertical))
911 .max(constraint.min_along(Axis::Vertical))
912 .min(line_height * max_lines as f32),
913 )
914 } else if let EditorMode::SingleLine = snapshot.mode {
915 size.set_y(
916 line_height
917 .min(constraint.max_along(Axis::Vertical))
918 .max(constraint.min_along(Axis::Vertical)),
919 )
920 } else if size.y().is_infinite() {
921 size.set_y(scroll_height);
922 }
923 let gutter_size = vec2f(gutter_width, size.y());
924 let text_size = vec2f(text_width, size.y());
925
926 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
927 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
928 let snapshot = view.snapshot(cx);
929 (autoscroll_horizontally, snapshot)
930 });
931
932 let scroll_position = snapshot.scroll_position();
933 let start_row = scroll_position.y() as u32;
934 let scroll_top = scroll_position.y() * line_height;
935
936 // Add 1 to ensure selections bleed off screen
937 let end_row = 1 + cmp::min(
938 ((scroll_top + size.y()) / line_height).ceil() as u32,
939 snapshot.max_point().row(),
940 );
941
942 let start_anchor = if start_row == 0 {
943 Anchor::min()
944 } else {
945 snapshot
946 .buffer_snapshot
947 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
948 };
949 let end_anchor = if end_row > snapshot.max_point().row() {
950 Anchor::max()
951 } else {
952 snapshot
953 .buffer_snapshot
954 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
955 };
956
957 let mut selections: Vec<(ReplicaId, Vec<SelectionLayout>)> = Vec::new();
958 let mut active_rows = BTreeMap::new();
959 let mut highlighted_rows = None;
960 let mut highlighted_ranges = Vec::new();
961 self.update_view(cx.app, |view, cx| {
962 let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
963
964 highlighted_rows = view.highlighted_rows();
965 let theme = cx.global::<Settings>().theme.as_ref();
966 highlighted_ranges = view.background_highlights_in_range(
967 start_anchor.clone()..end_anchor.clone(),
968 &display_map,
969 theme,
970 );
971
972 let mut remote_selections = HashMap::default();
973 for (replica_id, line_mode, selection) in display_map
974 .buffer_snapshot
975 .remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone()))
976 {
977 // The local selections match the leader's selections.
978 if Some(replica_id) == view.leader_replica_id {
979 continue;
980 }
981 remote_selections
982 .entry(replica_id)
983 .or_insert(Vec::new())
984 .push(SelectionLayout::new(selection, line_mode, &display_map));
985 }
986 selections.extend(remote_selections);
987
988 if view.show_local_selections {
989 let mut local_selections = view
990 .selections
991 .disjoint_in_range(start_anchor..end_anchor, cx);
992 local_selections.extend(view.selections.pending(cx));
993 for selection in &local_selections {
994 let is_empty = selection.start == selection.end;
995 let selection_start = snapshot.prev_line_boundary(selection.start).1;
996 let selection_end = snapshot.next_line_boundary(selection.end).1;
997 for row in cmp::max(selection_start.row(), start_row)
998 ..=cmp::min(selection_end.row(), end_row)
999 {
1000 let contains_non_empty_selection =
1001 active_rows.entry(row).or_insert(!is_empty);
1002 *contains_non_empty_selection |= !is_empty;
1003 }
1004 }
1005
1006 // Render the local selections in the leader's color when following.
1007 let local_replica_id = view.leader_replica_id.unwrap_or(view.replica_id(cx));
1008
1009 selections.push((
1010 local_replica_id,
1011 local_selections
1012 .into_iter()
1013 .map(|selection| {
1014 SelectionLayout::new(selection, view.selections.line_mode, &display_map)
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 self.update_view(cx.app, |view, cx| {
1049 let clamped = view.clamp_scroll_left(scroll_max.x());
1050 let autoscrolled;
1051 if autoscroll_horizontally {
1052 autoscrolled = view.autoscroll_horizontally(
1053 start_row,
1054 text_size.x(),
1055 scroll_width,
1056 em_width,
1057 &line_layouts,
1058 cx,
1059 );
1060 } else {
1061 autoscrolled = false;
1062 }
1063
1064 if clamped || autoscrolled {
1065 snapshot = view.snapshot(cx);
1066 }
1067 });
1068
1069 let mut context_menu = None;
1070 let mut code_actions_indicator = None;
1071 cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
1072 let newest_selection_head = view
1073 .selections
1074 .newest::<usize>(cx)
1075 .head()
1076 .to_display_point(&snapshot);
1077
1078 if (start_row..end_row).contains(&newest_selection_head.row()) {
1079 let style = view.style(cx);
1080 if view.context_menu_visible() {
1081 context_menu =
1082 view.render_context_menu(newest_selection_head, style.clone(), cx);
1083 }
1084
1085 code_actions_indicator = view
1086 .render_code_actions_indicator(&style, cx)
1087 .map(|indicator| (newest_selection_head.row(), indicator));
1088 }
1089 });
1090
1091 if let Some((_, context_menu)) = context_menu.as_mut() {
1092 context_menu.layout(
1093 SizeConstraint {
1094 min: Vector2F::zero(),
1095 max: vec2f(
1096 f32::INFINITY,
1097 (12. * line_height).min((size.y() - line_height) / 2.),
1098 ),
1099 },
1100 cx,
1101 );
1102 }
1103
1104 if let Some((_, indicator)) = code_actions_indicator.as_mut() {
1105 indicator.layout(
1106 SizeConstraint::strict_along(Axis::Vertical, line_height * 0.618),
1107 cx,
1108 );
1109 }
1110
1111 let blocks = self.layout_blocks(
1112 start_row..end_row,
1113 &snapshot,
1114 size.x().max(scroll_width + gutter_width),
1115 gutter_padding,
1116 gutter_width,
1117 em_width,
1118 gutter_width + gutter_margin,
1119 line_height,
1120 &style,
1121 &line_layouts,
1122 cx,
1123 );
1124
1125 (
1126 size,
1127 LayoutState {
1128 size,
1129 scroll_max,
1130 gutter_size,
1131 gutter_padding,
1132 text_size,
1133 gutter_margin,
1134 snapshot,
1135 active_rows,
1136 highlighted_rows,
1137 highlighted_ranges,
1138 line_layouts,
1139 line_number_layouts,
1140 blocks,
1141 line_height,
1142 em_width,
1143 em_advance,
1144 selections,
1145 context_menu,
1146 code_actions_indicator,
1147 },
1148 )
1149 }
1150
1151 fn paint(
1152 &mut self,
1153 bounds: RectF,
1154 visible_bounds: RectF,
1155 layout: &mut Self::LayoutState,
1156 cx: &mut PaintContext,
1157 ) -> Self::PaintState {
1158 cx.scene.push_layer(Some(bounds));
1159
1160 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
1161 let text_bounds = RectF::new(
1162 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
1163 layout.text_size,
1164 );
1165
1166 self.paint_background(gutter_bounds, text_bounds, layout, cx);
1167 if layout.gutter_size.x() > 0. {
1168 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
1169 }
1170 self.paint_text(text_bounds, visible_bounds, layout, cx);
1171
1172 if !layout.blocks.is_empty() {
1173 cx.scene.push_layer(Some(bounds));
1174 self.paint_blocks(bounds, visible_bounds, layout, cx);
1175 cx.scene.pop_layer();
1176 }
1177
1178 cx.scene.pop_layer();
1179
1180 PaintState {
1181 bounds,
1182 gutter_bounds,
1183 text_bounds,
1184 }
1185 }
1186
1187 fn dispatch_event(
1188 &mut self,
1189 event: &Event,
1190 _: RectF,
1191 _: RectF,
1192 layout: &mut LayoutState,
1193 paint: &mut PaintState,
1194 cx: &mut EventContext,
1195 ) -> bool {
1196 if let Some((_, context_menu)) = &mut layout.context_menu {
1197 if context_menu.dispatch_event(event, cx) {
1198 return true;
1199 }
1200 }
1201
1202 if let Some((_, indicator)) = &mut layout.code_actions_indicator {
1203 if indicator.dispatch_event(event, cx) {
1204 return true;
1205 }
1206 }
1207
1208 for (_, block) in &mut layout.blocks {
1209 if block.dispatch_event(event, cx) {
1210 return true;
1211 }
1212 }
1213
1214 match event {
1215 Event::LeftMouseDown {
1216 position,
1217 alt,
1218 shift,
1219 click_count,
1220 ..
1221 } => self.mouse_down(*position, *alt, *shift, *click_count, layout, paint, cx),
1222 Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx),
1223 Event::LeftMouseDragged { position } => {
1224 self.mouse_dragged(*position, layout, paint, cx)
1225 }
1226 Event::ScrollWheel {
1227 position,
1228 delta,
1229 precise,
1230 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
1231 Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
1232 _ => false,
1233 }
1234 }
1235
1236 fn debug(
1237 &self,
1238 bounds: RectF,
1239 _: &Self::LayoutState,
1240 _: &Self::PaintState,
1241 _: &gpui::DebugContext,
1242 ) -> json::Value {
1243 json!({
1244 "type": "BufferElement",
1245 "bounds": bounds.to_json()
1246 })
1247 }
1248}
1249
1250pub struct LayoutState {
1251 size: Vector2F,
1252 scroll_max: Vector2F,
1253 gutter_size: Vector2F,
1254 gutter_padding: f32,
1255 gutter_margin: f32,
1256 text_size: Vector2F,
1257 snapshot: EditorSnapshot,
1258 active_rows: BTreeMap<u32, bool>,
1259 highlighted_rows: Option<Range<u32>>,
1260 line_layouts: Vec<text_layout::Line>,
1261 line_number_layouts: Vec<Option<text_layout::Line>>,
1262 blocks: Vec<(u32, ElementBox)>,
1263 line_height: f32,
1264 em_width: f32,
1265 em_advance: f32,
1266 highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
1267 selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
1268 context_menu: Option<(DisplayPoint, ElementBox)>,
1269 code_actions_indicator: Option<(u32, ElementBox)>,
1270}
1271
1272fn layout_line(
1273 row: u32,
1274 snapshot: &EditorSnapshot,
1275 style: &EditorStyle,
1276 layout_cache: &TextLayoutCache,
1277) -> text_layout::Line {
1278 let mut line = snapshot.line(row);
1279
1280 if line.len() > MAX_LINE_LEN {
1281 let mut len = MAX_LINE_LEN;
1282 while !line.is_char_boundary(len) {
1283 len -= 1;
1284 }
1285
1286 line.truncate(len);
1287 }
1288
1289 layout_cache.layout_str(
1290 &line,
1291 style.text.font_size,
1292 &[(
1293 snapshot.line_len(row) as usize,
1294 RunStyle {
1295 font_id: style.text.font_id,
1296 color: Color::black(),
1297 underline: Default::default(),
1298 },
1299 )],
1300 )
1301}
1302
1303pub struct PaintState {
1304 bounds: RectF,
1305 gutter_bounds: RectF,
1306 text_bounds: RectF,
1307}
1308
1309impl PaintState {
1310 fn point_for_position(
1311 &self,
1312 snapshot: &EditorSnapshot,
1313 layout: &LayoutState,
1314 position: Vector2F,
1315 ) -> (DisplayPoint, u32) {
1316 let scroll_position = snapshot.scroll_position();
1317 let position = position - self.text_bounds.origin();
1318 let y = position.y().max(0.0).min(layout.size.y());
1319 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
1320 let row = cmp::min(row, snapshot.max_point().row());
1321 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
1322 let x = position.x() + (scroll_position.x() * layout.em_width);
1323
1324 let column = if x >= 0.0 {
1325 line.index_for_x(x)
1326 .map(|ix| ix as u32)
1327 .unwrap_or_else(|| snapshot.line_len(row))
1328 } else {
1329 0
1330 };
1331 let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
1332
1333 (DisplayPoint::new(row, column), overshoot)
1334 }
1335}
1336
1337#[derive(Copy, Clone, PartialEq, Eq)]
1338pub enum CursorShape {
1339 Bar,
1340 Block,
1341 Underscore,
1342}
1343
1344impl Default for CursorShape {
1345 fn default() -> Self {
1346 CursorShape::Bar
1347 }
1348}
1349
1350struct Cursor {
1351 origin: Vector2F,
1352 block_width: f32,
1353 line_height: f32,
1354 color: Color,
1355 shape: CursorShape,
1356 block_text: Option<Line>,
1357}
1358
1359impl Cursor {
1360 fn paint(&self, cx: &mut PaintContext) {
1361 let bounds = match self.shape {
1362 CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)),
1363 CursorShape::Block => {
1364 RectF::new(self.origin, vec2f(self.block_width, self.line_height))
1365 }
1366 CursorShape::Underscore => RectF::new(
1367 self.origin + Vector2F::new(0.0, self.line_height - 2.0),
1368 vec2f(self.block_width, 2.0),
1369 ),
1370 };
1371
1372 cx.scene.push_quad(Quad {
1373 bounds,
1374 background: Some(self.color),
1375 border: Border::new(0., Color::black()),
1376 corner_radius: 0.,
1377 });
1378
1379 if let Some(block_text) = &self.block_text {
1380 block_text.paint(self.origin, bounds, self.line_height, cx);
1381 }
1382 }
1383}
1384
1385#[derive(Debug)]
1386struct HighlightedRange {
1387 start_y: f32,
1388 line_height: f32,
1389 lines: Vec<HighlightedRangeLine>,
1390 color: Color,
1391 corner_radius: f32,
1392}
1393
1394#[derive(Debug)]
1395struct HighlightedRangeLine {
1396 start_x: f32,
1397 end_x: f32,
1398}
1399
1400impl HighlightedRange {
1401 fn paint(&self, bounds: RectF, scene: &mut Scene) {
1402 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
1403 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
1404 self.paint_lines(
1405 self.start_y + self.line_height,
1406 &self.lines[1..],
1407 bounds,
1408 scene,
1409 );
1410 } else {
1411 self.paint_lines(self.start_y, &self.lines, bounds, scene);
1412 }
1413 }
1414
1415 fn paint_lines(
1416 &self,
1417 start_y: f32,
1418 lines: &[HighlightedRangeLine],
1419 bounds: RectF,
1420 scene: &mut Scene,
1421 ) {
1422 if lines.is_empty() {
1423 return;
1424 }
1425
1426 let mut path = PathBuilder::new();
1427 let first_line = lines.first().unwrap();
1428 let last_line = lines.last().unwrap();
1429
1430 let first_top_left = vec2f(first_line.start_x, start_y);
1431 let first_top_right = vec2f(first_line.end_x, start_y);
1432
1433 let curve_height = vec2f(0., self.corner_radius);
1434 let curve_width = |start_x: f32, end_x: f32| {
1435 let max = (end_x - start_x) / 2.;
1436 let width = if max < self.corner_radius {
1437 max
1438 } else {
1439 self.corner_radius
1440 };
1441
1442 vec2f(width, 0.)
1443 };
1444
1445 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
1446 path.reset(first_top_right - top_curve_width);
1447 path.curve_to(first_top_right + curve_height, first_top_right);
1448
1449 let mut iter = lines.iter().enumerate().peekable();
1450 while let Some((ix, line)) = iter.next() {
1451 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
1452
1453 if let Some((_, next_line)) = iter.peek() {
1454 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
1455
1456 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
1457 Ordering::Equal => {
1458 path.line_to(bottom_right);
1459 }
1460 Ordering::Less => {
1461 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
1462 path.line_to(bottom_right - curve_height);
1463 if self.corner_radius > 0. {
1464 path.curve_to(bottom_right - curve_width, bottom_right);
1465 }
1466 path.line_to(next_top_right + curve_width);
1467 if self.corner_radius > 0. {
1468 path.curve_to(next_top_right + curve_height, next_top_right);
1469 }
1470 }
1471 Ordering::Greater => {
1472 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
1473 path.line_to(bottom_right - curve_height);
1474 if self.corner_radius > 0. {
1475 path.curve_to(bottom_right + curve_width, bottom_right);
1476 }
1477 path.line_to(next_top_right - curve_width);
1478 if self.corner_radius > 0. {
1479 path.curve_to(next_top_right + curve_height, next_top_right);
1480 }
1481 }
1482 }
1483 } else {
1484 let curve_width = curve_width(line.start_x, line.end_x);
1485 path.line_to(bottom_right - curve_height);
1486 if self.corner_radius > 0. {
1487 path.curve_to(bottom_right - curve_width, bottom_right);
1488 }
1489
1490 let bottom_left = vec2f(line.start_x, bottom_right.y());
1491 path.line_to(bottom_left + curve_width);
1492 if self.corner_radius > 0. {
1493 path.curve_to(bottom_left - curve_height, bottom_left);
1494 }
1495 }
1496 }
1497
1498 if first_line.start_x > last_line.start_x {
1499 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1500 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1501 path.line_to(second_top_left + curve_height);
1502 if self.corner_radius > 0. {
1503 path.curve_to(second_top_left + curve_width, second_top_left);
1504 }
1505 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1506 path.line_to(first_bottom_left - curve_width);
1507 if self.corner_radius > 0. {
1508 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1509 }
1510 }
1511
1512 path.line_to(first_top_left + curve_height);
1513 if self.corner_radius > 0. {
1514 path.curve_to(first_top_left + top_curve_width, first_top_left);
1515 }
1516 path.line_to(first_top_right - top_curve_width);
1517
1518 scene.push_path(path.build(self.color, Some(bounds)));
1519 }
1520}
1521
1522fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1523 delta.powf(1.5) / 100.0
1524}
1525
1526fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1527 delta.powf(1.2) / 300.0
1528}
1529
1530#[cfg(test)]
1531mod tests {
1532 use std::sync::Arc;
1533
1534 use super::*;
1535 use crate::{
1536 display_map::{BlockDisposition, BlockProperties},
1537 Editor, MultiBuffer,
1538 };
1539 use settings::Settings;
1540 use util::test::sample_text;
1541
1542 #[gpui::test]
1543 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1544 cx.set_global(Settings::test(cx));
1545 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
1546 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1547 Editor::new(EditorMode::Full, buffer, None, None, None, cx)
1548 });
1549 let element = EditorElement::new(
1550 editor.downgrade(),
1551 editor.read(cx).style(cx),
1552 CursorShape::Bar,
1553 );
1554
1555 let layouts = editor.update(cx, |editor, cx| {
1556 let snapshot = editor.snapshot(cx);
1557 let mut presenter = cx.build_presenter(window_id, 30.);
1558 let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
1559 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1560 });
1561 assert_eq!(layouts.len(), 6);
1562 }
1563
1564 #[gpui::test]
1565 fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::MutableAppContext) {
1566 cx.set_global(Settings::test(cx));
1567 let buffer = MultiBuffer::build_simple("", cx);
1568 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1569 Editor::new(EditorMode::Full, buffer, None, None, None, cx)
1570 });
1571
1572 editor.update(cx, |editor, cx| {
1573 editor.set_placeholder_text("hello", cx);
1574 editor.insert_blocks(
1575 [BlockProperties {
1576 disposition: BlockDisposition::Above,
1577 height: 3,
1578 position: Anchor::min(),
1579 render: Arc::new(|_| Empty::new().boxed()),
1580 }],
1581 cx,
1582 );
1583
1584 // Blur the editor so that it displays placeholder text.
1585 cx.blur();
1586 });
1587
1588 let mut element = EditorElement::new(
1589 editor.downgrade(),
1590 editor.read(cx).style(cx),
1591 CursorShape::Bar,
1592 );
1593
1594 let mut scene = Scene::new(1.0);
1595 let mut presenter = cx.build_presenter(window_id, 30.);
1596 let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
1597 let (size, mut state) = element.layout(
1598 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
1599 &mut layout_cx,
1600 );
1601
1602 assert_eq!(state.line_layouts.len(), 4);
1603 assert_eq!(
1604 state
1605 .line_number_layouts
1606 .iter()
1607 .map(Option::is_some)
1608 .collect::<Vec<_>>(),
1609 &[false, false, false, true]
1610 );
1611
1612 // Don't panic.
1613 let bounds = RectF::new(Default::default(), size);
1614 let mut paint_cx = presenter.build_paint_context(&mut scene, cx);
1615 element.paint(bounds, bounds, &mut state, &mut paint_cx);
1616 }
1617}