From 3c22ad9e93bf2e5ad9ac8835d4d8bbcb6a56224c Mon Sep 17 00:00:00 2001 From: "gcp-cherry-pick-bot[bot]" <98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 02:52:16 -0600 Subject: [PATCH] Make `copilot::SignIn` open sign-in modal when needed (cherry-pick #30239) (#30349) Cherry-picked Make `copilot::SignIn` open sign-in modal when needed (#30239) Also: * Makes sign out show status notifications and errors. * Reinstall now prompts for sign-in after start. Addresses some of #29250, but not all of it. Release Notes: - N/A Co-authored-by: Michael Sloan --- crates/copilot/src/copilot.rs | 53 +++--- crates/copilot/src/sign_in.rs | 155 +++++++++++++----- .../src/inline_completion_button.rs | 10 +- 3 files changed, 142 insertions(+), 76 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index cffff292c7..a0b849200a 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -3,6 +3,7 @@ mod copilot_completion_provider; pub mod request; mod sign_in; +use crate::sign_in::initiate_sign_in_within_workspace; use ::fs::Fs; use anyhow::{Result, anyhow}; use collections::{HashMap, HashSet}; @@ -24,6 +25,7 @@ use node_runtime::NodeRuntime; use parking_lot::Mutex; use request::StatusNotification; use settings::SettingsStore; +use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace}; use std::{ any::TypeId, env, @@ -34,9 +36,10 @@ use std::{ sync::Arc, }; use util::{ResultExt, fs::remove_matching}; +use workspace::Workspace; pub use crate::copilot_completion_provider::CopilotCompletionProvider; -pub use crate::sign_in::{CopilotCodeVerification, initiate_sign_in}; +pub use crate::sign_in::{CopilotCodeVerification, initiate_sign_in, reinstall_and_sign_in}; actions!( copilot, @@ -99,27 +102,25 @@ pub fn init( }) .detach(); - cx.on_action(|_: &SignIn, cx| { - if let Some(copilot) = Copilot::global(cx) { - copilot - .update(cx, |copilot, cx| copilot.sign_in(cx)) - .detach_and_log_err(cx); - } - }); - cx.on_action(|_: &SignOut, cx| { - if let Some(copilot) = Copilot::global(cx) { - copilot - .update(cx, |copilot, cx| copilot.sign_out(cx)) - .detach_and_log_err(cx); - } - }); - cx.on_action(|_: &Reinstall, cx| { - if let Some(copilot) = Copilot::global(cx) { - copilot - .update(cx, |copilot, cx| copilot.reinstall(cx)) - .detach(); - } - }); + cx.observe_new(|workspace: &mut Workspace, _window, _cx| { + workspace.register_action(|workspace, _: &SignIn, window, cx| { + if let Some(copilot) = Copilot::global(cx) { + let is_reinstall = false; + initiate_sign_in_within_workspace(workspace, copilot, is_reinstall, window, cx); + } + }); + workspace.register_action(|workspace, _: &Reinstall, window, cx| { + if let Some(copilot) = Copilot::global(cx) { + reinstall_and_sign_in_within_workspace(workspace, copilot, window, cx); + } + }); + workspace.register_action(|workspace, _: &SignOut, _window, cx| { + if let Some(copilot) = Copilot::global(cx) { + sign_out_within_workspace(workspace, copilot, cx); + } + }); + }) + .detach(); } enum CopilotServer { @@ -563,7 +564,7 @@ impl Copilot { .ok(); } - pub fn sign_in(&mut self, cx: &mut Context) -> Task> { + pub(crate) fn sign_in(&mut self, cx: &mut Context) -> Task> { if let CopilotServer::Running(server) = &mut self.server { let task = match &server.sign_in_status { SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(), @@ -647,7 +648,7 @@ impl Copilot { } } - pub fn sign_out(&mut self, cx: &mut Context) -> Task> { + pub(crate) fn sign_out(&mut self, cx: &mut Context) -> Task> { self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx); match &self.server { CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) => { @@ -667,7 +668,7 @@ impl Copilot { } } - pub fn reinstall(&mut self, cx: &mut Context) -> Task<()> { + pub(crate) fn reinstall(&mut self, cx: &mut Context) -> Shared> { let language_settings = all_language_settings(None, cx); let env = self.build_env(&language_settings.edit_predictions.copilot); let start_task = cx @@ -689,7 +690,7 @@ impl Copilot { cx.notify(); - cx.background_spawn(start_task) + start_task } pub fn language_server(&self) -> Option<&Arc> { diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 70ca6b7d47..464a114d4e 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -12,7 +12,7 @@ use workspace::{ModalView, Toast, Workspace}; const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot"; -struct CopilotStartingToast; +struct CopilotStatusToast; pub fn initiate_sign_in(window: &mut Window, cx: &mut App) { let Some(copilot) = Copilot::global(cx) else { @@ -21,50 +21,83 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) { let Some(workspace) = window.root::().flatten() else { return; }; + workspace.update(cx, |workspace, cx| { + let is_reinstall = false; + initiate_sign_in_within_workspace(workspace, copilot, is_reinstall, window, cx) + }); +} + +pub fn reinstall_and_sign_in(window: &mut Window, cx: &mut App) { + let Some(copilot) = Copilot::global(cx) else { + return; + }; + let Some(workspace) = window.root::().flatten() else { + return; + }; + workspace.update(cx, |workspace, cx| { + reinstall_and_sign_in_within_workspace(workspace, copilot, window, cx); + }); +} + +pub fn reinstall_and_sign_in_within_workspace( + workspace: &mut Workspace, + copilot: Entity, + window: &mut Window, + cx: &mut Context, +) { + let _ = copilot.update(cx, |copilot, cx| copilot.reinstall(cx)); + let is_reinstall = true; + initiate_sign_in_within_workspace(workspace, copilot, is_reinstall, window, cx); +} + +pub fn initiate_sign_in_within_workspace( + workspace: &mut Workspace, + copilot: Entity, + is_reinstall: bool, + window: &mut Window, + cx: &mut Context, +) { if matches!(copilot.read(cx).status(), Status::Disabled) { - copilot.update(cx, |this, cx| this.start_copilot(false, true, cx)); + copilot.update(cx, |copilot, cx| copilot.start_copilot(false, true, cx)); } match copilot.read(cx).status() { Status::Starting { task } => { - workspace.update(cx, |workspace, cx| { - workspace.show_toast( - Toast::new( - NotificationId::unique::(), - "Copilot is starting...", - ), - cx, - ); - }); + workspace.show_toast( + Toast::new( + NotificationId::unique::(), + if is_reinstall { + "Copilot is reinstalling..." + } else { + "Copilot is starting..." + }, + ), + cx, + ); - let workspace = workspace.downgrade(); - cx.spawn(async move |cx| { + cx.spawn_in(window, async move |workspace, cx| { task.await; - if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() { + if let Some(copilot) = cx.update(|_window, cx| Copilot::global(cx)).ok().flatten() { workspace - .update(cx, |workspace, cx| match copilot.read(cx).status() { - Status::Authorized => workspace.show_toast( - Toast::new( - NotificationId::unique::(), - "Copilot has started!", - ), - cx, - ), - _ => { - workspace.dismiss_toast( - &NotificationId::unique::(), + .update_in(cx, |workspace, window, cx| { + match copilot.read(cx).status() { + Status::Authorized => workspace.show_toast( + Toast::new( + NotificationId::unique::(), + "Copilot has started.", + ), cx, - ); - copilot - .update(cx, |copilot, cx| copilot.sign_in(cx)) - .detach_and_log_err(cx); - if let Some(window_handle) = cx.active_window() { - window_handle - .update(cx, |_, window, cx| { - workspace.toggle_modal(window, cx, |_, cx| { - CopilotCodeVerification::new(&copilot, cx) - }); - }) - .log_err(); + ), + _ => { + workspace.dismiss_toast( + &NotificationId::unique::(), + cx, + ); + copilot + .update(cx, |copilot, cx| copilot.sign_in(cx)) + .detach_and_log_err(cx); + workspace.toggle_modal(window, cx, |_, cx| { + CopilotCodeVerification::new(&copilot, cx) + }); } } }) @@ -74,16 +107,54 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) { .detach(); } _ => { - copilot.update(cx, |this, cx| this.sign_in(cx)).detach(); - workspace.update(cx, |this, cx| { - this.toggle_modal(window, cx, |_, cx| { - CopilotCodeVerification::new(&copilot, cx) - }); + copilot + .update(cx, |copilot, cx| copilot.sign_in(cx)) + .detach(); + workspace.toggle_modal(window, cx, |_, cx| { + CopilotCodeVerification::new(&copilot, cx) }); } } } +pub fn sign_out_within_workspace( + workspace: &mut Workspace, + copilot: Entity, + cx: &mut Context, +) { + workspace.show_toast( + Toast::new( + NotificationId::unique::(), + "Signing out of Copilot...", + ), + cx, + ); + let sign_out_task = copilot.update(cx, |copilot, cx| copilot.sign_out(cx)); + cx.spawn(async move |workspace, cx| match sign_out_task.await { + Ok(()) => { + workspace + .update(cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + NotificationId::unique::(), + "Signed out of Copilot.", + ), + cx, + ) + }) + .ok(); + } + Err(err) => { + workspace + .update(cx, |workspace, cx| { + workspace.show_error(&err, cx); + }) + .ok(); + } + }) + .detach(); +} + pub struct CopilotCodeVerification { status: Status, connect_clicked: bool, diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index caed4cdc84..e4f1c09a9c 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -106,14 +106,8 @@ impl Render for InlineCompletionButton { ) .on_click( "Reinstall Copilot", - |_, cx| { - if let Some(copilot) = Copilot::global(cx) { - copilot - .update(cx, |copilot, cx| { - copilot.reinstall(cx) - }) - .detach(); - } + |window, cx| { + copilot::reinstall_and_sign_in(window, cx) }, ), cx,