copilot: Fix references to uninitialized CopilotAuth global (#47954)

Closes: #46593 #32635 #47924

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Cole Miller <cole@zed.dev>

Release Notes:

- Fixed issues with signing into Copilot via the Settings UI

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
Co-authored-by: Anthony Eid <anthony@zed.dev>
This commit is contained in:
authored and GitHub committed 2026-01-29 15:47:04 -05:00
1 parent 67cc03938f
commit 832f7e50d9
8 files changed
+178 -110

No files matched your search

Generated
+2
View File
@@ -15177,8 +15177,10 @@ dependencies = [
"bm25",
"client",
"component",
"copilot",
"copilot_ui",
"edit_prediction",
"edit_prediction_ui",
"editor",
"feature_flags",
"fs",
+6 -7
View File
@@ -271,27 +271,26 @@ impl GlobalCopilotAuth {
fs: Arc<dyn Fs>,
node_runtime: NodeRuntime,
cx: &mut App,
) {
) -> GlobalCopilotAuth {
let auth =
GlobalCopilotAuth(cx.new(|cx| Copilot::new(None, server_id, fs, node_runtime, cx)));
cx.set_global(auth);
cx.set_global(auth.clone());
auth
}
pub fn try_global(cx: &mut App) -> Option<&GlobalCopilotAuth> {
cx.try_global()
}
pub fn get_or_init(cx: &mut App) -> Option<GlobalCopilotAuth> {
pub fn get_or_init(app_state: Arc<AppState>, cx: &mut App) -> GlobalCopilotAuth {
if let Some(copilot) = cx.try_global::<Self>() {
Some(copilot.clone())
copilot.clone()
} else {
let app_state = AppState::global(cx).upgrade()?;
Self::set_global(
app_state.languages.next_language_server_id(),
app_state.fs.clone(),
app_state.node_runtime.clone(),
cx,
);
cx.try_global::<Self>().cloned()
)
}
}
}
+11 -7
View File
@@ -10,7 +10,7 @@ use gpui::{
};
use ui::{ButtonLike, CommonAnimationExt, ConfiguredApiCard, Vector, VectorName, prelude::*};
use util::ResultExt as _;
use workspace::{Toast, Workspace, notifications::NotificationId};
use workspace::{AppState, Toast, Workspace, notifications::NotificationId};
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
const ERROR_LABEL: &str =
@@ -457,7 +457,7 @@ impl Render for CopilotCodeVerification {
pub struct ConfigurationView {
copilot_status: Option<Status>,
is_authenticated: Box<dyn Fn(&App) -> bool + 'static>,
is_authenticated: Box<dyn Fn(&mut App) -> bool + 'static>,
edit_prediction: bool,
_subscription: Option<Subscription>,
}
@@ -469,11 +469,13 @@ pub enum ConfigurationMode {
impl ConfigurationView {
pub fn new(
is_authenticated: impl Fn(&App) -> bool + 'static,
is_authenticated: impl Fn(&mut App) -> bool + 'static,
mode: ConfigurationMode,
cx: &mut Context<Self>,
) -> Self {
let copilot = GlobalCopilotAuth::try_global(cx).cloned();
let copilot = AppState::try_global(cx)
.and_then(|state| state.upgrade())
.map(|state| GlobalCopilotAuth::get_or_init(state, cx));
Self {
copilot_status: copilot.as_ref().map(|copilot| copilot.0.read(cx).status()),
@@ -567,7 +569,8 @@ impl ConfigurationView {
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.on_click(|_, window, cx| {
if let Some(copilot) = GlobalCopilotAuth::get_or_init(cx) {
if let Some(app_state) = AppState::global(cx).upgrade() {
let copilot = GlobalCopilotAuth::get_or_init(app_state, cx);
initiate_sign_in(copilot.0, window, cx)
}
})
@@ -594,8 +597,9 @@ impl ConfigurationView {
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.on_click(|_, window, cx| {
if let Some(copilot) = GlobalCopilotAuth::get_or_init(cx) {
reinstall_and_sign_in(copilot.0, window, cx)
if let Some(app_state) = AppState::global(cx).upgrade() {
let copilot = GlobalCopilotAuth::get_or_init(app_state, cx);
reinstall_and_sign_in(copilot.0, window, cx);
}
})
}
@@ -536,65 +536,13 @@ impl EditPredictionButton {
}
}
fn get_available_providers(&self, cx: &mut App) -> Vec<EditPredictionProvider> {
let mut providers = Vec::new();
providers.push(EditPredictionProvider::Zed);
if cx.has_flag::<Zeta2FeatureFlag>() {
providers.push(EditPredictionProvider::Experimental(
EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME,
));
}
if let Some(_) = EditPredictionStore::try_global(cx)
.and_then(|store| store.read(cx).copilot_for_project(&self.project.upgrade()?))
{
providers.push(EditPredictionProvider::Copilot);
}
if let Some(supermaven) = Supermaven::global(cx) {
if let Supermaven::Spawned(agent) = supermaven.read(cx) {
if matches!(agent.account_status, AccountStatus::Ready) {
providers.push(EditPredictionProvider::Supermaven);
}
}
}
if CodestralEditPredictionDelegate::has_api_key(cx) {
providers.push(EditPredictionProvider::Codestral);
}
if cx.has_flag::<SweepFeatureFlag>()
&& edit_prediction::sweep_ai::sweep_api_token(cx)
.read(cx)
.has_key()
{
providers.push(EditPredictionProvider::Experimental(
EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
));
}
if cx.has_flag::<MercuryFeatureFlag>()
&& edit_prediction::mercury::mercury_api_token(cx)
.read(cx)
.has_key()
{
providers.push(EditPredictionProvider::Experimental(
EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
));
}
providers
}
fn add_provider_switching_section(
&self,
mut menu: ContextMenu,
current_provider: EditPredictionProvider,
cx: &mut App,
) -> ContextMenu {
let available_providers = self.get_available_providers(cx);
let available_providers = get_available_providers(cx);
let providers: Vec<_> = available_providers
.into_iter()
@@ -605,28 +553,12 @@ impl EditPredictionButton {
menu = menu.separator().header("Providers");
for provider in providers {
let Some(name) = provider.display_name() else {
continue;
};
let is_current = provider == current_provider;
let fs = self.fs.clone();
let name = match provider {
EditPredictionProvider::Zed => "Zed AI",
EditPredictionProvider::Copilot => "GitHub Copilot",
EditPredictionProvider::Supermaven => "Supermaven",
EditPredictionProvider::Codestral => "Codestral",
EditPredictionProvider::Experimental(
EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
) => "Sweep",
EditPredictionProvider::Experimental(
EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
) => "Mercury",
EditPredictionProvider::Experimental(
EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME,
) => "Zeta2",
EditPredictionProvider::None | EditPredictionProvider::Experimental(_) => {
continue;
}
};
menu = menu.item(
ContextMenuEntry::new(name)
.toggleable(IconPosition::Start, is_current)
@@ -1339,7 +1271,7 @@ async fn open_disabled_globs_setting_in_editor(
anyhow::Ok(())
}
fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
pub fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
update_settings_file(fs, cx, move |settings, _| {
settings
.project
@@ -1350,6 +1282,61 @@ fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredicti
});
}
pub fn get_available_providers(cx: &mut App) -> Vec<EditPredictionProvider> {
let mut providers = Vec::new();
providers.push(EditPredictionProvider::Zed);
if cx.has_flag::<Zeta2FeatureFlag>() {
providers.push(EditPredictionProvider::Experimental(
EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME,
));
}
if let Some(app_state) = workspace::AppState::global(cx).upgrade()
&& copilot::GlobalCopilotAuth::get_or_init(app_state, cx)
.0
.read(cx)
.is_authenticated()
{
providers.push(EditPredictionProvider::Copilot);
};
if let Some(supermaven) = Supermaven::global(cx) {
if let Supermaven::Spawned(agent) = supermaven.read(cx) {
if matches!(agent.account_status, AccountStatus::Ready) {
providers.push(EditPredictionProvider::Supermaven);
}
}
}
if CodestralEditPredictionDelegate::has_api_key(cx) {
providers.push(EditPredictionProvider::Codestral);
}
if cx.has_flag::<SweepFeatureFlag>()
&& edit_prediction::sweep_ai::sweep_api_token(cx)
.read(cx)
.has_key()
{
providers.push(EditPredictionProvider::Experimental(
EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
));
}
if cx.has_flag::<MercuryFeatureFlag>()
&& edit_prediction::mercury::mercury_api_token(cx)
.read(cx)
.has_key()
{
providers.push(EditPredictionProvider::Experimental(
EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
));
}
providers
}
fn toggle_show_edit_predictions_for_language(
language: Arc<Language>,
fs: Arc<dyn Fs>,
@@ -16,7 +16,9 @@ use std::any::{Any as _, TypeId};
use ui::{App, prelude::*};
use workspace::{SplitDirection, Workspace};
pub use edit_prediction_button::{EditPredictionButton, ToggleMenu};
pub use edit_prediction_button::{
EditPredictionButton, ToggleMenu, get_available_providers, set_completion_provider,
};
use crate::rate_prediction_modal::PredictEditsRatePredictionsFeatureFlag;
+19
View File
@@ -167,6 +167,25 @@ impl EditPredictionProvider {
| EditPredictionProvider::Experimental(_) => false,
}
}
pub fn display_name(&self) -> Option<&'static str> {
match self {
EditPredictionProvider::Zed => Some("Zed AI"),
EditPredictionProvider::Copilot => Some("GitHub Copilot"),
EditPredictionProvider::Supermaven => Some("Supermaven"),
EditPredictionProvider::Codestral => Some("Codestral"),
EditPredictionProvider::Experimental(
EXPERIMENTAL_SWEEP_EDIT_PREDICTION_PROVIDER_NAME,
) => Some("Sweep"),
EditPredictionProvider::Experimental(
EXPERIMENTAL_MERCURY_EDIT_PREDICTION_PROVIDER_NAME,
) => Some("Mercury"),
EditPredictionProvider::Experimental(
EXPERIMENTAL_ZETA2_EDIT_PREDICTION_PROVIDER_NAME,
) => Some("Zeta2"),
EditPredictionProvider::None | EditPredictionProvider::Experimental(_) => None,
}
}
}
/// The contents of the edit prediction settings.
+2
View File
@@ -19,8 +19,10 @@ test-support = []
anyhow.workspace = true
bm25 = "2.3.2"
component.workspace = true
copilot.workspace = true
copilot_ui.workspace = true
edit_prediction.workspace = true
edit_prediction_ui.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
@@ -1,13 +1,16 @@
use edit_prediction::{
ApiKeyState, EditPredictionStore, MercuryFeatureFlag, SweepFeatureFlag,
ApiKeyState, MercuryFeatureFlag, SweepFeatureFlag,
mercury::{MERCURY_CREDENTIALS_URL, mercury_api_token},
sweep_ai::{SWEEP_CREDENTIALS_URL, sweep_api_token},
};
use edit_prediction_ui::{get_available_providers, set_completion_provider};
use feature_flags::FeatureFlagAppExt as _;
use gpui::{Entity, ScrollHandle, prelude::*};
use language::language_settings::AllLanguageSettings;
use language_models::provider::mistral::{CODESTRAL_API_URL, codestral_api_key};
use project::Project;
use ui::{ButtonLink, ConfiguredApiCard, prelude::*};
use settings::Settings as _;
use ui::{ButtonLink, ConfiguredApiCard, ContextMenu, DropdownMenu, DropdownStyle, prelude::*};
use workspace::AppState;
use crate::{
SettingField, SettingItem, SettingsFieldMetadata, SettingsPageItem, SettingsWindow, USER,
@@ -20,15 +23,9 @@ pub(crate) fn render_edit_prediction_setup_page(
window: &mut Window,
cx: &mut Context<SettingsWindow>,
) -> AnyElement {
let project = settings_window.original_window.as_ref().and_then(|window| {
window
.read_with(cx, |workspace, _| workspace.project().clone())
.ok()
});
let providers = [
project.and_then(|project| {
render_github_copilot_provider(project, window, cx).map(IntoElement::into_any_element)
}),
Some(render_provider_dropdown(window, cx)),
render_github_copilot_provider(window, cx).map(IntoElement::into_any_element),
cx.has_flag::<MercuryFeatureFlag>().then(|| {
render_api_key_provider(
IconName::Inception,
@@ -94,6 +91,65 @@ pub(crate) fn render_edit_prediction_setup_page(
.into_any_element()
}
fn render_provider_dropdown(window: &mut Window, cx: &mut App) -> AnyElement {
let current_provider = AllLanguageSettings::get_global(cx)
.edit_predictions
.provider;
let current_provider_name = current_provider.display_name().unwrap_or("No provider set");
let menu = ContextMenu::build(window, cx, move |mut menu, _, cx| {
let available_providers = get_available_providers(cx);
let fs = <dyn fs::Fs>::global(cx);
for provider in available_providers {
let Some(name) = provider.display_name() else {
continue;
};
let is_current = provider == current_provider;
menu = menu.toggleable_entry(name, is_current, IconPosition::Start, None, {
let fs = fs.clone();
move |_, cx| {
set_completion_provider(fs.clone(), cx, provider);
}
});
}
menu
});
v_flex()
.id("provider-selector")
.min_w_0()
.gap_1p5()
.child(
SettingsSectionHeader::new("Active Provider")
.icon(IconName::Sparkle)
.no_padding(true),
)
.child(
h_flex()
.pt_2p5()
.w_full()
.justify_between()
.child(
v_flex()
.w_full()
.max_w_1_2()
.child(Label::new("Provider"))
.child(
Label::new("Select which provider to use for edit predictions.")
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.child(
DropdownMenu::new("provider-dropdown", current_provider_name, menu)
.style(DropdownStyle::Outlined),
),
)
.into_any_element()
}
fn render_api_key_provider(
icon: IconName,
title: &'static str,
@@ -330,20 +386,16 @@ fn codestral_settings() -> Box<[SettingsPageItem]> {
])
}
fn render_github_copilot_provider(
project: Entity<Project>,
window: &mut Window,
cx: &mut App,
) -> Option<impl IntoElement> {
let copilot = EditPredictionStore::try_global(cx)?
.read(cx)
.copilot_for_project(&project);
fn render_github_copilot_provider(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
let configuration_view = window.use_state(cx, |_, cx| {
copilot_ui::ConfigurationView::new(
move |cx| {
copilot
.as_ref()
.is_some_and(|copilot| copilot.read(cx).is_authenticated())
if let Some(app_state) = AppState::global(cx).upgrade() {
let copilot = copilot::GlobalCopilotAuth::get_or_init(app_state, cx);
copilot.0.read(cx).is_authenticated()
} else {
false
}
},
copilot_ui::ConfigurationMode::EditPrediction,
cx,
@@ -354,6 +406,7 @@ fn render_github_copilot_provider(
v_flex()
.id("github-copilot")
.min_w_0()
.pt_8()
.gap_1p5()
.child(
SettingsSectionHeader::new("GitHub Copilot")