copilot: Decouple copilot sign in from edit prediction settings (#26689)

Closes #25883

This PR allows you to use copilot chat for assistant without setting
copilot as the edit prediction provider.


[copilot.webm](https://github.com/user-attachments/assets/fecfbde1-d72c-4c0c-b080-a07671fb846e)

Todos:
- [x] Remove redudant "copilot" key from settings
- [x] Do not disable copilot LSP when `edit_prediction_provider` is not
set to `copilot`
- [x] Start copilot LSP when:
  - [x]  `edit_prediction_provider` is set to `copilot`
  - [x] Copilot sign in clicked from assistant settings
- [x] Handle flicker for frame after starting LSP, but before signing in
caused due to signed out status
- [x] Fixed this by adding intermediate state for awaiting signing in in
sign out enum
- [x] Handle cancel button should sign out from `copilot` (existing bug)
- [x] Handle modal dismissal should sign out if not in signed in state
(existing bug)

Release Notes:

- You can now sign into Copilot from assistant settings without making
it your edit prediction provider. This is useful if you want to use
Copilot chat while keeping a different provider, like Zed, for
predictions.
- Removed the `copilot` key from `features` in settings. Use
`edit_prediction_provider` instead.
This commit is contained in:
Smit Barmase 2025-03-14 09:40:56 +00:00 committed by GitHub
parent 8d7b021f92
commit 6a95ec6a64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 114 additions and 61 deletions

View file

@ -1,9 +1,10 @@
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
use gpui::{
div, App, ClipboardItem, Context, DismissEvent, Element, Entity, EventEmitter, FocusHandle,
Focusable, InteractiveElement, IntoElement, MouseDownEvent, ParentElement, Render, Styled,
Subscription, Window,
div, percentage, svg, Animation, AnimationExt, App, ClipboardItem, Context, DismissEvent,
Element, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement,
MouseDownEvent, ParentElement, Render, Styled, Subscription, Transformation, Window,
};
use std::time::Duration;
use ui::{prelude::*, Button, Label, Vector, VectorName};
use util::ResultExt as _;
use workspace::notifications::NotificationId;
@ -17,11 +18,13 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
let Some(copilot) = Copilot::global(cx) else {
return;
};
let status = copilot.read(cx).status();
let Some(workspace) = window.root::<Workspace>().flatten() else {
return;
};
match status {
if matches!(copilot.read(cx).status(), Status::Disabled) {
copilot.update(cx, |this, cx| this.start_copilot(false, true, cx));
}
match copilot.read(cx).status() {
Status::Starting { task } => {
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
@ -54,6 +57,15 @@ pub fn initiate_sign_in(window: &mut Window, cx: &mut App) {
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();
}
}
})
.log_err();
@ -76,6 +88,7 @@ pub struct CopilotCodeVerification {
status: Status,
connect_clicked: bool,
focus_handle: FocusHandle,
copilot: Entity<Copilot>,
_subscription: Subscription,
}
@ -86,7 +99,20 @@ impl Focusable for CopilotCodeVerification {
}
impl EventEmitter<DismissEvent> for CopilotCodeVerification {}
impl ModalView for CopilotCodeVerification {}
impl ModalView for CopilotCodeVerification {
fn on_before_dismiss(
&mut self,
_: &mut Window,
cx: &mut Context<Self>,
) -> workspace::DismissDecision {
self.copilot.update(cx, |copilot, cx| {
if matches!(copilot.status(), Status::SigningIn { .. }) {
copilot.sign_out(cx).detach_and_log_err(cx);
}
});
workspace::DismissDecision::Dismiss(true)
}
}
impl CopilotCodeVerification {
pub fn new(copilot: &Entity<Copilot>, cx: &mut Context<Self>) -> Self {
@ -95,6 +121,7 @@ impl CopilotCodeVerification {
status,
connect_clicked: false,
focus_handle: cx.focus_handle(),
copilot: copilot.clone(),
_subscription: cx.observe(copilot, |this, copilot, cx| {
let status = copilot.read(cx).status();
match status {
@ -180,9 +207,12 @@ impl CopilotCodeVerification {
.child(
Button::new("copilot-enable-cancel-button", "Cancel")
.full_width()
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(DismissEvent);
})),
)
}
fn render_enabled_modal(cx: &mut Context<Self>) -> impl Element {
v_flex()
.gap_2()
@ -216,16 +246,27 @@ impl CopilotCodeVerification {
)
}
fn render_disabled_modal() -> impl Element {
v_flex()
.child(Headline::new("Copilot is disabled").size(HeadlineSize::Large))
.child(Label::new("You can enable Copilot in your settings."))
fn render_loading(window: &mut Window, _: &mut Context<Self>) -> impl Element {
let loading_icon = svg()
.size_8()
.path(IconName::ArrowCircle.path())
.text_color(window.text_style().color)
.with_animation(
"icon_circle_arrow",
Animation::new(Duration::from_secs(2)).repeat(),
|svg, delta| svg.with_transformation(Transformation::rotate(percentage(delta))),
);
h_flex().justify_center().child(loading_icon)
}
}
impl Render for CopilotCodeVerification {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let prompt = match &self.status {
Status::SigningIn { prompt: None } => {
Self::render_loading(window, cx).into_any_element()
}
Status::SigningIn {
prompt: Some(prompt),
} => Self::render_prompting_modal(self.connect_clicked, prompt, cx).into_any_element(),
@ -237,10 +278,6 @@ impl Render for CopilotCodeVerification {
self.connect_clicked = false;
Self::render_enabled_modal(cx).into_any_element()
}
Status::Disabled => {
self.connect_clicked = false;
Self::render_disabled_modal().into_any_element()
}
_ => div().into_any_element(),
};