project_panel: Add horizontal scroll setting (#51143)

K4YT3X created

This PR introduces the `project_panel.scrollbar.horizontal_scroll`
setting to allow users to toggle the horizontal scroll bar in the
project panel. This was Zed's design before PR #18513, and the default
behavior of VSCode (`workbench.list.horizontalScrolling`).


https://github.com/user-attachments/assets/f633f4e4-a585-4494-8f48-df77c6aca418

## Rationale

Zed's design used to be the same as the default behavior of VSCode.
I.e., no horizontal scrolling, and the view is always snapped to the
left, with long file names clipped of. If you want to see the content
that is out-of-frame, you'll need to drag the handle and expand the
project panel. This could be problematic, especially for large repos
with multiple levels of nested directories, as pointed out by issues
#5550 and #7001.

<img width="1398" height="992" alt="image"
src="https://github.com/user-attachments/assets/d86563f2-0f06-4e9e-818c-155ac45f0f56"
/>\
*VSCode's default setup, for reference.*

Then came PR #18513, which added horizontal scroll and addressed this
pain point, but users didn't have a choice. They're stuck with
horizontal scrolling always turned on. I, for instance, personally
prefer the old, VSCode-default behavior, for most projects I open are
small and don't need horizontal scrolling in the project panel. With
horizontal scrolling always turned on, I find it annoying to have my
project panel view accidentally scrolled to the middle, and I'll have to
grab my mouse and scroll it back. It's also visually redundant.

Thus, why not add an option like VSCode's
`workbench.list.horizontalScrolling` and let users choose? I'd love to
be able to, say, set a per-project override for the projects that need
horizontal scrolling, while having it disabled by default.

## Extra Notes

- I was originally thinking about using `ScrollbarAxes` from
`src/editor_settings.rs` and make the option
`project_panel.scrollbar.axes.horizontal` similar to the global editor
scrollbar settings, but this option is specific to the project panel and
it doesn't quite make sense to allow disabling vertical scrolling on the
project panel, so I added a standalone option for it instead, similar to
VSCode's `workbench.list.horizontalScrolling`.

- I went the conservative route and set horizontal scrolling to enabled
(current behavior) by default. Imo it might make more sense to disable
it by default instead, similar to VSCode, but I'll leave this for the
Zed team to decide.

- I named it `horizontal_scroll` instead of `horizontal_scrolling` to be
consistent with the adjacent setting `sticky_scroll`.

- As for tests, I don't see tests for the scrollbar, so I didn't add
any.

I'd be glad to update the PR if anything is not inline with the
project's requirements or conventions.

---

Release Notes:

- Added `project_panel.scrollbar.horizontal_scroll` setting to allow
toggling horizontal scrolling in the project panel

Signed-off-by: k4yt3x <i@k4yt3x.com>

Change summary

assets/settings/default.json                       |  3 +
crates/project_panel/src/project_panel.rs          | 31 +++++++++-----
crates/project_panel/src/project_panel_settings.rs | 13 +++++
crates/settings/src/vscode_import.rs               |  7 ++
crates/settings_content/src/workspace.rs           | 23 +++++++++-
crates/settings_ui/src/page_data.rs                | 28 ++++++++++++
docs/src/reference/all-settings.md                 | 35 +++------------
7 files changed, 95 insertions(+), 45 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -768,6 +768,9 @@
       // 5. Never show the scrollbar:
       //    "never"
       "show": null,
+      // Whether to allow horizontal scrolling in the project panel.
+      // When false, the view is locked to the leftmost position and long file names are clipped.
+      "horizontal_scroll": true,
     },
     // Which files containing diagnostic errors/warnings to mark in the project panel.
     // This setting can take the following three values:

crates/project_panel/src/project_panel.rs 🔗

@@ -6341,6 +6341,7 @@ impl Render for ProjectPanel {
         let panel_settings = ProjectPanelSettings::get_global(cx);
         let indent_size = panel_settings.indent_size;
         let show_indent_guides = panel_settings.indent_guides.show == ShowIndentGuides::Always;
+        let horizontal_scroll = panel_settings.scrollbar.horizontal_scroll;
         let show_sticky_entries = {
             if panel_settings.sticky_scroll {
                 let is_scrollable = self.scroll_handle.is_scrollable();
@@ -6713,10 +6714,14 @@ impl Render for ProjectPanel {
                                 })
                             })
                             .with_sizing_behavior(ListSizingBehavior::Infer)
-                            .with_horizontal_sizing_behavior(
-                                ListHorizontalSizingBehavior::Unconstrained,
-                            )
-                            .with_width_from_item(self.state.max_width_item_index)
+                            .with_horizontal_sizing_behavior(if horizontal_scroll {
+                                ListHorizontalSizingBehavior::Unconstrained
+                            } else {
+                                ListHorizontalSizingBehavior::FitList
+                            })
+                            .when(horizontal_scroll, |list| {
+                                list.with_width_from_item(self.state.max_width_item_index)
+                            })
                             .track_scroll(&self.scroll_handle),
                         )
                         .child(
@@ -6877,13 +6882,17 @@ impl Render for ProjectPanel {
                         .size_full(),
                 )
                 .custom_scrollbars(
-                    Scrollbars::for_settings::<ProjectPanelSettings>()
-                        .tracked_scroll_handle(&self.scroll_handle)
-                        .with_track_along(
-                            ScrollAxes::Horizontal,
-                            cx.theme().colors().panel_background,
-                        )
-                        .notify_content(),
+                    {
+                        let mut scrollbars = Scrollbars::for_settings::<ProjectPanelSettings>()
+                            .tracked_scroll_handle(&self.scroll_handle);
+                        if horizontal_scroll {
+                            scrollbars = scrollbars.with_track_along(
+                                ScrollAxes::Horizontal,
+                                cx.theme().colors().panel_background,
+                            );
+                        }
+                        scrollbars.notify_content()
+                    },
                     window,
                     cx,
                 )

crates/project_panel/src/project_panel_settings.rs 🔗

@@ -49,6 +49,11 @@ pub struct ScrollbarSettings {
     ///
     /// Default: inherits editor scrollbar settings
     pub show: Option<ShowScrollbar>,
+    /// Whether to allow horizontal scrolling in the project panel.
+    /// When false, the view is locked to the leftmost position and long file names are clipped.
+    ///
+    /// Default: true
+    pub horizontal_scroll: bool,
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -111,8 +116,12 @@ impl Settings for ProjectPanelSettings {
             auto_fold_dirs: project_panel.auto_fold_dirs.unwrap(),
             bold_folder_labels: project_panel.bold_folder_labels.unwrap(),
             starts_open: project_panel.starts_open.unwrap(),
-            scrollbar: ScrollbarSettings {
-                show: project_panel.scrollbar.unwrap().show.map(Into::into),
+            scrollbar: {
+                let scrollbar = project_panel.scrollbar.unwrap();
+                ScrollbarSettings {
+                    show: scrollbar.show.map(Into::into),
+                    horizontal_scroll: scrollbar.horizontal_scroll.unwrap(),
+                }
             },
             show_diagnostics: project_panel.show_diagnostics.unwrap(),
             hide_root: project_panel.hide_root.unwrap(),

crates/settings/src/vscode_import.rs 🔗

@@ -793,7 +793,12 @@ impl VsCodeSettings {
             hide_root: None,
             indent_guides: None,
             indent_size: None,
-            scrollbar: None,
+            scrollbar: self.read_bool("workbench.list.horizontalScrolling").map(
+                |horizontal_scrolling| ProjectPanelScrollbarSettingsContent {
+                    show: None,
+                    horizontal_scroll: Some(horizontal_scrolling),
+                },
+            ),
             show_diagnostics: self
                 .read_bool("problems.decorations.enabled")
                 .and_then(|b| if b { Some(ShowDiagnostics::Off) } else { None }),

crates/settings_content/src/workspace.rs 🔗

@@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize};
 use settings_macros::{MergeFrom, with_fallible_options};
 
 use crate::{
-    CenteredPaddingSettings, DelayMs, DockPosition, DockSide, InactiveOpacity,
-    ScrollbarSettingsContent, ShowIndentGuides, serialize_optional_f32_with_two_decimal_places,
+    CenteredPaddingSettings, DelayMs, DockPosition, DockSide, InactiveOpacity, ShowIndentGuides,
+    ShowScrollbar, serialize_optional_f32_with_two_decimal_places,
 };
 
 #[with_fallible_options]
@@ -710,7 +710,7 @@ pub struct ProjectPanelSettingsContent {
     /// Default: true
     pub starts_open: Option<bool>,
     /// Scrollbar-related settings
-    pub scrollbar: Option<ScrollbarSettingsContent>,
+    pub scrollbar: Option<ProjectPanelScrollbarSettingsContent>,
     /// Which files containing diagnostic errors/warnings to mark in the project panel.
     ///
     /// Default: all
@@ -793,6 +793,23 @@ pub enum ProjectPanelSortMode {
     FilesFirst,
 }
 
+#[with_fallible_options]
+#[derive(
+    Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
+)]
+pub struct ProjectPanelScrollbarSettingsContent {
+    /// When to show the scrollbar in the project panel.
+    ///
+    /// Default: inherits editor scrollbar settings
+    pub show: Option<ShowScrollbar>,
+    /// Whether to allow horizontal scrolling in the project panel.
+    /// When false, the view is locked to the leftmost position and
+    /// long file names are clipped.
+    ///
+    /// Default: true
+    pub horizontal_scroll: Option<bool>,
+}
+
 #[with_fallible_options]
 #[derive(
     Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,

crates/settings_ui/src/page_data.rs 🔗

@@ -4238,7 +4238,7 @@ fn window_and_layout_page() -> SettingsPage {
 }
 
 fn panels_page() -> SettingsPage {
-    fn project_panel_section() -> [SettingsPageItem; 22] {
+    fn project_panel_section() -> [SettingsPageItem; 23] {
         [
             SettingsPageItem::SectionHeader("Project Panel"),
             SettingsPageItem::SettingItem(SettingItem {
@@ -4516,6 +4516,32 @@ fn panels_page() -> SettingsPage {
                 metadata: None,
                 files: USER,
             }),
+            SettingsPageItem::SettingItem(SettingItem {
+                title: "Horizontal Scroll",
+                description: "Whether to allow horizontal scrolling in the project panel. When disabled, the view is always locked to the leftmost position and long file names are clipped.",
+                field: Box::new(SettingField {
+                    json_path: Some("project_panel.scrollbar.horizontal_scroll"),
+                    pick: |settings_content| {
+                        settings_content
+                            .project_panel
+                            .as_ref()?
+                            .scrollbar
+                            .as_ref()?
+                            .horizontal_scroll
+                            .as_ref()
+                    },
+                    write: |settings_content, value| {
+                        settings_content
+                            .project_panel
+                            .get_or_insert_default()
+                            .scrollbar
+                            .get_or_insert_default()
+                            .horizontal_scroll = value;
+                    },
+                }),
+                metadata: None,
+                files: USER,
+            }),
             SettingsPageItem::SettingItem(SettingItem {
                 title: "Show Diagnostics",
                 description: "Which files containing diagnostic errors/warnings to mark in the project panel.",

docs/src/reference/all-settings.md 🔗

@@ -4695,7 +4695,8 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
     "bold_folder_labels": false,
     "drag_and_drop": true,
     "scrollbar": {
-      "show": null
+      "show": null,
+      "horizontal_scroll": true
     },
     "sticky_scroll": true,
     "show_diagnostics": "all",
@@ -4941,9 +4942,9 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
 }
 ```
 
-### Scrollbar: Show
+### Scrollbar
 
-- Description: Whether to show a scrollbar in the project panel. Possible values: null, "auto", "system", "always", "never". Inherits editor settings when absent, see its description for more details.
+- Description: Scrollbar-related settings for the project panel.
 - Setting: `scrollbar`
 - Default:
 
@@ -4951,7 +4952,8 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
 {
   "project_panel": {
     "scrollbar": {
-      "show": null
+      "show": null,
+      "horizontal_scroll": true
     }
   }
 }
@@ -4959,29 +4961,8 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
 
 **Options**
 
-1. Show scrollbar in the project panel
-
-```json [settings]
-{
-  "project_panel": {
-    "scrollbar": {
-      "show": "always"
-    }
-  }
-}
-```
-
-2. Hide scrollbar in the project panel
-
-```json [settings]
-{
-  "project_panel": {
-    "scrollbar": {
-      "show": "never"
-    }
-  }
-}
-```
+- `show`: Whether to show a scrollbar in the project panel. Possible values: null, "auto", "system", "always", "never". Inherits editor settings when absent, see its description for more details.
+- `horizontal_scroll`: Whether to allow horizontal scrolling in the project panel. When `false`, the view is locked to the leftmost position and long file names are clipped.
 
 ### Sort Mode