Fix active line number highlight (#23266)

João Marcos and Danilo created

Closes #22734

Release Notes:

- Fixed active line number highlight.

---------

Co-authored-by: Danilo <danilo@zed.dev>

Change summary

crates/editor/src/element.rs     | 87 +++++++++++++++++++--------------
crates/project/src/task_store.rs |  2 
2 files changed, 52 insertions(+), 37 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -510,7 +510,7 @@ impl EditorElement {
         position_map: &PositionMap,
         text_hitbox: &Hitbox,
         gutter_hitbox: &Hitbox,
-        line_numbers: &HashMap<MultiBufferRow, (ShapedLine, Option<Hitbox>)>,
+        line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
         cx: &mut ViewContext<Editor>,
     ) {
         if cx.default_prevented() {
@@ -618,22 +618,24 @@ impl EditorElement {
                 .snapshot
                 .display_point_to_point(DisplayPoint::new(DisplayRow(display_row), 0), Bias::Right)
                 .row;
-            if let Some((_, Some(hitbox))) = line_numbers.get(&MultiBufferRow(multi_buffer_row)) {
-                if hitbox.contains(&event.position) {
-                    let scroll_position_row =
-                        position_map.scroll_pixel_position.y / position_map.line_height;
-                    let line_offset_from_top = display_row - scroll_position_row as u32;
-
-                    editor.open_excerpts_common(
-                        Some(JumpData::MultiBufferRow {
-                            row: MultiBufferRow(multi_buffer_row),
-                            line_offset_from_top,
-                        }),
-                        modifiers.alt,
-                        cx,
-                    );
-                    cx.stop_propagation();
-                }
+            if line_numbers
+                .get(&MultiBufferRow(multi_buffer_row))
+                .and_then(|line_number| line_number.hitbox.as_ref())
+                .is_some_and(|hitbox| hitbox.contains(&event.position))
+            {
+                let scroll_position_row =
+                    position_map.scroll_pixel_position.y / position_map.line_height;
+                let line_offset_from_top = display_row - scroll_position_row as u32;
+
+                editor.open_excerpts_common(
+                    Some(JumpData::MultiBufferRow {
+                        row: MultiBufferRow(multi_buffer_row),
+                        line_offset_from_top,
+                    }),
+                    modifiers.alt,
+                    cx,
+                );
+                cx.stop_propagation();
             }
         }
     }
@@ -2033,11 +2035,10 @@ impl EditorElement {
         scroll_position: gpui::Point<f32>,
         rows: Range<DisplayRow>,
         buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
-        active_rows: &BTreeMap<DisplayRow, bool>,
         newest_selection_head: Option<DisplayPoint>,
         snapshot: &EditorSnapshot,
         cx: &mut WindowContext,
-    ) -> Arc<HashMap<MultiBufferRow, (ShapedLine, Option<Hitbox>)>> {
+    ) -> Arc<HashMap<MultiBufferRow, LineNumberLayout>> {
         let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
             EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full
         });
@@ -2075,19 +2076,15 @@ impl EditorElement {
             .enumerate()
             .flat_map(|(ix, buffer_row)| {
                 let buffer_row = buffer_row?;
-                let display_row = DisplayRow(rows.start.0 + ix as u32);
-                let color = if active_rows.contains_key(&display_row) {
-                    cx.theme().colors().editor_active_line_number
-                } else {
-                    cx.theme().colors().editor_line_number
-                };
                 line_number.clear();
-                let default_number = buffer_row.0 + 1;
+                let display_row = DisplayRow(rows.start.0 + ix as u32);
+                let non_relative_number = buffer_row.0 + 1;
                 let number = relative_rows
-                    .get(&DisplayRow(ix as u32 + rows.start.0))
-                    .unwrap_or(&default_number);
+                    .get(&display_row)
+                    .unwrap_or(&non_relative_number);
                 write!(&mut line_number, "{number}").unwrap();
 
+                let color = cx.theme().colors().editor_line_number;
                 let shaped_line = self
                     .shape_line_number(SharedString::from(&line_number), color, cx)
                     .log_err()?;
@@ -2115,7 +2112,12 @@ impl EditorElement {
 
                 let multi_buffer_row = DisplayPoint::new(display_row, 0).to_point(snapshot).row;
                 let multi_buffer_row = MultiBufferRow(multi_buffer_row);
-                Some((multi_buffer_row, (shaped_line, hitbox)))
+                let line_number = LineNumberLayout {
+                    shaped_line,
+                    hitbox,
+                    display_row,
+                };
+                Some((multi_buffer_row, line_number))
             })
             .collect();
         Arc::new(line_numbers)
@@ -3961,17 +3963,26 @@ impl EditorElement {
         let line_height = layout.position_map.line_height;
         cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
 
-        for (_, (line, hitbox)) in layout.line_numbers.iter() {
+        for LineNumberLayout {
+            shaped_line,
+            hitbox,
+            display_row,
+        } in layout.line_numbers.values()
+        {
             let Some(hitbox) = hitbox else {
                 continue;
             };
-            let color = if !is_singleton && hitbox.is_hovered(cx) {
+
+            let is_active = layout.active_rows.contains_key(&display_row);
+
+            let color = if is_active {
                 cx.theme().colors().editor_active_line_number
             } else {
                 cx.theme().colors().editor_line_number
             };
+
             let Some(line) = self
-                .shape_line_number(line.text.clone(), color, cx)
+                .shape_line_number(shaped_line.text.clone(), color, cx)
                 .log_err()
             else {
                 continue;
@@ -6280,7 +6291,6 @@ impl Element for EditorElement {
                         scroll_position,
                         start_row..end_row,
                         buffer_rows.iter().copied(),
-                        &active_rows,
                         newest_selection_head,
                         &snapshot,
                         cx,
@@ -6991,7 +7001,7 @@ pub struct EditorLayout {
     active_rows: BTreeMap<DisplayRow, bool>,
     highlighted_rows: BTreeMap<DisplayRow, Hsla>,
     line_elements: SmallVec<[AnyElement; 1]>,
-    line_numbers: Arc<HashMap<MultiBufferRow, (ShapedLine, Option<Hitbox>)>>,
+    line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
     display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
     blamed_display_rows: Option<Vec<AnyElement>>,
     inline_blame: Option<AnyElement>,
@@ -7019,6 +7029,12 @@ impl EditorLayout {
     }
 }
 
+struct LineNumberLayout {
+    shaped_line: ShapedLine,
+    hitbox: Option<Hitbox>,
+    display_row: DisplayRow,
+}
+
 struct ColoredRange<T> {
     start: T,
     end: T,
@@ -7661,7 +7677,6 @@ mod tests {
                     gpui::Point::default(),
                     DisplayRow(0)..DisplayRow(6),
                     (0..6).map(MultiBufferRow).map(Some),
-                    &Default::default(),
                     Some(DisplayPoint::new(DisplayRow(0), 0)),
                     &snapshot,
                     cx,
@@ -7911,7 +7926,7 @@ mod tests {
             state
                 .line_numbers
                 .get(&MultiBufferRow(0))
-                .and_then(|(line, _)| line.text.as_str()),
+                .and_then(|line_number| line_number.shaped_line.text.as_str()),
             Some("1")
         );
     }

crates/project/src/task_store.rs 🔗

@@ -20,7 +20,7 @@ use crate::{
     ProjectEnvironment,
 };
 
-#[expect(clippy::large_enum_variant)]
+#[allow(clippy::large_enum_variant)] // platform-dependent warning
 pub enum TaskStore {
     Functional(StoreState),
     Noop,