1use super::{
2 DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
3 SelectPhase, Snapshot, MAX_LINE_LEN,
4};
5use buffer::HighlightId;
6use clock::ReplicaId;
7use gpui::{
8 color::Color,
9 geometry::{
10 rect::RectF,
11 vector::{vec2f, Vector2F},
12 PathBuilder,
13 },
14 json::{self, ToJson},
15 keymap::Keystroke,
16 text_layout::{self, RunStyle, TextLayoutCache},
17 AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext,
18 MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
19};
20use json::json;
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: false,
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, soft_wrapped)) 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 soft_wrapped {
426 layouts.push(None);
427 } else {
428 line_number.clear();
429 write!(&mut line_number, "{}", buffer_row + 1).unwrap();
430 layouts.push(Some(cx.text_layout_cache.layout_str(
431 &line_number,
432 style.text.font_size,
433 &[(
434 line_number.len(),
435 RunStyle {
436 font_id: style.text.font_id,
437 color,
438 underline: false,
439 },
440 )],
441 )));
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: false,
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.highlighted_chunks_for_rows(rows.clone());
497
498 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) {
499 for (ix, mut line_chunk) in chunk.split('\n').enumerate() {
500 if ix > 0 {
501 layouts.push(cx.text_layout_cache.layout_str(
502 &line,
503 style.text.font_size,
504 &styles,
505 ));
506 line.clear();
507 styles.clear();
508 row += 1;
509 line_exceeded_max_len = false;
510 if row == rows.end {
511 break 'outer;
512 }
513 }
514
515 if !line_chunk.is_empty() && !line_exceeded_max_len {
516 let highlight_style = style_ix
517 .style(&style.syntax)
518 .unwrap_or(style.text.clone().into());
519 // Avoid a lookup if the font properties match the previous ones.
520 let font_id = if highlight_style.font_properties == prev_font_properties {
521 prev_font_id
522 } else {
523 cx.font_cache
524 .select_font(
525 style.text.font_family_id,
526 &highlight_style.font_properties,
527 )
528 .unwrap_or(style.text.font_id)
529 };
530
531 if line.len() + line_chunk.len() > MAX_LINE_LEN {
532 let mut chunk_len = MAX_LINE_LEN - line.len();
533 while !line_chunk.is_char_boundary(chunk_len) {
534 chunk_len -= 1;
535 }
536 line_chunk = &line_chunk[..chunk_len];
537 line_exceeded_max_len = true;
538 }
539
540 line.push_str(line_chunk);
541 styles.push((
542 line_chunk.len(),
543 RunStyle {
544 font_id,
545 color: highlight_style.color,
546 underline: highlight_style.underline,
547 },
548 ));
549 prev_font_id = font_id;
550 prev_font_properties = highlight_style.font_properties;
551 }
552 }
553 }
554
555 layouts
556 }
557}
558
559impl Element for EditorElement {
560 type LayoutState = Option<LayoutState>;
561 type PaintState = Option<PaintState>;
562
563 fn layout(
564 &mut self,
565 constraint: SizeConstraint,
566 cx: &mut LayoutContext,
567 ) -> (Vector2F, Self::LayoutState) {
568 let mut size = constraint.max;
569 if size.x().is_infinite() {
570 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
571 }
572
573 let snapshot = self.snapshot(cx.app);
574 let style = self.settings.style.clone();
575 let line_height = style.text.line_height(cx.font_cache);
576
577 let gutter_padding;
578 let gutter_width;
579 if snapshot.mode == EditorMode::Full {
580 gutter_padding = style.text.em_width(cx.font_cache);
581 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
582 } else {
583 gutter_padding = 0.0;
584 gutter_width = 0.0
585 };
586
587 let text_width = size.x() - gutter_width;
588 let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.);
589 let em_width = style.text.em_width(cx.font_cache);
590 let overscroll = vec2f(em_width, 0.);
591 let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
592 let snapshot = self.update_view(cx.app, |view, cx| {
593 if view.set_wrap_width(wrap_width, cx) {
594 view.snapshot(cx)
595 } else {
596 snapshot
597 }
598 });
599
600 let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
601 if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
602 size.set_y(
603 scroll_height
604 .min(constraint.max_along(Axis::Vertical))
605 .max(constraint.min_along(Axis::Vertical))
606 .min(line_height * max_lines as f32),
607 )
608 } else if size.y().is_infinite() {
609 size.set_y(scroll_height);
610 }
611 let gutter_size = vec2f(gutter_width, size.y());
612 let text_size = vec2f(text_width, size.y());
613
614 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
615 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
616 let snapshot = view.snapshot(cx);
617 (autoscroll_horizontally, snapshot)
618 });
619
620 let scroll_position = snapshot.scroll_position();
621 let start_row = scroll_position.y() as u32;
622 let scroll_top = scroll_position.y() * line_height;
623 let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
624
625 let mut selections = HashMap::new();
626 let mut active_rows = BTreeMap::new();
627 self.update_view(cx.app, |view, cx| {
628 for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
629 let mut set = Vec::new();
630 for selection in view.selections_in_range(
631 selection_set_id,
632 DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
633 cx,
634 ) {
635 set.push(selection.clone());
636 if selection_set_id == view.selection_set_id {
637 let is_empty = selection.start == selection.end;
638 let mut selection_start;
639 let mut selection_end;
640 if selection.start < selection.end {
641 selection_start = selection.start;
642 selection_end = selection.end;
643 } else {
644 selection_start = selection.end;
645 selection_end = selection.start;
646 };
647 selection_start = snapshot.prev_row_boundary(selection_start).0;
648 selection_end = snapshot.next_row_boundary(selection_end).0;
649 for row in cmp::max(selection_start.row(), start_row)
650 ..=cmp::min(selection_end.row(), end_row)
651 {
652 let contains_non_empty_selection =
653 active_rows.entry(row).or_insert(!is_empty);
654 *contains_non_empty_selection |= !is_empty;
655 }
656 }
657 }
658
659 selections.insert(selection_set_id.replica_id, set);
660 }
661 });
662
663 let line_number_layouts = if snapshot.mode == EditorMode::Full {
664 self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx)
665 } else {
666 Vec::new()
667 };
668
669 let mut max_visible_line_width = 0.0;
670 let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
671 for line in &line_layouts {
672 if line.width() > max_visible_line_width {
673 max_visible_line_width = line.width();
674 }
675 }
676
677 let mut layout = LayoutState {
678 size,
679 gutter_size,
680 gutter_padding,
681 text_size,
682 overscroll,
683 text_offset,
684 snapshot,
685 style: self.settings.style.clone(),
686 active_rows,
687 line_layouts,
688 line_number_layouts,
689 line_height,
690 em_width,
691 selections,
692 max_visible_line_width,
693 };
694
695 let scroll_max = layout.scroll_max(cx.font_cache, cx.text_layout_cache).x();
696 let scroll_width = layout.scroll_width(cx.text_layout_cache);
697 let max_glyph_width = style.text.em_width(&cx.font_cache);
698 self.update_view(cx.app, |view, cx| {
699 let clamped = view.clamp_scroll_left(scroll_max);
700 let autoscrolled;
701 if autoscroll_horizontally {
702 autoscrolled = view.autoscroll_horizontally(
703 start_row,
704 layout.text_size.x(),
705 scroll_width,
706 max_glyph_width,
707 &layout.line_layouts,
708 cx,
709 );
710 } else {
711 autoscrolled = false;
712 }
713
714 if clamped || autoscrolled {
715 layout.snapshot = view.snapshot(cx);
716 }
717 });
718
719 (size, Some(layout))
720 }
721
722 fn paint(
723 &mut self,
724 bounds: RectF,
725 visible_bounds: RectF,
726 layout: &mut Self::LayoutState,
727 cx: &mut PaintContext,
728 ) -> Self::PaintState {
729 if let Some(layout) = layout {
730 cx.scene.push_layer(Some(bounds));
731
732 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
733 let text_bounds = RectF::new(
734 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
735 layout.text_size,
736 );
737
738 self.paint_background(gutter_bounds, text_bounds, layout, cx);
739 if layout.gutter_size.x() > 0. {
740 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
741 }
742 self.paint_text(text_bounds, visible_bounds, layout, cx);
743
744 cx.scene.pop_layer();
745
746 Some(PaintState {
747 bounds,
748 text_bounds,
749 })
750 } else {
751 None
752 }
753 }
754
755 fn dispatch_event(
756 &mut self,
757 event: &Event,
758 _: RectF,
759 layout: &mut Self::LayoutState,
760 paint: &mut Self::PaintState,
761 cx: &mut EventContext,
762 ) -> bool {
763 if let (Some(layout), Some(paint)) = (layout, paint) {
764 match event {
765 Event::LeftMouseDown { position, cmd } => {
766 self.mouse_down(*position, *cmd, layout, paint, cx)
767 }
768 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
769 Event::LeftMouseDragged { position } => {
770 self.mouse_dragged(*position, layout, paint, cx)
771 }
772 Event::ScrollWheel {
773 position,
774 delta,
775 precise,
776 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
777 Event::KeyDown {
778 chars, keystroke, ..
779 } => self.key_down(chars, keystroke, cx),
780 _ => false,
781 }
782 } else {
783 false
784 }
785 }
786
787 fn debug(
788 &self,
789 bounds: RectF,
790 _: &Self::LayoutState,
791 _: &Self::PaintState,
792 _: &gpui::DebugContext,
793 ) -> json::Value {
794 json!({
795 "type": "BufferElement",
796 "bounds": bounds.to_json()
797 })
798 }
799}
800
801pub struct LayoutState {
802 size: Vector2F,
803 gutter_size: Vector2F,
804 gutter_padding: f32,
805 text_size: Vector2F,
806 style: EditorStyle,
807 snapshot: Snapshot,
808 active_rows: BTreeMap<u32, bool>,
809 line_layouts: Vec<text_layout::Line>,
810 line_number_layouts: Vec<Option<text_layout::Line>>,
811 line_height: f32,
812 em_width: f32,
813 selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
814 overscroll: Vector2F,
815 text_offset: Vector2F,
816 max_visible_line_width: f32,
817}
818
819impl LayoutState {
820 fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 {
821 let row = self.snapshot.longest_row();
822 let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width();
823 longest_line_width.max(self.max_visible_line_width) + self.overscroll.x()
824 }
825
826 fn scroll_max(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> Vector2F {
827 let text_width = self.text_size.x();
828 let scroll_width = self.scroll_width(layout_cache);
829 let em_width = self.style.text.em_width(font_cache);
830 let max_row = self.snapshot.max_point().row();
831
832 vec2f(
833 ((scroll_width - text_width) / em_width).max(0.0),
834 max_row.saturating_sub(1) as f32,
835 )
836 }
837
838 pub fn layout_line(
839 &self,
840 row: u32,
841 snapshot: &Snapshot,
842 layout_cache: &TextLayoutCache,
843 ) -> text_layout::Line {
844 let mut line = snapshot.line(row);
845
846 if line.len() > MAX_LINE_LEN {
847 let mut len = MAX_LINE_LEN;
848 while !line.is_char_boundary(len) {
849 len -= 1;
850 }
851 line.truncate(len);
852 }
853
854 layout_cache.layout_str(
855 &line,
856 self.style.text.font_size,
857 &[(
858 snapshot.line_len(row) as usize,
859 RunStyle {
860 font_id: self.style.text.font_id,
861 color: Color::black(),
862 underline: false,
863 },
864 )],
865 )
866 }
867}
868
869pub struct PaintState {
870 bounds: RectF,
871 text_bounds: RectF,
872}
873
874impl PaintState {
875 fn point_for_position(
876 &self,
877 snapshot: &Snapshot,
878 layout: &LayoutState,
879 position: Vector2F,
880 ) -> DisplayPoint {
881 let scroll_position = snapshot.scroll_position();
882 let position = position - self.text_bounds.origin();
883 let y = position.y().max(0.0).min(layout.size.y());
884 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
885 let row = cmp::min(row, snapshot.max_point().row());
886 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
887 let x = position.x() + (scroll_position.x() * layout.em_width);
888
889 let column = if x >= 0.0 {
890 line.index_for_x(x)
891 .map(|ix| ix as u32)
892 .unwrap_or(snapshot.line_len(row))
893 } else {
894 0
895 };
896
897 DisplayPoint::new(row, column)
898 }
899}
900
901struct Cursor {
902 origin: Vector2F,
903 line_height: f32,
904 color: Color,
905}
906
907impl Cursor {
908 fn paint(&self, cx: &mut PaintContext) {
909 cx.scene.push_quad(Quad {
910 bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
911 background: Some(self.color),
912 border: Border::new(0., Color::black()),
913 corner_radius: 0.,
914 });
915 }
916}
917
918#[derive(Debug)]
919struct Selection {
920 start_y: f32,
921 line_height: f32,
922 lines: Vec<SelectionLine>,
923 color: Color,
924}
925
926#[derive(Debug)]
927struct SelectionLine {
928 start_x: f32,
929 end_x: f32,
930}
931
932impl Selection {
933 fn paint(&self, bounds: RectF, scene: &mut Scene) {
934 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
935 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
936 self.paint_lines(
937 self.start_y + self.line_height,
938 &self.lines[1..],
939 bounds,
940 scene,
941 );
942 } else {
943 self.paint_lines(self.start_y, &self.lines, bounds, scene);
944 }
945 }
946
947 fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], bounds: RectF, scene: &mut Scene) {
948 if lines.is_empty() {
949 return;
950 }
951
952 let mut path = PathBuilder::new();
953 let corner_radius = 0.15 * self.line_height;
954 let first_line = lines.first().unwrap();
955 let last_line = lines.last().unwrap();
956
957 let first_top_left = vec2f(first_line.start_x, start_y);
958 let first_top_right = vec2f(first_line.end_x, start_y);
959
960 let curve_height = vec2f(0., corner_radius);
961 let curve_width = |start_x: f32, end_x: f32| {
962 let max = (end_x - start_x) / 2.;
963 let width = if max < corner_radius {
964 max
965 } else {
966 corner_radius
967 };
968
969 vec2f(width, 0.)
970 };
971
972 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
973 path.reset(first_top_right - top_curve_width);
974 path.curve_to(first_top_right + curve_height, first_top_right);
975
976 let mut iter = lines.iter().enumerate().peekable();
977 while let Some((ix, line)) = iter.next() {
978 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
979
980 if let Some((_, next_line)) = iter.peek() {
981 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
982
983 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
984 Ordering::Equal => {
985 path.line_to(bottom_right);
986 }
987 Ordering::Less => {
988 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
989 path.line_to(bottom_right - curve_height);
990 path.curve_to(bottom_right - curve_width, bottom_right);
991 path.line_to(next_top_right + curve_width);
992 path.curve_to(next_top_right + curve_height, next_top_right);
993 }
994 Ordering::Greater => {
995 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
996 path.line_to(bottom_right - curve_height);
997 path.curve_to(bottom_right + curve_width, bottom_right);
998 path.line_to(next_top_right - curve_width);
999 path.curve_to(next_top_right + curve_height, next_top_right);
1000 }
1001 }
1002 } else {
1003 let curve_width = curve_width(line.start_x, line.end_x);
1004 path.line_to(bottom_right - curve_height);
1005 path.curve_to(bottom_right - curve_width, bottom_right);
1006
1007 let bottom_left = vec2f(line.start_x, bottom_right.y());
1008 path.line_to(bottom_left + curve_width);
1009 path.curve_to(bottom_left - curve_height, bottom_left);
1010 }
1011 }
1012
1013 if first_line.start_x > last_line.start_x {
1014 let curve_width = curve_width(last_line.start_x, first_line.start_x);
1015 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
1016 path.line_to(second_top_left + curve_height);
1017 path.curve_to(second_top_left + curve_width, second_top_left);
1018 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
1019 path.line_to(first_bottom_left - curve_width);
1020 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
1021 }
1022
1023 path.line_to(first_top_left + curve_height);
1024 path.curve_to(first_top_left + top_curve_width, first_top_left);
1025 path.line_to(first_top_right - top_curve_width);
1026
1027 scene.push_path(path.build(self.color, Some(bounds)));
1028 }
1029}
1030
1031fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
1032 delta.powf(1.5) / 100.0
1033}
1034
1035fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
1036 delta.powf(1.2) / 300.0
1037}
1038
1039#[cfg(test)]
1040mod tests {
1041 use super::*;
1042 use crate::{
1043 test::sample_text,
1044 {Editor, EditorSettings},
1045 };
1046 use buffer::Buffer;
1047
1048 #[gpui::test]
1049 fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
1050 let settings = EditorSettings::test(cx);
1051
1052 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx));
1053 let (window_id, editor) = cx.add_window(Default::default(), |cx| {
1054 Editor::for_buffer(
1055 buffer,
1056 {
1057 let settings = settings.clone();
1058 move |_| settings.clone()
1059 },
1060 cx,
1061 )
1062 });
1063 let element = EditorElement::new(editor.downgrade(), settings);
1064
1065 let layouts = editor.update(cx, |editor, cx| {
1066 let snapshot = editor.snapshot(cx);
1067 let mut presenter = cx.build_presenter(window_id, 30.);
1068 let mut layout_cx = presenter.build_layout_context(false, cx);
1069 element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx)
1070 });
1071 assert_eq!(layouts.len(), 6);
1072 }
1073}