@@ -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,