copilot: Fix onboarding into Copilot requires Zed restart (#26330)
Closes #25594 This PR fixes an issue where signing into Copilot required restarting Zed. Copilot depends on an OAuth token that comes from either `hosts.json` or `apps.json`. Initially, both files don't exist. If neither file is found, we fallback to watching `hosts.json` for updates. However, if the auth process creates `apps.json`, we won't receive updates from it, causing the UI to remain outdated. This PR fixes that by watching the parent `github-copilot` directory instead, which will always contain one of those files along with an additional version file. I have tested this on macOS and Linux Wayland. Release Notes: - Fixed an issue where signing into Copilot required restarting Zed.
This commit is contained in:
parent
22d9b5d8ca
commit
f14d6670ba
2 changed files with 62 additions and 20 deletions
|
@ -4,13 +4,14 @@ use std::sync::OnceLock;
|
|||
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::DateTime;
|
||||
use collections::HashSet;
|
||||
use fs::Fs;
|
||||
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
|
||||
use gpui::{prelude::*, App, AsyncApp, Global};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use paths::home_dir;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::watch_config_file;
|
||||
use settings::watch_config_dir;
|
||||
use strum::EnumIter;
|
||||
|
||||
pub const COPILOT_CHAT_COMPLETION_URL: &str = "https://api.githubcopilot.com/chat/completions";
|
||||
|
@ -237,27 +238,18 @@ impl CopilotChat {
|
|||
}
|
||||
|
||||
pub fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &App) -> Self {
|
||||
let config_paths = copilot_chat_config_paths();
|
||||
|
||||
let resolve_config_path = {
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for config_path in config_paths.iter() {
|
||||
if fs.metadata(config_path).await.is_ok_and(|v| v.is_some()) {
|
||||
return config_path.clone();
|
||||
}
|
||||
}
|
||||
config_paths[0].clone()
|
||||
}
|
||||
};
|
||||
let config_paths: HashSet<PathBuf> = copilot_chat_config_paths().into_iter().collect();
|
||||
let dir_path = copilot_chat_config_dir();
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
let config_file = resolve_config_path.await;
|
||||
let mut config_file_rx = watch_config_file(cx.background_executor(), fs, config_file);
|
||||
|
||||
while let Some(contents) = config_file_rx.next().await {
|
||||
let mut parent_watch_rx = watch_config_dir(
|
||||
cx.background_executor(),
|
||||
fs.clone(),
|
||||
dir_path.clone(),
|
||||
config_paths,
|
||||
);
|
||||
while let Some(contents) = parent_watch_rx.next().await {
|
||||
let oauth_token = extract_oauth_token(contents);
|
||||
|
||||
cx.update(|cx| {
|
||||
if let Some(this) = Self::global(cx).as_ref() {
|
||||
this.update(cx, |this, cx| {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{settings_store::SettingsStore, Settings};
|
||||
use fs::Fs;
|
||||
use collections::HashSet;
|
||||
use fs::{Fs, PathEventKind};
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use gpui::{App, BackgroundExecutor, ReadGlobal};
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
|
@ -78,6 +79,55 @@ pub fn watch_config_file(
|
|||
rx
|
||||
}
|
||||
|
||||
pub fn watch_config_dir(
|
||||
executor: &BackgroundExecutor,
|
||||
fs: Arc<dyn Fs>,
|
||||
dir_path: PathBuf,
|
||||
config_paths: HashSet<PathBuf>,
|
||||
) -> mpsc::UnboundedReceiver<String> {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
executor
|
||||
.spawn(async move {
|
||||
for file_path in &config_paths {
|
||||
if fs.metadata(file_path).await.is_ok_and(|v| v.is_some()) {
|
||||
if let Ok(contents) = fs.load(file_path).await {
|
||||
if tx.unbounded_send(contents).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (events, _) = fs.watch(&dir_path, Duration::from_millis(100)).await;
|
||||
futures::pin_mut!(events);
|
||||
|
||||
while let Some(event_batch) = events.next().await {
|
||||
for event in event_batch {
|
||||
if config_paths.contains(&event.path) {
|
||||
match event.kind {
|
||||
Some(PathEventKind::Removed) => {
|
||||
if tx.unbounded_send(String::new()).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(PathEventKind::Created) | Some(PathEventKind::Changed) => {
|
||||
if let Ok(contents) = fs.load(&event.path).await {
|
||||
if tx.unbounded_send(contents).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
pub fn update_settings_file<T: Settings>(
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &App,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue