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