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