WIP

Antonio Scandurra created

Change summary

crates/editor2/src/editor.rs         |   6 
crates/editor2/src/element.rs        | 907 +++++++++++++++--------------
crates/gpui2/src/text_system/line.rs |   4 
3 files changed, 469 insertions(+), 448 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -8693,9 +8693,9 @@ impl Editor {
         }
     }
 
-    //     pub fn show_local_cursors(&self, cx: &AppContext) -> bool {
-    //         self.blink_manager.read(cx).visible() && self.focused
-    //     }
+    pub fn show_local_cursors(&self, cx: &AppContext) -> bool {
+        self.blink_manager.read(cx).visible() && self.focused
+    }
 
     fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {
         cx.notify();

crates/editor2/src/element.rs 🔗

@@ -9,12 +9,13 @@ use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
     black, hsla, point, px, relative, size, transparent_black, AnyElement, BorrowWindow, Bounds,
-    ContentMask, Corners, Edges, Element, Hsla, Line, Pixels, Size, Style, TextRun, TextStyle,
-    TextSystem, ViewContext, WindowContext,
+    ContentMask, Corners, Edges, Element, Hsla, Line, Pixels, ShapedGlyph, Size, Style, TextRun,
+    TextStyle, TextSystem, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
 use multi_buffer::Anchor;
+use project::project_settings::{GitGutterSetting, ProjectSettings};
 use settings::Settings;
 use smallvec::SmallVec;
 use std::{
@@ -373,29 +374,29 @@ impl EditorElement {
     //     );
 
     //     if editor.has_pending_selection() {
-    //         let mut scroll_delta = gpui::Point<Pixels>::zero();
+    //         let mut scroll_delta = gpui::Point::<Pixels>::zero();
 
     //         let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
     //         let top = text_bounds.origin.y + vertical_margin;
-    //         let bottom = text_bounds.lower_left().y() - vertical_margin;
-    //         if position.y() < top {
-    //             scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
+    //         let bottom = text_bounds.lower_left().y - vertical_margin;
+    //         if position.y < top {
+    //             scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y))
     //         }
-    //         if position.y() > bottom {
-    //             scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
+    //         if position.y > bottom {
+    //             scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y - bottom))
     //         }
 
     //         let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0);
     //         let left = text_bounds.origin.x + horizontal_margin;
-    //         let right = text_bounds.upper_right().x() - horizontal_margin;
-    //         if position.x() < left {
+    //         let right = text_bounds.upper_right().x - horizontal_margin;
+    //         if position.x < left {
     //             scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
-    //                 left - position.x(),
+    //                 left - position.x,
     //             ))
     //         }
-    //         if position.x() > right {
+    //         if position.x > right {
     //             scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
-    //                 position.x() - right,
+    //                 position.x - right,
     //             ))
     //         }
 
@@ -406,7 +407,7 @@ impl EditorElement {
     //                 position: point_for_position.previous_valid,
     //                 goal_column: point_for_position.exact_unclipped.column(),
     //                 scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
-    //                     .clamp(gpui::Point<Pixels>::zero(), position_map.scroll_max),
+    //                     .clamp(gpui::Point::<Pixels>::zero(), position_map.scroll_max),
     //             },
     //             cx,
     //         );
@@ -489,9 +490,9 @@ impl EditorElement {
     //     };
 
     //     let scroll_position = position_map.snapshot.scroll_position();
-    //     let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
-    //     let y = (scroll_position.y() * line_height - delta.y()) / line_height;
-    //     let scroll_position = point(x, y).clamp(gpui::Point<Pixels>::zero(), position_map.scroll_max);
+    //     let x = (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width;
+    //     let y = (scroll_position.y * line_height - delta.y) / line_height;
+    //     let scroll_position = point(x, y).clamp(gpui::Point::<Pixels>::zero(), position_map.scroll_max);
     //     editor.scroll(scroll_position, axis, cx);
 
     //     true
@@ -608,452 +609,476 @@ impl EditorElement {
         }
     }
 
-    // fn paint_gutter(
-    //     &mut self,
-    //     bounds: Bounds<Pixels>,
-    //     visible_bounds: Bounds<Pixels>,
-    //     layout: &mut LayoutState,
-    //     editor: &mut Editor,
-    //     cx: &mut ViewContext<Editor>,
-    // ) {
-    //     let line_height = layout.position_map.line_height;
-
-    //     let scroll_position = layout.position_map.snapshot.scroll_position();
-    //     let scroll_top = scroll_position.y() * line_height;
-
-    //     let show_gutter = matches!(
-    //         settings::get::<ProjectSettings>(cx).git.git_gutter,
-    //         Some(GitGutterSetting::TrackedFiles)
-    //     );
-
-    //     if show_gutter {
-    //         Self::paint_diff_hunks(bounds, layout, cx);
-    //     }
-
-    //     for (ix, line) in layout.line_number_layouts.iter().enumerate() {
-    //         if let Some(line) = line {
-    //             let line_origin = bounds.origin()
-    //                 + point(
-    //                     bounds.width() - line.width() - layout.gutter_padding,
-    //                     ix as f32 * line_height - (scroll_top % line_height),
-    //                 );
-
-    //             line.paint(line_origin, visible_bounds, line_height, cx);
-    //         }
-    //     }
-
-    //     for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
-    //         if let Some(indicator) = fold_indicator.as_mut() {
-    //             let position = point(
-    //                 bounds.width() - layout.gutter_padding,
-    //                 ix as f32 * line_height - (scroll_top % line_height),
-    //             );
-    //             let centering_offset = point(
-    //                 (layout.gutter_padding + layout.gutter_margin - indicator.size().x()) / 2.,
-    //                 (line_height - indicator.size().y()) / 2.,
-    //             );
-
-    //             let indicator_origin = bounds.origin() + position + centering_offset;
-
-    //             indicator.paint(indicator_origin, visible_bounds, editor, cx);
-    //         }
-    //     }
-
-    //     if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
-    //         let mut x = 0.;
-    //         let mut y = *row as f32 * line_height - scroll_top;
-    //         x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
-    //         y += (line_height - indicator.size().y()) / 2.;
-    //         indicator.paint(bounds.origin() + point(x, y), visible_bounds, editor, cx);
-    //     }
-    // }
-
-    // fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &mut LayoutState, cx: &mut ViewContext<Editor>) {
-    //     let diff_style = &theme::current(cx).editor.diff.clone();
-    //     let line_height = layout.position_map.line_height;
-
-    //     let scroll_position = layout.position_map.snapshot.scroll_position();
-    //     let scroll_top = scroll_position.y() * line_height;
-
-    //     for hunk in &layout.display_hunks {
-    //         let (display_row_range, status) = match hunk {
-    //             //TODO: This rendering is entirely a horrible hack
-    //             &DisplayDiffHunk::Folded { display_row: row } => {
-    //                 let start_y = row as f32 * line_height - scroll_top;
-    //                 let end_y = start_y + line_height;
-
-    //                 let width = diff_style.removed_width_em * line_height;
-    //                 let highlight_origin = bounds.origin() + point(-width, start_y);
-    //                 let highlight_size = point(width * 2., end_y - start_y);
-    //                 let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
-
-    //                 cx.paint_quad(Quad {
-    //                     bounds: highlight_bounds,
-    //                     background: Some(diff_style.modified),
-    //                     border: Border::new(0., Color::transparent_black()).into(),
-    //                     corner_radii: (1. * line_height).into(),
-    //                 });
-
-    //                 continue;
-    //             }
-
-    //             DisplayDiffHunk::Unfolded {
-    //                 display_row_range,
-    //                 status,
-    //             } => (display_row_range, status),
-    //         };
-
-    //         let color = match status {
-    //             DiffHunkStatus::Added => diff_style.inserted,
-    //             DiffHunkStatus::Modified => diff_style.modified,
-
-    //             //TODO: This rendering is entirely a horrible hack
-    //             DiffHunkStatus::Removed => {
-    //                 let row = display_row_range.start;
+    fn paint_gutter(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        layout: &mut LayoutState,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let line_height = layout.position_map.line_height;
 
-    //                 let offset = line_height / 2.;
-    //                 let start_y = row as f32 * line_height - offset - scroll_top;
-    //                 let end_y = start_y + line_height;
+        let scroll_position = layout.position_map.snapshot.scroll_position();
+        let scroll_top = scroll_position.y * line_height;
 
-    //                 let width = diff_style.removed_width_em * line_height;
-    //                 let highlight_origin = bounds.origin() + point(-width, start_y);
-    //                 let highlight_size = point(width * 2., end_y - start_y);
-    //                 let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
+        let show_gutter = matches!(
+            ProjectSettings::get_global(cx).git.git_gutter,
+            Some(GitGutterSetting::TrackedFiles)
+        );
 
-    //                 cx.paint_quad(Quad {
-    //                     bounds: highlight_bounds,
-    //                     background: Some(diff_style.deleted),
-    //                     border: Border::new(0., Color::transparent_black()).into(),
-    //                     corner_radii: (1. * line_height).into(),
-    //                 });
+        if show_gutter {
+            Self::paint_diff_hunks(bounds, layout, cx);
+        }
 
-    //                 continue;
-    //             }
-    //         };
+        for (ix, line) in layout.line_number_layouts.iter().enumerate() {
+            if let Some(line) = line {
+                let line_origin = bounds.origin
+                    + point(
+                        bounds.size.width - line.width - layout.gutter_padding,
+                        ix as f32 * line_height - (scroll_top % line_height),
+                    );
 
-    //         let start_row = display_row_range.start;
-    //         let end_row = display_row_range.end;
+                line.paint(line_origin, line_height, cx);
+            }
+        }
 
-    //         let start_y = start_row as f32 * line_height - scroll_top;
-    //         let end_y = end_row as f32 * line_height - scroll_top;
+        // todo!("fold indicators")
+        // for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
+        //     if let Some(indicator) = fold_indicator.as_mut() {
+        //         let position = point(
+        //             bounds.width() - layout.gutter_padding,
+        //             ix as f32 * line_height - (scroll_top % line_height),
+        //         );
+        //         let centering_offset = point(
+        //             (layout.gutter_padding + layout.gutter_margin - indicator.size().x) / 2.,
+        //             (line_height - indicator.size().y) / 2.,
+        //         );
 
-    //         let width = diff_style.width_em * line_height;
-    //         let highlight_origin = bounds.origin() + point(-width, start_y);
-    //         let highlight_size = point(width * 2., end_y - start_y);
-    //         let highlight_bounds = Bounds<Pixels>::new(highlight_origin, highlight_size);
+        //         let indicator_origin = bounds.origin + position + centering_offset;
 
-    //         cx.paint_quad(Quad {
-    //             bounds: highlight_bounds,
-    //             background: Some(color),
-    //             border: Border::new(0., Color::transparent_black()).into(),
-    //             corner_radii: (diff_style.corner_radius * line_height).into(),
-    //         });
-    //     }
-    // }
+        //         indicator.paint(indicator_origin, visible_bounds, editor, cx);
+        //     }
+        // }
 
-    // fn paint_text(
-    //     &mut self,
-    //     bounds: Bounds<Pixels>,
-    //     visible_bounds: Bounds<Pixels>,
-    //     layout: &mut LayoutState,
-    //     editor: &mut Editor,
-    //     cx: &mut ViewContext<Editor>,
-    // ) {
-    //     let style = &self.style;
-    //     let scroll_position = layout.position_map.snapshot.scroll_position();
-    //     let start_row = layout.visible_display_row_range.start;
-    //     let scroll_top = scroll_position.y() * layout.position_map.line_height;
-    //     let max_glyph_width = layout.position_map.em_width;
-    //     let scroll_left = scroll_position.x() * max_glyph_width;
-    //     let content_origin = bounds.origin() + point(layout.gutter_margin, 0.);
-    //     let line_end_overshoot = 0.15 * layout.position_map.line_height;
-    //     let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
+        // todo!("code actions indicator")
+        // if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
+        //     let mut x = 0.;
+        //     let mut y = *row as f32 * line_height - scroll_top;
+        //     x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x) / 2.;
+        //     y += (line_height - indicator.size().y) / 2.;
+        //     indicator.paint(bounds.origin + point(x, y), visible_bounds, editor, cx);
+        // }
+    }
 
-    //     cx.scene().push_layer(Some(bounds));
+    fn paint_diff_hunks(
+        bounds: Bounds<Pixels>,
+        layout: &mut LayoutState,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        // todo!()
+        // let diff_style = &theme::current(cx).editor.diff.clone();
+        // let line_height = layout.position_map.line_height;
+
+        // let scroll_position = layout.position_map.snapshot.scroll_position();
+        // let scroll_top = scroll_position.y * line_height;
+
+        // for hunk in &layout.display_hunks {
+        //     let (display_row_range, status) = match hunk {
+        //         //TODO: This rendering is entirely a horrible hack
+        //         &DisplayDiffHunk::Folded { display_row: row } => {
+        //             let start_y = row as f32 * line_height - scroll_top;
+        //             let end_y = start_y + line_height;
+
+        //             let width = diff_style.removed_width_em * line_height;
+        //             let highlight_origin = bounds.origin + point(-width, start_y);
+        //             let highlight_size = point(width * 2., end_y - start_y);
+        //             let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
+
+        //             cx.paint_quad(Quad {
+        //                 bounds: highlight_bounds,
+        //                 background: Some(diff_style.modified),
+        //                 border: Border::new(0., Color::transparent_black()).into(),
+        //                 corner_radii: (1. * line_height).into(),
+        //             });
+
+        //             continue;
+        //         }
 
-    //     cx.scene().push_cursor_region(CursorRegion {
-    //         bounds,
-    //         style: if !editor.link_go_to_definition_state.definitions.is_empty() {
-    //             CursorStyle::PointingHand
-    //         } else {
-    //             CursorStyle::IBeam
-    //         },
-    //     });
+        //         DisplayDiffHunk::Unfolded {
+        //             display_row_range,
+        //             status,
+        //         } => (display_row_range, status),
+        //     };
+
+        //     let color = match status {
+        //         DiffHunkStatus::Added => diff_style.inserted,
+        //         DiffHunkStatus::Modified => diff_style.modified,
+
+        //         //TODO: This rendering is entirely a horrible hack
+        //         DiffHunkStatus::Removed => {
+        //             let row = display_row_range.start;
+
+        //             let offset = line_height / 2.;
+        //             let start_y = row as f32 * line_height - offset - scroll_top;
+        //             let end_y = start_y + line_height;
+
+        //             let width = diff_style.removed_width_em * line_height;
+        //             let highlight_origin = bounds.origin + point(-width, start_y);
+        //             let highlight_size = point(width * 2., end_y - start_y);
+        //             let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
+
+        //             cx.paint_quad(Quad {
+        //                 bounds: highlight_bounds,
+        //                 background: Some(diff_style.deleted),
+        //                 border: Border::new(0., Color::transparent_black()).into(),
+        //                 corner_radii: (1. * line_height).into(),
+        //             });
+
+        //             continue;
+        //         }
+        //     };
 
-    //     let fold_corner_radius =
-    //         self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
-    //     for (id, range, color) in layout.fold_ranges.iter() {
-    //         self.paint_highlighted_range(
-    //             range.clone(),
-    //             *color,
-    //             fold_corner_radius,
-    //             fold_corner_radius * 2.,
-    //             layout,
-    //             content_origin,
-    //             scroll_top,
-    //             scroll_left,
-    //             bounds,
-    //             cx,
-    //         );
+        //     let start_row = display_row_range.start;
+        //     let end_row = display_row_range.end;
 
-    //         for bound in range_to_bounds(
-    //             &range,
-    //             content_origin,
-    //             scroll_left,
-    //             scroll_top,
-    //             &layout.visible_display_row_range,
-    //             line_end_overshoot,
-    //             &layout.position_map,
-    //         ) {
-    //             cx.scene().push_cursor_region(CursorRegion {
-    //                 bounds: bound,
-    //                 style: CursorStyle::PointingHand,
-    //             });
-
-    //             let display_row = range.start.row();
-
-    //             let buffer_row = DisplayPoint::new(display_row, 0)
-    //                 .to_point(&layout.position_map.snapshot.display_snapshot)
-    //                 .row;
-
-    //             let view_id = cx.view_id();
-    //             cx.scene().push_mouse_region(
-    //                 MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
-    //                     .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
-    //                         editor.unfold_at(&UnfoldAt { buffer_row }, cx)
-    //                     })
-    //                     .with_notify_on_hover(true)
-    //                     .with_notify_on_click(true),
-    //             )
-    //         }
-    //     }
+        //     let start_y = start_row as f32 * line_height - scroll_top;
+        //     let end_y = end_row as f32 * line_height - scroll_top;
 
-    //     for (range, color) in &layout.highlighted_ranges {
-    //         self.paint_highlighted_range(
-    //             range.clone(),
-    //             *color,
-    //             0.,
-    //             line_end_overshoot,
-    //             layout,
-    //             content_origin,
-    //             scroll_top,
-    //             scroll_left,
-    //             bounds,
-    //             cx,
-    //         );
-    //     }
+        //     let width = diff_style.width_em * line_height;
+        //     let highlight_origin = bounds.origin + point(-width, start_y);
+        //     let highlight_size = point(width * 2., end_y - start_y);
+        //     let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
 
-    //     let mut cursors = SmallVec::<[Cursor; 32]>::new();
-    //     let corner_radius = 0.15 * layout.position_map.line_height;
-    //     let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
-
-    //     for (selection_style, selections) in &layout.selections {
-    //         for selection in selections {
-    //             self.paint_highlighted_range(
-    //                 selection.range.clone(),
-    //                 selection_style.selection,
-    //                 corner_radius,
-    //                 corner_radius * 2.,
-    //                 layout,
-    //                 content_origin,
-    //                 scroll_top,
-    //                 scroll_left,
-    //                 bounds,
-    //                 cx,
-    //             );
-
-    //             if selection.is_local && !selection.range.is_empty() {
-    //                 invisible_display_ranges.push(selection.range.clone());
-    //             }
-    //             if !selection.is_local || editor.show_local_cursors(cx) {
-    //                 let cursor_position = selection.head;
-    //                 if layout
-    //                     .visible_display_row_range
-    //                     .contains(&cursor_position.row())
-    //                 {
-    //                     let cursor_row_layout = &layout.position_map.line_layouts
-    //                         [(cursor_position.row() - start_row) as usize]
-    //                         .line;
-    //                     let cursor_column = cursor_position.column() as usize;
-
-    //                     let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
-    //                     let mut block_width =
-    //                         cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
-    //                     if block_width == 0.0 {
-    //                         block_width = layout.position_map.em_width;
-    //                     }
-    //                     let block_text = if let CursorShape::Block = selection.cursor_shape {
-    //                         layout
-    //                             .position_map
-    //                             .snapshot
-    //                             .chars_at(cursor_position)
-    //                             .next()
-    //                             .and_then(|(character, _)| {
-    //                                 let font_id =
-    //                                     cursor_row_layout.font_for_index(cursor_column)?;
-    //                                 let text = character.to_string();
-
-    //                                 Some(cx.text_layout_cache().layout_str(
-    //                                     &text,
-    //                                     cursor_row_layout.font_size(),
-    //                                     &[(
-    //                                         text.chars().count(),
-    //                                         RunStyle {
-    //                                             font_id,
-    //                                             color: style.background,
-    //                                             underline: Default::default(),
-    //                                         },
-    //                                     )],
-    //                                 ))
-    //                             })
-    //                     } else {
-    //                         None
-    //                     };
+        //     cx.paint_quad(Quad {
+        //         bounds: highlight_bounds,
+        //         background: Some(color),
+        //         border: Border::new(0., Color::transparent_black()).into(),
+        //         corner_radii: (diff_style.corner_radius * line_height).into(),
+        //     });
+        // }
+    }
 
-    //                     let x = cursor_character_x - scroll_left;
-    //                     let y = cursor_position.row() as f32 * layout.position_map.line_height
-    //                         - scroll_top;
-    //                     if selection.is_newest {
-    //                         editor.pixel_position_of_newest_cursor = Some(point(
-    //                             bounds.origin.x + x + block_width / 2.,
-    //                             bounds.origin.y + y + layout.position_map.line_height / 2.,
-    //                         ));
-    //                     }
-    //                     cursors.push(Cursor {
-    //                         color: selection_style.cursor,
-    //                         block_width,
-    //                         origin: point(x, y),
-    //                         line_height: layout.position_map.line_height,
-    //                         shape: selection.cursor_shape,
-    //                         block_text,
-    //                     });
-    //                 }
-    //             }
-    //         }
-    //     }
+    fn paint_text(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        layout: &mut LayoutState,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let style = &self.style;
+        let scroll_position = layout.position_map.snapshot.scroll_position();
+        let start_row = layout.visible_display_row_range.start;
+        let scroll_top = scroll_position.y * layout.position_map.line_height;
+        let max_glyph_width = layout.position_map.em_width;
+        let scroll_left = scroll_position.x * max_glyph_width;
+        let content_origin = bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
+        let line_end_overshoot = 0.15 * layout.position_map.line_height;
+        let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
 
-    //     if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) {
-    //         for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
-    //             let row = start_row + ix as u32;
-    //             line_with_invisibles.draw(
-    //                 layout,
-    //                 row,
-    //                 scroll_top,
-    //                 content_origin,
-    //                 scroll_left,
-    //                 visible_text_bounds,
-    //                 whitespace_setting,
-    //                 &invisible_display_ranges,
-    //                 visible_bounds,
-    //                 cx,
-    //             )
-    //         }
-    //     }
+        cx.with_content_mask(ContentMask { bounds }, |cx| {
+            // todo!("cursor region")
+            // cx.scene().push_cursor_region(CursorRegion {
+            //     bounds,
+            //     style: if !editor.link_go_to_definition_state.definitions.is_empty {
+            //         CursorStyle::PointingHand
+            //     } else {
+            //         CursorStyle::IBeam
+            //     },
+            // });
+
+            // todo!("fold ranges")
+            // let fold_corner_radius =
+            //     self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
+            // for (id, range, color) in layout.fold_ranges.iter() {
+            //     self.paint_highlighted_range(
+            //         range.clone(),
+            //         *color,
+            //         fold_corner_radius,
+            //         fold_corner_radius * 2.,
+            //         layout,
+            //         content_origin,
+            //         scroll_top,
+            //         scroll_left,
+            //         bounds,
+            //         cx,
+            //     );
+
+            //     for bound in range_to_bounds(
+            //         &range,
+            //         content_origin,
+            //         scroll_left,
+            //         scroll_top,
+            //         &layout.visible_display_row_range,
+            //         line_end_overshoot,
+            //         &layout.position_map,
+            //     ) {
+            //         cx.scene().push_cursor_region(CursorRegion {
+            //             bounds: bound,
+            //             style: CursorStyle::PointingHand,
+            //         });
+
+            //         let display_row = range.start.row();
+
+            //         let buffer_row = DisplayPoint::new(display_row, 0)
+            //             .to_point(&layout.position_map.snapshot.display_snapshot)
+            //             .row;
+
+            //         let view_id = cx.view_id();
+            //         cx.scene().push_mouse_region(
+            //             MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
+            //                 .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
+            //                     editor.unfold_at(&UnfoldAt { buffer_row }, cx)
+            //                 })
+            //                 .with_notify_on_hover(true)
+            //                 .with_notify_on_click(true),
+            //         )
+            //     }
+            // }
+
+            for (range, color) in &layout.highlighted_ranges {
+                self.paint_highlighted_range(
+                    range.clone(),
+                    *color,
+                    Pixels::ZERO,
+                    line_end_overshoot,
+                    layout,
+                    content_origin,
+                    scroll_top,
+                    scroll_left,
+                    bounds,
+                    cx,
+                );
+            }
 
-    //     cx.scene().push_layer(Some(bounds));
-    //     for cursor in cursors {
-    //         cursor.paint(content_origin, cx);
-    //     }
-    //     cx.scene().pop_layer();
-
-    //     if let Some((position, context_menu)) = layout.context_menu.as_mut() {
-    //         cx.scene().push_stacking_context(None, None);
-    //         let cursor_row_layout =
-    //             &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
-    //         let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
-    //         let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
-    //         let mut list_origin = content_origin + point(x, y);
-    //         let list_width = context_menu.size().x();
-    //         let list_height = context_menu.size().y();
-
-    //         // Snap the right edge of the list to the right edge of the window if
-    //         // its horizontal bounds overflow.
-    //         if list_origin.x() + list_width > cx.window_size().x() {
-    //             list_origin.set_x((cx.window_size().x() - list_width).max(0.));
-    //         }
+            let mut cursors = SmallVec::<[Cursor; 32]>::new();
+            let corner_radius = 0.15 * layout.position_map.line_height;
+            let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
+
+            for (selection_style, selections) in &layout.selections {
+                for selection in selections {
+                    self.paint_highlighted_range(
+                        selection.range.clone(),
+                        selection_style.selection,
+                        corner_radius,
+                        corner_radius * 2.,
+                        layout,
+                        content_origin,
+                        scroll_top,
+                        scroll_left,
+                        bounds,
+                        cx,
+                    );
 
-    //         if list_origin.y() + list_height > bounds.max_y() {
-    //             list_origin.set_y(list_origin.y() - layout.position_map.line_height - list_height);
-    //         }
+                    if selection.is_local && !selection.range.is_empty() {
+                        invisible_display_ranges.push(selection.range.clone());
+                    }
+                    if !selection.is_local || editor.show_local_cursors(cx) {
+                        let cursor_position = selection.head;
+                        if layout
+                            .visible_display_row_range
+                            .contains(&cursor_position.row())
+                        {
+                            let cursor_row_layout = &layout.position_map.line_layouts
+                                [(cursor_position.row() - start_row) as usize]
+                                .line;
+                            let cursor_column = cursor_position.column() as usize;
+
+                            let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
+                            let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1)
+                                - cursor_character_x;
+                            if block_width == Pixels::ZERO {
+                                block_width = layout.position_map.em_width;
+                            }
+                            let block_text = if let CursorShape::Block = selection.cursor_shape {
+                                layout
+                                    .position_map
+                                    .snapshot
+                                    .chars_at(cursor_position)
+                                    .next()
+                                    .and_then(|(character, _)| {
+                                        let font_id =
+                                            cursor_row_layout.font_for_index(cursor_column)?;
+                                        let text = character.to_string();
+
+                                        cx.text_system().layout_text(
+                                            &text,
+                                            cursor_row_layout.font_size,
+                                            &[TextRun {
+                                                len: text.len(),
+                                                font: todo!(),
+                                                color: todo!(),
+                                                underline: todo!(),
+                                            }],
+                                            None,
+                                        );
+                                        Some(cx.text_layout_cache().layout_str(
+                                            &text,
+                                            cursor_row_layout.font_size,
+                                            &[(
+                                                text.chars().count(),
+                                                RunStyle {
+                                                    font_id,
+                                                    color: style.background,
+                                                    underline: Default::default(),
+                                                },
+                                            )],
+                                        ))
+                                    })
+                            } else {
+                                None
+                            };
+
+                            let x = cursor_character_x - scroll_left;
+                            let y = cursor_position.row() as f32 * layout.position_map.line_height
+                                - scroll_top;
+                            if selection.is_newest {
+                                editor.pixel_position_of_newest_cursor = Some(point(
+                                    bounds.origin.x + x + block_width / 2.,
+                                    bounds.origin.y + y + layout.position_map.line_height / 2.,
+                                ));
+                            }
+                            cursors.push(Cursor {
+                                color: selection_style.cursor,
+                                block_width,
+                                origin: point(x, y),
+                                line_height: layout.position_map.line_height,
+                                shape: selection.cursor_shape,
+                                block_text,
+                            });
+                        }
+                    }
+                }
+            }
 
-    //         context_menu.paint(
-    //             list_origin,
-    //             Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
-    //             editor,
-    //             cx,
-    //         );
+            for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
+                let row = start_row + ix as u32;
+                line_with_invisibles.draw(
+                    layout,
+                    row,
+                    scroll_top,
+                    content_origin,
+                    scroll_left,
+                    visible_text_bounds,
+                    whitespace_setting,
+                    &invisible_display_ranges,
+                    visible_bounds,
+                    cx,
+                )
+            }
 
-    //         cx.scene().pop_stacking_context();
-    //     }
+            cx.scene().push_layer(Some(bounds));
+            for cursor in cursors {
+                cursor.paint(content_origin, cx);
+            }
+            cx.scene().pop_layer();
+
+            if let Some((position, context_menu)) = layout.context_menu.as_mut() {
+                cx.scene().push_stacking_context(None, None);
+                let cursor_row_layout =
+                    &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
+                let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
+                let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top;
+                let mut list_origin = content_origin + point(x, y);
+                let list_width = context_menu.size().x;
+                let list_height = context_menu.size().y;
+
+                // Snap the right edge of the list to the right edge of the window if
+                // its horizontal bounds overflow.
+                if list_origin.x + list_width > cx.window_size().x {
+                    list_origin.set_x((cx.window_size().x - list_width).max(0.));
+                }
 
-    //     if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
-    //         cx.scene().push_stacking_context(None, None);
-
-    //         // This is safe because we check on layout whether the required row is available
-    //         let hovered_row_layout =
-    //             &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
-
-    //         // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
-    //         // height. This is the size we will use to decide whether to render popovers above or below
-    //         // the hovered line.
-    //         let first_size = hover_popovers[0].size();
-    //         let height_to_reserve = first_size.y()
-    //             + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;
-
-    //         // Compute Hovered Point
-    //         let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
-    //         let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
-    //         let hovered_point = content_origin + point(x, y);
-
-    //         if hovered_point.y() - height_to_reserve > 0.0 {
-    //             // There is enough space above. Render popovers above the hovered point
-    //             let mut current_y = hovered_point.y();
-    //             for hover_popover in hover_popovers {
-    //                 let size = hover_popover.size();
-    //                 let mut popover_origin = point(hovered_point.x(), current_y - size.y());
-
-    //                 let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
-    //                 if x_out_of_bounds < 0.0 {
-    //                     popover_origin.set_x(popover_origin.x() + x_out_of_bounds);
-    //                 }
+                if list_origin.y + list_height > bounds.max_y {
+                    list_origin
+                        .set_y(list_origin.y - layout.position_map.line_height - list_height);
+                }
 
-    //                 hover_popover.paint(
-    //                     popover_origin,
-    //                     Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
-    //                     editor,
-    //                     cx,
-    //                 );
+                context_menu.paint(
+                    list_origin,
+                    Bounds::<Pixels>::from_points(
+                        gpui::Point::<Pixels>::zero(),
+                        point(f32::MAX, f32::MAX),
+                    ), // Let content bleed outside of editor
+                    editor,
+                    cx,
+                );
 
-    //                 current_y = popover_origin.y() - HOVER_POPOVER_GAP;
-    //             }
-    //         } else {
-    //             // There is not enough space above. Render popovers below the hovered point
-    //             let mut current_y = hovered_point.y() + layout.position_map.line_height;
-    //             for hover_popover in hover_popovers {
-    //                 let size = hover_popover.size();
-    //                 let mut popover_origin = point(hovered_point.x(), current_y);
-
-    //                 let x_out_of_bounds = bounds.max_x() - (popover_origin.x() + size.x());
-    //                 if x_out_of_bounds < 0.0 {
-    //                     popover_origin.set_x(popover_origin.x() + x_out_of_bounds);
-    //                 }
+                cx.scene().pop_stacking_context();
+            }
 
-    //                 hover_popover.paint(
-    //                     popover_origin,
-    //                     Bounds<Pixels>::from_points(gpui::Point<Pixels>::zero(), point(f32::MAX, f32::MAX)), // Let content bleed outside of editor
-    //                     editor,
-    //                     cx,
-    //                 );
+            if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
+                cx.scene().push_stacking_context(None, None);
+
+                // This is safe because we check on layout whether the required row is available
+                let hovered_row_layout =
+                    &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
+
+                // Minimum required size: Take the first popover, and add 1.5 times the minimum popover
+                // height. This is the size we will use to decide whether to render popovers above or below
+                // the hovered line.
+                let first_size = hover_popovers[0].size();
+                let height_to_reserve = first_size.y
+                    + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height;
+
+                // Compute Hovered Point
+                let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left;
+                let y = position.row() as f32 * layout.position_map.line_height - scroll_top;
+                let hovered_point = content_origin + point(x, y);
+
+                if hovered_point.y - height_to_reserve > 0.0 {
+                    // There is enough space above. Render popovers above the hovered point
+                    let mut current_y = hovered_point.y;
+                    for hover_popover in hover_popovers {
+                        let size = hover_popover.size();
+                        let mut popover_origin = point(hovered_point.x, current_y - size.y);
+
+                        let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);
+                        if x_out_of_bounds < 0.0 {
+                            popover_origin.set_x(popover_origin.x + x_out_of_bounds);
+                        }
 
-    //                 current_y = popover_origin.y() + size.y() + HOVER_POPOVER_GAP;
-    //             }
-    //         }
+                        hover_popover.paint(
+                            popover_origin,
+                            Bounds::<Pixels>::from_points(
+                                gpui::Point::<Pixels>::zero(),
+                                point(f32::MAX, f32::MAX),
+                            ), // Let content bleed outside of editor
+                            editor,
+                            cx,
+                        );
+
+                        current_y = popover_origin.y - HOVER_POPOVER_GAP;
+                    }
+                } else {
+                    // There is not enough space above. Render popovers below the hovered point
+                    let mut current_y = hovered_point.y + layout.position_map.line_height;
+                    for hover_popover in hover_popovers {
+                        let size = hover_popover.size();
+                        let mut popover_origin = point(hovered_point.x, current_y);
+
+                        let x_out_of_bounds = bounds.max_x - (popover_origin.x + size.x);
+                        if x_out_of_bounds < 0.0 {
+                            popover_origin.set_x(popover_origin.x + x_out_of_bounds);
+                        }
 
-    //         cx.scene().pop_stacking_context();
-    //     }
+                        hover_popover.paint(
+                            popover_origin,
+                            Bounds::<Pixels>::from_points(
+                                gpui::Point::<Pixels>::zero(),
+                                point(f32::MAX, f32::MAX),
+                            ), // Let content bleed outside of editor
+                            editor,
+                            cx,
+                        );
+
+                        current_y = popover_origin.y + size.y + HOVER_POPOVER_GAP;
+                    }
+                }
 
-    //     cx.scene().pop_layer();
-    // }
+                cx.scene().pop_stacking_context();
+            }
+        })
+    }
 
     fn scrollbar_left(&self, bounds: &Bounds<Pixels>) -> Pixels {
         bounds.upper_right().x - self.style.scrollbar_width
@@ -1073,9 +1098,9 @@ impl EditorElement {
 
     //     let style = &self.style.theme.scrollbar;
 
-    //     let top = bounds.min_y();
-    //     let bottom = bounds.max_y();
-    //     let right = bounds.max_x();
+    //     let top = bounds.min_y;
+    //     let bottom = bounds.max_y;
+    //     let right = bounds.max_x;
     //     let left = self.scrollbar_left(&bounds);
     //     let row_range = &layout.scrollbar_row_range;
     //     let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
@@ -1097,8 +1122,8 @@ impl EditorElement {
 
     //     let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
     //     let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
-    //     let track_bounds = Bounds<Pixels>::from_points(point(left, top), point(right, bottom));
-    //     let thumb_bounds = Bounds<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
+    //     let track_bounds = Bounds::<Pixels>::from_points(point(left, top), point(right, bottom));
+    //     let thumb_bounds = Bounds::<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
 
     //     if layout.show_scrollbars {
     //         cx.paint_quad(Quad {
@@ -1112,7 +1137,7 @@ impl EditorElement {
     //         let scrollbar_theme = &theme.editor.scrollbar;
     //         if layout.is_singleton && scrollbar_settings.selections {
     //             let start_anchor = Anchor::min();
-    //             let end_anchor = Anchor::max();
+    //             let end_anchor = Anchor::max;
     //             let color = scrollbar_theme.selections;
     //             let border = Border {
     //                 width: 1.,

crates/gpui2/src/text_system/line.rs 🔗

@@ -29,10 +29,6 @@ impl Line {
         )
     }
 
-    pub fn width(&self) -> Pixels {
-        self.layout.width
-    }
-
     pub fn wrap_count(&self) -> usize {
         self.layout.wrap_boundaries.len()
     }