editor: Current line highlight options (#11710)

Andrew Lygin created

None:

<img width="717" alt="none"
src="https://github.com/zed-industries/zed/assets/2101250/b2a741db-c64a-4275-a612-5a0d15c9cab7">

Gutter:

<img width="715" alt="gutter"
src="https://github.com/zed-industries/zed/assets/2101250/f7a68a6e-6eba-41b4-9042-5a5fe2ee21a4">

Line:

<img width="717" alt="line"
src="https://github.com/zed-industries/zed/assets/2101250/117f5b00-abd7-425b-8047-1a6fab8293a7">

All:

<img width="715" alt="all"
src="https://github.com/zed-industries/zed/assets/2101250/ebccc0da-0fa0-44e5-903c-cc49d975db76">

This PR adds the `current_line_highlight` setting that defines how to
highlight the current line in the editor:

- `none`: Don't highlight the current line.
- `gutter`: Highlight the gutter area only.
- `line`: Highlight the editor area only.
- `all` (default): Highlight the whole line.

The options have been borrowed from VSCode.

Fixes #5222
Part of #4382

Release Notes:

- Added the `current_line_highlight` setting that defines how to
highlight the current line in the editor (#5222).

Change summary

assets/settings/default.json         | 11 ++++++
crates/editor/src/editor.rs          | 15 +++++++++
crates/editor/src/editor_settings.rs | 18 +++++++++++
crates/editor/src/element.rs         | 49 ++++++++++++++++++++++-------
docs/src/configuring-zed.md          | 32 +++++++++++++++++++
5 files changed, 112 insertions(+), 13 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -73,6 +73,17 @@
   "drop_target_size": 0.2,
   // Whether the cursor blinks in the editor.
   "cursor_blink": true,
+  // How to highlight the current line in the editor.
+  //
+  // 1. Don't highlight the current line:
+  //    "none"
+  // 2. Highlight the gutter area:
+  //    "gutter"
+  // 3. Highlight the editor area:
+  //    "line"
+  // 4. Highlight the full line (default):
+  //    "all"
+  "current_line_highlight": "all",
   // Whether to pop the completions menu while typing in an editor without
   // explicitly requesting it.
   "show_completions_on_input": true,

crates/editor/src/editor.rs 🔗

@@ -53,6 +53,7 @@ use convert_case::{Case, Casing};
 use debounced_delay::DebouncedDelay;
 pub use display_map::DisplayPoint;
 use display_map::*;
+use editor_settings::CurrentLineHighlight;
 pub use editor_settings::EditorSettings;
 use element::LineWithInvisibles;
 pub use element::{
@@ -465,6 +466,7 @@ pub struct Editor {
     pending_rename: Option<RenameState>,
     searchable: bool,
     cursor_shape: CursorShape,
+    current_line_highlight: CurrentLineHighlight,
     collapse_matches: bool,
     autoindent_mode: Option<AutoindentMode>,
     workspace: Option<(WeakView<Workspace>, WorkspaceId)>,
@@ -519,6 +521,7 @@ pub struct EditorSnapshot {
     is_focused: bool,
     scroll_anchor: ScrollAnchor,
     ongoing_scroll: OngoingScroll,
+    current_line_highlight: CurrentLineHighlight,
 }
 
 const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
@@ -1636,6 +1639,7 @@ impl Editor {
             pending_rename: Default::default(),
             searchable: true,
             cursor_shape: Default::default(),
+            current_line_highlight: EditorSettings::get_global(cx).current_line_highlight,
             autoindent_mode: Some(AutoindentMode::EachLine),
             collapse_matches: false,
             workspace: None,
@@ -1850,6 +1854,7 @@ impl Editor {
             ongoing_scroll: self.scroll_manager.ongoing_scroll(),
             placeholder_text: self.placeholder_text.clone(),
             is_focused: self.focus_handle.is_focused(cx),
+            current_line_highlight: self.current_line_highlight,
         }
     }
 
@@ -1938,6 +1943,15 @@ impl Editor {
         cx.notify();
     }
 
+    pub fn set_current_line_highlight(
+        &mut self,
+        current_line_highlight: CurrentLineHighlight,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.current_line_highlight = current_line_highlight;
+        cx.notify();
+    }
+
     pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
         self.collapse_matches = collapse_matches;
     }
@@ -10283,6 +10297,7 @@ impl Editor {
         let editor_settings = EditorSettings::get_global(cx);
         self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
         self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
+        self.current_line_highlight = editor_settings.current_line_highlight;
 
         if self.mode == EditorMode::Full {
             let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();

crates/editor/src/editor_settings.rs 🔗

@@ -6,6 +6,7 @@ use settings::{Settings, SettingsSources};
 #[derive(Deserialize, Clone)]
 pub struct EditorSettings {
     pub cursor_blink: bool,
+    pub current_line_highlight: CurrentLineHighlight,
     pub hover_popover_enabled: bool,
     pub show_completions_on_input: bool,
     pub show_completion_documentation: bool,
@@ -24,6 +25,19 @@ pub struct EditorSettings {
     pub double_click_in_multibuffer: DoubleClickInMultibuffer,
 }
 
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum CurrentLineHighlight {
+    // Don't highlight the current line.
+    None,
+    // Highlight the gutter area.
+    Gutter,
+    // Highlight the editor area.
+    Line,
+    // Highlight the full line.
+    All,
+}
+
 /// When to populate a new search's query based on the text under the cursor.
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
@@ -105,6 +119,10 @@ pub struct EditorSettingsContent {
     ///
     /// Default: true
     pub cursor_blink: Option<bool>,
+    /// How to highlight the current line in the editor.
+    ///
+    /// Default: all
+    pub current_line_highlight: Option<CurrentLineHighlight>,
     /// Whether to show the informational hover box when moving the mouse
     /// over symbols in the editor.
     ///

crates/editor/src/element.rs 🔗

@@ -4,7 +4,9 @@ use crate::{
         BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
         TransformBlock,
     },
-    editor_settings::{DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar},
+    editor_settings::{
+        CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar,
+    },
     git::{
         blame::{CommitDetails, GitBlame},
         diff_hunk_to_display, DisplayDiffHunk,
@@ -2289,18 +2291,39 @@ impl EditorElement {
                     }
 
                     if !contains_non_empty_selection {
-                        let origin = point(
-                            layout.hitbox.origin.x,
-                            layout.hitbox.origin.y
-                                + (start_row.as_f32() - scroll_top)
-                                    * layout.position_map.line_height,
-                        );
-                        let size = size(
-                            layout.hitbox.size.width,
-                            layout.position_map.line_height * (end_row - start_row.0 + 1) as f32,
-                        );
-                        let active_line_bg = cx.theme().colors().editor_active_line_background;
-                        cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
+                        let highlight_h_range =
+                            match layout.position_map.snapshot.current_line_highlight {
+                                CurrentLineHighlight::Gutter => Some(Range {
+                                    start: layout.hitbox.left(),
+                                    end: layout.gutter_hitbox.right(),
+                                }),
+                                CurrentLineHighlight::Line => Some(Range {
+                                    start: layout.text_hitbox.bounds.left(),
+                                    end: layout.text_hitbox.bounds.right(),
+                                }),
+                                CurrentLineHighlight::All => Some(Range {
+                                    start: layout.hitbox.left(),
+                                    end: layout.hitbox.right(),
+                                }),
+                                CurrentLineHighlight::None => None,
+                            };
+                        if let Some(range) = highlight_h_range {
+                            let active_line_bg = cx.theme().colors().editor_active_line_background;
+                            let bounds = Bounds {
+                                origin: point(
+                                    range.start,
+                                    layout.hitbox.origin.y
+                                        + (start_row.as_f32() - scroll_top)
+                                            * layout.position_map.line_height,
+                                ),
+                                size: size(
+                                    range.end - range.start,
+                                    layout.position_map.line_height
+                                        * (end_row - start_row.0 + 1) as f32,
+                                ),
+                            };
+                            cx.paint_quad(fill(bounds, active_line_bg));
+                        }
                     }
                 }
 

docs/src/configuring-zed.md 🔗

@@ -186,6 +186,38 @@ left and right padding of the central pane from the workspace when the centered
 
 List of `string` values
 
+## Current Line Highlight
+
+- Description: How to highlight the current line in the editor.
+- Setting: `current_line_highlight`
+- Default: `all`
+
+**Options**
+
+1. Don't highlight the current line:
+
+```json
+"current_line_highlight": "none"
+```
+
+2. Highlight the gutter area.
+
+```json
+"current_line_highlight": "gutter"
+```
+
+3. Highlight the editor area.
+
+```json
+"current_line_highlight": "line"
+```
+
+4. Highlight the full line.
+
+```json
+"current_line_highlight": "all"
+```
+
 ## Cursor Blink
 
 - Description: Whether or not the cursor blinks.