Paint sticky lines

Agus Zubiaga and MrSubidubi created

Co-authored-by: MrSubidubi <dev@bahn.sh>

Change summary

crates/editor/src/element.rs | 125 +++++++++++++++++++++++++++++++++----
1 file changed, 109 insertions(+), 16 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -81,7 +81,7 @@ use std::{
     time::{Duration, Instant},
 };
 use sum_tree::Bias;
-use text::{BufferId, SelectionGoal};
+use text::{BufferId, SelectionGoal, ToPoint as _};
 use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
 use ui::utils::ensure_minimum_contrast;
 use ui::{
@@ -6196,6 +6196,7 @@ impl EditorElement {
             let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
             line_with_invisibles.draw(
                 layout,
+                layout.position_map.scroll_pixel_position,
                 row,
                 layout.content_origin,
                 whitespace_setting,
@@ -6210,6 +6211,43 @@ impl EditorElement {
         }
     }
 
+    fn paint_sticky_lines(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
+        window.paint_layer(layout.hitbox.bounds, |window| {
+            let gutter_bg = cx.theme().colors().editor_gutter_background;
+            let mut background_bounds = layout.position_map.text_hitbox.bounds;
+            background_bounds.size.height =
+                layout.position_map.sticky_line_layouts.len() * layout.position_map.line_height;
+            let mut gutter_bounds = layout.gutter_hitbox.bounds;
+            gutter_bounds.size.height = background_bounds.size.height;
+
+            window.paint_quad(fill(gutter_bounds, gutter_bg));
+            window.paint_quad(fill(background_bounds, self.style.background));
+        });
+
+        for (row, line) in layout.position_map.sticky_line_layouts.iter().enumerate() {
+            line.draw_background(
+                layout,
+                gpui::Point::default(),
+                DisplayRow(row as u32),
+                layout.content_origin,
+                window,
+                cx,
+            );
+
+            line.draw(
+                layout,
+                gpui::Point::default(),
+                DisplayRow(row as u32),
+                layout.content_origin,
+                // todo!
+                ShowWhitespaceSetting::All,
+                &[],
+                window,
+                cx,
+            )
+        }
+    }
+
     fn paint_lines_background(
         &mut self,
         layout: &mut EditorLayout,
@@ -6218,7 +6256,14 @@ impl EditorElement {
     ) {
         for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {
             let row = DisplayRow(layout.visible_display_row_range.start.0 + ix as u32);
-            line_with_invisibles.draw_background(layout, row, layout.content_origin, window, cx);
+            line_with_invisibles.draw_background(
+                layout,
+                layout.position_map.scroll_pixel_position,
+                row,
+                layout.content_origin,
+                window,
+                cx,
+            );
         }
     }
 
@@ -7919,6 +7964,7 @@ impl LineWithInvisibles {
     fn draw(
         &self,
         layout: &EditorLayout,
+        scroll_pixel_position: gpui::Point<Pixels>,
         row: DisplayRow,
         content_origin: gpui::Point<Pixels>,
         whitespace_setting: ShowWhitespaceSetting,
@@ -7927,11 +7973,9 @@ impl LineWithInvisibles {
         cx: &mut App,
     ) {
         let line_height = layout.position_map.line_height;
-        let line_y = line_height
-            * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
+        let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
 
-        let mut fragment_origin =
-            content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
+        let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
 
         for fragment in &self.fragments {
             match fragment {
@@ -7949,6 +7993,7 @@ impl LineWithInvisibles {
         self.draw_invisibles(
             selection_ranges,
             layout,
+            scroll_pixel_position,
             content_origin,
             line_y,
             row,
@@ -7962,17 +8007,16 @@ impl LineWithInvisibles {
     fn draw_background(
         &self,
         layout: &EditorLayout,
+        scroll_pixel_position: gpui::Point<Pixels>,
         row: DisplayRow,
         content_origin: gpui::Point<Pixels>,
         window: &mut Window,
         cx: &mut App,
     ) {
         let line_height = layout.position_map.line_height;
-        let line_y = line_height
-            * (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
+        let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
 
-        let mut fragment_origin =
-            content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
+        let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
 
         for fragment in &self.fragments {
             match fragment {
@@ -7992,6 +8036,7 @@ impl LineWithInvisibles {
         &self,
         selection_ranges: &[Range<DisplayPoint>],
         layout: &EditorLayout,
+        scroll_pixel_position: gpui::Point<Pixels>,
         content_origin: gpui::Point<Pixels>,
         line_y: Pixels,
         row: DisplayRow,
@@ -8016,7 +8061,7 @@ impl LineWithInvisibles {
                 (layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
             let origin = content_origin
                 + gpui::point(
-                    x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
+                    x_offset + invisible_offset - scroll_pixel_position.x,
                     line_y,
                 );
 
@@ -8726,6 +8771,56 @@ impl Element for EditorElement {
                         cx,
                     );
 
+                    let start_buffer_row =
+                        MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
+                    let end_buffer_row =
+                        MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
+
+                    let sticky_line_layouts = snapshot
+                        .buffer_snapshot
+                        .as_singleton()
+                        .map(|(_, _, singleton_snapshot)| {
+                            let outline_items = singleton_snapshot.outline_items_containing(
+                                language::Point::new(start_buffer_row.0, 0)
+                                    ..language::Point::new(start_buffer_row.0 + 1, 0),
+                                false,
+                                None,
+                            );
+
+                            let mut sticky_line_layouts = Vec::with_capacity(outline_items.len());
+
+                            for item in outline_items {
+                                let point = item.range.start.to_point(&singleton_snapshot);
+                                let display_row = snapshot
+                                    .display_snapshot
+                                    .point_to_display_point(
+                                        MultiBufferPoint::new(point.row, point.column),
+                                        Bias::Right,
+                                    )
+                                    .row();
+                                // todo! do we need to check if valid?
+                                let rows = display_row..DisplayRow(display_row.0 + 1);
+
+                                let chunks =
+                                    snapshot.highlighted_chunks(rows.clone(), true, &self.style);
+                                sticky_line_layouts.extend(LineWithInvisibles::from_chunks(
+                                    chunks,
+                                    style,
+                                    MAX_LINE_LEN,
+                                    1,
+                                    &snapshot.mode,
+                                    editor_width,
+                                    is_row_soft_wrapped,
+                                    Default::default(),
+                                    window,
+                                    cx,
+                                ))
+                            }
+
+                            sticky_line_layouts
+                        })
+                        .unwrap_or_default();
+
                     // We add the gutter breakpoint indicator to breakpoint_rows after painting
                     // line numbers so we don't paint a line number debug accent color if a user
                     // has their mouse over that line when a breakpoint isn't there
@@ -8947,11 +9042,6 @@ impl Element for EditorElement {
                         })
                     });
 
-                    let start_buffer_row =
-                        MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
-                    let end_buffer_row =
-                        MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
-
                     let scroll_max = point(
                         ((scroll_width - editor_width) / em_advance).max(0.0),
                         max_scroll_top,
@@ -9392,6 +9482,7 @@ impl Element for EditorElement {
                         visible_row_range,
                         scroll_pixel_position,
                         scroll_max,
+                        sticky_line_layouts,
                         line_layouts,
                         line_height,
                         em_width,
@@ -9518,6 +9609,7 @@ impl Element for EditorElement {
                         }
                     });
 
+                    self.paint_sticky_lines(layout, window, cx);
                     self.paint_minimap(layout, window, cx);
                     self.paint_scrollbars(layout, window, cx);
                     self.paint_edit_prediction_popover(layout, window, cx);
@@ -10057,6 +10149,7 @@ pub(crate) struct PositionMap {
     pub em_width: Pixels,
     pub em_advance: Pixels,
     pub visible_row_range: Range<DisplayRow>,
+    pub sticky_line_layouts: Vec<LineWithInvisibles>,
     pub line_layouts: Vec<LineWithInvisibles>,
     pub snapshot: EditorSnapshot,
     pub text_hitbox: Hitbox,