copilot: Fix missing sign-out button when Zed is the edit prediction provider (#26340)

Smit Barmase created

Closes #25884

Added a sign-out button for Copilot in Assistant settings, allowing
sign-out even when copilot is disabled.

<img width="500" alt="image"
src="https://github.com/user-attachments/assets/43fc97ad-f73c-49e1-a7b6-a3910434d661"
/>



Release Notes:

- Added a sign-out button for Copilot in Assistant settings.

Change summary

crates/copilot/src/copilot.rs                       | 27 ++++++++++-----
crates/copilot/src/copilot_chat.rs                  |  2 
crates/language_models/src/provider/copilot_chat.rs | 21 ++++++++--
3 files changed, 35 insertions(+), 15 deletions(-)

Detailed changes

crates/copilot/src/copilot.rs 🔗

@@ -623,16 +623,21 @@ impl Copilot {
 
     pub fn sign_out(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
         self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
-        if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
-            let server = server.clone();
-            cx.background_spawn(async move {
-                server
-                    .request::<request::SignOut>(request::SignOutParams {})
-                    .await?;
+        match &self.server {
+            CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) => {
+                let server = server.clone();
+                cx.background_spawn(async move {
+                    server
+                        .request::<request::SignOut>(request::SignOutParams {})
+                        .await?;
+                    anyhow::Ok(())
+                })
+            }
+            CopilotServer::Disabled => cx.background_spawn(async move {
+                clear_copilot_config_dir().await;
                 anyhow::Ok(())
-            })
-        } else {
-            Task::ready(Err(anyhow!("copilot hasn't started yet")))
+            }),
+            _ => Task::ready(Err(anyhow!("copilot hasn't started yet"))),
         }
     }
 
@@ -1016,6 +1021,10 @@ async fn clear_copilot_dir() {
     remove_matching(paths::copilot_dir(), |_| true).await
 }
 
+async fn clear_copilot_config_dir() {
+    remove_matching(copilot_chat::copilot_chat_config_dir(), |_| true).await
+}
+
 async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
     const SERVER_PATH: &str = "dist/language-server.js";
 

crates/copilot/src/copilot_chat.rs 🔗

@@ -213,7 +213,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut App) {
     cx.set_global(GlobalCopilotChat(copilot_chat));
 }
 
-fn copilot_chat_config_dir() -> &'static PathBuf {
+pub fn copilot_chat_config_dir() -> &'static PathBuf {
     static COPILOT_CHAT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
 
     COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {

crates/language_models/src/provider/copilot_chat.rs 🔗

@@ -11,8 +11,8 @@ use futures::future::BoxFuture;
 use futures::stream::BoxStream;
 use futures::{FutureExt, StreamExt};
 use gpui::{
-    percentage, svg, Animation, AnimationExt, AnyView, App, AsyncApp, Entity, Render, Subscription,
-    Task, Transformation,
+    percentage, svg, Action, Animation, AnimationExt, AnyView, App, AsyncApp, Entity, Render,
+    Subscription, Task, Transformation,
 };
 use language_model::{
     AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
@@ -337,9 +337,20 @@ impl Render for ConfigurationView {
         if self.state.read(cx).is_authenticated(cx) {
             const LABEL: &str = "Authorized.";
             h_flex()
-                .gap_1()
-                .child(Icon::new(IconName::Check).color(Color::Success))
-                .child(Label::new(LABEL))
+                .justify_between()
+                .child(
+                    h_flex()
+                        .gap_1()
+                        .child(Icon::new(IconName::Check).color(Color::Success))
+                        .child(Label::new(LABEL)),
+                )
+                .child(
+                    Button::new("sign_out", "Sign Out")
+                        .style(ui::ButtonStyle::Filled)
+                        .on_click(|_, window, cx| {
+                            window.dispatch_action(copilot::SignOut.boxed_clone(), cx);
+                        }),
+                )
         } else {
             let loading_icon = svg()
                 .size_8()