1use crate::{
2 display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint},
3 editor_settings::ShowScrollbar,
4 git::{diff_hunk_to_display, DisplayDiffHunk},
5 hover_popover::hover_at,
6 link_go_to_definition::{
7 update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger,
8 },
9 scroll::scroll_amount::ScrollAmount,
10 CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
11 HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, PageDown, PageUp, Point, SelectPhase,
12 Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
13};
14use anyhow::Result;
15use collections::{BTreeMap, HashMap};
16use gpui::{
17 black, hsla, point, px, relative, size, transparent_black, Action, AnyElement,
18 BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
19 Edges, Element, ElementId, Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Modifiers,
20 MouseDownEvent, MouseMoveEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
21 StatefulInteraction, Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext,
22};
23use itertools::Itertools;
24use language::language_settings::ShowWhitespaceSetting;
25use multi_buffer::Anchor;
26use project::project_settings::{GitGutterSetting, ProjectSettings};
27use settings::Settings;
28use smallvec::SmallVec;
29use std::{
30 any::TypeId,
31 borrow::Cow,
32 cmp::{self, Ordering},
33 fmt::Write,
34 iter,
35 ops::Range,
36 sync::Arc,
37};
38use sum_tree::Bias;
39use theme::{ActiveTheme, PlayerColor};
40use util::ResultExt;
41use workspace::item::Item;
42
43enum FoldMarkers {}
44
45struct SelectionLayout {
46 head: DisplayPoint,
47 cursor_shape: CursorShape,
48 is_newest: bool,
49 is_local: bool,
50 range: Range<DisplayPoint>,
51 active_rows: Range<u32>,
52}
53
54impl SelectionLayout {
55 fn new<T: ToPoint + ToDisplayPoint + Clone>(
56 selection: Selection<T>,
57 line_mode: bool,
58 cursor_shape: CursorShape,
59 map: &DisplaySnapshot,
60 is_newest: bool,
61 is_local: bool,
62 ) -> Self {
63 let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
64 let display_selection = point_selection.map(|p| p.to_display_point(map));
65 let mut range = display_selection.range();
66 let mut head = display_selection.head();
67 let mut active_rows = map.prev_line_boundary(point_selection.start).1.row()
68 ..map.next_line_boundary(point_selection.end).1.row();
69
70 // vim visual line mode
71 if line_mode {
72 let point_range = map.expand_to_line(point_selection.range());
73 range = point_range.start.to_display_point(map)..point_range.end.to_display_point(map);
74 }
75
76 // any vim visual mode (including line mode)
77 if cursor_shape == CursorShape::Block && !range.is_empty() && !selection.reversed {
78 if head.column() > 0 {
79 head = map.clip_point(DisplayPoint::new(head.row(), head.column() - 1), Bias::Left)
80 } else if head.row() > 0 && head != map.max_point() {
81 head = map.clip_point(
82 DisplayPoint::new(head.row() - 1, map.line_len(head.row() - 1)),
83 Bias::Left,
84 );
85 // updating range.end is a no-op unless you're cursor is
86 // on the newline containing a multi-buffer divider
87 // in which case the clip_point may have moved the head up
88 // an additional row.
89 range.end = DisplayPoint::new(head.row() + 1, 0);
90 active_rows.end = head.row();
91 }
92 }
93
94 Self {
95 head,
96 cursor_shape,
97 is_newest,
98 is_local,
99 range,
100 active_rows,
101 }
102 }
103}
104
105pub struct EditorElement {
106 style: EditorStyle,
107}
108
109impl EditorElement {
110 pub fn new(style: EditorStyle) -> Self {
111 Self { style }
112 }
113
114 // fn attach_mouse_handlers(
115 // position_map: &Arc<PositionMap>,
116 // has_popovers: bool,
117 // visible_bounds: Bounds<Pixels>,
118 // text_bounds: Bounds<Pixels>,
119 // gutter_bounds: Bounds<Pixels>,
120 // bounds: Bounds<Pixels>,
121 // cx: &mut ViewContext<Editor>,
122 // ) {
123 // enum EditorElementMouseHandlers {}
124 // let view_id = cx.view_id();
125 // cx.scene().push_mouse_region(
126 // MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
127 // .on_down(MouseButton::Left, {
128 // let position_map = position_map.clone();
129 // move |event, editor, cx| {
130 // if !Self::mouse_down(
131 // editor,
132 // event.platform_event,
133 // position_map.as_ref(),
134 // text_bounds,
135 // gutter_bounds,
136 // cx,
137 // ) {
138 // cx.propagate_event();
139 // }
140 // }
141 // })
142 // .on_down(MouseButton::Right, {
143 // let position_map = position_map.clone();
144 // move |event, editor, cx| {
145 // if !Self::mouse_right_down(
146 // editor,
147 // event.position,
148 // position_map.as_ref(),
149 // text_bounds,
150 // cx,
151 // ) {
152 // cx.propagate_event();
153 // }
154 // }
155 // })
156 // .on_up(MouseButton::Left, {
157 // let position_map = position_map.clone();
158 // move |event, editor, cx| {
159 // if !Self::mouse_up(
160 // editor,
161 // event.position,
162 // event.cmd,
163 // event.shift,
164 // event.alt,
165 // position_map.as_ref(),
166 // text_bounds,
167 // cx,
168 // ) {
169 // cx.propagate_event()
170 // }
171 // }
172 // })
173 // .on_drag(MouseButton::Left, {
174 // let position_map = position_map.clone();
175 // move |event, editor, cx| {
176 // if event.end {
177 // return;
178 // }
179
180 // if !Self::mouse_dragged(
181 // editor,
182 // event.platform_event,
183 // position_map.as_ref(),
184 // text_bounds,
185 // cx,
186 // ) {
187 // cx.propagate_event()
188 // }
189 // }
190 // })
191 // .on_move({
192 // let position_map = position_map.clone();
193 // move |event, editor, cx| {
194 // if !Self::mouse_moved(
195 // editor,
196 // event.platform_event,
197 // &position_map,
198 // text_bounds,
199 // cx,
200 // ) {
201 // cx.propagate_event()
202 // }
203 // }
204 // })
205 // .on_move_out(move |_, editor: &mut Editor, cx| {
206 // if has_popovers {
207 // hide_hover(editor, cx);
208 // }
209 // })
210 // .on_scroll({
211 // let position_map = position_map.clone();
212 // move |event, editor, cx| {
213 // if !Self::scroll(
214 // editor,
215 // event.position,
216 // *event.delta.raw(),
217 // event.delta.precise(),
218 // &position_map,
219 // bounds,
220 // cx,
221 // ) {
222 // cx.propagate_event()
223 // }
224 // }
225 // }),
226 // );
227
228 // enum GutterHandlers {}
229 // let view_id = cx.view_id();
230 // let region_id = cx.view_id() + 1;
231 // cx.scene().push_mouse_region(
232 // MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
233 // |hover, editor: &mut Editor, cx| {
234 // editor.gutter_hover(
235 // &GutterHover {
236 // hovered: hover.started,
237 // },
238 // cx,
239 // );
240 // },
241 // ),
242 // )
243 // }
244
245 fn mouse_down(
246 editor: &mut Editor,
247 event: &MouseDownEvent,
248 position_map: &PositionMap,
249 text_bounds: Bounds<Pixels>,
250 gutter_bounds: Bounds<Pixels>,
251 cx: &mut ViewContext<Editor>,
252 ) -> bool {
253 let mut click_count = event.click_count;
254 let modifiers = event.modifiers;
255
256 if gutter_bounds.contains_point(&event.position) {
257 click_count = 3; // Simulate triple-click when clicking the gutter to select lines
258 } else if !text_bounds.contains_point(&event.position) {
259 return false;
260 }
261
262 let point_for_position = position_map.point_for_position(text_bounds, event.position);
263 let position = point_for_position.previous_valid;
264 if modifiers.shift && modifiers.alt {
265 editor.select(
266 SelectPhase::BeginColumnar {
267 position,
268 goal_column: point_for_position.exact_unclipped.column(),
269 },
270 cx,
271 );
272 } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
273 editor.select(
274 SelectPhase::Extend {
275 position,
276 click_count,
277 },
278 cx,
279 );
280 } else {
281 editor.select(
282 SelectPhase::Begin {
283 position,
284 add: modifiers.alt,
285 click_count,
286 },
287 cx,
288 );
289 }
290
291 true
292 }
293
294 // fn mouse_right_down(
295 // editor: &mut Editor,
296 // position: gpui::Point<Pixels>,
297 // position_map: &PositionMap,
298 // text_bounds: Bounds<Pixels>,
299 // cx: &mut EventContext<Editor>,
300 // ) -> bool {
301 // if !text_bounds.contains_point(position) {
302 // return false;
303 // }
304 // let point_for_position = position_map.point_for_position(text_bounds, position);
305 // mouse_context_menu::deploy_context_menu(
306 // editor,
307 // position,
308 // point_for_position.previous_valid,
309 // cx,
310 // );
311 // true
312 // }
313
314 // fn mouse_up(
315 // editor: &mut Editor,
316 // position: gpui::Point<Pixels>,
317 // cmd: bool,
318 // shift: bool,
319 // alt: bool,
320 // position_map: &PositionMap,
321 // text_bounds: Bounds<Pixels>,
322 // cx: &mut EventContext<Editor>,
323 // ) -> bool {
324 // let end_selection = editor.has_pending_selection();
325 // let pending_nonempty_selections = editor.has_pending_nonempty_selection();
326
327 // if end_selection {
328 // editor.select(SelectPhase::End, cx);
329 // }
330
331 // if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
332 // let point = position_map.point_for_position(text_bounds, position);
333 // let could_be_inlay = point.as_valid().is_none();
334 // if shift || could_be_inlay {
335 // go_to_fetched_type_definition(editor, point, alt, cx);
336 // } else {
337 // go_to_fetched_definition(editor, point, alt, cx);
338 // }
339
340 // return true;
341 // }
342
343 // end_selection
344 // }
345
346 // fn mouse_dragged(
347 // editor: &mut Editor,
348 // MouseMovedEvent {
349 // modifiers: Modifiers { cmd, shift, .. },
350 // position,
351 // ..
352 // }: MouseMovedEvent,
353 // position_map: &PositionMap,
354 // text_bounds: Bounds<Pixels>,
355 // cx: &mut EventContext<Editor>,
356 // ) -> bool {
357 // // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
358 // // Don't trigger hover popover if mouse is hovering over context menu
359 // let point = if text_bounds.contains_point(position) {
360 // position_map
361 // .point_for_position(text_bounds, position)
362 // .as_valid()
363 // } else {
364 // None
365 // };
366
367 // update_go_to_definition_link(
368 // editor,
369 // point.map(GoToDefinitionTrigger::Text),
370 // cmd,
371 // shift,
372 // cx,
373 // );
374
375 // if editor.has_pending_selection() {
376 // let mut scroll_delta = gpui::Point::<Pixels>::zero();
377
378 // let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
379 // let top = text_bounds.origin.y + vertical_margin;
380 // let bottom = text_bounds.lower_left().y - vertical_margin;
381 // if position.y < top {
382 // scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y))
383 // }
384 // if position.y > bottom {
385 // scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y - bottom))
386 // }
387
388 // let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
389 // let left = text_bounds.origin.x + horizontal_margin;
390 // let right = text_bounds.upper_right().x - horizontal_margin;
391 // if position.x < left {
392 // scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
393 // left - position.x,
394 // ))
395 // }
396 // if position.x > right {
397 // scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
398 // position.x - right,
399 // ))
400 // }
401
402 // let point_for_position = position_map.point_for_position(text_bounds, position);
403
404 // editor.select(
405 // SelectPhase::Update {
406 // position: point_for_position.previous_valid,
407 // goal_column: point_for_position.exact_unclipped.column(),
408 // scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
409 // .clamp(gpui::Point::<Pixels>::zero(), position_map.scroll_max),
410 // },
411 // cx,
412 // );
413 // hover_at(editor, point, cx);
414 // true
415 // } else {
416 // hover_at(editor, point, cx);
417 // false
418 // }
419 // }
420
421 fn mouse_moved(
422 editor: &mut Editor,
423 MouseMoveEvent {
424 modifiers: Modifiers { shift, command, .. },
425 position,
426 ..
427 }: &MouseMoveEvent,
428 position_map: &PositionMap,
429 text_bounds: Bounds<Pixels>,
430 cx: &mut ViewContext<Editor>,
431 ) -> bool {
432 // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
433 // Don't trigger hover popover if mouse is hovering over context menu
434 if text_bounds.contains_point(position) {
435 let point_for_position = position_map.point_for_position(text_bounds, position.clone());
436 match point_for_position.as_valid() {
437 Some(point) => {
438 update_go_to_definition_link(
439 editor,
440 Some(GoToDefinitionTrigger::Text(point)),
441 *command,
442 *shift,
443 cx,
444 );
445 hover_at(editor, Some(point), cx);
446 }
447 None => {
448 update_inlay_link_and_hover_points(
449 &position_map.snapshot,
450 point_for_position,
451 editor,
452 *command,
453 *shift,
454 cx,
455 );
456 }
457 }
458 } else {
459 update_go_to_definition_link(editor, None, *command, *shift, cx);
460 hover_at(editor, None, cx);
461 }
462
463 true
464 }
465
466 fn scroll(
467 editor: &mut Editor,
468 event: &ScrollWheelEvent,
469 position_map: &PositionMap,
470 bounds: Bounds<Pixels>,
471 cx: &mut ViewContext<Editor>,
472 ) -> bool {
473 if !bounds.contains_point(&event.position) {
474 return false;
475 }
476
477 let line_height = position_map.line_height;
478 let max_glyph_width = position_map.em_width;
479 let (delta, axis) = match event.delta {
480 gpui::ScrollDelta::Pixels(mut pixels) => {
481 //Trackpad
482 let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels);
483 (pixels, axis)
484 }
485
486 gpui::ScrollDelta::Lines(lines) => {
487 //Not trackpad
488 let pixels = point(lines.x * max_glyph_width, lines.y * line_height);
489 (pixels, None)
490 }
491 };
492
493 let scroll_position = position_map.snapshot.scroll_position();
494 let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width);
495 let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
496 let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
497 editor.scroll(scroll_position, axis, cx);
498
499 true
500 }
501
502 fn paint_background(
503 &self,
504 gutter_bounds: Bounds<Pixels>,
505 text_bounds: Bounds<Pixels>,
506 layout: &LayoutState,
507 cx: &mut ViewContext<Editor>,
508 ) {
509 let bounds = gutter_bounds.union(&text_bounds);
510 let scroll_top =
511 layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
512 let gutter_bg = cx.theme().colors().editor_gutter_background;
513 cx.paint_quad(
514 gutter_bounds,
515 Corners::default(),
516 gutter_bg,
517 Edges::default(),
518 transparent_black(),
519 );
520 cx.paint_quad(
521 text_bounds,
522 Corners::default(),
523 self.style.background,
524 Edges::default(),
525 transparent_black(),
526 );
527
528 if let EditorMode::Full = layout.mode {
529 let mut active_rows = layout.active_rows.iter().peekable();
530 while let Some((start_row, contains_non_empty_selection)) = active_rows.next() {
531 let mut end_row = *start_row;
532 while active_rows.peek().map_or(false, |r| {
533 *r.0 == end_row + 1 && r.1 == contains_non_empty_selection
534 }) {
535 active_rows.next().unwrap();
536 end_row += 1;
537 }
538
539 if !contains_non_empty_selection {
540 let origin = point(
541 bounds.origin.x,
542 bounds.origin.y + (layout.position_map.line_height * *start_row as f32)
543 - scroll_top,
544 );
545 let size = size(
546 bounds.size.width,
547 layout.position_map.line_height * (end_row - start_row + 1) as f32,
548 );
549 let active_line_bg = cx.theme().colors().editor_active_line_background;
550 cx.paint_quad(
551 Bounds { origin, size },
552 Corners::default(),
553 active_line_bg,
554 Edges::default(),
555 transparent_black(),
556 );
557 }
558 }
559
560 if let Some(highlighted_rows) = &layout.highlighted_rows {
561 let origin = point(
562 bounds.origin.x,
563 bounds.origin.y
564 + (layout.position_map.line_height * highlighted_rows.start as f32)
565 - scroll_top,
566 );
567 let size = size(
568 bounds.size.width,
569 layout.position_map.line_height * highlighted_rows.len() as f32,
570 );
571 let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
572 cx.paint_quad(
573 Bounds { origin, size },
574 Corners::default(),
575 highlighted_line_bg,
576 Edges::default(),
577 transparent_black(),
578 );
579 }
580
581 let scroll_left =
582 layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width;
583
584 for (wrap_position, active) in layout.wrap_guides.iter() {
585 let x = (text_bounds.origin.x + *wrap_position + layout.position_map.em_width / 2.)
586 - scroll_left;
587
588 if x < text_bounds.origin.x
589 || (layout.show_scrollbars && x > self.scrollbar_left(&bounds))
590 {
591 continue;
592 }
593
594 let color = if *active {
595 cx.theme().colors().editor_active_wrap_guide
596 } else {
597 cx.theme().colors().editor_wrap_guide
598 };
599 cx.paint_quad(
600 Bounds {
601 origin: point(x, text_bounds.origin.y),
602 size: size(px(1.), text_bounds.size.height),
603 },
604 Corners::default(),
605 color,
606 Edges::default(),
607 transparent_black(),
608 );
609 }
610 }
611 }
612
613 fn paint_gutter(
614 &mut self,
615 bounds: Bounds<Pixels>,
616 layout: &LayoutState,
617 editor: &mut Editor,
618 cx: &mut ViewContext<Editor>,
619 ) {
620 let line_height = layout.position_map.line_height;
621
622 let scroll_position = layout.position_map.snapshot.scroll_position();
623 let scroll_top = scroll_position.y * line_height;
624
625 let show_gutter = matches!(
626 ProjectSettings::get_global(cx).git.git_gutter,
627 Some(GitGutterSetting::TrackedFiles)
628 );
629
630 if show_gutter {
631 Self::paint_diff_hunks(bounds, layout, cx);
632 }
633
634 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
635 if let Some(line) = line {
636 let line_origin = bounds.origin
637 + point(
638 bounds.size.width - line.width - layout.gutter_padding,
639 ix as f32 * line_height - (scroll_top % line_height),
640 );
641
642 line.paint(line_origin, line_height, cx);
643 }
644 }
645
646 // todo!("fold indicators")
647 // for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
648 // if let Some(indicator) = fold_indicator.as_mut() {
649 // let position = point(
650 // bounds.width() - layout.gutter_padding,
651 // ix as f32 * line_height - (scroll_top % line_height),
652 // );
653 // let centering_offset = point(
654 // (layout.gutter_padding + layout.gutter_margin - indicator.size().x) / 2.,
655 // (line_height - indicator.size().y) / 2.,
656 // );
657
658 // let indicator_origin = bounds.origin + position + centering_offset;
659
660 // indicator.paint(indicator_origin, visible_bounds, editor, cx);
661 // }
662 // }
663
664 // todo!("code actions indicator")
665 // if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
666 // let mut x = 0.;
667 // let mut y = *row as f32 * line_height - scroll_top;
668 // x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x) / 2.;
669 // y += (line_height - indicator.size().y) / 2.;
670 // indicator.paint(bounds.origin + point(x, y), visible_bounds, editor, cx);
671 // }
672 }
673
674 fn paint_diff_hunks(
675 bounds: Bounds<Pixels>,
676 layout: &LayoutState,
677 cx: &mut ViewContext<Editor>,
678 ) {
679 // todo!()
680 // let diff_style = &theme::current(cx).editor.diff.clone();
681 // let line_height = layout.position_map.line_height;
682
683 // let scroll_position = layout.position_map.snapshot.scroll_position();
684 // let scroll_top = scroll_position.y * line_height;
685
686 // for hunk in &layout.display_hunks {
687 // let (display_row_range, status) = match hunk {
688 // //TODO: This rendering is entirely a horrible hack
689 // &DisplayDiffHunk::Folded { display_row: row } => {
690 // let start_y = row as f32 * line_height - scroll_top;
691 // let end_y = start_y + line_height;
692
693 // let width = diff_style.removed_width_em * line_height;
694 // let highlight_origin = bounds.origin + point(-width, start_y);
695 // let highlight_size = point(width * 2., end_y - start_y);
696 // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
697
698 // cx.paint_quad(Quad {
699 // bounds: highlight_bounds,
700 // background: Some(diff_style.modified),
701 // border: Border::new(0., Color::transparent_black()).into(),
702 // corner_radii: (1. * line_height).into(),
703 // });
704
705 // continue;
706 // }
707
708 // DisplayDiffHunk::Unfolded {
709 // display_row_range,
710 // status,
711 // } => (display_row_range, status),
712 // };
713
714 // let color = match status {
715 // DiffHunkStatus::Added => diff_style.inserted,
716 // DiffHunkStatus::Modified => diff_style.modified,
717
718 // //TODO: This rendering is entirely a horrible hack
719 // DiffHunkStatus::Removed => {
720 // let row = display_row_range.start;
721
722 // let offset = line_height / 2.;
723 // let start_y = row as f32 * line_height - offset - scroll_top;
724 // let end_y = start_y + line_height;
725
726 // let width = diff_style.removed_width_em * line_height;
727 // let highlight_origin = bounds.origin + point(-width, start_y);
728 // let highlight_size = point(width * 2., end_y - start_y);
729 // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
730
731 // cx.paint_quad(Quad {
732 // bounds: highlight_bounds,
733 // background: Some(diff_style.deleted),
734 // border: Border::new(0., Color::transparent_black()).into(),
735 // corner_radii: (1. * line_height).into(),
736 // });
737
738 // continue;
739 // }
740 // };
741
742 // let start_row = display_row_range.start;
743 // let end_row = display_row_range.end;
744
745 // let start_y = start_row as f32 * line_height - scroll_top;
746 // let end_y = end_row as f32 * line_height - scroll_top;
747
748 // let width = diff_style.width_em * line_height;
749 // let highlight_origin = bounds.origin + point(-width, start_y);
750 // let highlight_size = point(width * 2., end_y - start_y);
751 // let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
752
753 // cx.paint_quad(Quad {
754 // bounds: highlight_bounds,
755 // background: Some(color),
756 // border: Border::new(0., Color::transparent_black()).into(),
757 // corner_radii: (diff_style.corner_radius * line_height).into(),
758 // });
759 // }
760 }
761
762 fn paint_text(
763 &mut self,
764 bounds: Bounds<Pixels>,
765 layout: &LayoutState,
766 editor: &mut Editor,
767 cx: &mut ViewContext<Editor>,
768 ) {
769 let scroll_position = layout.position_map.snapshot.scroll_position();
770 let start_row = layout.visible_display_row_range.start;
771 let scroll_top = scroll_position.y * layout.position_map.line_height;
772 let max_glyph_width = layout.position_map.em_width;
773 let scroll_left = scroll_position.x * max_glyph_width;
774 let content_origin = bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
775 let line_end_overshoot = 0.15 * layout.position_map.line_height;
776 let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
777
778 cx.with_content_mask(ContentMask { bounds }, |cx| {
779 // todo!("cursor region")
780 // cx.scene().push_cursor_region(CursorRegion {
781 // bounds,
782 // style: if !editor.link_go_to_definition_state.definitions.is_empty {
783 // CursorStyle::PointingHand
784 // } else {
785 // CursorStyle::IBeam
786 // },
787 // });
788
789 // todo!("fold ranges")
790 // let fold_corner_radius =
791 // self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
792 // for (id, range, color) in layout.fold_ranges.iter() {
793 // self.paint_highlighted_range(
794 // range.clone(),
795 // *color,
796 // fold_corner_radius,
797 // fold_corner_radius * 2.,
798 // layout,
799 // content_origin,
800 // scroll_top,
801 // scroll_left,
802 // bounds,
803 // cx,
804 // );
805
806 // for bound in range_to_bounds(
807 // &range,
808 // content_origin,
809 // scroll_left,
810 // scroll_top,
811 // &layout.visible_display_row_range,
812 // line_end_overshoot,
813 // &layout.position_map,
814 // ) {
815 // cx.scene().push_cursor_region(CursorRegion {
816 // bounds: bound,
817 // style: CursorStyle::PointingHand,
818 // });
819
820 // let display_row = range.start.row();
821
822 // let buffer_row = DisplayPoint::new(display_row, 0)
823 // .to_point(&layout.position_map.snapshot.display_snapshot)
824 // .row;
825
826 // let view_id = cx.view_id();
827 // cx.scene().push_mouse_region(
828 // MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
829 // .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
830 // editor.unfold_at(&UnfoldAt { buffer_row }, cx)
831 // })
832 // .with_notify_on_hover(true)
833 // .with_notify_on_click(true),
834 // )
835 // }
836 // }
837
838 for (range, color) in &layout.highlighted_ranges {
839 self.paint_highlighted_range(
840 range.clone(),
841 *color,
842 Pixels::ZERO,
843 line_end_overshoot,
844 layout,
845 content_origin,
846 scroll_top,
847 scroll_left,
848 bounds,
849 cx,
850 );
851 }
852
853 let mut cursors = SmallVec::<[Cursor; 32]>::new();
854 let corner_radius = 0.15 * layout.position_map.line_height;
855 let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
856
857 for (selection_style, selections) in &layout.selections {
858 for selection in selections {
859 self.paint_highlighted_range(
860 selection.range.clone(),
861 selection_style.selection,
862 corner_radius,
863 corner_radius * 2.,
864 layout,
865 content_origin,
866 scroll_top,
867 scroll_left,
868 bounds,
869 cx,
870 );
871
872 if selection.is_local && !selection.range.is_empty() {
873 invisible_display_ranges.push(selection.range.clone());
874 }
875
876 if !selection.is_local || editor.show_local_cursors(cx) {
877 let cursor_position = selection.head;
878 if layout
879 .visible_display_row_range
880 .contains(&cursor_position.row())
881 {
882 let cursor_row_layout = &layout.position_map.line_layouts
883 [(cursor_position.row() - start_row) as usize]
884 .line;
885 let cursor_column = cursor_position.column() as usize;
886
887 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
888 let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1)
889 - cursor_character_x;
890 if block_width == Pixels::ZERO {
891 block_width = layout.position_map.em_width;
892 }
893 let block_text = if let CursorShape::Block = selection.cursor_shape {
894 layout
895 .position_map
896 .snapshot
897 .chars_at(cursor_position)
898 .next()
899 .and_then(|(character, _)| {
900 let text = character.to_string();
901 cx.text_system()
902 .layout_text(
903 &text,
904 cursor_row_layout.font_size,
905 &[TextRun {
906 len: text.len(),
907 font: self.style.text.font(),
908 color: self.style.background,
909 underline: None,
910 }],
911 None,
912 )
913 .unwrap()
914 .pop()
915 })
916 } else {
917 None
918 };
919
920 let x = cursor_character_x - scroll_left;
921 let y = cursor_position.row() as f32 * layout.position_map.line_height
922 - scroll_top;
923 if selection.is_newest {
924 editor.pixel_position_of_newest_cursor = Some(point(
925 bounds.origin.x + x + block_width / 2.,
926 bounds.origin.y + y + layout.position_map.line_height / 2.,
927 ));
928 }
929 cursors.push(Cursor {
930 color: selection_style.cursor,
931 block_width,
932 origin: point(x, y),
933 line_height: layout.position_map.line_height,
934 shape: selection.cursor_shape,
935 block_text,
936 });
937 }
938 }
939 }
940 }
941
942 for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
943 let row = start_row + ix as u32;
944 line_with_invisibles.draw(
945 layout,
946 row,
947 scroll_top,
948 content_origin,
949 scroll_left,
950 whitespace_setting,
951 &invisible_display_ranges,
952 cx,
953 )
954 }
955
956 cx.stack(0, |cx| {
957 for cursor in cursors {
958 cursor.paint(content_origin, cx);
959 }
960 });
961 // cx.scene().push_layer(Some(bounds));
962
963 // cx.scene().pop_layer();
964
965 // if let Some((position, context_menu)) = layout.context_menu.as_mut() {
966 // cx.scene().push_stacking_context(None, None);
967 // let cursor_row_layout =
968 // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
969 // let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
970 // let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
971 // let mut list_origin = content_origin + point(x, y);
972 // let list_width = context_menu.size().x;
973 // let list_height = context_menu.size().y;
974
975 // // Snap the right edge of the list to the right edge of the window if
976 // // its horizontal bounds overflow.
977 // if list_origin.x + list_width > cx.window_size().x {
978 // list_origin.set_x((cx.window_size().x - list_width).max(0.));
979 // }
980
981 // if list_origin.y + list_height > bounds.max_y {
982 // list_origin
983 // .set_y(list_origin.y - layout.position_map.line_height - list_height);
984 // }
985
986 // context_menu.paint(
987 // list_origin,
988 // Bounds::<Pixels>::from_points(
989 // gpui::Point::<Pixels>::zero(),
990 // point(f32::MAX, f32::MAX),
991 // ), // Let content bleed outside of editor
992 // editor,
993 // cx,
994 // );
995
996 // cx.scene().pop_stacking_context();
997 // }
998
999 // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
1000 // cx.scene().push_stacking_context(None, None);
1001
1002 // // This is safe because we check on layout whether the required row is available
1003 // let hovered_row_layout =
1004 // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
1005
1006 // // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
1007 // // height. This is the size we will use to decide whether to render popovers above or below
1008 // // the hovered line.
1009 // let first_size = hover_popovers[0].size();
1010 // let height_to_reserve = first_size.y
1011 // + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;
1012
1013 // // Compute Hovered Point
1014 // let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
1015 // let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
1016 // let hovered_point = content_origin + point(x, y);
1017
1018 // if hovered_point.y - height_to_reserve > 0.0 {
1019 // // There is enough space above. Render popovers above the hovered point
1020 // let mut current_y = hovered_point.y;
1021 // for hover_popover in hover_popovers {
1022 // let size = hover_popover.size();
1023 // let mut popover_origin = point(hovered_point.x, current_y - size.y);
1024
1025 // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);
1026 // if x_out_of_bounds < 0.0 {
1027 // popover_origin.set_x(popover_origin.x + x_out_of_bounds);
1028 // }
1029
1030 // hover_popover.paint(
1031 // popover_origin,
1032 // Bounds::<Pixels>::from_points(
1033 // gpui::Point::<Pixels>::zero(),
1034 // point(f32::MAX, f32::MAX),
1035 // ), // Let content bleed outside of editor
1036 // editor,
1037 // cx,
1038 // );
1039
1040 // current_y = popover_origin.y - HOVER_POPOVER_GAP;
1041 // }
1042 // } else {
1043 // // There is not enough space above. Render popovers below the hovered point
1044 // let mut current_y = hovered_point.y + layout.position_map.line_height;
1045 // for hover_popover in hover_popovers {
1046 // let size = hover_popover.size();
1047 // let mut popover_origin = point(hovered_point.x, current_y);
1048
1049 // let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);
1050 // if x_out_of_bounds < 0.0 {
1051 // popover_origin.set_x(popover_origin.x + x_out_of_bounds);
1052 // }
1053
1054 // hover_popover.paint(
1055 // popover_origin,
1056 // Bounds::<Pixels>::from_points(
1057 // gpui::Point::<Pixels>::zero(),
1058 // point(f32::MAX, f32::MAX),
1059 // ), // Let content bleed outside of editor
1060 // editor,
1061 // cx,
1062 // );
1063
1064 // current_y = popover_origin.y + size.y + HOVER_POPOVER_GAP;
1065 // }
1066 // }
1067
1068 // cx.scene().pop_stacking_context();
1069 // }
1070 })
1071 }
1072
1073 fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
1074 bounds.upper_right().x - self.style.scrollbar_width
1075 }
1076
1077 // fn paint_scrollbar(
1078 // &mut self,
1079 // bounds: Bounds<Pixels>,
1080 // layout: &mut LayoutState,
1081 // editor: &Editor,
1082 // cx: &mut ViewContext<Editor>,
1083 // ) {
1084 // enum ScrollbarMouseHandlers {}
1085 // if layout.mode != EditorMode::Full {
1086 // return;
1087 // }
1088
1089 // let style = &self.style.theme.scrollbar;
1090
1091 // let top = bounds.min_y;
1092 // let bottom = bounds.max_y;
1093 // let right = bounds.max_x;
1094 // let left = self.scrollbar_left(&bounds);
1095 // let row_range = &layout.scrollbar_row_range;
1096 // let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
1097
1098 // let mut height = bounds.height();
1099 // let mut first_row_y_offset = 0.0;
1100
1101 // // Impose a minimum height on the scrollbar thumb
1102 // let row_height = height / max_row;
1103 // let min_thumb_height =
1104 // style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
1105 // let thumb_height = (row_range.end - row_range.start) * row_height;
1106 // if thumb_height < min_thumb_height {
1107 // first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
1108 // height -= min_thumb_height - thumb_height;
1109 // }
1110
1111 // let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height };
1112
1113 // let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
1114 // let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
1115 // let track_bounds = Bounds::<Pixels>::from_points(point(left, top), point(right, bottom));
1116 // let thumb_bounds = Bounds::<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
1117
1118 // if layout.show_scrollbars {
1119 // cx.paint_quad(Quad {
1120 // bounds: track_bounds,
1121 // border: style.track.border.into(),
1122 // background: style.track.background_color,
1123 // ..Default::default()
1124 // });
1125 // let scrollbar_settings = settings::get::<EditorSettings>(cx).scrollbar;
1126 // let theme = theme::current(cx);
1127 // let scrollbar_theme = &theme.editor.scrollbar;
1128 // if layout.is_singleton && scrollbar_settings.selections {
1129 // let start_anchor = Anchor::min();
1130 // let end_anchor = Anchor::max;
1131 // let color = scrollbar_theme.selections;
1132 // let border = Border {
1133 // width: 1.,
1134 // color: style.thumb.border.color,
1135 // overlay: false,
1136 // top: false,
1137 // right: true,
1138 // bottom: false,
1139 // left: true,
1140 // };
1141 // let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
1142 // let start_y = y_for_row(start.row() as f32);
1143 // let mut end_y = y_for_row(end.row() as f32);
1144 // if end_y - start_y < 1. {
1145 // end_y = start_y + 1.;
1146 // }
1147 // let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
1148
1149 // cx.paint_quad(Quad {
1150 // bounds,
1151 // background: Some(color),
1152 // border: border.into(),
1153 // corner_radii: style.thumb.corner_radii.into(),
1154 // })
1155 // };
1156 // let background_ranges = editor
1157 // .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
1158 // start_anchor..end_anchor,
1159 // &layout.position_map.snapshot,
1160 // 50000,
1161 // );
1162 // for row in background_ranges {
1163 // let start = row.start();
1164 // let end = row.end();
1165 // push_region(*start, *end);
1166 // }
1167 // }
1168
1169 // if layout.is_singleton && scrollbar_settings.git_diff {
1170 // let diff_style = scrollbar_theme.git.clone();
1171 // for hunk in layout
1172 // .position_map
1173 // .snapshot
1174 // .buffer_snapshot
1175 // .git_diff_hunks_in_range(0..(max_row.floor() as u32))
1176 // {
1177 // let start_display = Point::new(hunk.buffer_range.start, 0)
1178 // .to_display_point(&layout.position_map.snapshot.display_snapshot);
1179 // let end_display = Point::new(hunk.buffer_range.end, 0)
1180 // .to_display_point(&layout.position_map.snapshot.display_snapshot);
1181 // let start_y = y_for_row(start_display.row() as f32);
1182 // let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
1183 // y_for_row((end_display.row() + 1) as f32)
1184 // } else {
1185 // y_for_row((end_display.row()) as f32)
1186 // };
1187
1188 // if end_y - start_y < 1. {
1189 // end_y = start_y + 1.;
1190 // }
1191 // let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
1192
1193 // let color = match hunk.status() {
1194 // DiffHunkStatus::Added => diff_style.inserted,
1195 // DiffHunkStatus::Modified => diff_style.modified,
1196 // DiffHunkStatus::Removed => diff_style.deleted,
1197 // };
1198
1199 // let border = Border {
1200 // width: 1.,
1201 // color: style.thumb.border.color,
1202 // overlay: false,
1203 // top: false,
1204 // right: true,
1205 // bottom: false,
1206 // left: true,
1207 // };
1208
1209 // cx.paint_quad(Quad {
1210 // bounds,
1211 // background: Some(color),
1212 // border: border.into(),
1213 // corner_radii: style.thumb.corner_radii.into(),
1214 // })
1215 // }
1216 // }
1217
1218 // cx.paint_quad(Quad {
1219 // bounds: thumb_bounds,
1220 // border: style.thumb.border.into(),
1221 // background: style.thumb.background_color,
1222 // corner_radii: style.thumb.corner_radii.into(),
1223 // });
1224 // }
1225
1226 // cx.scene().push_cursor_region(CursorRegion {
1227 // bounds: track_bounds,
1228 // style: CursorStyle::Arrow,
1229 // });
1230 // let region_id = cx.view_id();
1231 // cx.scene().push_mouse_region(
1232 // MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
1233 // .on_move(move |event, editor: &mut Editor, cx| {
1234 // if event.pressed_button.is_none() {
1235 // editor.scroll_manager.show_scrollbar(cx);
1236 // }
1237 // })
1238 // .on_down(MouseButton::Left, {
1239 // let row_range = row_range.clone();
1240 // move |event, editor: &mut Editor, cx| {
1241 // let y = event.position.y;
1242 // if y < thumb_top || thumb_bottom < y {
1243 // let center_row = ((y - top) * max_row as f32 / height).round() as u32;
1244 // let top_row = center_row
1245 // .saturating_sub((row_range.end - row_range.start) as u32 / 2);
1246 // let mut position = editor.scroll_position(cx);
1247 // position.set_y(top_row as f32);
1248 // editor.set_scroll_position(position, cx);
1249 // } else {
1250 // editor.scroll_manager.show_scrollbar(cx);
1251 // }
1252 // }
1253 // })
1254 // .on_drag(MouseButton::Left, {
1255 // move |event, editor: &mut Editor, cx| {
1256 // if event.end {
1257 // return;
1258 // }
1259
1260 // let y = event.prev_mouse_position.y;
1261 // let new_y = event.position.y;
1262 // if thumb_top < y && y < thumb_bottom {
1263 // let mut position = editor.scroll_position(cx);
1264 // position.set_y(position.y + (new_y - y) * (max_row as f32) / height);
1265 // if position.y < 0.0 {
1266 // position.set_y(0.);
1267 // }
1268 // editor.set_scroll_position(position, cx);
1269 // }
1270 // }
1271 // }),
1272 // );
1273 // }
1274
1275 #[allow(clippy::too_many_arguments)]
1276 fn paint_highlighted_range(
1277 &self,
1278 range: Range<DisplayPoint>,
1279 color: Hsla,
1280 corner_radius: Pixels,
1281 line_end_overshoot: Pixels,
1282 layout: &LayoutState,
1283 content_origin: gpui::Point<Pixels>,
1284 scroll_top: Pixels,
1285 scroll_left: Pixels,
1286 bounds: Bounds<Pixels>,
1287 cx: &mut ViewContext<Editor>,
1288 ) {
1289 let start_row = layout.visible_display_row_range.start;
1290 let end_row = layout.visible_display_row_range.end;
1291 if range.start != range.end {
1292 let row_range = if range.end.column() == 0 {
1293 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
1294 } else {
1295 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
1296 };
1297
1298 let highlighted_range = HighlightedRange {
1299 color,
1300 line_height: layout.position_map.line_height,
1301 corner_radius,
1302 start_y: content_origin.y
1303 + row_range.start as f32 * layout.position_map.line_height
1304 - scroll_top,
1305 lines: row_range
1306 .into_iter()
1307 .map(|row| {
1308 let line_layout =
1309 &layout.position_map.line_layouts[(row - start_row) as usize].line;
1310 HighlightedRangeLine {
1311 start_x: if row == range.start.row() {
1312 content_origin.x
1313 + line_layout.x_for_index(range.start.column() as usize)
1314 - scroll_left
1315 } else {
1316 content_origin.x - scroll_left
1317 },
1318 end_x: if row == range.end.row() {
1319 content_origin.x
1320 + line_layout.x_for_index(range.end.column() as usize)
1321 - scroll_left
1322 } else {
1323 content_origin.x + line_layout.width + line_end_overshoot
1324 - scroll_left
1325 },
1326 }
1327 })
1328 .collect(),
1329 };
1330
1331 highlighted_range.paint(bounds, cx);
1332 }
1333 }
1334
1335 // fn paint_blocks(
1336 // &mut self,
1337 // bounds: Bounds<Pixels>,
1338 // visible_bounds: Bounds<Pixels>,
1339 // layout: &mut LayoutState,
1340 // editor: &mut Editor,
1341 // cx: &mut ViewContext<Editor>,
1342 // ) {
1343 // let scroll_position = layout.position_map.snapshot.scroll_position();
1344 // let scroll_left = scroll_position.x * layout.position_map.em_width;
1345 // let scroll_top = scroll_position.y * layout.position_map.line_height;
1346
1347 // for block in &mut layout.blocks {
1348 // let mut origin = bounds.origin
1349 // + point(
1350 // 0.,
1351 // block.row as f32 * layout.position_map.line_height - scroll_top,
1352 // );
1353 // if !matches!(block.style, BlockStyle::Sticky) {
1354 // origin += point(-scroll_left, 0.);
1355 // }
1356 // block.element.paint(origin, visible_bounds, editor, cx);
1357 // }
1358 // }
1359
1360 fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
1361 let style = &self.style;
1362 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1363 let layout = cx
1364 .text_system()
1365 .layout_text(
1366 " ".repeat(column).as_str(),
1367 font_size,
1368 &[TextRun {
1369 len: column,
1370 font: style.text.font(),
1371 color: Hsla::default(),
1372 underline: None,
1373 }],
1374 None,
1375 )
1376 .unwrap();
1377
1378 layout[0].width
1379 }
1380
1381 fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {
1382 let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
1383 self.column_pixels(digit_count, cx)
1384 }
1385
1386 //Folds contained in a hunk are ignored apart from shrinking visual size
1387 //If a fold contains any hunks then that fold line is marked as modified
1388 fn layout_git_gutters(
1389 &self,
1390 display_rows: Range<u32>,
1391 snapshot: &EditorSnapshot,
1392 ) -> Vec<DisplayDiffHunk> {
1393 let buffer_snapshot = &snapshot.buffer_snapshot;
1394
1395 let buffer_start_row = DisplayPoint::new(display_rows.start, 0)
1396 .to_point(snapshot)
1397 .row;
1398 let buffer_end_row = DisplayPoint::new(display_rows.end, 0)
1399 .to_point(snapshot)
1400 .row;
1401
1402 buffer_snapshot
1403 .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
1404 .map(|hunk| diff_hunk_to_display(hunk, snapshot))
1405 .dedup()
1406 .collect()
1407 }
1408
1409 fn calculate_relative_line_numbers(
1410 &self,
1411 snapshot: &EditorSnapshot,
1412 rows: &Range<u32>,
1413 relative_to: Option<u32>,
1414 ) -> HashMap<u32, u32> {
1415 let mut relative_rows: HashMap<u32, u32> = Default::default();
1416 let Some(relative_to) = relative_to else {
1417 return relative_rows;
1418 };
1419
1420 let start = rows.start.min(relative_to);
1421 let end = rows.end.max(relative_to);
1422
1423 let buffer_rows = snapshot
1424 .buffer_rows(start)
1425 .take(1 + (end - start) as usize)
1426 .collect::<Vec<_>>();
1427
1428 let head_idx = relative_to - start;
1429 let mut delta = 1;
1430 let mut i = head_idx + 1;
1431 while i < buffer_rows.len() as u32 {
1432 if buffer_rows[i as usize].is_some() {
1433 if rows.contains(&(i + start)) {
1434 relative_rows.insert(i + start, delta);
1435 }
1436 delta += 1;
1437 }
1438 i += 1;
1439 }
1440 delta = 1;
1441 i = head_idx.min(buffer_rows.len() as u32 - 1);
1442 while i > 0 && buffer_rows[i as usize].is_none() {
1443 i -= 1;
1444 }
1445
1446 while i > 0 {
1447 i -= 1;
1448 if buffer_rows[i as usize].is_some() {
1449 if rows.contains(&(i + start)) {
1450 relative_rows.insert(i + start, delta);
1451 }
1452 delta += 1;
1453 }
1454 }
1455
1456 relative_rows
1457 }
1458
1459 fn layout_line_numbers(
1460 &self,
1461 rows: Range<u32>,
1462 active_rows: &BTreeMap<u32, bool>,
1463 newest_selection_head: DisplayPoint,
1464 is_singleton: bool,
1465 snapshot: &EditorSnapshot,
1466 cx: &ViewContext<Editor>,
1467 ) -> (
1468 Vec<Option<gpui::Line>>,
1469 Vec<Option<(FoldStatus, BufferRow, bool)>>,
1470 ) {
1471 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1472 let include_line_numbers = snapshot.mode == EditorMode::Full;
1473 let mut line_number_layouts = Vec::with_capacity(rows.len());
1474 let mut fold_statuses = Vec::with_capacity(rows.len());
1475 let mut line_number = String::new();
1476 let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
1477 let relative_to = if is_relative {
1478 Some(newest_selection_head.row())
1479 } else {
1480 None
1481 };
1482
1483 let relative_rows = self.calculate_relative_line_numbers(&snapshot, &rows, relative_to);
1484
1485 for (ix, row) in snapshot
1486 .buffer_rows(rows.start)
1487 .take((rows.end - rows.start) as usize)
1488 .enumerate()
1489 {
1490 let display_row = rows.start + ix as u32;
1491 let (active, color) = if active_rows.contains_key(&display_row) {
1492 (true, cx.theme().colors().editor_active_line_number)
1493 } else {
1494 (false, cx.theme().colors().editor_line_number)
1495 };
1496 if let Some(buffer_row) = row {
1497 if include_line_numbers {
1498 line_number.clear();
1499 let default_number = buffer_row + 1;
1500 let number = relative_rows
1501 .get(&(ix as u32 + rows.start))
1502 .unwrap_or(&default_number);
1503 write!(&mut line_number, "{}", number).unwrap();
1504 let run = TextRun {
1505 len: line_number.len(),
1506 font: self.style.text.font(),
1507 color,
1508 underline: None,
1509 };
1510 let layout = cx
1511 .text_system()
1512 .layout_text(&line_number, font_size, &[run], None)
1513 .unwrap()
1514 .pop()
1515 .unwrap();
1516 line_number_layouts.push(Some(layout));
1517 fold_statuses.push(
1518 is_singleton
1519 .then(|| {
1520 snapshot
1521 .fold_for_line(buffer_row)
1522 .map(|fold_status| (fold_status, buffer_row, active))
1523 })
1524 .flatten(),
1525 )
1526 }
1527 } else {
1528 fold_statuses.push(None);
1529 line_number_layouts.push(None);
1530 }
1531 }
1532
1533 (line_number_layouts, fold_statuses)
1534 }
1535
1536 fn layout_lines(
1537 &mut self,
1538 rows: Range<u32>,
1539 line_number_layouts: &[Option<Line>],
1540 snapshot: &EditorSnapshot,
1541 cx: &ViewContext<Editor>,
1542 ) -> Vec<LineWithInvisibles> {
1543 if rows.start >= rows.end {
1544 return Vec::new();
1545 }
1546
1547 // When the editor is empty and unfocused, then show the placeholder.
1548 if snapshot.is_empty() {
1549 let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
1550 let placeholder_color = cx.theme().styles.colors.text_placeholder;
1551 let placeholder_text = snapshot.placeholder_text();
1552 let placeholder_lines = placeholder_text
1553 .as_ref()
1554 .map_or("", AsRef::as_ref)
1555 .split('\n')
1556 .skip(rows.start as usize)
1557 .chain(iter::repeat(""))
1558 .take(rows.len());
1559 placeholder_lines
1560 .map(|line| {
1561 let run = TextRun {
1562 len: line.len(),
1563 font: self.style.text.font(),
1564 color: placeholder_color,
1565 underline: Default::default(),
1566 };
1567 cx.text_system()
1568 .layout_text(line, font_size, &[run], None)
1569 .unwrap()
1570 .pop()
1571 .unwrap()
1572 })
1573 .map(|line| LineWithInvisibles {
1574 line,
1575 invisibles: Vec::new(),
1576 })
1577 .collect()
1578 } else {
1579 let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
1580 LineWithInvisibles::from_chunks(
1581 chunks,
1582 &self.style.text,
1583 MAX_LINE_LEN,
1584 rows.len() as usize,
1585 line_number_layouts,
1586 snapshot.mode,
1587 cx,
1588 )
1589 }
1590 }
1591
1592 fn compute_layout(
1593 &mut self,
1594 editor: &mut Editor,
1595 cx: &mut ViewContext<'_, Editor>,
1596 mut bounds: Bounds<Pixels>,
1597 ) -> LayoutState {
1598 // let mut size = constraint.max;
1599 // if size.x.is_infinite() {
1600 // unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
1601 // }
1602
1603 let snapshot = editor.snapshot(cx);
1604 let style = self.style.clone();
1605 let font_id = cx.text_system().font_id(&style.text.font()).unwrap();
1606 let font_size = style.text.font_size.to_pixels(cx.rem_size());
1607 let line_height = style.text.line_height_in_pixels(cx.rem_size());
1608 let em_width = cx
1609 .text_system()
1610 .typographic_bounds(font_id, font_size, 'm')
1611 .unwrap()
1612 .size
1613 .width;
1614 let em_advance = cx
1615 .text_system()
1616 .advance(font_id, font_size, 'm')
1617 .unwrap()
1618 .width;
1619
1620 let gutter_padding;
1621 let gutter_width;
1622 let gutter_margin;
1623 if snapshot.show_gutter {
1624 let descent = cx.text_system().descent(font_id, font_size).unwrap();
1625
1626 let gutter_padding_factor = 3.5;
1627 gutter_padding = (em_width * gutter_padding_factor).round();
1628 gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
1629 gutter_margin = -descent;
1630 } else {
1631 gutter_padding = Pixels::ZERO;
1632 gutter_width = Pixels::ZERO;
1633 gutter_margin = Pixels::ZERO;
1634 };
1635
1636 let text_width = bounds.size.width - gutter_width;
1637 let overscroll = size(em_width, px(0.));
1638 let snapshot = {
1639 editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
1640
1641 let editor_width = text_width - gutter_margin - overscroll.width - em_width;
1642 let wrap_width = match editor.soft_wrap_mode(cx) {
1643 SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
1644 SoftWrap::EditorWidth => editor_width,
1645 SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
1646 };
1647
1648 if editor.set_wrap_width(Some(wrap_width), cx) {
1649 editor.snapshot(cx)
1650 } else {
1651 snapshot
1652 }
1653 };
1654
1655 let wrap_guides = editor
1656 .wrap_guides(cx)
1657 .iter()
1658 .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
1659 .collect::<SmallVec<[_; 2]>>();
1660
1661 let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
1662 // todo!("this should happen during layout")
1663 let editor_mode = snapshot.mode;
1664 if let EditorMode::AutoHeight { max_lines } = editor_mode {
1665 todo!()
1666 // size.set_y(
1667 // scroll_height
1668 // .min(constraint.max_along(Axis::Vertical))
1669 // .max(constraint.min_along(Axis::Vertical))
1670 // .max(line_height)
1671 // .min(line_height * max_lines as f32),
1672 // )
1673 } else if let EditorMode::SingleLine = editor_mode {
1674 bounds.size.height = line_height.min(bounds.size.height);
1675 }
1676 // todo!()
1677 // else if size.y.is_infinite() {
1678 // // size.set_y(scroll_height);
1679 // }
1680 //
1681 let gutter_size = size(gutter_width, bounds.size.height);
1682 let text_size = size(text_width, bounds.size.height);
1683
1684 let autoscroll_horizontally =
1685 editor.autoscroll_vertically(bounds.size.height, line_height, cx);
1686 let mut snapshot = editor.snapshot(cx);
1687
1688 let scroll_position = snapshot.scroll_position();
1689 // The scroll position is a fractional point, the whole number of which represents
1690 // the top of the window in terms of display rows.
1691 let start_row = scroll_position.y as u32;
1692 let height_in_lines = f32::from(bounds.size.height / line_height);
1693 let max_row = snapshot.max_point().row();
1694
1695 // Add 1 to ensure selections bleed off screen
1696 let end_row = 1 + cmp::min((scroll_position.y + height_in_lines).ceil() as u32, max_row);
1697
1698 let start_anchor = if start_row == 0 {
1699 Anchor::min()
1700 } else {
1701 snapshot
1702 .buffer_snapshot
1703 .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
1704 };
1705 let end_anchor = if end_row > max_row {
1706 Anchor::max()
1707 } else {
1708 snapshot
1709 .buffer_snapshot
1710 .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
1711 };
1712
1713 let mut selections: Vec<(PlayerColor, Vec<SelectionLayout>)> = Vec::new();
1714 let mut active_rows = BTreeMap::new();
1715 let mut fold_ranges = Vec::new();
1716 let is_singleton = editor.is_singleton(cx);
1717
1718 let highlighted_rows = editor.highlighted_rows();
1719 let highlighted_ranges = editor.background_highlights_in_range(
1720 start_anchor..end_anchor,
1721 &snapshot.display_snapshot,
1722 cx.theme().colors(),
1723 );
1724
1725 fold_ranges.extend(
1726 snapshot
1727 .folds_in_range(start_anchor..end_anchor)
1728 .map(|anchor| {
1729 let start = anchor.start.to_point(&snapshot.buffer_snapshot);
1730 (
1731 start.row,
1732 start.to_display_point(&snapshot.display_snapshot)
1733 ..anchor.end.to_display_point(&snapshot),
1734 )
1735 }),
1736 );
1737
1738 let mut newest_selection_head = None;
1739
1740 if editor.show_local_selections {
1741 let mut local_selections: Vec<Selection<Point>> = editor
1742 .selections
1743 .disjoint_in_range(start_anchor..end_anchor, cx);
1744 local_selections.extend(editor.selections.pending(cx));
1745 let mut layouts = Vec::new();
1746 let newest = editor.selections.newest(cx);
1747 for selection in local_selections.drain(..) {
1748 let is_empty = selection.start == selection.end;
1749 let is_newest = selection == newest;
1750
1751 let layout = SelectionLayout::new(
1752 selection,
1753 editor.selections.line_mode,
1754 editor.cursor_shape,
1755 &snapshot.display_snapshot,
1756 is_newest,
1757 true,
1758 );
1759 if is_newest {
1760 newest_selection_head = Some(layout.head);
1761 }
1762
1763 for row in cmp::max(layout.active_rows.start, start_row)
1764 ..=cmp::min(layout.active_rows.end, end_row)
1765 {
1766 let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
1767 *contains_non_empty_selection |= !is_empty;
1768 }
1769 layouts.push(layout);
1770 }
1771
1772 selections.push((style.local_player, layouts));
1773 }
1774
1775 if let Some(collaboration_hub) = &editor.collaboration_hub {
1776 // When following someone, render the local selections in their color.
1777 if let Some(leader_id) = editor.leader_peer_id {
1778 if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
1779 if let Some(participant_index) = collaboration_hub
1780 .user_participant_indices(cx)
1781 .get(&collaborator.user_id)
1782 {
1783 if let Some((local_selection_style, _)) = selections.first_mut() {
1784 *local_selection_style = cx
1785 .theme()
1786 .players()
1787 .color_for_participant(participant_index.0);
1788 }
1789 }
1790 }
1791 }
1792
1793 let mut remote_selections = HashMap::default();
1794 for selection in snapshot.remote_selections_in_range(
1795 &(start_anchor..end_anchor),
1796 collaboration_hub.as_ref(),
1797 cx,
1798 ) {
1799 let selection_style = if let Some(participant_index) = selection.participant_index {
1800 cx.theme()
1801 .players()
1802 .color_for_participant(participant_index.0)
1803 } else {
1804 cx.theme().players().absent()
1805 };
1806
1807 // Don't re-render the leader's selections, since the local selections
1808 // match theirs.
1809 if Some(selection.peer_id) == editor.leader_peer_id {
1810 continue;
1811 }
1812
1813 remote_selections
1814 .entry(selection.replica_id)
1815 .or_insert((selection_style, Vec::new()))
1816 .1
1817 .push(SelectionLayout::new(
1818 selection.selection,
1819 selection.line_mode,
1820 selection.cursor_shape,
1821 &snapshot.display_snapshot,
1822 false,
1823 false,
1824 ));
1825 }
1826
1827 selections.extend(remote_selections.into_values());
1828 }
1829
1830 let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
1831 let show_scrollbars = match scrollbar_settings.show {
1832 ShowScrollbar::Auto => {
1833 // Git
1834 (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
1835 ||
1836 // Selections
1837 (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty())
1838 // Scrollmanager
1839 || editor.scroll_manager.scrollbars_visible()
1840 }
1841 ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
1842 ShowScrollbar::Always => true,
1843 ShowScrollbar::Never => false,
1844 };
1845
1846 let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
1847 .into_iter()
1848 .map(|(id, fold)| {
1849 todo!("folds!")
1850 // let color = self
1851 // .style
1852 // .folds
1853 // .ellipses
1854 // .background
1855 // .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
1856 // .color;
1857
1858 // (id, fold, color)
1859 })
1860 .collect();
1861
1862 let head_for_relative = newest_selection_head.unwrap_or_else(|| {
1863 let newest = editor.selections.newest::<Point>(cx);
1864 SelectionLayout::new(
1865 newest,
1866 editor.selections.line_mode,
1867 editor.cursor_shape,
1868 &snapshot.display_snapshot,
1869 true,
1870 true,
1871 )
1872 .head
1873 });
1874
1875 let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
1876 start_row..end_row,
1877 &active_rows,
1878 head_for_relative,
1879 is_singleton,
1880 &snapshot,
1881 cx,
1882 );
1883
1884 let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
1885
1886 let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
1887
1888 let mut max_visible_line_width = Pixels::ZERO;
1889 let line_layouts =
1890 self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
1891 for line_with_invisibles in &line_layouts {
1892 if line_with_invisibles.line.width > max_visible_line_width {
1893 max_visible_line_width = line_with_invisibles.line.width;
1894 }
1895 }
1896
1897 let longest_line_width = layout_line(snapshot.longest_row(), &snapshot, &style, cx)
1898 .unwrap()
1899 .width;
1900 let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
1901 // todo!("blocks")
1902 // let (scroll_width, blocks) = self.layout_blocks(
1903 // start_row..end_row,
1904 // &snapshot,
1905 // size.x,
1906 // scroll_width,
1907 // gutter_padding,
1908 // gutter_width,
1909 // em_width,
1910 // gutter_width + gutter_margin,
1911 // line_height,
1912 // &style,
1913 // &line_layouts,
1914 // editor,
1915 // cx,
1916 // );
1917
1918 let scroll_max = point(
1919 f32::from((scroll_width - text_size.width) / em_width).max(0.0),
1920 max_row as f32,
1921 );
1922
1923 let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
1924
1925 let autoscrolled = if autoscroll_horizontally {
1926 editor.autoscroll_horizontally(
1927 start_row,
1928 text_size.width,
1929 scroll_width,
1930 em_width,
1931 &line_layouts,
1932 cx,
1933 )
1934 } else {
1935 false
1936 };
1937
1938 if clamped || autoscrolled {
1939 snapshot = editor.snapshot(cx);
1940 }
1941
1942 // todo!("context menu")
1943 // let mut context_menu = None;
1944 // let mut code_actions_indicator = None;
1945 // if let Some(newest_selection_head) = newest_selection_head {
1946 // if (start_row..end_row).contains(&newest_selection_head.row()) {
1947 // if editor.context_menu_visible() {
1948 // context_menu =
1949 // editor.render_context_menu(newest_selection_head, style.clone(), cx);
1950 // }
1951
1952 // let active = matches!(
1953 // editor.context_menu.read().as_ref(),
1954 // Some(crate::ContextMenu::CodeActions(_))
1955 // );
1956
1957 // code_actions_indicator = editor
1958 // .render_code_actions_indicator(&style, active, cx)
1959 // .map(|indicator| (newest_selection_head.row(), indicator));
1960 // }
1961 // }
1962
1963 let visible_rows = start_row..start_row + line_layouts.len() as u32;
1964 // todo!("hover")
1965 // let mut hover = editor.hover_state.render(
1966 // &snapshot,
1967 // &style,
1968 // visible_rows,
1969 // editor.workspace.as_ref().map(|(w, _)| w.clone()),
1970 // cx,
1971 // );
1972 // let mode = editor.mode;
1973
1974 // todo!("fold_indicators")
1975 // let mut fold_indicators = editor.render_fold_indicators(
1976 // fold_statuses,
1977 // &style,
1978 // editor.gutter_hovered,
1979 // line_height,
1980 // gutter_margin,
1981 // cx,
1982 // );
1983
1984 // todo!("context_menu")
1985 // if let Some((_, context_menu)) = context_menu.as_mut() {
1986 // context_menu.layout(
1987 // SizeConstraint {
1988 // min: gpui::Point::<Pixels>::zero(),
1989 // max: point(
1990 // cx.window_size().x * 0.7,
1991 // (12. * line_height).min((size.y - line_height) / 2.),
1992 // ),
1993 // },
1994 // editor,
1995 // cx,
1996 // );
1997 // }
1998
1999 // todo!("code actions")
2000 // if let Some((_, indicator)) = code_actions_indicator.as_mut() {
2001 // indicator.layout(
2002 // SizeConstraint::strict_along(
2003 // Axis::Vertical,
2004 // line_height * style.code_actions.vertical_scale,
2005 // ),
2006 // editor,
2007 // cx,
2008 // );
2009 // }
2010
2011 // todo!("fold indicators")
2012 // for fold_indicator in fold_indicators.iter_mut() {
2013 // if let Some(indicator) = fold_indicator.as_mut() {
2014 // indicator.layout(
2015 // SizeConstraint::strict_along(
2016 // Axis::Vertical,
2017 // line_height * style.code_actions.vertical_scale,
2018 // ),
2019 // editor,
2020 // cx,
2021 // );
2022 // }
2023 // }
2024
2025 // todo!("hover popovers")
2026 // if let Some((_, hover_popovers)) = hover.as_mut() {
2027 // for hover_popover in hover_popovers.iter_mut() {
2028 // hover_popover.layout(
2029 // SizeConstraint {
2030 // min: gpui::Point::<Pixels>::zero(),
2031 // max: point(
2032 // (120. * em_width) // Default size
2033 // .min(size.x / 2.) // Shrink to half of the editor width
2034 // .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
2035 // (16. * line_height) // Default size
2036 // .min(size.y / 2.) // Shrink to half of the editor height
2037 // .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
2038 // ),
2039 // },
2040 // editor,
2041 // cx,
2042 // );
2043 // }
2044 // }
2045
2046 let invisible_symbol_font_size = font_size / 2.;
2047 let tab_invisible = cx
2048 .text_system()
2049 .layout_text(
2050 "→",
2051 invisible_symbol_font_size,
2052 &[TextRun {
2053 len: "→".len(),
2054 font: self.style.text.font(),
2055 color: cx.theme().colors().editor_invisible,
2056 underline: None,
2057 }],
2058 None,
2059 )
2060 .unwrap()
2061 .pop()
2062 .unwrap();
2063 let space_invisible = cx
2064 .text_system()
2065 .layout_text(
2066 "•",
2067 invisible_symbol_font_size,
2068 &[TextRun {
2069 len: "•".len(),
2070 font: self.style.text.font(),
2071 color: cx.theme().colors().editor_invisible,
2072 underline: None,
2073 }],
2074 None,
2075 )
2076 .unwrap()
2077 .pop()
2078 .unwrap();
2079
2080 LayoutState {
2081 mode: editor_mode,
2082 position_map: Arc::new(PositionMap {
2083 size: bounds.size,
2084 scroll_max,
2085 line_layouts,
2086 line_height,
2087 em_width,
2088 em_advance,
2089 snapshot,
2090 }),
2091 visible_display_row_range: start_row..end_row,
2092 wrap_guides,
2093 gutter_size,
2094 gutter_padding,
2095 text_size,
2096 scrollbar_row_range,
2097 show_scrollbars,
2098 is_singleton,
2099 max_row,
2100 gutter_margin,
2101 active_rows,
2102 highlighted_rows,
2103 highlighted_ranges,
2104 fold_ranges,
2105 line_number_layouts,
2106 display_hunks,
2107 // blocks,
2108 selections,
2109 // context_menu,
2110 // code_actions_indicator,
2111 // fold_indicators,
2112 tab_invisible,
2113 space_invisible,
2114 // hover_popovers: hover,
2115 }
2116 }
2117
2118 // #[allow(clippy::too_many_arguments)]
2119 // fn layout_blocks(
2120 // &mut self,
2121 // rows: Range<u32>,
2122 // snapshot: &EditorSnapshot,
2123 // editor_width: f32,
2124 // scroll_width: f32,
2125 // gutter_padding: f32,
2126 // gutter_width: f32,
2127 // em_width: f32,
2128 // text_x: f32,
2129 // line_height: f32,
2130 // style: &EditorStyle,
2131 // line_layouts: &[LineWithInvisibles],
2132 // editor: &mut Editor,
2133 // cx: &mut ViewContext<Editor>,
2134 // ) -> (f32, Vec<BlockLayout>) {
2135 // let mut block_id = 0;
2136 // let scroll_x = snapshot.scroll_anchor.offset.x;
2137 // let (fixed_blocks, non_fixed_blocks) = snapshot
2138 // .blocks_in_range(rows.clone())
2139 // .partition::<Vec<_>, _>(|(_, block)| match block {
2140 // TransformBlock::ExcerptHeader { .. } => false,
2141 // TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
2142 // });
2143 // let mut render_block = |block: &TransformBlock, width: f32, block_id: usize| {
2144 // let mut element = match block {
2145 // TransformBlock::Custom(block) => {
2146 // let align_to = block
2147 // .position()
2148 // .to_point(&snapshot.buffer_snapshot)
2149 // .to_display_point(snapshot);
2150 // let anchor_x = text_x
2151 // + if rows.contains(&align_to.row()) {
2152 // line_layouts[(align_to.row() - rows.start) as usize]
2153 // .line
2154 // .x_for_index(align_to.column() as usize)
2155 // } else {
2156 // layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
2157 // .x_for_index(align_to.column() as usize)
2158 // };
2159
2160 // block.render(&mut BlockContext {
2161 // view_context: cx,
2162 // anchor_x,
2163 // gutter_padding,
2164 // line_height,
2165 // scroll_x,
2166 // gutter_width,
2167 // em_width,
2168 // block_id,
2169 // })
2170 // }
2171 // TransformBlock::ExcerptHeader {
2172 // id,
2173 // buffer,
2174 // range,
2175 // starts_new_buffer,
2176 // ..
2177 // } => {
2178 // let tooltip_style = theme::current(cx).tooltip.clone();
2179 // let include_root = editor
2180 // .project
2181 // .as_ref()
2182 // .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
2183 // .unwrap_or_default();
2184 // let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
2185 // let jump_path = ProjectPath {
2186 // worktree_id: file.worktree_id(cx),
2187 // path: file.path.clone(),
2188 // };
2189 // let jump_anchor = range
2190 // .primary
2191 // .as_ref()
2192 // .map_or(range.context.start, |primary| primary.start);
2193 // let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
2194
2195 // enum JumpIcon {}
2196 // MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
2197 // let style = style.jump_icon.style_for(state);
2198 // Svg::new("icons/arrow_up_right.svg")
2199 // .with_color(style.color)
2200 // .constrained()
2201 // .with_width(style.icon_width)
2202 // .aligned()
2203 // .contained()
2204 // .with_style(style.container)
2205 // .constrained()
2206 // .with_width(style.button_width)
2207 // .with_height(style.button_width)
2208 // })
2209 // .with_cursor_style(CursorStyle::PointingHand)
2210 // .on_click(MouseButton::Left, move |_, editor, cx| {
2211 // if let Some(workspace) = editor
2212 // .workspace
2213 // .as_ref()
2214 // .and_then(|(workspace, _)| workspace.upgrade(cx))
2215 // {
2216 // workspace.update(cx, |workspace, cx| {
2217 // Editor::jump(
2218 // workspace,
2219 // jump_path.clone(),
2220 // jump_position,
2221 // jump_anchor,
2222 // cx,
2223 // );
2224 // });
2225 // }
2226 // })
2227 // .with_tooltip::<JumpIcon>(
2228 // (*id).into(),
2229 // "Jump to Buffer".to_string(),
2230 // Some(Box::new(crate::OpenExcerpts)),
2231 // tooltip_style.clone(),
2232 // cx,
2233 // )
2234 // .aligned()
2235 // .flex_float()
2236 // });
2237
2238 // if *starts_new_buffer {
2239 // let editor_font_size = style.text.font_size;
2240 // let style = &style.diagnostic_path_header;
2241 // let font_size = (style.text_scale_factor * editor_font_size).round();
2242
2243 // let path = buffer.resolve_file_path(cx, include_root);
2244 // let mut filename = None;
2245 // let mut parent_path = None;
2246 // // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
2247 // if let Some(path) = path {
2248 // filename = path.file_name().map(|f| f.to_string_lossy.to_string());
2249 // parent_path =
2250 // path.parent().map(|p| p.to_string_lossy.to_string() + "/");
2251 // }
2252
2253 // Flex::row()
2254 // .with_child(
2255 // Label::new(
2256 // filename.unwrap_or_else(|| "untitled".to_string()),
2257 // style.filename.text.clone().with_font_size(font_size),
2258 // )
2259 // .contained()
2260 // .with_style(style.filename.container)
2261 // .aligned(),
2262 // )
2263 // .with_children(parent_path.map(|path| {
2264 // Label::new(path, style.path.text.clone().with_font_size(font_size))
2265 // .contained()
2266 // .with_style(style.path.container)
2267 // .aligned()
2268 // }))
2269 // .with_children(jump_icon)
2270 // .contained()
2271 // .with_style(style.container)
2272 // .with_padding_left(gutter_padding)
2273 // .with_padding_right(gutter_padding)
2274 // .expanded()
2275 // .into_any_named("path header block")
2276 // } else {
2277 // let text_style = style.text.clone();
2278 // Flex::row()
2279 // .with_child(Label::new("⋯", text_style))
2280 // .with_children(jump_icon)
2281 // .contained()
2282 // .with_padding_left(gutter_padding)
2283 // .with_padding_right(gutter_padding)
2284 // .expanded()
2285 // .into_any_named("collapsed context")
2286 // }
2287 // }
2288 // };
2289
2290 // element.layout(
2291 // SizeConstraint {
2292 // min: gpui::Point::<Pixels>::zero(),
2293 // max: point(width, block.height() as f32 * line_height),
2294 // },
2295 // editor,
2296 // cx,
2297 // );
2298 // element
2299 // };
2300
2301 // let mut fixed_block_max_width = 0f32;
2302 // let mut blocks = Vec::new();
2303 // for (row, block) in fixed_blocks {
2304 // let element = render_block(block, f32::INFINITY, block_id);
2305 // block_id += 1;
2306 // fixed_block_max_width = fixed_block_max_width.max(element.size().x + em_width);
2307 // blocks.push(BlockLayout {
2308 // row,
2309 // element,
2310 // style: BlockStyle::Fixed,
2311 // });
2312 // }
2313 // for (row, block) in non_fixed_blocks {
2314 // let style = match block {
2315 // TransformBlock::Custom(block) => block.style(),
2316 // TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
2317 // };
2318 // let width = match style {
2319 // BlockStyle::Sticky => editor_width,
2320 // BlockStyle::Flex => editor_width
2321 // .max(fixed_block_max_width)
2322 // .max(gutter_width + scroll_width),
2323 // BlockStyle::Fixed => unreachable!(),
2324 // };
2325 // let element = render_block(block, width, block_id);
2326 // block_id += 1;
2327 // blocks.push(BlockLayout {
2328 // row,
2329 // element,
2330 // style,
2331 // });
2332 // }
2333 // (
2334 // scroll_width.max(fixed_block_max_width - gutter_width),
2335 // blocks,
2336 // )
2337 // }
2338}
2339
2340#[derive(Debug)]
2341pub struct LineWithInvisibles {
2342 pub line: Line,
2343 invisibles: Vec<Invisible>,
2344}
2345
2346impl LineWithInvisibles {
2347 fn from_chunks<'a>(
2348 chunks: impl Iterator<Item = HighlightedChunk<'a>>,
2349 text_style: &TextStyle,
2350 max_line_len: usize,
2351 max_line_count: usize,
2352 line_number_layouts: &[Option<Line>],
2353 editor_mode: EditorMode,
2354 cx: &WindowContext,
2355 ) -> Vec<Self> {
2356 let mut layouts = Vec::with_capacity(max_line_count);
2357 let mut line = String::new();
2358 let mut invisibles = Vec::new();
2359 let mut styles = Vec::new();
2360 let mut non_whitespace_added = false;
2361 let mut row = 0;
2362 let mut line_exceeded_max_len = false;
2363 let font_size = text_style.font_size.to_pixels(cx.rem_size());
2364
2365 for highlighted_chunk in chunks.chain([HighlightedChunk {
2366 chunk: "\n",
2367 style: None,
2368 is_tab: false,
2369 }]) {
2370 for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
2371 if ix > 0 {
2372 let layout = cx
2373 .text_system()
2374 .layout_text(&line, font_size, &styles, None);
2375 layouts.push(Self {
2376 line: layout.unwrap().pop().unwrap(),
2377 invisibles: invisibles.drain(..).collect(),
2378 });
2379
2380 line.clear();
2381 styles.clear();
2382 row += 1;
2383 line_exceeded_max_len = false;
2384 non_whitespace_added = false;
2385 if row == max_line_count {
2386 return layouts;
2387 }
2388 }
2389
2390 if !line_chunk.is_empty() && !line_exceeded_max_len {
2391 let text_style = if let Some(style) = highlighted_chunk.style {
2392 text_style
2393 .clone()
2394 .highlight(style)
2395 .map(Cow::Owned)
2396 .unwrap_or_else(|_| Cow::Borrowed(text_style))
2397 } else {
2398 Cow::Borrowed(text_style)
2399 };
2400
2401 if line.len() + line_chunk.len() > max_line_len {
2402 let mut chunk_len = max_line_len - line.len();
2403 while !line_chunk.is_char_boundary(chunk_len) {
2404 chunk_len -= 1;
2405 }
2406 line_chunk = &line_chunk[..chunk_len];
2407 line_exceeded_max_len = true;
2408 }
2409
2410 styles.push(TextRun {
2411 len: line_chunk.len(),
2412 font: text_style.font(),
2413 color: text_style.color,
2414 underline: text_style.underline,
2415 });
2416
2417 if editor_mode == EditorMode::Full {
2418 // Line wrap pads its contents with fake whitespaces,
2419 // avoid printing them
2420 let inside_wrapped_string = line_number_layouts
2421 .get(row)
2422 .and_then(|layout| layout.as_ref())
2423 .is_none();
2424 if highlighted_chunk.is_tab {
2425 if non_whitespace_added || !inside_wrapped_string {
2426 invisibles.push(Invisible::Tab {
2427 line_start_offset: line.len(),
2428 });
2429 }
2430 } else {
2431 invisibles.extend(
2432 line_chunk
2433 .chars()
2434 .enumerate()
2435 .filter(|(_, line_char)| {
2436 let is_whitespace = line_char.is_whitespace();
2437 non_whitespace_added |= !is_whitespace;
2438 is_whitespace
2439 && (non_whitespace_added || !inside_wrapped_string)
2440 })
2441 .map(|(whitespace_index, _)| Invisible::Whitespace {
2442 line_offset: line.len() + whitespace_index,
2443 }),
2444 )
2445 }
2446 }
2447
2448 line.push_str(line_chunk);
2449 }
2450 }
2451 }
2452
2453 layouts
2454 }
2455
2456 fn draw(
2457 &self,
2458 layout: &LayoutState,
2459 row: u32,
2460 scroll_top: Pixels,
2461 content_origin: gpui::Point<Pixels>,
2462 scroll_left: Pixels,
2463 whitespace_setting: ShowWhitespaceSetting,
2464 selection_ranges: &[Range<DisplayPoint>],
2465 cx: &mut ViewContext<Editor>,
2466 ) {
2467 let line_height = layout.position_map.line_height;
2468 let line_y = line_height * row as f32 - scroll_top;
2469
2470 self.line.paint(
2471 content_origin + gpui::point(-scroll_left, line_y),
2472 line_height,
2473 cx,
2474 );
2475
2476 self.draw_invisibles(
2477 &selection_ranges,
2478 layout,
2479 content_origin,
2480 scroll_left,
2481 line_y,
2482 row,
2483 line_height,
2484 whitespace_setting,
2485 cx,
2486 );
2487 }
2488
2489 fn draw_invisibles(
2490 &self,
2491 selection_ranges: &[Range<DisplayPoint>],
2492 layout: &LayoutState,
2493 content_origin: gpui::Point<Pixels>,
2494 scroll_left: Pixels,
2495 line_y: Pixels,
2496 row: u32,
2497 line_height: Pixels,
2498 whitespace_setting: ShowWhitespaceSetting,
2499 cx: &mut ViewContext<Editor>,
2500 ) {
2501 let allowed_invisibles_regions = match whitespace_setting {
2502 ShowWhitespaceSetting::None => return,
2503 ShowWhitespaceSetting::Selection => Some(selection_ranges),
2504 ShowWhitespaceSetting::All => None,
2505 };
2506
2507 for invisible in &self.invisibles {
2508 let (&token_offset, invisible_symbol) = match invisible {
2509 Invisible::Tab { line_start_offset } => (line_start_offset, &layout.tab_invisible),
2510 Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
2511 };
2512
2513 let x_offset = self.line.x_for_index(token_offset);
2514 let invisible_offset =
2515 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
2516 let origin =
2517 content_origin + gpui::point(-scroll_left + x_offset + invisible_offset, line_y);
2518
2519 if let Some(allowed_regions) = allowed_invisibles_regions {
2520 let invisible_point = DisplayPoint::new(row, token_offset as u32);
2521 if !allowed_regions
2522 .iter()
2523 .any(|region| region.start <= invisible_point && invisible_point < region.end)
2524 {
2525 continue;
2526 }
2527 }
2528 invisible_symbol.paint(origin, line_height, cx);
2529 }
2530 }
2531}
2532
2533#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2534enum Invisible {
2535 Tab { line_start_offset: usize },
2536 Whitespace { line_offset: usize },
2537}
2538
2539impl Element<Editor> for EditorElement {
2540 type ElementState = ();
2541
2542 fn id(&self) -> Option<gpui::ElementId> {
2543 None
2544 }
2545
2546 fn initialize(
2547 &mut self,
2548 editor: &mut Editor,
2549 element_state: Option<Self::ElementState>,
2550 cx: &mut gpui::ViewContext<Editor>,
2551 ) -> Self::ElementState {
2552 editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
2553
2554 let dispatch_context = editor.dispatch_context(cx);
2555 cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
2556 cx.with_key_dispatch_context(dispatch_context, |cx| {
2557 cx.with_key_listeners(
2558 [
2559 build_action_listener(Editor::move_left),
2560 build_action_listener(Editor::move_right),
2561 build_action_listener(Editor::move_down),
2562 build_action_listener(Editor::move_up),
2563 // build_action_listener(Editor::new_file), todo!()
2564 // build_action_listener(Editor::new_file_in_direction), todo!()
2565 build_action_listener(Editor::cancel),
2566 build_action_listener(Editor::newline),
2567 build_action_listener(Editor::newline_above),
2568 build_action_listener(Editor::newline_below),
2569 build_action_listener(Editor::backspace),
2570 build_action_listener(Editor::delete),
2571 build_action_listener(Editor::tab),
2572 build_action_listener(Editor::tab_prev),
2573 build_action_listener(Editor::indent),
2574 build_action_listener(Editor::outdent),
2575 build_action_listener(Editor::delete_line),
2576 build_action_listener(Editor::join_lines),
2577 build_action_listener(Editor::sort_lines_case_sensitive),
2578 build_action_listener(Editor::sort_lines_case_insensitive),
2579 build_action_listener(Editor::reverse_lines),
2580 build_action_listener(Editor::shuffle_lines),
2581 build_action_listener(Editor::convert_to_upper_case),
2582 build_action_listener(Editor::convert_to_lower_case),
2583 build_action_listener(Editor::convert_to_title_case),
2584 build_action_listener(Editor::convert_to_snake_case),
2585 build_action_listener(Editor::convert_to_kebab_case),
2586 build_action_listener(Editor::convert_to_upper_camel_case),
2587 build_action_listener(Editor::convert_to_lower_camel_case),
2588 build_action_listener(Editor::delete_to_previous_word_start),
2589 build_action_listener(Editor::delete_to_previous_subword_start),
2590 build_action_listener(Editor::delete_to_next_word_end),
2591 build_action_listener(Editor::delete_to_next_subword_end),
2592 build_action_listener(Editor::delete_to_beginning_of_line),
2593 build_action_listener(Editor::delete_to_end_of_line),
2594 build_action_listener(Editor::cut_to_end_of_line),
2595 build_action_listener(Editor::duplicate_line),
2596 build_action_listener(Editor::move_line_up),
2597 build_action_listener(Editor::move_line_down),
2598 build_action_listener(Editor::transpose),
2599 build_action_listener(Editor::cut),
2600 build_action_listener(Editor::copy),
2601 build_action_listener(Editor::paste),
2602 build_action_listener(Editor::undo),
2603 build_action_listener(Editor::redo),
2604 build_action_listener(Editor::move_page_up),
2605 build_action_listener(Editor::move_page_down),
2606 build_action_listener(Editor::next_screen),
2607 build_action_listener(Editor::scroll_cursor_top),
2608 build_action_listener(Editor::scroll_cursor_center),
2609 build_action_listener(Editor::scroll_cursor_bottom),
2610 build_action_listener(|editor, _: &LineDown, cx| {
2611 editor.scroll_screen(&ScrollAmount::Line(1.), cx)
2612 }),
2613 build_action_listener(|editor, _: &LineUp, cx| {
2614 editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
2615 }),
2616 build_action_listener(|editor, _: &HalfPageDown, cx| {
2617 editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
2618 }),
2619 build_action_listener(|editor, _: &HalfPageUp, cx| {
2620 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
2621 }),
2622 build_action_listener(|editor, _: &PageDown, cx| {
2623 editor.scroll_screen(&ScrollAmount::Page(1.), cx)
2624 }),
2625 build_action_listener(|editor, _: &PageUp, cx| {
2626 editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
2627 }),
2628 build_action_listener(Editor::move_to_previous_word_start),
2629 build_action_listener(Editor::move_to_previous_subword_start),
2630 build_action_listener(Editor::move_to_next_word_end),
2631 build_action_listener(Editor::move_to_next_subword_end),
2632 build_action_listener(Editor::move_to_beginning_of_line),
2633 build_action_listener(Editor::move_to_end_of_line),
2634 build_action_listener(Editor::move_to_start_of_paragraph),
2635 build_action_listener(Editor::move_to_end_of_paragraph),
2636 build_action_listener(Editor::move_to_beginning),
2637 build_action_listener(Editor::move_to_end),
2638 build_action_listener(Editor::select_up),
2639 build_action_listener(Editor::select_down),
2640 build_action_listener(Editor::select_left),
2641 build_action_listener(Editor::select_right),
2642 build_action_listener(Editor::select_to_previous_word_start),
2643 build_action_listener(Editor::select_to_previous_subword_start),
2644 build_action_listener(Editor::select_to_next_word_end),
2645 build_action_listener(Editor::select_to_next_subword_end),
2646 build_action_listener(Editor::select_to_beginning_of_line),
2647 build_action_listener(Editor::select_to_end_of_line),
2648 build_action_listener(Editor::select_to_start_of_paragraph),
2649 build_action_listener(Editor::select_to_end_of_paragraph),
2650 build_action_listener(Editor::select_to_beginning),
2651 build_action_listener(Editor::select_to_end),
2652 build_action_listener(Editor::select_all),
2653 build_action_listener(|editor, action, cx| {
2654 editor.select_all_matches(action, cx).log_err();
2655 }),
2656 build_action_listener(Editor::select_line),
2657 build_action_listener(Editor::split_selection_into_lines),
2658 build_action_listener(Editor::add_selection_above),
2659 build_action_listener(Editor::add_selection_below),
2660 build_action_listener(|editor, action, cx| {
2661 editor.select_next(action, cx).log_err();
2662 }),
2663 build_action_listener(|editor, action, cx| {
2664 editor.select_previous(action, cx).log_err();
2665 }),
2666 build_action_listener(Editor::toggle_comments),
2667 build_action_listener(Editor::select_larger_syntax_node),
2668 build_action_listener(Editor::select_smaller_syntax_node),
2669 build_action_listener(Editor::move_to_enclosing_bracket),
2670 build_action_listener(Editor::undo_selection),
2671 build_action_listener(Editor::redo_selection),
2672 build_action_listener(Editor::go_to_diagnostic),
2673 build_action_listener(Editor::go_to_prev_diagnostic),
2674 build_action_listener(Editor::go_to_hunk),
2675 build_action_listener(Editor::go_to_prev_hunk),
2676 build_action_listener(Editor::go_to_definition),
2677 build_action_listener(Editor::go_to_definition_split),
2678 build_action_listener(Editor::go_to_type_definition),
2679 build_action_listener(Editor::go_to_type_definition_split),
2680 build_action_listener(Editor::fold),
2681 build_action_listener(Editor::fold_at),
2682 build_action_listener(Editor::unfold_lines),
2683 build_action_listener(Editor::unfold_at),
2684 // build_action_listener(Editor::gutter_hover), todo!()
2685 build_action_listener(Editor::fold_selected_ranges),
2686 build_action_listener(Editor::show_completions),
2687 // build_action_listener(Editor::toggle_code_actions), todo!()
2688 // build_action_listener(Editor::open_excerpts), todo!()
2689 build_action_listener(Editor::toggle_soft_wrap),
2690 build_action_listener(Editor::toggle_inlay_hints),
2691 build_action_listener(Editor::reveal_in_finder),
2692 build_action_listener(Editor::copy_path),
2693 build_action_listener(Editor::copy_relative_path),
2694 build_action_listener(Editor::copy_highlight_json),
2695 build_action_listener(|editor, action, cx| {
2696 editor
2697 .format(action, cx)
2698 .map(|task| task.detach_and_log_err(cx));
2699 }),
2700 build_action_listener(Editor::restart_language_server),
2701 build_action_listener(Editor::show_character_palette),
2702 // build_action_listener(Editor::confirm_completion), todo!()
2703 // build_action_listener(Editor::confirm_code_action), todo!()
2704 // build_action_listener(Editor::rename), todo!()
2705 // build_action_listener(Editor::confirm_rename), todo!()
2706 // build_action_listener(Editor::find_all_references), todo!()
2707 build_action_listener(Editor::next_copilot_suggestion),
2708 build_action_listener(Editor::previous_copilot_suggestion),
2709 build_action_listener(Editor::copilot_suggest),
2710 build_key_listener(
2711 move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
2712 if phase == DispatchPhase::Bubble {
2713 if let KeyMatch::Some(action) = cx.match_keystroke(
2714 &global_id,
2715 &key_down.keystroke,
2716 dispatch_context,
2717 ) {
2718 return Some(action);
2719 }
2720 }
2721
2722 None
2723 },
2724 ),
2725 ],
2726 |cx| cx.with_focus(editor.focus_handle.clone(), |_| {}),
2727 );
2728 })
2729 });
2730 }
2731
2732 fn layout(
2733 &mut self,
2734 editor: &mut Editor,
2735 element_state: &mut Self::ElementState,
2736 cx: &mut gpui::ViewContext<Editor>,
2737 ) -> gpui::LayoutId {
2738 let rem_size = cx.rem_size();
2739 let mut style = Style::default();
2740 style.size.width = relative(1.).into();
2741 style.size.height = match editor.mode {
2742 EditorMode::SingleLine => self.style.text.line_height_in_pixels(cx.rem_size()).into(),
2743 EditorMode::AutoHeight { .. } => todo!(),
2744 EditorMode::Full => relative(1.).into(),
2745 };
2746 cx.request_layout(&style, None)
2747 }
2748
2749 fn paint(
2750 &mut self,
2751 bounds: Bounds<gpui::Pixels>,
2752 editor: &mut Editor,
2753 element_state: &mut Self::ElementState,
2754 cx: &mut gpui::ViewContext<Editor>,
2755 ) {
2756 let layout = self.compute_layout(editor, cx, bounds);
2757 let gutter_bounds = Bounds {
2758 origin: bounds.origin,
2759 size: layout.gutter_size,
2760 };
2761 let text_bounds = Bounds {
2762 origin: gutter_bounds.upper_right(),
2763 size: layout.text_size,
2764 };
2765
2766 cx.on_mouse_event({
2767 let position_map = layout.position_map.clone();
2768 move |editor, event: &ScrollWheelEvent, phase, cx| {
2769 if phase != DispatchPhase::Bubble {
2770 return;
2771 }
2772
2773 if Self::scroll(editor, event, &position_map, bounds, cx) {
2774 cx.stop_propagation();
2775 }
2776 }
2777 });
2778 cx.on_mouse_event({
2779 let position_map = layout.position_map.clone();
2780 move |editor, event: &MouseMoveEvent, phase, cx| {
2781 if phase != DispatchPhase::Bubble {
2782 return;
2783 }
2784
2785 if Self::mouse_moved(editor, event, &position_map, text_bounds, cx) {
2786 cx.stop_propagation()
2787 }
2788 }
2789 });
2790 cx.on_mouse_event({
2791 let position_map = layout.position_map.clone();
2792 move |editor, event: &MouseDownEvent, phase, cx| {
2793 if phase != DispatchPhase::Bubble {
2794 return;
2795 }
2796
2797 if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
2798 cx.stop_propagation()
2799 }
2800 }
2801 });
2802
2803 if editor.focus_handle.is_focused(cx) {
2804 cx.handle_text_input();
2805 }
2806
2807 cx.with_content_mask(ContentMask { bounds }, |cx| {
2808 self.paint_background(gutter_bounds, text_bounds, &layout, cx);
2809 if layout.gutter_size.width > Pixels::ZERO {
2810 self.paint_gutter(gutter_bounds, &layout, editor, cx);
2811 }
2812 self.paint_text(text_bounds, &layout, editor, cx);
2813 });
2814 }
2815}
2816
2817// impl EditorElement {
2818// type LayoutState = LayoutState;
2819// type PaintState = ();
2820
2821// fn layout(
2822// &mut self,
2823// constraint: SizeConstraint,
2824// editor: &mut Editor,
2825// cx: &mut ViewContext<Editor>,
2826// ) -> (gpui::Point<Pixels>, Self::LayoutState) {
2827// let mut size = constraint.max;
2828// if size.x.is_infinite() {
2829// unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
2830// }
2831
2832// let snapshot = editor.snapshot(cx);
2833// let style = self.style.clone();
2834
2835// let line_height = (style.text.font_size * style.line_height_scalar).round();
2836
2837// let gutter_padding;
2838// let gutter_width;
2839// let gutter_margin;
2840// if snapshot.show_gutter {
2841// let em_width = style.text.em_width(cx.font_cache());
2842// gutter_padding = (em_width * style.gutter_padding_factor).round();
2843// gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
2844// gutter_margin = -style.text.descent(cx.font_cache());
2845// } else {
2846// gutter_padding = 0.0;
2847// gutter_width = 0.0;
2848// gutter_margin = 0.0;
2849// };
2850
2851// let text_width = size.x - gutter_width;
2852// let em_width = style.text.em_width(cx.font_cache());
2853// let em_advance = style.text.em_advance(cx.font_cache());
2854// let overscroll = point(em_width, 0.);
2855// let snapshot = {
2856// editor.set_visible_line_count(size.y / line_height, cx);
2857
2858// let editor_width = text_width - gutter_margin - overscroll.x - em_width;
2859// let wrap_width = match editor.soft_wrap_mode(cx) {
2860// SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
2861// SoftWrap::EditorWidth => editor_width,
2862// SoftWrap::Column(column) => editor_width.min(column as f32 * em_advance),
2863// };
2864
2865// if editor.set_wrap_width(Some(wrap_width), cx) {
2866// editor.snapshot(cx)
2867// } else {
2868// snapshot
2869// }
2870// };
2871
2872// let wrap_guides = editor
2873// .wrap_guides(cx)
2874// .iter()
2875// .map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
2876// .collect();
2877
2878// let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
2879// if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
2880// size.set_y(
2881// scroll_height
2882// .min(constraint.max_along(Axis::Vertical))
2883// .max(constraint.min_along(Axis::Vertical))
2884// .max(line_height)
2885// .min(line_height * max_lines as f32),
2886// )
2887// } else if let EditorMode::SingleLine = snapshot.mode {
2888// size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
2889// } else if size.y.is_infinite() {
2890// size.set_y(scroll_height);
2891// }
2892// let gutter_size = point(gutter_width, size.y);
2893// let text_size = point(text_width, size.y);
2894
2895// let autoscroll_horizontally = editor.autoscroll_vertically(size.y, line_height, cx);
2896// let mut snapshot = editor.snapshot(cx);
2897
2898// let scroll_position = snapshot.scroll_position();
2899// // The scroll position is a fractional point, the whole number of which represents
2900// // the top of the window in terms of display rows.
2901// let start_row = scroll_position.y as u32;
2902// let height_in_lines = size.y / line_height;
2903// let max_row = snapshot.max_point().row();
2904
2905// // Add 1 to ensure selections bleed off screen
2906// let end_row = 1 + cmp::min(
2907// (scroll_position.y + height_in_lines).ceil() as u32,
2908// max_row,
2909// );
2910
2911// let start_anchor = if start_row == 0 {
2912// Anchor::min()
2913// } else {
2914// snapshot
2915// .buffer_snapshot
2916// .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left))
2917// };
2918// let end_anchor = if end_row > max_row {
2919// Anchor::max
2920// } else {
2921// snapshot
2922// .buffer_snapshot
2923// .anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
2924// };
2925
2926// let mut selections: Vec<(SelectionStyle, Vec<SelectionLayout>)> = Vec::new();
2927// let mut active_rows = BTreeMap::new();
2928// let mut fold_ranges = Vec::new();
2929// let is_singleton = editor.is_singleton(cx);
2930
2931// let highlighted_rows = editor.highlighted_rows();
2932// let theme = theme::current(cx);
2933// let highlighted_ranges = editor.background_highlights_in_range(
2934// start_anchor..end_anchor,
2935// &snapshot.display_snapshot,
2936// theme.as_ref(),
2937// );
2938
2939// fold_ranges.extend(
2940// snapshot
2941// .folds_in_range(start_anchor..end_anchor)
2942// .map(|anchor| {
2943// let start = anchor.start.to_point(&snapshot.buffer_snapshot);
2944// (
2945// start.row,
2946// start.to_display_point(&snapshot.display_snapshot)
2947// ..anchor.end.to_display_point(&snapshot),
2948// )
2949// }),
2950// );
2951
2952// let mut newest_selection_head = None;
2953
2954// if editor.show_local_selections {
2955// let mut local_selections: Vec<Selection<Point>> = editor
2956// .selections
2957// .disjoint_in_range(start_anchor..end_anchor, cx);
2958// local_selections.extend(editor.selections.pending(cx));
2959// let mut layouts = Vec::new();
2960// let newest = editor.selections.newest(cx);
2961// for selection in local_selections.drain(..) {
2962// let is_empty = selection.start == selection.end;
2963// let is_newest = selection == newest;
2964
2965// let layout = SelectionLayout::new(
2966// selection,
2967// editor.selections.line_mode,
2968// editor.cursor_shape,
2969// &snapshot.display_snapshot,
2970// is_newest,
2971// true,
2972// );
2973// if is_newest {
2974// newest_selection_head = Some(layout.head);
2975// }
2976
2977// for row in cmp::max(layout.active_rows.start, start_row)
2978// ..=cmp::min(layout.active_rows.end, end_row)
2979// {
2980// let contains_non_empty_selection = active_rows.entry(row).or_insert(!is_empty);
2981// *contains_non_empty_selection |= !is_empty;
2982// }
2983// layouts.push(layout);
2984// }
2985
2986// selections.push((style.selection, layouts));
2987// }
2988
2989// if let Some(collaboration_hub) = &editor.collaboration_hub {
2990// // When following someone, render the local selections in their color.
2991// if let Some(leader_id) = editor.leader_peer_id {
2992// if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id) {
2993// if let Some(participant_index) = collaboration_hub
2994// .user_participant_indices(cx)
2995// .get(&collaborator.user_id)
2996// {
2997// if let Some((local_selection_style, _)) = selections.first_mut() {
2998// *local_selection_style =
2999// style.selection_style_for_room_participant(participant_index.0);
3000// }
3001// }
3002// }
3003// }
3004
3005// let mut remote_selections = HashMap::default();
3006// for selection in snapshot.remote_selections_in_range(
3007// &(start_anchor..end_anchor),
3008// collaboration_hub.as_ref(),
3009// cx,
3010// ) {
3011// let selection_style = if let Some(participant_index) = selection.participant_index {
3012// style.selection_style_for_room_participant(participant_index.0)
3013// } else {
3014// style.absent_selection
3015// };
3016
3017// // Don't re-render the leader's selections, since the local selections
3018// // match theirs.
3019// if Some(selection.peer_id) == editor.leader_peer_id {
3020// continue;
3021// }
3022
3023// remote_selections
3024// .entry(selection.replica_id)
3025// .or_insert((selection_style, Vec::new()))
3026// .1
3027// .push(SelectionLayout::new(
3028// selection.selection,
3029// selection.line_mode,
3030// selection.cursor_shape,
3031// &snapshot.display_snapshot,
3032// false,
3033// false,
3034// ));
3035// }
3036
3037// selections.extend(remote_selections.into_values());
3038// }
3039
3040// let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
3041// let show_scrollbars = match scrollbar_settings.show {
3042// ShowScrollbar::Auto => {
3043// // Git
3044// (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
3045// ||
3046// // Selections
3047// (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty)
3048// // Scrollmanager
3049// || editor.scroll_manager.scrollbars_visible()
3050// }
3051// ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
3052// ShowScrollbar::Always => true,
3053// ShowScrollbar::Never => false,
3054// };
3055
3056// let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)> = fold_ranges
3057// .into_iter()
3058// .map(|(id, fold)| {
3059// let color = self
3060// .style
3061// .folds
3062// .ellipses
3063// .background
3064// .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
3065// .color;
3066
3067// (id, fold, color)
3068// })
3069// .collect();
3070
3071// let head_for_relative = newest_selection_head.unwrap_or_else(|| {
3072// let newest = editor.selections.newest::<Point>(cx);
3073// SelectionLayout::new(
3074// newest,
3075// editor.selections.line_mode,
3076// editor.cursor_shape,
3077// &snapshot.display_snapshot,
3078// true,
3079// true,
3080// )
3081// .head
3082// });
3083
3084// let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
3085// start_row..end_row,
3086// &active_rows,
3087// head_for_relative,
3088// is_singleton,
3089// &snapshot,
3090// cx,
3091// );
3092
3093// let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
3094
3095// let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
3096
3097// let mut max_visible_line_width = 0.0;
3098// let line_layouts =
3099// self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
3100// for line_with_invisibles in &line_layouts {
3101// if line_with_invisibles.line.width() > max_visible_line_width {
3102// max_visible_line_width = line_with_invisibles.line.width();
3103// }
3104// }
3105
3106// let style = self.style.clone();
3107// let longest_line_width = layout_line(
3108// snapshot.longest_row(),
3109// &snapshot,
3110// &style,
3111// cx.text_layout_cache(),
3112// )
3113// .width();
3114// let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x;
3115// let em_width = style.text.em_width(cx.font_cache());
3116// let (scroll_width, blocks) = self.layout_blocks(
3117// start_row..end_row,
3118// &snapshot,
3119// size.x,
3120// scroll_width,
3121// gutter_padding,
3122// gutter_width,
3123// em_width,
3124// gutter_width + gutter_margin,
3125// line_height,
3126// &style,
3127// &line_layouts,
3128// editor,
3129// cx,
3130// );
3131
3132// let scroll_max = point(
3133// ((scroll_width - text_size.x) / em_width).max(0.0),
3134// max_row as f32,
3135// );
3136
3137// let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
3138
3139// let autoscrolled = if autoscroll_horizontally {
3140// editor.autoscroll_horizontally(
3141// start_row,
3142// text_size.x,
3143// scroll_width,
3144// em_width,
3145// &line_layouts,
3146// cx,
3147// )
3148// } else {
3149// false
3150// };
3151
3152// if clamped || autoscrolled {
3153// snapshot = editor.snapshot(cx);
3154// }
3155
3156// let style = editor.style(cx);
3157
3158// let mut context_menu = None;
3159// let mut code_actions_indicator = None;
3160// if let Some(newest_selection_head) = newest_selection_head {
3161// if (start_row..end_row).contains(&newest_selection_head.row()) {
3162// if editor.context_menu_visible() {
3163// context_menu =
3164// editor.render_context_menu(newest_selection_head, style.clone(), cx);
3165// }
3166
3167// let active = matches!(
3168// editor.context_menu.read().as_ref(),
3169// Some(crate::ContextMenu::CodeActions(_))
3170// );
3171
3172// code_actions_indicator = editor
3173// .render_code_actions_indicator(&style, active, cx)
3174// .map(|indicator| (newest_selection_head.row(), indicator));
3175// }
3176// }
3177
3178// let visible_rows = start_row..start_row + line_layouts.len() as u32;
3179// let mut hover = editor.hover_state.render(
3180// &snapshot,
3181// &style,
3182// visible_rows,
3183// editor.workspace.as_ref().map(|(w, _)| w.clone()),
3184// cx,
3185// );
3186// let mode = editor.mode;
3187
3188// let mut fold_indicators = editor.render_fold_indicators(
3189// fold_statuses,
3190// &style,
3191// editor.gutter_hovered,
3192// line_height,
3193// gutter_margin,
3194// cx,
3195// );
3196
3197// if let Some((_, context_menu)) = context_menu.as_mut() {
3198// context_menu.layout(
3199// SizeConstraint {
3200// min: gpui::Point::<Pixels>::zero(),
3201// max: point(
3202// cx.window_size().x * 0.7,
3203// (12. * line_height).min((size.y - line_height) / 2.),
3204// ),
3205// },
3206// editor,
3207// cx,
3208// );
3209// }
3210
3211// if let Some((_, indicator)) = code_actions_indicator.as_mut() {
3212// indicator.layout(
3213// SizeConstraint::strict_along(
3214// Axis::Vertical,
3215// line_height * style.code_actions.vertical_scale,
3216// ),
3217// editor,
3218// cx,
3219// );
3220// }
3221
3222// for fold_indicator in fold_indicators.iter_mut() {
3223// if let Some(indicator) = fold_indicator.as_mut() {
3224// indicator.layout(
3225// SizeConstraint::strict_along(
3226// Axis::Vertical,
3227// line_height * style.code_actions.vertical_scale,
3228// ),
3229// editor,
3230// cx,
3231// );
3232// }
3233// }
3234
3235// if let Some((_, hover_popovers)) = hover.as_mut() {
3236// for hover_popover in hover_popovers.iter_mut() {
3237// hover_popover.layout(
3238// SizeConstraint {
3239// min: gpui::Point::<Pixels>::zero(),
3240// max: point(
3241// (120. * em_width) // Default size
3242// .min(size.x / 2.) // Shrink to half of the editor width
3243// .max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
3244// (16. * line_height) // Default size
3245// .min(size.y / 2.) // Shrink to half of the editor height
3246// .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
3247// ),
3248// },
3249// editor,
3250// cx,
3251// );
3252// }
3253// }
3254
3255// let invisible_symbol_font_size = self.style.text.font_size / 2.0;
3256// let invisible_symbol_style = RunStyle {
3257// color: self.style.whitespace,
3258// font_id: self.style.text.font_id,
3259// underline: Default::default(),
3260// };
3261
3262// (
3263// size,
3264// LayoutState {
3265// mode,
3266// position_map: Arc::new(PositionMap {
3267// size,
3268// scroll_max,
3269// line_layouts,
3270// line_height,
3271// em_width,
3272// em_advance,
3273// snapshot,
3274// }),
3275// visible_display_row_range: start_row..end_row,
3276// wrap_guides,
3277// gutter_size,
3278// gutter_padding,
3279// text_size,
3280// scrollbar_row_range,
3281// show_scrollbars,
3282// is_singleton,
3283// max_row,
3284// gutter_margin,
3285// active_rows,
3286// highlighted_rows,
3287// highlighted_ranges,
3288// fold_ranges,
3289// line_number_layouts,
3290// display_hunks,
3291// blocks,
3292// selections,
3293// context_menu,
3294// code_actions_indicator,
3295// fold_indicators,
3296// tab_invisible: cx.text_layout_cache().layout_str(
3297// "→",
3298// invisible_symbol_font_size,
3299// &[("→".len(), invisible_symbol_style)],
3300// ),
3301// space_invisible: cx.text_layout_cache().layout_str(
3302// "•",
3303// invisible_symbol_font_size,
3304// &[("•".len(), invisible_symbol_style)],
3305// ),
3306// hover_popovers: hover,
3307// },
3308// )
3309// }
3310
3311// fn paint(
3312// &mut self,
3313// bounds: Bounds<Pixels>,
3314// visible_bounds: Bounds<Pixels>,
3315// layout: &mut Self::LayoutState,
3316// editor: &mut Editor,
3317// cx: &mut ViewContext<Editor>,
3318// ) -> Self::PaintState {
3319// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
3320// cx.scene().push_layer(Some(visible_bounds));
3321
3322// let gutter_bounds = Bounds::<Pixels>::new(bounds.origin, layout.gutter_size);
3323// let text_bounds = Bounds::<Pixels>::new(
3324// bounds.origin + point(layout.gutter_size.x, 0.0),
3325// layout.text_size,
3326// );
3327
3328// Self::attach_mouse_handlers(
3329// &layout.position_map,
3330// layout.hover_popovers.is_some(),
3331// visible_bounds,
3332// text_bounds,
3333// gutter_bounds,
3334// bounds,
3335// cx,
3336// );
3337
3338// self.paint_background(gutter_bounds, text_bounds, layout, cx);
3339// if layout.gutter_size.x > 0. {
3340// self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
3341// }
3342// self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
3343
3344// cx.scene().push_layer(Some(bounds));
3345// if !layout.blocks.is_empty {
3346// self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
3347// }
3348// self.paint_scrollbar(bounds, layout, &editor, cx);
3349// cx.scene().pop_layer();
3350// cx.scene().pop_layer();
3351// }
3352
3353// fn rect_for_text_range(
3354// &self,
3355// range_utf16: Range<usize>,
3356// bounds: Bounds<Pixels>,
3357// _: Bounds<Pixels>,
3358// layout: &Self::LayoutState,
3359// _: &Self::PaintState,
3360// _: &Editor,
3361// _: &ViewContext<Editor>,
3362// ) -> Option<Bounds<Pixels>> {
3363// let text_bounds = Bounds::<Pixels>::new(
3364// bounds.origin + point(layout.gutter_size.x, 0.0),
3365// layout.text_size,
3366// );
3367// let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.);
3368// let scroll_position = layout.position_map.snapshot.scroll_position();
3369// let start_row = scroll_position.y as u32;
3370// let scroll_top = scroll_position.y * layout.position_map.line_height;
3371// let scroll_left = scroll_position.x * layout.position_map.em_width;
3372
3373// let range_start = OffsetUtf16(range_utf16.start)
3374// .to_display_point(&layout.position_map.snapshot.display_snapshot);
3375// if range_start.row() < start_row {
3376// return None;
3377// }
3378
3379// let line = &layout
3380// .position_map
3381// .line_layouts
3382// .get((range_start.row() - start_row) as usize)?
3383// .line;
3384// let range_start_x = line.x_for_index(range_start.column() as usize);
3385// let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
3386// Some(Bounds::<Pixels>::new(
3387// content_origin
3388// + point(
3389// range_start_x,
3390// range_start_y + layout.position_map.line_height,
3391// )
3392// - point(scroll_left, scroll_top),
3393// point(
3394// layout.position_map.em_width,
3395// layout.position_map.line_height,
3396// ),
3397// ))
3398// }
3399
3400// fn debug(
3401// &self,
3402// bounds: Bounds<Pixels>,
3403// _: &Self::LayoutState,
3404// _: &Self::PaintState,
3405// _: &Editor,
3406// _: &ViewContext<Editor>,
3407// ) -> json::Value {
3408// json!({
3409// "type": "BufferElement",
3410// "bounds": bounds.to_json()
3411// })
3412// }
3413// }
3414
3415type BufferRow = u32;
3416
3417pub struct LayoutState {
3418 position_map: Arc<PositionMap>,
3419 gutter_size: Size<Pixels>,
3420 gutter_padding: Pixels,
3421 gutter_margin: Pixels,
3422 text_size: gpui::Size<Pixels>,
3423 mode: EditorMode,
3424 wrap_guides: SmallVec<[(Pixels, bool); 2]>,
3425 visible_display_row_range: Range<u32>,
3426 active_rows: BTreeMap<u32, bool>,
3427 highlighted_rows: Option<Range<u32>>,
3428 line_number_layouts: Vec<Option<gpui::Line>>,
3429 display_hunks: Vec<DisplayDiffHunk>,
3430 // blocks: Vec<BlockLayout>,
3431 highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
3432 fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)>,
3433 selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
3434 scrollbar_row_range: Range<f32>,
3435 show_scrollbars: bool,
3436 is_singleton: bool,
3437 max_row: u32,
3438 // context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
3439 // code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
3440 // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
3441 // fold_indicators: Vec<Option<AnyElement<Editor>>>,
3442 tab_invisible: Line,
3443 space_invisible: Line,
3444}
3445
3446struct PositionMap {
3447 size: Size<Pixels>,
3448 line_height: Pixels,
3449 scroll_max: gpui::Point<f32>,
3450 em_width: Pixels,
3451 em_advance: Pixels,
3452 line_layouts: Vec<LineWithInvisibles>,
3453 snapshot: EditorSnapshot,
3454}
3455
3456#[derive(Debug, Copy, Clone)]
3457pub struct PointForPosition {
3458 pub previous_valid: DisplayPoint,
3459 pub next_valid: DisplayPoint,
3460 pub exact_unclipped: DisplayPoint,
3461 pub column_overshoot_after_line_end: u32,
3462}
3463
3464impl PointForPosition {
3465 #[cfg(test)]
3466 pub fn valid(valid: DisplayPoint) -> Self {
3467 Self {
3468 previous_valid: valid,
3469 next_valid: valid,
3470 exact_unclipped: valid,
3471 column_overshoot_after_line_end: 0,
3472 }
3473 }
3474
3475 pub fn as_valid(&self) -> Option<DisplayPoint> {
3476 if self.previous_valid == self.exact_unclipped && self.next_valid == self.exact_unclipped {
3477 Some(self.previous_valid)
3478 } else {
3479 None
3480 }
3481 }
3482}
3483
3484impl PositionMap {
3485 fn point_for_position(
3486 &self,
3487 text_bounds: Bounds<Pixels>,
3488 position: gpui::Point<Pixels>,
3489 ) -> PointForPosition {
3490 let scroll_position = self.snapshot.scroll_position();
3491 let position = position - text_bounds.origin;
3492 let y = position.y.max(px(0.)).min(self.size.width);
3493 let x = position.x + (scroll_position.x * self.em_width);
3494 let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
3495
3496 let (column, x_overshoot_after_line_end) = if let Some(line) = self
3497 .line_layouts
3498 .get(row as usize - scroll_position.y as usize)
3499 .map(|&LineWithInvisibles { ref line, .. }| line)
3500 {
3501 if let Some(ix) = line.index_for_x(x) {
3502 (ix as u32, px(0.))
3503 } else {
3504 (line.len as u32, px(0.).max(x - line.width))
3505 }
3506 } else {
3507 (0, x)
3508 };
3509
3510 let mut exact_unclipped = DisplayPoint::new(row, column);
3511 let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
3512 let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
3513
3514 let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into();
3515 *exact_unclipped.column_mut() += column_overshoot_after_line_end;
3516 PointForPosition {
3517 previous_valid,
3518 next_valid,
3519 exact_unclipped,
3520 column_overshoot_after_line_end,
3521 }
3522 }
3523}
3524
3525struct BlockLayout {
3526 row: u32,
3527 element: AnyElement<Editor>,
3528 style: BlockStyle,
3529}
3530
3531fn layout_line(
3532 row: u32,
3533 snapshot: &EditorSnapshot,
3534 style: &EditorStyle,
3535 cx: &WindowContext,
3536) -> Result<Line> {
3537 let mut line = snapshot.line(row);
3538
3539 if line.len() > MAX_LINE_LEN {
3540 let mut len = MAX_LINE_LEN;
3541 while !line.is_char_boundary(len) {
3542 len -= 1;
3543 }
3544
3545 line.truncate(len);
3546 }
3547
3548 Ok(cx
3549 .text_system()
3550 .layout_text(
3551 &line,
3552 style.text.font_size.to_pixels(cx.rem_size()),
3553 &[TextRun {
3554 len: snapshot.line_len(row) as usize,
3555 font: style.text.font(),
3556 color: Hsla::default(),
3557 underline: None,
3558 }],
3559 None,
3560 )?
3561 .pop()
3562 .unwrap())
3563}
3564
3565#[derive(Debug)]
3566pub struct Cursor {
3567 origin: gpui::Point<Pixels>,
3568 block_width: Pixels,
3569 line_height: Pixels,
3570 color: Hsla,
3571 shape: CursorShape,
3572 block_text: Option<Line>,
3573}
3574
3575impl Cursor {
3576 pub fn new(
3577 origin: gpui::Point<Pixels>,
3578 block_width: Pixels,
3579 line_height: Pixels,
3580 color: Hsla,
3581 shape: CursorShape,
3582 block_text: Option<Line>,
3583 ) -> Cursor {
3584 Cursor {
3585 origin,
3586 block_width,
3587 line_height,
3588 color,
3589 shape,
3590 block_text,
3591 }
3592 }
3593
3594 pub fn bounding_rect(&self, origin: gpui::Point<Pixels>) -> Bounds<Pixels> {
3595 Bounds {
3596 origin: self.origin + origin,
3597 size: size(self.block_width, self.line_height),
3598 }
3599 }
3600
3601 pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
3602 let bounds = match self.shape {
3603 CursorShape::Bar => Bounds {
3604 origin: self.origin + origin,
3605 size: size(px(2.0), self.line_height),
3606 },
3607 CursorShape::Block | CursorShape::Hollow => Bounds {
3608 origin: self.origin + origin,
3609 size: size(self.block_width, self.line_height),
3610 },
3611 CursorShape::Underscore => Bounds {
3612 origin: self.origin
3613 + origin
3614 + gpui::Point::new(Pixels::ZERO, self.line_height - px(2.0)),
3615 size: size(self.block_width, px(2.0)),
3616 },
3617 };
3618
3619 //Draw background or border quad
3620 if matches!(self.shape, CursorShape::Hollow) {
3621 cx.paint_quad(
3622 bounds,
3623 Corners::default(),
3624 transparent_black(),
3625 Edges::all(px(1.)),
3626 self.color,
3627 );
3628 } else {
3629 cx.paint_quad(
3630 bounds,
3631 Corners::default(),
3632 self.color,
3633 Edges::default(),
3634 transparent_black(),
3635 );
3636 }
3637
3638 if let Some(block_text) = &self.block_text {
3639 block_text.paint(self.origin + origin, self.line_height, cx);
3640 }
3641 }
3642
3643 pub fn shape(&self) -> CursorShape {
3644 self.shape
3645 }
3646}
3647
3648#[derive(Debug)]
3649pub struct HighlightedRange {
3650 pub start_y: Pixels,
3651 pub line_height: Pixels,
3652 pub lines: Vec<HighlightedRangeLine>,
3653 pub color: Hsla,
3654 pub corner_radius: Pixels,
3655}
3656
3657#[derive(Debug)]
3658pub struct HighlightedRangeLine {
3659 pub start_x: Pixels,
3660 pub end_x: Pixels,
3661}
3662
3663impl HighlightedRange {
3664 pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
3665 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
3666 self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
3667 self.paint_lines(
3668 self.start_y + self.line_height,
3669 &self.lines[1..],
3670 bounds,
3671 cx,
3672 );
3673 } else {
3674 self.paint_lines(self.start_y, &self.lines, bounds, cx);
3675 }
3676 }
3677
3678 fn paint_lines(
3679 &self,
3680 start_y: Pixels,
3681 lines: &[HighlightedRangeLine],
3682 bounds: Bounds<Pixels>,
3683 cx: &mut WindowContext,
3684 ) {
3685 if lines.is_empty() {
3686 return;
3687 }
3688
3689 let first_line = lines.first().unwrap();
3690 let last_line = lines.last().unwrap();
3691
3692 let first_top_left = point(first_line.start_x, start_y);
3693 let first_top_right = point(first_line.end_x, start_y);
3694
3695 let curve_height = point(Pixels::ZERO, self.corner_radius);
3696 let curve_width = |start_x: Pixels, end_x: Pixels| {
3697 let max = (end_x - start_x) / 2.;
3698 let width = if max < self.corner_radius {
3699 max
3700 } else {
3701 self.corner_radius
3702 };
3703
3704 point(width, Pixels::ZERO)
3705 };
3706
3707 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
3708 let mut path = gpui::Path::new(first_top_right - top_curve_width);
3709 path.curve_to(first_top_right + curve_height, first_top_right);
3710
3711 let mut iter = lines.iter().enumerate().peekable();
3712 while let Some((ix, line)) = iter.next() {
3713 let bottom_right = point(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
3714
3715 if let Some((_, next_line)) = iter.peek() {
3716 let next_top_right = point(next_line.end_x, bottom_right.y);
3717
3718 match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
3719 Ordering::Equal => {
3720 path.line_to(bottom_right);
3721 }
3722 Ordering::Less => {
3723 let curve_width = curve_width(next_top_right.x, bottom_right.x);
3724 path.line_to(bottom_right - curve_height);
3725 if self.corner_radius > Pixels::ZERO {
3726 path.curve_to(bottom_right - curve_width, bottom_right);
3727 }
3728 path.line_to(next_top_right + curve_width);
3729 if self.corner_radius > Pixels::ZERO {
3730 path.curve_to(next_top_right + curve_height, next_top_right);
3731 }
3732 }
3733 Ordering::Greater => {
3734 let curve_width = curve_width(bottom_right.x, next_top_right.x);
3735 path.line_to(bottom_right - curve_height);
3736 if self.corner_radius > Pixels::ZERO {
3737 path.curve_to(bottom_right + curve_width, bottom_right);
3738 }
3739 path.line_to(next_top_right - curve_width);
3740 if self.corner_radius > Pixels::ZERO {
3741 path.curve_to(next_top_right + curve_height, next_top_right);
3742 }
3743 }
3744 }
3745 } else {
3746 let curve_width = curve_width(line.start_x, line.end_x);
3747 path.line_to(bottom_right - curve_height);
3748 if self.corner_radius > Pixels::ZERO {
3749 path.curve_to(bottom_right - curve_width, bottom_right);
3750 }
3751
3752 let bottom_left = point(line.start_x, bottom_right.y);
3753 path.line_to(bottom_left + curve_width);
3754 if self.corner_radius > Pixels::ZERO {
3755 path.curve_to(bottom_left - curve_height, bottom_left);
3756 }
3757 }
3758 }
3759
3760 if first_line.start_x > last_line.start_x {
3761 let curve_width = curve_width(last_line.start_x, first_line.start_x);
3762 let second_top_left = point(last_line.start_x, start_y + self.line_height);
3763 path.line_to(second_top_left + curve_height);
3764 if self.corner_radius > Pixels::ZERO {
3765 path.curve_to(second_top_left + curve_width, second_top_left);
3766 }
3767 let first_bottom_left = point(first_line.start_x, second_top_left.y);
3768 path.line_to(first_bottom_left - curve_width);
3769 if self.corner_radius > Pixels::ZERO {
3770 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
3771 }
3772 }
3773
3774 path.line_to(first_top_left + curve_height);
3775 if self.corner_radius > Pixels::ZERO {
3776 path.curve_to(first_top_left + top_curve_width, first_top_left);
3777 }
3778 path.line_to(first_top_right - top_curve_width);
3779
3780 cx.paint_path(path, self.color);
3781 }
3782}
3783
3784// fn range_to_bounds(
3785// range: &Range<DisplayPoint>,
3786// content_origin: gpui::Point<Pixels>,
3787// scroll_left: f32,
3788// scroll_top: f32,
3789// visible_row_range: &Range<u32>,
3790// line_end_overshoot: f32,
3791// position_map: &PositionMap,
3792// ) -> impl Iterator<Item = Bounds<Pixels>> {
3793// let mut bounds: SmallVec<[Bounds<Pixels>; 1]> = SmallVec::new();
3794
3795// if range.start == range.end {
3796// return bounds.into_iter();
3797// }
3798
3799// let start_row = visible_row_range.start;
3800// let end_row = visible_row_range.end;
3801
3802// let row_range = if range.end.column() == 0 {
3803// cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
3804// } else {
3805// cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
3806// };
3807
3808// let first_y =
3809// content_origin.y + row_range.start as f32 * position_map.line_height - scroll_top;
3810
3811// for (idx, row) in row_range.enumerate() {
3812// let line_layout = &position_map.line_layouts[(row - start_row) as usize].line;
3813
3814// let start_x = if row == range.start.row() {
3815// content_origin.x + line_layout.x_for_index(range.start.column() as usize)
3816// - scroll_left
3817// } else {
3818// content_origin.x - scroll_left
3819// };
3820
3821// let end_x = if row == range.end.row() {
3822// content_origin.x + line_layout.x_for_index(range.end.column() as usize) - scroll_left
3823// } else {
3824// content_origin.x + line_layout.width() + line_end_overshoot - scroll_left
3825// };
3826
3827// bounds.push(Bounds::<Pixels>::from_points(
3828// point(start_x, first_y + position_map.line_height * idx as f32),
3829// point(end_x, first_y + position_map.line_height * (idx + 1) as f32),
3830// ))
3831// }
3832
3833// bounds.into_iter()
3834// }
3835
3836pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
3837 delta.powf(1.5) / 100.0
3838}
3839
3840fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
3841 delta.powf(1.2) / 300.0
3842}
3843
3844// #[cfg(test)]
3845// mod tests {
3846// use super::*;
3847// use crate::{
3848// display_map::{BlockDisposition, BlockProperties},
3849// editor_tests::{init_test, update_test_language_settings},
3850// Editor, MultiBuffer,
3851// };
3852// use gpui::TestAppContext;
3853// use language::language_settings;
3854// use log::info;
3855// use std::{num::NonZeroU32, sync::Arc};
3856// use util::test::sample_text;
3857
3858// #[gpui::test]
3859// fn test_layout_line_numbers(cx: &mut TestAppContext) {
3860// init_test(cx, |_| {});
3861// let editor = cx
3862// .add_window(|cx| {
3863// let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
3864// Editor::new(EditorMode::Full, buffer, None, None, cx)
3865// })
3866// .root(cx);
3867// let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3868
3869// let layouts = editor.update(cx, |editor, cx| {
3870// let snapshot = editor.snapshot(cx);
3871// element
3872// .layout_line_numbers(
3873// 0..6,
3874// &Default::default(),
3875// DisplayPoint::new(0, 0),
3876// false,
3877// &snapshot,
3878// cx,
3879// )
3880// .0
3881// });
3882// assert_eq!(layouts.len(), 6);
3883
3884// let relative_rows = editor.update(cx, |editor, cx| {
3885// let snapshot = editor.snapshot(cx);
3886// element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
3887// });
3888// assert_eq!(relative_rows[&0], 3);
3889// assert_eq!(relative_rows[&1], 2);
3890// assert_eq!(relative_rows[&2], 1);
3891// // current line has no relative number
3892// assert_eq!(relative_rows[&4], 1);
3893// assert_eq!(relative_rows[&5], 2);
3894
3895// // works if cursor is before screen
3896// let relative_rows = editor.update(cx, |editor, cx| {
3897// let snapshot = editor.snapshot(cx);
3898
3899// element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
3900// });
3901// assert_eq!(relative_rows.len(), 3);
3902// assert_eq!(relative_rows[&3], 2);
3903// assert_eq!(relative_rows[&4], 3);
3904// assert_eq!(relative_rows[&5], 4);
3905
3906// // works if cursor is after screen
3907// let relative_rows = editor.update(cx, |editor, cx| {
3908// let snapshot = editor.snapshot(cx);
3909
3910// element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
3911// });
3912// assert_eq!(relative_rows.len(), 3);
3913// assert_eq!(relative_rows[&0], 5);
3914// assert_eq!(relative_rows[&1], 4);
3915// assert_eq!(relative_rows[&2], 3);
3916// }
3917
3918// #[gpui::test]
3919// async fn test_vim_visual_selections(cx: &mut TestAppContext) {
3920// init_test(cx, |_| {});
3921
3922// let editor = cx
3923// .add_window(|cx| {
3924// let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
3925// Editor::new(EditorMode::Full, buffer, None, None, cx)
3926// })
3927// .root(cx);
3928// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
3929// let (_, state) = editor.update(cx, |editor, cx| {
3930// editor.cursor_shape = CursorShape::Block;
3931// editor.change_selections(None, cx, |s| {
3932// s.select_ranges([
3933// Point::new(0, 0)..Point::new(1, 0),
3934// Point::new(3, 2)..Point::new(3, 3),
3935// Point::new(5, 6)..Point::new(6, 0),
3936// ]);
3937// });
3938// element.layout(
3939// SizeConstraint::new(point(500., 500.), point(500., 500.)),
3940// editor,
3941// cx,
3942// )
3943// });
3944// assert_eq!(state.selections.len(), 1);
3945// let local_selections = &state.selections[0].1;
3946// assert_eq!(local_selections.len(), 3);
3947// // moves cursor back one line
3948// assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
3949// assert_eq!(
3950// local_selections[0].range,
3951// DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
3952// );
3953
3954// // moves cursor back one column
3955// assert_eq!(
3956// local_selections[1].range,
3957// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
3958// );
3959// assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
3960
3961// // leaves cursor on the max point
3962// assert_eq!(
3963// local_selections[2].range,
3964// DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
3965// );
3966// assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
3967
3968// // active lines does not include 1 (even though the range of the selection does)
3969// assert_eq!(
3970// state.active_rows.keys().cloned().collect::<Vec<u32>>(),
3971// vec![0, 3, 5, 6]
3972// );
3973
3974// // multi-buffer support
3975// // in DisplayPoint co-ordinates, this is what we're dealing with:
3976// // 0: [[file
3977// // 1: header]]
3978// // 2: aaaaaa
3979// // 3: bbbbbb
3980// // 4: cccccc
3981// // 5:
3982// // 6: ...
3983// // 7: ffffff
3984// // 8: gggggg
3985// // 9: hhhhhh
3986// // 10:
3987// // 11: [[file
3988// // 12: header]]
3989// // 13: bbbbbb
3990// // 14: cccccc
3991// // 15: dddddd
3992// let editor = cx
3993// .add_window(|cx| {
3994// let buffer = MultiBuffer::build_multi(
3995// [
3996// (
3997// &(sample_text(8, 6, 'a') + "\n"),
3998// vec![
3999// Point::new(0, 0)..Point::new(3, 0),
4000// Point::new(4, 0)..Point::new(7, 0),
4001// ],
4002// ),
4003// (
4004// &(sample_text(8, 6, 'a') + "\n"),
4005// vec![Point::new(1, 0)..Point::new(3, 0)],
4006// ),
4007// ],
4008// cx,
4009// );
4010// Editor::new(EditorMode::Full, buffer, None, None, cx)
4011// })
4012// .root(cx);
4013// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
4014// let (_, state) = editor.update(cx, |editor, cx| {
4015// editor.cursor_shape = CursorShape::Block;
4016// editor.change_selections(None, cx, |s| {
4017// s.select_display_ranges([
4018// DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
4019// DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
4020// ]);
4021// });
4022// element.layout(
4023// SizeConstraint::new(point(500., 500.), point(500., 500.)),
4024// editor,
4025// cx,
4026// )
4027// });
4028
4029// assert_eq!(state.selections.len(), 1);
4030// let local_selections = &state.selections[0].1;
4031// assert_eq!(local_selections.len(), 2);
4032
4033// // moves cursor on excerpt boundary back a line
4034// // and doesn't allow selection to bleed through
4035// assert_eq!(
4036// local_selections[0].range,
4037// DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
4038// );
4039// assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
4040
4041// // moves cursor on buffer boundary back two lines
4042// // and doesn't allow selection to bleed through
4043// assert_eq!(
4044// local_selections[1].range,
4045// DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
4046// );
4047// assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
4048// }
4049
4050// #[gpui::test]
4051// fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
4052// init_test(cx, |_| {});
4053
4054// let editor = cx
4055// .add_window(|cx| {
4056// let buffer = MultiBuffer::build_simple("", cx);
4057// Editor::new(EditorMode::Full, buffer, None, None, cx)
4058// })
4059// .root(cx);
4060
4061// editor.update(cx, |editor, cx| {
4062// editor.set_placeholder_text("hello", cx);
4063// editor.insert_blocks(
4064// [BlockProperties {
4065// style: BlockStyle::Fixed,
4066// disposition: BlockDisposition::Above,
4067// height: 3,
4068// position: Anchor::min(),
4069// render: Arc::new(|_| Empty::new().into_any),
4070// }],
4071// None,
4072// cx,
4073// );
4074
4075// // Blur the editor so that it displays placeholder text.
4076// cx.blur();
4077// });
4078
4079// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
4080// let (size, mut state) = editor.update(cx, |editor, cx| {
4081// element.layout(
4082// SizeConstraint::new(point(500., 500.), point(500., 500.)),
4083// editor,
4084// cx,
4085// )
4086// });
4087
4088// assert_eq!(state.position_map.line_layouts.len(), 4);
4089// assert_eq!(
4090// state
4091// .line_number_layouts
4092// .iter()
4093// .map(Option::is_some)
4094// .collect::<Vec<_>>(),
4095// &[false, false, false, true]
4096// );
4097
4098// // Don't panic.
4099// let bounds = Bounds::<Pixels>::new(Default::default(), size);
4100// editor.update(cx, |editor, cx| {
4101// element.paint(bounds, bounds, &mut state, editor, cx);
4102// });
4103// }
4104
4105// #[gpui::test]
4106// fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
4107// const TAB_SIZE: u32 = 4;
4108
4109// let input_text = "\t \t|\t| a b";
4110// let expected_invisibles = vec![
4111// Invisible::Tab {
4112// line_start_offset: 0,
4113// },
4114// Invisible::Whitespace {
4115// line_offset: TAB_SIZE as usize,
4116// },
4117// Invisible::Tab {
4118// line_start_offset: TAB_SIZE as usize + 1,
4119// },
4120// Invisible::Tab {
4121// line_start_offset: TAB_SIZE as usize * 2 + 1,
4122// },
4123// Invisible::Whitespace {
4124// line_offset: TAB_SIZE as usize * 3 + 1,
4125// },
4126// Invisible::Whitespace {
4127// line_offset: TAB_SIZE as usize * 3 + 3,
4128// },
4129// ];
4130// assert_eq!(
4131// expected_invisibles.len(),
4132// input_text
4133// .chars()
4134// .filter(|initial_char| initial_char.is_whitespace())
4135// .count(),
4136// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
4137// );
4138
4139// init_test(cx, |s| {
4140// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
4141// s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
4142// });
4143
4144// let actual_invisibles =
4145// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
4146
4147// assert_eq!(expected_invisibles, actual_invisibles);
4148// }
4149
4150// #[gpui::test]
4151// fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
4152// init_test(cx, |s| {
4153// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
4154// s.defaults.tab_size = NonZeroU32::new(4);
4155// });
4156
4157// for editor_mode_without_invisibles in [
4158// EditorMode::SingleLine,
4159// EditorMode::AutoHeight { max_lines: 100 },
4160// ] {
4161// let invisibles = collect_invisibles_from_new_editor(
4162// cx,
4163// editor_mode_without_invisibles,
4164// "\t\t\t| | a b",
4165// 500.0,
4166// );
4167// assert!(invisibles.is_empty,
4168// "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
4169// }
4170// }
4171
4172// #[gpui::test]
4173// fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
4174// let tab_size = 4;
4175// let input_text = "a\tbcd ".repeat(9);
4176// let repeated_invisibles = [
4177// Invisible::Tab {
4178// line_start_offset: 1,
4179// },
4180// Invisible::Whitespace {
4181// line_offset: tab_size as usize + 3,
4182// },
4183// Invisible::Whitespace {
4184// line_offset: tab_size as usize + 4,
4185// },
4186// Invisible::Whitespace {
4187// line_offset: tab_size as usize + 5,
4188// },
4189// ];
4190// let expected_invisibles = std::iter::once(repeated_invisibles)
4191// .cycle()
4192// .take(9)
4193// .flatten()
4194// .collect::<Vec<_>>();
4195// assert_eq!(
4196// expected_invisibles.len(),
4197// input_text
4198// .chars()
4199// .filter(|initial_char| initial_char.is_whitespace())
4200// .count(),
4201// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
4202// );
4203// info!("Expected invisibles: {expected_invisibles:?}");
4204
4205// init_test(cx, |_| {});
4206
4207// // Put the same string with repeating whitespace pattern into editors of various size,
4208// // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
4209// let resize_step = 10.0;
4210// let mut editor_width = 200.0;
4211// while editor_width <= 1000.0 {
4212// update_test_language_settings(cx, |s| {
4213// s.defaults.tab_size = NonZeroU32::new(tab_size);
4214// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
4215// s.defaults.preferred_line_length = Some(editor_width as u32);
4216// s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
4217// });
4218
4219// let actual_invisibles =
4220// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, editor_width);
4221
4222// // Whatever the editor size is, ensure it has the same invisible kinds in the same order
4223// // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
4224// let mut i = 0;
4225// for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
4226// i = actual_index;
4227// match expected_invisibles.get(i) {
4228// Some(expected_invisible) => match (expected_invisible, actual_invisible) {
4229// (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
4230// | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
4231// _ => {
4232// panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
4233// }
4234// },
4235// None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
4236// }
4237// }
4238// let missing_expected_invisibles = &expected_invisibles[i + 1..];
4239// assert!(
4240// missing_expected_invisibles.is_empty,
4241// "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
4242// );
4243
4244// editor_width += resize_step;
4245// }
4246// }
4247
4248// fn collect_invisibles_from_new_editor(
4249// cx: &mut TestAppContext,
4250// editor_mode: EditorMode,
4251// input_text: &str,
4252// editor_width: f32,
4253// ) -> Vec<Invisible> {
4254// info!(
4255// "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
4256// );
4257// let editor = cx
4258// .add_window(|cx| {
4259// let buffer = MultiBuffer::build_simple(&input_text, cx);
4260// Editor::new(editor_mode, buffer, None, None, cx)
4261// })
4262// .root(cx);
4263
4264// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
4265// let (_, layout_state) = editor.update(cx, |editor, cx| {
4266// editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
4267// editor.set_wrap_width(Some(editor_width), cx);
4268
4269// element.layout(
4270// SizeConstraint::new(point(editor_width, 500.), point(editor_width, 500.)),
4271// editor,
4272// cx,
4273// )
4274// });
4275
4276// layout_state
4277// .position_map
4278// .line_layouts
4279// .iter()
4280// .map(|line_with_invisibles| &line_with_invisibles.invisibles)
4281// .flatten()
4282// .cloned()
4283// .collect()
4284// }
4285// }
4286
4287fn build_key_listener<T: 'static>(
4288 listener: impl Fn(
4289 &mut Editor,
4290 &T,
4291 &[&DispatchContext],
4292 DispatchPhase,
4293 &mut ViewContext<Editor>,
4294 ) -> Option<Box<dyn Action>>
4295 + 'static,
4296) -> (TypeId, KeyListener<Editor>) {
4297 (
4298 TypeId::of::<T>(),
4299 Box::new(move |editor, event, dispatch_context, phase, cx| {
4300 let key_event = event.downcast_ref::<T>()?;
4301 listener(editor, key_event, dispatch_context, phase, cx)
4302 }),
4303 )
4304}
4305
4306fn build_action_listener<T: Action>(
4307 listener: impl Fn(&mut Editor, &T, &mut ViewContext<Editor>) + 'static,
4308) -> (TypeId, KeyListener<Editor>) {
4309 build_key_listener(move |editor, action: &T, dispatch_context, phase, cx| {
4310 if phase == DispatchPhase::Bubble {
4311 listener(editor, action, cx);
4312 }
4313 None
4314 })
4315}