diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs index a528af6a13..90e164e38b 100644 --- a/crates/copilot/src/copilot_chat.rs +++ b/crates/copilot/src/copilot_chat.rs @@ -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, client: Arc, 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 = 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| { diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 1277044a63..4580fa71a6 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -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, + dir_path: PathBuf, + config_paths: HashSet, +) -> mpsc::UnboundedReceiver { + 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( fs: Arc, cx: &App,