1use super::{
2 DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
3 SelectPhase, Snapshot, MAX_LINE_LEN,
4};
5use clock::ReplicaId;
6use gpui::{
7 color::Color,
8 geometry::{
9 rect::RectF,
10 vector::{vec2f, Vector2F},
11 PathBuilder,
12 },
13 json::{self, ToJson},
14 keymap::Keystroke,
15 text_layout::{self, RunStyle, TextLayoutCache},
16 AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext,
17 MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
18};
19use json::json;
20use language::{Chunk, DiagnosticSeverity};
21use smallvec::SmallVec;
22use std::{
23 cmp::{self, Ordering},
24 collections::{BTreeMap, HashMap},
25 fmt::Write,
26 ops::Range,
27};
28
29pub struct EditorElement {
30 view: WeakViewHandle<Editor>,
31 settings: EditorSettings,
32}
33
34impl EditorElement {
35 pub fn new(view: WeakViewHandle<Editor>, settings: EditorSettings) -> Self {
36 Self { view, settings }
37 }
38
39 fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
40 self.view.upgrade(cx).unwrap().read(cx)
41 }
42
43 fn update_view<F, T>(&self, cx: &mut MutableAppContext, f: F) -> T
44 where
45 F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
46 {
47 self.view.upgrade(cx).unwrap().update(cx, f)
48 }
49
50 fn snapshot(&self, cx: &mut MutableAppContext) -> Snapshot {
51 self.update_view(cx, |view, cx| view.snapshot(cx))
52 }
53
54 fn mouse_down(
55 &self,
56 position: Vector2F,
57 cmd: bool,
58 layout: &mut LayoutState,
59 paint: &mut PaintState,
60 cx: &mut EventContext,
61 ) -> bool {
62 if paint.text_bounds.contains_point(position) {
63 let snapshot = self.snapshot(cx.app);
64 let position = paint.point_for_position(&snapshot, layout, position);
65 cx.dispatch_action(Select(SelectPhase::Begin { position, add: cmd }));
66 true
67 } else {
68 false
69 }
70 }
71
72 fn mouse_up(&self, _position: Vector2F, cx: &mut EventContext) -> bool {
73 if self.view(cx.app.as_ref()).is_selecting() {
74 cx.dispatch_action(Select(SelectPhase::End));
75 true
76 } else {
77 false
78 }
79 }
80
81 fn mouse_dragged(
82 &self,
83 position: Vector2F,
84 layout: &mut LayoutState,
85 paint: &mut PaintState,
86 cx: &mut EventContext,
87 ) -> bool {
88 let view = self.view(cx.app.as_ref());
89
90 if view.is_selecting() {
91 let rect = paint.text_bounds;
92 let mut scroll_delta = Vector2F::zero();
93
94 let vertical_margin = layout.line_height.min(rect.height() / 3.0);
95 let top = rect.origin_y() + vertical_margin;
96 let bottom = rect.lower_left().y() - vertical_margin;
97 if position.y() < top {
98 scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
99 }
100 if position.y() > bottom {
101 scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
102 }
103
104 let horizontal_margin = layout.line_height.min(rect.width() / 3.0);
105 let left = rect.origin_x() + horizontal_margin;
106 let right = rect.upper_right().x() - horizontal_margin;
107 if position.x() < left {
108 scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
109 left - position.x(),
110 ))
111 }
112 if position.x() > right {
113 scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
114 position.x() - right,
115 ))
116 }
117
118 let font_cache = cx.font_cache.clone();
119 let text_layout_cache = cx.text_layout_cache.clone();
120 let snapshot = self.snapshot(cx.app);
121 let position = paint.point_for_position(&snapshot, layout, position);
122
123 cx.dispatch_action(Select(SelectPhase::Update {
124 position,
125 scroll_position: (snapshot.scroll_position() + scroll_delta).clamp(
126 Vector2F::zero(),
127 layout.scroll_max(&font_cache, &text_layout_cache),
128 ),
129 }));
130 true
131 } else {
132 false
133 }
134 }
135
136 fn key_down(&self, chars: &str, keystroke: &Keystroke, cx: &mut EventContext) -> bool {
137 let view = self.view.upgrade(cx.app).unwrap();
138
139 if view.is_focused(cx.app) {
140 if chars.is_empty() {
141 false
142 } else {
143 if chars.chars().any(|c| c.is_control()) || keystroke.cmd || keystroke.ctrl {
144 false
145 } else {
146 cx.dispatch_action(Input(chars.to_string()));
147 true
148 }
149 }
150 } else {
151 false
152 }
153 }
154
155 fn scroll(
156 &self,
157 position: Vector2F,
158 mut delta: Vector2F,
159 precise: bool,
160 layout: &mut LayoutState,
161 paint: &mut PaintState,
162 cx: &mut EventContext,
163 ) -> bool {
164 if !paint.bounds.contains_point(position) {
165 return false;
166 }
167
168 let snapshot = self.snapshot(cx.app);
169 let font_cache = &cx.font_cache;
170 let layout_cache = &cx.text_layout_cache;
171 let max_glyph_width = layout.em_width;
172 if !precise {
173 delta *= vec2f(max_glyph_width, layout.line_height);
174 }
175
176 let scroll_position = snapshot.scroll_position();
177 let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
178 let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height;
179 let scroll_position = vec2f(x, y).clamp(
180 Vector2F::zero(),
181 layout.scroll_max(font_cache, layout_cache),
182 );
183
184 cx.dispatch_action(Scroll(scroll_position));
185
186 true
187 }
188
189 fn paint_background(
190 &self,
191 gutter_bounds: RectF,
192 text_bounds: RectF,
193 layout: &LayoutState,
194 cx: &mut PaintContext,
195 ) {
196 let bounds = gutter_bounds.union_rect(text_bounds);
197 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
198 let editor = self.view(cx.app);
199 let style = &self.settings.style;
200 cx.scene.push_quad(Quad {
201 bounds: gutter_bounds,
202 background: Some(style.gutter_background),
203 border: Border::new(0., Color::transparent_black()),
204 corner_radius: 0.,
205 });
206 cx.scene.push_quad(Quad {
207 bounds: text_bounds,
208 background: Some(style.background),
209 border: Border::new(0., Color::transparent_black()),
210 corner_radius: 0.,
211 });
212
213 if let EditorMode::Full = editor.mode {
214 let mut active_rows = layout.active_rows.iter().peekable();
215 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
216 let mut end_row = *start_row;
217 while active_rows.peek().map_or(false, |r| {
218 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
219 }) {
220 active_rows.next().unwrap();
221 end_row += 1;
222 }
223
224 if !contains_non_empty_selection {
225 let origin = vec2f(
226 bounds.origin_x(),
227 bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top,
228 );
229 let size = vec2f(
230 bounds.width(),
231 layout.line_height * (end_row - start_row + 1) as f32,
232 );
233 cx.scene.push_quad(Quad {
234 bounds: RectF::new(origin, size),
235 background: Some(style.active_line_background),
236 border: Border::default(),
237 corner_radius: 0.,
238 });
239 }
240 }
241 }
242 }
243
244 fn paint_gutter(
245 &mut self,
246 bounds: RectF,
247 visible_bounds: RectF,
248 layout: &LayoutState,
249 cx: &mut PaintContext,
250 ) {
251 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
252 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
253 if let Some(line) = line {
254 let line_origin = bounds.origin()
255 + vec2f(
256 bounds.width() - line.width() - layout.gutter_padding,
257 ix as f32 * layout.line_height - (scroll_top % layout.line_height),
258 );
259 line.paint(line_origin, visible_bounds, layout.line_height, cx);
260 }
261 }
262 }
263
264 fn paint_text(
265 &mut self,
266 bounds: RectF,
267 visible_bounds: RectF,
268 layout: &LayoutState,
269 cx: &mut PaintContext,
270 ) {
271 let view = self.view(cx.app);
272 let style = &self.settings.style;
273 let local_replica_id = view.replica_id(cx);
274 let scroll_position = layout.snapshot.scroll_position();
275 let start_row = scroll_position.y() as u32;
276 let scroll_top = scroll_position.y() * layout.line_height;
277 let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
278 let max_glyph_width = layout.em_width;
279 let scroll_left = scroll_position.x() * max_glyph_width;
280
281 cx.scene.push_layer(Some(bounds));
282
283 // Draw selections
284 let corner_radius = 2.5;
285 let mut cursors = SmallVec::<[Cursor; 32]>::new();
286
287 let content_origin = bounds.origin() + layout.text_offset;
288
289 for (replica_id, selections) in &layout.selections {
290 let style_ix = *replica_id as usize % (style.guest_selections.len() + 1);
291 let style = if style_ix == 0 {
292 &style.selection
293 } else {
294 &style.guest_selections[style_ix - 1]
295 };
296
297 for selection in selections {
298 if selection.start != selection.end {
299 let range_start = cmp::min(selection.start, selection.end);
300 let range_end = cmp::max(selection.start, selection.end);
301 let row_range = if range_end.column() == 0 {
302 cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
303 } else {
304 cmp::max(range_start.row(), start_row)
305 ..cmp::min(range_end.row() + 1, end_row)
306 };
307
308 let selection = Selection {
309 color: style.selection,
310 line_height: layout.line_height,
311 start_y: content_origin.y() + row_range.start as f32 * layout.line_height
312 - scroll_top,
313 lines: row_range
314 .into_iter()
315 .map(|row| {
316 let line_layout = &layout.line_layouts[(row - start_row) as usize];
317 SelectionLine {
318 start_x: if row == range_start.row() {
319 content_origin.x()
320 + line_layout.x_for_index(range_start.column() as usize)
321 - scroll_left
322 } else {
323 content_origin.x() - scroll_left
324 },
325 end_x: if row == range_end.row() {
326 content_origin.x()
327 + line_layout.x_for_index(range_end.column() as usize)
328 - scroll_left
329 } else {
330 content_origin.x()
331 + line_layout.width()
332 + corner_radius * 2.0
333 - scroll_left
334 },
335 }
336 })
337 .collect(),
338 };
339
340 selection.paint(bounds, cx.scene);
341 }
342
343 if view.show_local_cursors() || *replica_id != local_replica_id {
344 let cursor_position = selection.end;
345 if (start_row..end_row).contains(&cursor_position.row()) {
346 let cursor_row_layout =
347 &layout.line_layouts[(selection.end.row() - start_row) as usize];
348 let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
349 - scroll_left;
350 let y = selection.end.row() as f32 * layout.line_height - scroll_top;
351 cursors.push(Cursor {
352 color: style.cursor,
353 origin: content_origin + vec2f(x, y),
354 line_height: layout.line_height,
355 });
356 }
357 }
358 }
359 }
360
361 if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
362 // Draw glyphs
363 for (ix, line) in layout.line_layouts.iter().enumerate() {
364 let row = start_row + ix as u32;
365 line.paint(
366 content_origin
367 + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top),
368 visible_text_bounds,
369 layout.line_height,
370 cx,
371 );
372 }
373 }
374
375 cx.scene.push_layer(Some(bounds));
376 for cursor in cursors {
377 cursor.paint(cx);
378 }
379 cx.scene.pop_layer();
380
381 cx.scene.pop_layer();
382 }
383
384 fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 {
385 let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1;
386 let style = &self.settings.style;
387
388 cx.text_layout_cache
389 .layout_str(
390 "1".repeat(digit_count).as_str(),
391 style.text.font_size,
392 &[(
393 digit_count,
394 RunStyle {
395 font_id: style.text.font_id,
396 color: Color::black(),
397 underline: None,
398 },
399 )],
400 )
401 .width()
402 }
403
404 fn layout_line_numbers(
405 &self,
406 rows: Range<u32>,
407 active_rows: &BTreeMap<u32, bool>,
408 snapshot: &Snapshot,
409 cx: &LayoutContext,
410 ) -> Vec<Option<text_layout::Line>> {
411 let style = &self.settings.style;
412 let mut layouts = Vec::with_capacity(rows.len());
413 let mut line_number = String::new();
414 for (ix, buffer_row) in snapshot
415 .buffer_rows(rows.start)
416 .take((rows.end - rows.start) as usize)
417 .enumerate()
418 {
419 let display_row = rows.start + ix as u32;
420 let color = if active_rows.contains_key(&display_row) {
421 style.line_number_active
422 } else {
423 style.line_number
424 };
425 if let Some(buffer_row) = buffer_row {
426 line_number.clear();
427 write!(&mut line_number, "{}", buffer_row + 1).unwrap();
428 layouts.push(Some(cx.text_layout_cache.layout_str(
429 &line_number,
430 style.text.font_size,
431 &[(
432 line_number.len(),
433 RunStyle {
434 font_id: style.text.font_id,
435 color,
436 underline: None,
437 },
438 )],
439 )));
440 } else {
441 layouts.push(None);
442 }
443 }
444
445 layouts
446 }
447
448 fn layout_lines(
449 &mut self,
450 mut rows: Range<u32>,
451 snapshot: &mut Snapshot,
452 cx: &LayoutContext,
453 ) -> Vec<text_layout::Line> {
454 rows.end = cmp::min(rows.end, snapshot.max_point().row() + 1);
455 if rows.start >= rows.end {
456 return Vec::new();
457 }
458
459 // When the editor is empty and unfocused, then show the placeholder.
460 if snapshot.is_empty() && !snapshot.is_focused() {
461 let placeholder_style = self.settings.style.placeholder_text();
462 let placeholder_text = snapshot.placeholder_text();
463 let placeholder_lines = placeholder_text
464 .as_ref()
465 .map_or("", AsRef::as_ref)
466 .split('\n')
467 .skip(rows.start as usize)
468 .take(rows.len());
469 return placeholder_lines
470 .map(|line| {
471 cx.text_layout_cache.layout_str(
472 line,
473 placeholder_style.font_size,
474 &[(
475 line.len(),
476 RunStyle {
477 font_id: placeholder_style.font_id,
478 color: placeholder_style.color,
479 underline: None,
480 },
481 )],
482 )
483 })
484 .collect();
485 }
486
487 let style = &self.settings.style;
488 let mut prev_font_properties = style.text.font_properties.clone();
489 let mut prev_font_id = style.text.font_id;
490
491 let mut layouts = Vec::with_capacity(rows.len());
492 let mut line = String::new();
493 let mut styles = Vec::new();
494 let mut row = rows.start;
495 let mut line_exceeded_max_len = false;
496 let chunks = snapshot.chunks(rows.clone());
497
498 let newline_chunk = Chunk {
499 text: "\n",
500 ..Default::default()
501 };
502 'outer: for chunk in chunks.chain([newline_chunk]) {
503 for (ix, mut line_chunk) in chunk.text.split('\n').enumerate() {
504 if ix > 0 {
505 layouts.push(cx.text_layout_cache.layout_str(
506 &line,
507 style.text.font_size,
508 &styles,
509 ));
510 line.clear();
511 styles.clear();
512 row += 1;
513 line_exceeded_max_len = false;
514 if row == rows.end {
515 break 'outer;
516 }
517 }
518
519 if !line_chunk.is_empty() && !line_exceeded_max_len {
520 let highlight_style = chunk
521 .highlight_id
522 .style(&style.syntax)
523 .unwrap_or(style.text.clone().into());
524 // Avoid a lookup if the font properties match the previous ones.
525 let font_id = if highlight_style.font_properties == prev_font_properties {
526 prev_font_id
527 } else {
528 cx.font_cache
529 .select_font(
530 style.text.font_family_id,
531 &highlight_style.font_properties,
532 )
533 .unwrap_or(style.text.font_id)
534 };
535
536 if line.len() + line_chunk.len() > MAX_LINE_LEN {
537 let mut chunk_len = MAX_LINE_LEN - line.len();
538 while !line_chunk.is_char_boundary(chunk_len) {
539 chunk_len -= 1;
540 }
541 line_chunk = &line_chunk[..chunk_len];
542 line_exceeded_max_len = true;
543 }
544
545 let underline = if let Some(severity) = chunk.diagnostic {
546 match severity {
547 DiagnosticSeverity::ERROR => Some(style.error_underline),
548 DiagnosticSeverity::WARNING => Some(style.warning_underline),
549 DiagnosticSeverity::INFORMATION => Some(style.information_underline),
550 DiagnosticSeverity::HINT => Some(style.hint_underline),
551 _ => highlight_style.underline,
552 }
553 } else {
554 highlight_style.underline
555 };
556
557 line.push_str(line_chunk);
558 styles.push((
559 line_chunk.len(),
560 RunStyle {
561 font_id,
562 color: highlight_style.color,
563 underline,
564 },
565 ));
566 prev_font_id = font_id;
567 prev_font_properties = highlight_style.font_properties;
568 }
569 }
570 }
571
572 layouts
573 }
574}
575
576impl Element for EditorElement {
577 type LayoutState = Option<LayoutState>;
578 type PaintState = Option<PaintState>;
579
580 fn layout(
581 &mut self,
582 constraint: SizeConstraint,
583 cx: &mut LayoutContext,
584 ) -> (Vector2F, Self::LayoutState) {
585 let mut size = constraint.max;
586 if size.x().is_infinite() {
587 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
588 }
589
590 let snapshot = self.snapshot(cx.app);
591 let style = self.settings.style.clone();
592 let line_height = style.text.line_height(cx.font_cache);
593
594 let gutter_padding;
595 let gutter_width;
596 if snapshot.mode == EditorMode::Full {
597 gutter_padding = style.text.em_width(cx.font_cache);
598 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
599 } else {
600 gutter_padding = 0.0;
601 gutter_width = 0.0
602 };
603
604 let text_width = size.x() - gutter_width;
605 let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.);
606 let em_width = style.text.em_width(cx.font_cache);
607 let overscroll = vec2f(em_width, 0.);
608 let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
609 let snapshot = self.update_view(cx.app, |view, cx| {
610 if view.set_wrap_width(wrap_width, cx) {
611 view.snapshot(cx)
612 } else {
613 snapshot
614 }
615 });
616
617 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
618 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
619 size.set_y(
620 scroll_height
621 .min(constraint.max_along(Axis::Vertical))
622 .max(constraint.min_along(Axis::Vertical))
623 .min(line_height * max_lines as f32),
624 )
625 } else if size.y().is_infinite() {
626 size.set_y(scroll_height);
627 }
628 let gutter_size = vec2f(gutter_width, size.y());
629 let text_size = vec2f(text_width, size.y());
630
631 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
632 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
633 let snapshot = view.snapshot(cx);
634 (autoscroll_horizontally, snapshot)
635 });
636
637 let scroll_position = snapshot.scroll_position();
638 let start_row = scroll_position.y() as u32;
639 let scroll_top = scroll_position.y() * line_height;
640 let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
641
642 let mut selections = HashMap::new();
643 let mut active_rows = BTreeMap::new();
644 self.update_view(cx.app, |view, cx| {
645 for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
646 let mut set = Vec::new();
647 for selection in view.selections_in_range(
648 selection_set_id,
649 DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
650 cx,
651 ) {
652 set.push(selection.clone());
653 if selection_set_id == view.selection_set_id {
654 let is_empty = selection.start == selection.end;
655 let mut selection_start;
656 let mut selection_end;
657 if selection.start < selection.end {
658 selection_start = selection.start;
659 selection_end = selection.end;
660 } else {
661 selection_start = selection.end;
662 selection_end = selection.start;
663 };
664 selection_start = snapshot.prev_row_boundary(selection_start).0;
665 selection_end = snapshot.next_row_boundary(selection_end).0;
666 for row in cmp::max(selection_start.row(), start_row)
667 ..=cmp::min(selection_end.row(), end_row)
668 {
669 let contains_non_empty_selection =
670 active_rows.entry(row).or_insert(!is_empty);
671 *contains_non_empty_selection |= !is_empty;
672 }
673 }
674 }
675
676 selections.insert(selection_set_id.replica_id, set);
677 }
678 });
679
680 let line_number_layouts = if snapshot.mode == EditorMode::Full {
681 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
682 } else {
683 Vec::new()
684 };
685
686 let mut max_visible_line_width = 0.0;
687 let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
688 for line in &line_layouts {
689 if line.width() > max_visible_line_width {
690 max_visible_line_width = line.width();
691 }
692 }
693
694 let mut layout = LayoutState {
695 size,
696 gutter_size,
697 gutter_padding,
698 text_size,
699 overscroll,
700 text_offset,
701 snapshot,
702 style: self.settings.style.clone(),
703 active_rows,
704 line_layouts,
705 line_number_layouts,
706 line_height,
707 em_width,
708 selections,
709 max_visible_line_width,
710 };
711
712 let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x();
713 let scroll_width = layout.scroll_width(cx.text_layout_cache);
714 let max_glyph_width = style.text.em_width(&cx.font_cache);
715 self.update_view(cx.app, |view, cx| {
716 let clamped = view.clamp_scroll_left(scroll_max);
717 let autoscrolled;
718 if autoscroll_horizontally {
719 autoscrolled = view.autoscroll_horizontally(
720 start_row,
721 layout.text_size.x(),
722 scroll_width,
723 max_glyph_width,
724 &layout.line_layouts,
725 cx,
726 );
727 } else {
728 autoscrolled = false;
729 }
730
731 if clamped || autoscrolled {
732 layout.snapshot = view.snapshot(cx);
733 }
734 });
735
736 (size, Some(layout))
737 }
738
739 fn paint(
740 &mut self,
741 bounds: RectF,
742 visible_bounds: RectF,
743 layout: &mut Self::LayoutState,
744 cx: &mut PaintContext,
745 ) -> Self::PaintState {
746 if let Some(layout) = layout {
747 cx.scene.push_layer(Some(bounds));
748
749 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
750 let text_bounds = RectF::new(
751 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
752 layout.text_size,
753 );
754
755 self.paint_background(gutter_bounds, text_bounds, layout, cx);
756 if layout.gutter_size.x() > 0. {
757 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
758 }
759 self.paint_text(text_bounds, visible_bounds, layout, cx);
760
761 cx.scene.pop_layer();
762
763 Some(PaintState {
764 bounds,
765 text_bounds,
766 })
767 } else {
768 None
769 }
770 }
771
772 fn dispatch_event(
773 &mut self,
774 event: &Event,
775 _: RectF,
776 layout: &mut Self::LayoutState,
777 paint: &mut Self::PaintState,
778 cx: &mut EventContext,
779 ) -> bool {
780 if let (Some(layout), Some(paint)) = (layout, paint) {
781 match event {
782 Event::LeftMouseDown { position, cmd } => {
783 self.mouse_down(*position, *cmd, layout, paint, cx)
784 }
785 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
786 Event::LeftMouseDragged { position } => {
787 self.mouse_dragged(*position, layout, paint, cx)
788 }
789 Event::ScrollWheel {
790 position,
791 delta,
792 precise,
793 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
794 Event::KeyDown {
795 chars, keystroke, ..
796 } => self.key_down(chars, keystroke, cx),
797 _ => false,
798 }
799 } else {
800 false
801 }
802 }
803
804 fn debug(
805 &self,
806 bounds: RectF,
807 _: &Self::LayoutState,
808 _: &Self::PaintState,
809 _: &gpui::DebugContext,
810 ) -> json::Value {
811 json!({
812 "type": "BufferElement",
813 "bounds": bounds.to_json()
814 })
815 }
816}
817
818pub struct LayoutState {
819 size: Vector2F,
820 gutter_size: Vector2F,
821 gutter_padding: f32,
822 text_size: Vector2F,
823 style: EditorStyle,
824 snapshot: Snapshot,
825 active_rows: BTreeMap<u32, bool>,
826 line_layouts: Vec<text_layout::Line>,
827 line_number_layouts: Vec<Option<text_layout::Line>>,
828 line_height: f32,
829 em_width: f32,
830 selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
831 overscroll: Vector2F,
832 text_offset: Vector2F,
833 max_visible_line_width: f32,
834}
835
836impl LayoutState {
837 fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 {
838 let row = self.snapshot.longest_row();
839 let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width();
840 longest_line_width.max(self.max_visible_line_width) + self.overscroll.x()
841 }
842
843 fn scroll_max(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> Vector2F {
844 let text_width = self.text_size.x();
845 let scroll_width = self.scroll_width(layout_cache);
846 let em_width = self.style.text.em_width(font_cache);
847 let max_row = self.snapshot.max_point().row();
848
849 vec2f(
850 ((scroll_width - text_width) / em_width).max(0.0),
851 max_row.saturating_sub(1) as f32,
852 )
853 }
854
855 pub fn layout_line(
856 &self,
857 row: u32,
858 snapshot: &Snapshot,
859 layout_cache: &TextLayoutCache,
860 ) -> text_layout::Line {
861 let mut line = snapshot.line(row);
862
863 if line.len() > MAX_LINE_LEN {
864 let mut len = MAX_LINE_LEN;
865 while !line.is_char_boundary(len) {
866 len -= 1;
867 }
868 line.truncate(len);
869 }
870
871 layout_cache.layout_str(
872 &line,
873 self.style.text.font_size,
874 &[(
875 snapshot.line_len(row) as usize,
876 RunStyle {
877 font_id: self.style.text.font_id,
878 color: Color::black(),
879 underline: None,
880 },
881 )],
882 )
883 }
884}
885
886pub struct PaintState {
887 bounds: RectF,
888 text_bounds: RectF,
889}
890
891impl PaintState {
892 fn point_for_position(
893 &self,
894 snapshot: &Snapshot,
895 layout: &LayoutState,
896 position: Vector2F,
897 ) -> DisplayPoint {
898 let scroll_position = snapshot.scroll_position();
899 let position = position - self.text_bounds.origin();
900 let y = position.y().max(0.0).min(layout.size.y());
901 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
902 let row = cmp::min(row, snapshot.max_point().row());
903 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
904 let x = position.x() + (scroll_position.x() * layout.em_width);
905
906 let column = if x >= 0.0 {
907 line.index_for_x(x)
908 .map(|ix| ix as u32)
909 .unwrap_or(snapshot.line_len(row))
910 } else {
911 0
912 };
913
914 DisplayPoint::new(row, column)
915 }
916}
917
918struct Cursor {
919 origin: Vector2F,
920 line_height: f32,
921 color: Color,
922}
923
924impl Cursor {
925 fn paint(&self, cx: &mut PaintContext) {
926 cx.scene.push_quad(Quad {
927 bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
928 background: Some(self.color),
929 border: Border::new(0., Color::black()),
930 corner_radius: 0.,
931 });
932 }
933}
934
935#[derive(Debug)]
936struct Selection {
937 start_y: f32,
938 line_height: f32,
939 lines: Vec<SelectionLine>,
940 color: Color,
941}
942
943#[derive(Debug)]
944struct SelectionLine {
945 start_x: f32,
946 end_x: f32,
947}
948
949impl Selection {
950 fn paint(&self, bounds: RectF, scene: &mut Scene) {
951 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
952 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
953 self.paint_lines(
954 self.start_y + self.line_height,
955 &self.lines[1..],
956 bounds,
957 scene,
958 );
959 } else {
960 self.paint_lines(self.start_y, &self.lines, bounds, scene);
961 }
962 }
963
964 fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], bounds: RectF, scene: &mut Scene) {
965 if lines.is_empty() {
966 return;
967 }
968
969 let mut path = PathBuilder::new();
970 let corner_radius = 0.15 * self.line_height;
971 let first_line = lines.first().unwrap();
972 let last_line = lines.last().unwrap();
973
974 let first_top_left = vec2f(first_line.start_x, start_y);
975 let first_top_right = vec2f(first_line.end_x, start_y);
976
977 let curve_height = vec2f(0., corner_radius);
978 let curve_width = |start_x: f32, end_x: f32| {
979 let max = (end_x - start_x) / 2.;
980 let width = if max < corner_radius {
981 max
982 } else {
983 corner_radius
984 };
985
986 vec2f(width, 0.)
987 };
988
989 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
990 path.reset(first_top_right - top_curve_width);
991 path.curve_to(first_top_right + curve_height, first_top_right);
992
993 let mut iter = lines.iter().enumerate().peekable();
994 while let Some((ix, line)) = iter.next() {
995 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
996
997 if let Some((_, next_line)) = iter.peek() {
998 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
999
1000 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
1001 Ordering::Equal => {
1002 path.line_to(bottom_right);
1003 }
1004 Ordering::Less => {
1005 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
1006 path.line_to(bottom_right - curve_height);
1007 path.curve_to(bottom_right - curve_width, bottom_right);
1008 path.line_to(next_top_right + curve_width);
1009 path.curve_to(next_top_right + curve_height, next_top_right);
1010 }
1011 Ordering::Greater => {
1012 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
1013 path.line_to(bottom_right - curve_height);
1014 path.curve_to(bottom_right + curve_width, bottom_right);
1015 path.line_to(next_top_right - curve_width);
1016 path.curve_to(next_top_right + curve_height, next_top_right);
1017 }
1018 }
1019 } else {
1020 let curve_width = curve_width(line.start_x, line.end_x);
1021 path.line_to(bottom_right - curve_height);
1022 path.curve_to(bottom_right - curve_width, bottom_right);
1023
1024 let bottom_left = vec2f(line.start_x, bottom_right.y());
1025 path.line_to(bottom_left + curve_width);
1026 path.curve_to(bottom_left - curve_height, bottom_left);
1027 }
1028 }
1029
1030 if first_line.start_x > last_line.start_x {
1031 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1032 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1033 path.line_to(second_top_left + curve_height);
1034 path.curve_to(second_top_left + curve_width, second_top_left);
1035 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1036 path.line_to(first_bottom_left - curve_width);
1037 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1038 }
1039
1040 path.line_to(first_top_left + curve_height);
1041 path.curve_to(first_top_left + top_curve_width, first_top_left);
1042 path.line_to(first_top_right - top_curve_width);
1043
1044 scene.push_path(path.build(self.color, Some(bounds)));
1045 }
1046}
1047
1048fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1049 delta.powf(1.5) / 100.0
1050}
1051
1052fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1053 delta.powf(1.2) / 300.0
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059 use crate::{
1060 test::sample_text,
1061 {Editor, EditorSettings},
1062 };
1063 use language::Buffer;
1064
1065 #[gpui::test]
1066 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1067 let settings = EditorSettings::test(cx);
1068
1069 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
1070 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1071 Editor::for_buffer(
1072 buffer,
1073 {
1074 let settings = settings.clone();
1075 move |_| settings.clone()
1076 },
1077 cx,
1078 )
1079 });
1080 let element = EditorElement::new(editor.downgrade(), settings);
1081
1082 let layouts = editor.update(cx, |editor, cx| {
1083 let snapshot = editor.snapshot(cx);
1084 let mut presenter = cx.build_presenter(window_id, 30.);
1085 let mut layout_cx = presenter.build_layout_context(false, cx);
1086 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1087 });
1088 assert_eq!(layouts.len(), 6);
1089 }
1090}