Add setting to allow disabling the Assistant (#9706)

Marshall Bowers created

This PR adds a new `assistant.enabled` setting that controls whether the
Zed Assistant is enabled.

Some users have requested the ability to disable the AI-related features
in Zed if they don't use them. Changing `assistant.enabled` to `false`
will hide the Assistant icon in the status bar (taking priority over the
`assistant.button` setting) as well as filter out the `assistant:`
actions.

The Assistant is enabled by default.

Release Notes:

- Added an `assistant.enabled` setting to control whether the Assistant
is enabled.

Change summary

Cargo.lock                                 |  1 
assets/settings/default.json               |  2 
crates/assistant/Cargo.toml                |  1 
crates/assistant/src/assistant.rs          | 56 +++++++++++++++++++++
crates/assistant/src/assistant_panel.rs    | 17 ++++++
crates/assistant/src/assistant_settings.rs | 59 ++++++++++-------------
crates/zed/src/zed.rs                      |  1 
7 files changed, 100 insertions(+), 37 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -318,6 +318,7 @@ dependencies = [
  "chrono",
  "client",
  "collections",
+ "command_palette_hooks",
  "ctor",
  "editor",
  "env_logger",

assets/settings/default.json 🔗

@@ -245,6 +245,8 @@
   "assistant": {
     // Version of this setting.
     "version": "1",
+    // Whether the assistant is enabled.
+    "enabled": true,
     // Whether to show the assistant panel button in the status bar.
     "button": true,
     // Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'.

crates/assistant/Cargo.toml 🔗

@@ -14,6 +14,7 @@ anyhow.workspace = true
 chrono.workspace = true
 client.workspace = true
 collections.workspace = true
+command_palette_hooks.workspace = true
 editor.workspace = true
 fs.workspace = true
 futures.workspace = true

crates/assistant/src/assistant.rs 🔗

@@ -10,11 +10,12 @@ pub use assistant_panel::AssistantPanel;
 use assistant_settings::{AssistantSettings, OpenAiModel, ZedDotDevModel};
 use chrono::{DateTime, Local};
 use client::{proto, Client};
+use command_palette_hooks::CommandPaletteFilter;
 pub(crate) use completion_provider::*;
-use gpui::{actions, AppContext, SharedString};
+use gpui::{actions, AppContext, Global, SharedString};
 pub(crate) use saved_conversation::*;
 use serde::{Deserialize, Serialize};
-use settings::Settings;
+use settings::{Settings, SettingsStore};
 use std::{
     fmt::{self, Display},
     sync::Arc,
@@ -182,10 +183,61 @@ enum MessageStatus {
     Error(SharedString),
 }
 
+/// The state pertaining to the Assistant.
+#[derive(Default)]
+struct Assistant {
+    /// Whether the Assistant is enabled.
+    enabled: bool,
+}
+
+impl Global for Assistant {}
+
+impl Assistant {
+    const NAMESPACE: &'static str = "assistant";
+
+    fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
+        if self.enabled == enabled {
+            return;
+        }
+
+        self.enabled = enabled;
+
+        if !enabled {
+            CommandPaletteFilter::update_global(cx, |filter, _cx| {
+                filter.hide_namespace(Self::NAMESPACE);
+            });
+
+            return;
+        }
+
+        CommandPaletteFilter::update_global(cx, |filter, _cx| {
+            filter.show_namespace(Self::NAMESPACE);
+        });
+    }
+}
+
 pub fn init(client: Arc<Client>, cx: &mut AppContext) {
+    cx.set_global(Assistant::default());
     AssistantSettings::register(cx);
     completion_provider::init(client, cx);
     assistant_panel::init(cx);
+
+    CommandPaletteFilter::update_global(cx, |filter, _cx| {
+        filter.hide_namespace(Assistant::NAMESPACE);
+    });
+    cx.update_global(|assistant: &mut Assistant, cx: &mut AppContext| {
+        let settings = AssistantSettings::get_global(cx);
+
+        assistant.set_enabled(settings.enabled, cx);
+    });
+    cx.observe_global::<SettingsStore>(|cx| {
+        cx.update_global(|assistant: &mut Assistant, cx: &mut AppContext| {
+            let settings = AssistantSettings::get_global(cx);
+
+            assistant.set_enabled(settings.enabled, cx);
+        });
+    })
+    .detach();
 }
 
 #[cfg(test)]

crates/assistant/src/assistant_panel.rs 🔗

@@ -55,6 +55,11 @@ pub fn init(cx: &mut AppContext) {
         |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
             workspace
                 .register_action(|workspace, _: &ToggleFocus, cx| {
+                    let settings = AssistantSettings::get_global(cx);
+                    if !settings.enabled {
+                        return;
+                    }
+
                     workspace.toggle_panel_focus::<AssistantPanel>(cx);
                 })
                 .register_action(AssistantPanel::inline_assist)
@@ -229,6 +234,11 @@ impl AssistantPanel {
         _: &InlineAssist,
         cx: &mut ViewContext<Workspace>,
     ) {
+        let settings = AssistantSettings::get_global(cx);
+        if !settings.enabled {
+            return;
+        }
+
         let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
             return;
         };
@@ -1217,7 +1227,12 @@ impl Panel for AssistantPanel {
     }
 
     fn icon(&self, cx: &WindowContext) -> Option<IconName> {
-        Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button)
+        let settings = AssistantSettings::get_global(cx);
+        if !settings.enabled || !settings.button {
+            return None;
+        }
+
+        Some(IconName::Ai)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

crates/assistant/src/assistant_settings.rs 🔗

@@ -160,6 +160,7 @@ fn open_ai_url() -> String {
 
 #[derive(Default, Debug, Deserialize, Serialize)]
 pub struct AssistantSettings {
+    pub enabled: bool,
     pub button: bool,
     pub dock: AssistantDockPosition,
     pub default_width: Pixels,
@@ -201,42 +202,26 @@ impl AssistantSettingsContent {
             AssistantSettingsContent::Versioned(settings) => match settings {
                 VersionedAssistantSettingsContent::V1(settings) => settings.clone(),
             },
-            AssistantSettingsContent::Legacy(settings) => {
-                if let Some(open_ai_api_url) = settings.openai_api_url.as_ref() {
-                    AssistantSettingsContentV1 {
-                        button: settings.button,
-                        dock: settings.dock,
-                        default_width: settings.default_width,
-                        default_height: settings.default_height,
-                        provider: Some(AssistantProvider::OpenAi {
-                            default_model: settings
-                                .default_open_ai_model
-                                .clone()
-                                .unwrap_or_default(),
-                            api_url: open_ai_api_url.clone(),
-                        }),
-                    }
-                } else if let Some(open_ai_model) = settings.default_open_ai_model.clone() {
-                    AssistantSettingsContentV1 {
-                        button: settings.button,
-                        dock: settings.dock,
-                        default_width: settings.default_width,
-                        default_height: settings.default_height,
-                        provider: Some(AssistantProvider::OpenAi {
+            AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV1 {
+                enabled: None,
+                button: settings.button,
+                dock: settings.dock,
+                default_width: settings.default_width,
+                default_height: settings.default_height,
+                provider: if let Some(open_ai_api_url) = settings.openai_api_url.as_ref() {
+                    Some(AssistantProvider::OpenAi {
+                        default_model: settings.default_open_ai_model.clone().unwrap_or_default(),
+                        api_url: open_ai_api_url.clone(),
+                    })
+                } else {
+                    settings.default_open_ai_model.clone().map(|open_ai_model| {
+                        AssistantProvider::OpenAi {
                             default_model: open_ai_model,
                             api_url: open_ai_url(),
-                        }),
-                    }
-                } else {
-                    AssistantSettingsContentV1 {
-                        button: settings.button,
-                        dock: settings.dock,
-                        default_width: settings.default_width,
-                        default_height: settings.default_height,
-                        provider: None,
-                    }
-                }
-            }
+                        }
+                    })
+                },
+            },
         }
     }
 
@@ -264,6 +249,7 @@ pub enum VersionedAssistantSettingsContent {
 impl Default for VersionedAssistantSettingsContent {
     fn default() -> Self {
         Self::V1(AssistantSettingsContentV1 {
+            enabled: None,
             button: None,
             dock: None,
             default_width: None,
@@ -275,6 +261,10 @@ impl Default for VersionedAssistantSettingsContent {
 
 #[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
 pub struct AssistantSettingsContentV1 {
+    /// Whether the Assistant is enabled.
+    ///
+    /// Default: true
+    enabled: Option<bool>,
     /// Whether to show the assistant panel button in the status bar.
     ///
     /// Default: true
@@ -340,6 +330,7 @@ impl Settings for AssistantSettings {
 
         for value in [default_value].iter().chain(user_values) {
             let value = value.upgrade();
+            merge(&mut settings.enabled, value.enabled);
             merge(&mut settings.button, value.button);
             merge(&mut settings.dock, value.dock);
             merge(

crates/zed/src/zed.rs 🔗

@@ -3073,6 +3073,7 @@ mod tests {
             notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             workspace::init(app_state.clone(), cx);
             Project::init_settings(cx);
+            command_palette::init(cx);
             language::init(cx);
             editor::init(cx);
             project_panel::init_settings(cx);