Skip to content

Commit f14d667

Browse files
authored
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.
1 parent 22d9b5d commit f14d667

File tree

2 files changed

+62
-20
lines changed

2 files changed

+62
-20
lines changed

crates/copilot/src/copilot_chat.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ use std::sync::OnceLock;
44

55
use anyhow::{anyhow, Result};
66
use chrono::DateTime;
7+
use collections::HashSet;
78
use fs::Fs;
89
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
910
use gpui::{prelude::*, App, AsyncApp, Global};
1011
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
1112
use paths::home_dir;
1213
use serde::{Deserialize, Serialize};
13-
use settings::watch_config_file;
14+
use settings::watch_config_dir;
1415
use strum::EnumIter;
1516

1617
pub const COPILOT_CHAT_COMPLETION_URL: &str = "https://api.githubcopilot.com/chat/completions";
@@ -237,27 +238,18 @@ impl CopilotChat {
237238
}
238239

239240
pub fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &App) -> Self {
240-
let config_paths = copilot_chat_config_paths();
241-
242-
let resolve_config_path = {
243-
let fs = fs.clone();
244-
async move {
245-
for config_path in config_paths.iter() {
246-
if fs.metadata(config_path).await.is_ok_and(|v| v.is_some()) {
247-
return config_path.clone();
248-
}
249-
}
250-
config_paths[0].clone()
251-
}
252-
};
241+
let config_paths: HashSet<PathBuf> = copilot_chat_config_paths().into_iter().collect();
242+
let dir_path = copilot_chat_config_dir();
253243

254244
cx.spawn(|cx| async move {
255-
let config_file = resolve_config_path.await;
256-
let mut config_file_rx = watch_config_file(cx.background_executor(), fs, config_file);
257-
258-
while let Some(contents) = config_file_rx.next().await {
245+
let mut parent_watch_rx = watch_config_dir(
246+
cx.background_executor(),
247+
fs.clone(),
248+
dir_path.clone(),
249+
config_paths,
250+
);
251+
while let Some(contents) = parent_watch_rx.next().await {
259252
let oauth_token = extract_oauth_token(contents);
260-
261253
cx.update(|cx| {
262254
if let Some(this) = Self::global(cx).as_ref() {
263255
this.update(cx, |this, cx| {

crates/settings/src/settings_file.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{settings_store::SettingsStore, Settings};
2-
use fs::Fs;
2+
use collections::HashSet;
3+
use fs::{Fs, PathEventKind};
34
use futures::{channel::mpsc, StreamExt};
45
use gpui::{App, BackgroundExecutor, ReadGlobal};
56
use std::{path::PathBuf, sync::Arc, time::Duration};
@@ -78,6 +79,55 @@ pub fn watch_config_file(
7879
rx
7980
}
8081

82+
pub fn watch_config_dir(
83+
executor: &BackgroundExecutor,
84+
fs: Arc<dyn Fs>,
85+
dir_path: PathBuf,
86+
config_paths: HashSet<PathBuf>,
87+
) -> mpsc::UnboundedReceiver<String> {
88+
let (tx, rx) = mpsc::unbounded();
89+
executor
90+
.spawn(async move {
91+
for file_path in &config_paths {
92+
if fs.metadata(file_path).await.is_ok_and(|v| v.is_some()) {
93+
if let Ok(contents) = fs.load(file_path).await {
94+
if tx.unbounded_send(contents).is_err() {
95+
return;
96+
}
97+
}
98+
}
99+
}
100+
101+
let (events, _) = fs.watch(&dir_path, Duration::from_millis(100)).await;
102+
futures::pin_mut!(events);
103+
104+
while let Some(event_batch) = events.next().await {
105+
for event in event_batch {
106+
if config_paths.contains(&event.path) {
107+
match event.kind {
108+
Some(PathEventKind::Removed) => {
109+
if tx.unbounded_send(String::new()).is_err() {
110+
return;
111+
}
112+
}
113+
Some(PathEventKind::Created) | Some(PathEventKind::Changed) => {
114+
if let Ok(contents) = fs.load(&event.path).await {
115+
if tx.unbounded_send(contents).is_err() {
116+
return;
117+
}
118+
}
119+
}
120+
_ => {}
121+
}
122+
}
123+
}
124+
}
125+
})
126+
.detach();
127+
128+
rx
129+
}
130+
81131
pub fn update_settings_file<T: Settings>(
82132
fs: Arc<dyn Fs>,
83133
cx: &App,

0 commit comments

Comments
 (0)