Editor toolbar configuration (#7338)

Andrew Lygin created

Adds settings for hiding breadcrumbs and quick action bar from
the editor toolbar. If both elements are hidden, the toolbar disappears
completely.

Example:

```json
"toolbar": {
  "breadcrumbs": true,
  "quick_actions": false
}
```

- It intentionally doesn't hide breadcrumbs in other views (for
instance, in the search result window) because their usage there differ
from the main editor.
- The editor controls how breadcrumbs are displayed in the toolbar, so
implementation differs a bit for breadcrumbs and quick actions bar.

Release Notes:

- Added support for configuring the editor toolbar ([4756](https://github.com/zed-industries/zed/issues/4756))

Change summary

Cargo.lock                                      |  1 
assets/settings/default.json                    |  7 +
crates/editor/src/editor.rs                     |  7 +
crates/editor/src/editor_settings.rs            | 22 ++++
crates/editor/src/items.rs                      |  6 +
crates/quick_action_bar/Cargo.toml              |  1 
crates/quick_action_bar/src/quick_action_bar.rs | 88 +++++++++++-------
crates/workspace/src/toolbar.rs                 |  2 
crates/zed/src/zed.rs                           |  2 
docs/src/configuring_zed.md                     | 17 +++
10 files changed, 114 insertions(+), 39 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6064,6 +6064,7 @@ dependencies = [
  "editor",
  "gpui",
  "search",
+ "settings",
  "ui",
  "workspace",
 ]

assets/settings/default.json 🔗

@@ -109,6 +109,13 @@
     // Share your project when you are the first to join a channel
     "share_on_join": true
   },
+  // Toolbar related settings
+  "toolbar": {
+    // Whether to show breadcrumbs.
+    "breadcrumbs": true,
+    // Whether to show quick action buttons.
+    "quick_actions": true
+  },
   // Scrollbar related settings
   "scrollbar": {
     // When to show the scrollbar in the editor.

crates/editor/src/editor.rs 🔗

@@ -374,6 +374,7 @@ pub struct Editor {
     hovered_cursors: HashMap<HoveredCursor, Task<()>>,
     pub show_local_selections: bool,
     mode: EditorMode,
+    show_breadcrumbs: bool,
     show_gutter: bool,
     show_wrap_guides: Option<bool>,
     placeholder_text: Option<Arc<str>>,
@@ -1448,6 +1449,7 @@ impl Editor {
             blink_manager: blink_manager.clone(),
             show_local_selections: true,
             mode,
+            show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
             show_gutter: mode == EditorMode::Full,
             show_wrap_guides: None,
             placeholder_text: None,
@@ -8762,8 +8764,9 @@ impl Editor {
             )),
             cx,
         );
-        self.scroll_manager.vertical_scroll_margin =
-            EditorSettings::get_global(cx).vertical_scroll_margin;
+        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;
         cx.notify();
     }
 

crates/editor/src/editor_settings.rs 🔗

@@ -10,6 +10,7 @@ pub struct EditorSettings {
     pub show_completion_documentation: bool,
     pub completion_documentation_secondary_query_debounce: u64,
     pub use_on_type_format: bool,
+    pub toolbar: Toolbar,
     pub scrollbar: Scrollbar,
     pub vertical_scroll_margin: f32,
     pub relative_line_numbers: bool,
@@ -29,6 +30,12 @@ pub enum SeedQuerySetting {
     Never,
 }
 
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct Toolbar {
+    pub breadcrumbs: bool,
+    pub quick_actions: bool,
+}
+
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 pub struct Scrollbar {
     pub show: ShowScrollbar,
@@ -86,6 +93,8 @@ pub struct EditorSettingsContent {
     ///
     /// Default: true
     pub use_on_type_format: Option<bool>,
+    /// Toolbar related settings
+    pub toolbar: Option<ToolbarContent>,
     /// Scrollbar related settings
     pub scrollbar: Option<ScrollbarContent>,
 
@@ -110,6 +119,19 @@ pub struct EditorSettingsContent {
     pub redact_private_values: Option<bool>,
 }
 
+// Toolbar related settings
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct ToolbarContent {
+    /// Whether to display breadcrumbs in the editor toolbar.
+    ///
+    /// Default: true
+    pub breadcrumbs: Option<bool>,
+    /// Whether to display quik action buttons in the editor toolbar.
+    ///
+    /// Default: true
+    pub quick_actions: Option<bool>,
+}
+
 /// Scrollbar related settings
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 pub struct ScrollbarContent {

crates/editor/src/items.rs 🔗

@@ -800,7 +800,11 @@ impl Item for Editor {
     }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::PrimaryLeft
+        if self.show_breadcrumbs {
+            ToolbarItemLocation::PrimaryLeft
+        } else {
+            ToolbarItemLocation::Hidden
+        }
     }
 
     fn breadcrumbs(&self, variant: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {

crates/quick_action_bar/Cargo.toml 🔗

@@ -14,6 +14,7 @@ assistant = { path = "../assistant" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 search = { path = "../search" }
+settings = { path = "../settings" }
 ui = { path = "../ui" }
 workspace = { path = "../workspace" }
 

crates/quick_action_bar/src/quick_action_bar.rs 🔗

@@ -1,11 +1,12 @@
 use assistant::{AssistantPanel, InlineAssist};
-use editor::Editor;
+use editor::{Editor, EditorSettings};
 
 use gpui::{
     Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
     Subscription, View, ViewContext, WeakView,
 };
 use search::{buffer_search, BufferSearchBar};
+use settings::{Settings, SettingsStore};
 use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
 use workspace::{
     item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -16,16 +17,26 @@ pub struct QuickActionBar {
     active_item: Option<Box<dyn ItemHandle>>,
     _inlay_hints_enabled_subscription: Option<Subscription>,
     workspace: WeakView<Workspace>,
+    show: bool,
 }
 
 impl QuickActionBar {
-    pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
-        Self {
+    pub fn new(
+        buffer_search_bar: View<BufferSearchBar>,
+        workspace: &Workspace,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        let mut this = Self {
             buffer_search_bar,
             active_item: None,
             _inlay_hints_enabled_subscription: None,
             workspace: workspace.weak_handle(),
-        }
+            show: true,
+        };
+        this.apply_settings(cx);
+        cx.observe_global::<SettingsStore>(|this, cx| this.apply_settings(cx))
+            .detach();
+        this
     }
 
     fn active_editor(&self) -> Option<View<Editor>> {
@@ -33,6 +44,24 @@ impl QuickActionBar {
             .as_ref()
             .and_then(|item| item.downcast::<Editor>())
     }
+
+    fn apply_settings(&mut self, cx: &mut ViewContext<Self>) {
+        let new_show = EditorSettings::get_global(cx).toolbar.quick_actions;
+        if new_show != self.show {
+            self.show = new_show;
+            cx.emit(ToolbarItemEvent::ChangeLocation(
+                self.get_toolbar_item_location(),
+            ));
+        }
+    }
+
+    fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
+        if self.show && self.active_editor().is_some() {
+            ToolbarItemLocation::PrimaryRight
+        } else {
+            ToolbarItemLocation::Hidden
+        }
+    }
 }
 
 impl Render for QuickActionBar {
@@ -40,7 +69,6 @@ impl Render for QuickActionBar {
         let Some(editor) = self.active_editor() else {
             return div().id("empty quick action bar");
         };
-
         let inlay_hints_button = Some(QuickActionBarButton::new(
             "toggle inlay hints",
             IconName::InlayHint,
@@ -155,36 +183,28 @@ impl ToolbarItemView for QuickActionBar {
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut ViewContext<Self>,
     ) -> ToolbarItemLocation {
-        match active_pane_item {
-            Some(active_item) => {
-                self.active_item = Some(active_item.boxed_clone());
-                self._inlay_hints_enabled_subscription.take();
-
-                if let Some(editor) = active_item.downcast::<Editor>() {
-                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
-                    let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
-                    self._inlay_hints_enabled_subscription =
-                        Some(cx.observe(&editor, move |_, editor, cx| {
-                            let editor = editor.read(cx);
-                            let new_inlay_hints_enabled = editor.inlay_hints_enabled();
-                            let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
-                            let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
-                                || supports_inlay_hints != new_supports_inlay_hints;
-                            inlay_hints_enabled = new_inlay_hints_enabled;
-                            supports_inlay_hints = new_supports_inlay_hints;
-                            if should_notify {
-                                cx.notify()
-                            }
-                        }));
-                    ToolbarItemLocation::PrimaryRight
-                } else {
-                    ToolbarItemLocation::Hidden
-                }
-            }
-            None => {
-                self.active_item = None;
-                ToolbarItemLocation::Hidden
+        self.active_item = active_pane_item.map(ItemHandle::boxed_clone);
+        if let Some(active_item) = active_pane_item {
+            self._inlay_hints_enabled_subscription.take();
+
+            if let Some(editor) = active_item.downcast::<Editor>() {
+                let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
+                let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
+                self._inlay_hints_enabled_subscription =
+                    Some(cx.observe(&editor, move |_, editor, cx| {
+                        let editor = editor.read(cx);
+                        let new_inlay_hints_enabled = editor.inlay_hints_enabled();
+                        let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
+                        let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
+                            || supports_inlay_hints != new_supports_inlay_hints;
+                        inlay_hints_enabled = new_inlay_hints_enabled;
+                        supports_inlay_hints = new_supports_inlay_hints;
+                        if should_notify {
+                            cx.notify()
+                        }
+                    }));
             }
         }
+        self.get_toolbar_item_location()
     }
 }

crates/workspace/src/toolbar.rs 🔗

@@ -127,7 +127,7 @@ impl Render for Toolbar {
                             h_flex()
                                 // We're using `flex_none` here to prevent some flickering that can occur when the
                                 // size of the left items container changes.
-                                .flex_none()
+                                .when_else(has_left_items, Div::flex_none, Div::flex_auto)
                                 .justify_end()
                                 .children(self.right_items().map(|item| item.to_any())),
                         )

crates/zed/src/zed.rs 🔗

@@ -353,7 +353,7 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewCo
             toolbar.add_item(buffer_search_bar.clone(), cx);
 
             let quick_action_bar =
-                cx.new_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
+                cx.new_view(|cx| QuickActionBar::new(buffer_search_bar, workspace, cx));
             toolbar.add_item(quick_action_bar, cx);
             let diagnostic_editor_controls = cx.new_view(|_| diagnostics::ToolbarControls::new());
             toolbar.add_item(diagnostic_editor_controls, cx);

docs/src/configuring_zed.md 🔗

@@ -190,6 +190,23 @@ List of `string` values
 2. Position the dock to the right of the workspace like a side panel: `right`
 3. Position the dock full screen over the entire workspace: `expanded`
 
+## Editor Toolbar
+
+- Description: Whether or not to show various elements in the editor toolbar.
+- Setting: `toolbar`
+- Default:
+
+```json
+"toolbar": {
+  "breadcrumbs": true,
+  "quick_actions": true
+},
+```
+
+**Options**
+
+Each option controls displaying of a particular toolbar element. If all elements are hidden, the editor toolbar is not displayed.
+
 ## Enable Language Server
 
 - Description: Whether or not to use language servers to provide code intelligence.