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