diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index c182b8490d29160817881693389cd60530bf6991..9b95eb664cedc9b72ade6a667bf28502f555771a 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -104,6 +104,7 @@ pub use worktree::{ const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100); +pub const FS_WATCH_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FormatTrigger { @@ -9220,7 +9221,9 @@ impl LspStore { return; } - let Some(local) = self.as_local() else { return }; + let Some(local) = self.as_local_mut() else { + return; + }; local.prettier_store.update(cx, |prettier_store, cx| { prettier_store.update_prettier_settings(&worktree_handle, changes, cx) @@ -9240,22 +9243,53 @@ impl LspStore { language_server_ids.dedup(); let abs_path = worktree_handle.read(cx).abs_path(); + for server_id in &language_server_ids { - if let Some(LanguageServerState::Running { server, .. }) = - local.language_servers.get(server_id) - { - if let Some(watched_paths) = local - .language_server_watched_paths - .get(server_id) - .and_then(|paths| paths.worktree_paths.get(&worktree_id)) - { + let Some(watch) = local.language_server_watched_paths.get_mut(&server_id) else { + continue; + }; + let Some(watched_paths) = watch.worktree_paths.get(&worktree_id) else { + continue; + }; + + for (path, _, change) in changes { + if !watched_paths.is_match(path) { + continue; + } + + let file_abs_path = abs_path.join(path); + + watch.pending_events.insert(file_abs_path, *change); + } + + if watch.pending_events.is_empty() { + continue; + } + let server_id = *server_id; + + watch.flush_timer_task = Some(cx.spawn(async move |this, cx| { + cx.background_executor() + .timer(FS_WATCH_DEBOUNCE_TIMEOUT) + .await; + this.update(cx, |this, _cx| { + let Some(this) = this.as_local_mut() else { + return; + }; + let Some(LanguageServerState::Running { server, .. }) = + this.language_servers.get(&server_id) + else { + return; + }; + + let Some(watch) = this.language_server_watched_paths.get_mut(&server_id) else { + return; + }; + let params = lsp::DidChangeWatchedFilesParams { - changes: changes - .iter() - .filter_map(|(path, _, change)| { - if !watched_paths.is_match(path) { - return None; - } + changes: watch + .pending_events + .drain() + .filter_map(|(path, change)| { let typ = match change { PathChange::Loaded => return None, PathChange::Added => lsp::FileChangeType::CREATED, @@ -9264,19 +9298,21 @@ impl LspStore { PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, }; Some(lsp::FileEvent { - uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), + uri: lsp::Url::from_file_path(&path).unwrap(), typ, }) }) .collect(), }; + if !params.changes.is_empty() { server .notify::(¶ms) .ok(); } - } - } + }) + .log_err(); + })); } } @@ -9721,6 +9757,8 @@ impl RenameActionPredicate { #[derive(Default)] struct LanguageServerWatchedPaths { worktree_paths: HashMap, + pending_events: HashMap, + flush_timer_task: Option>, abs_paths: HashMap, (GlobSet, Task<()>)>, } @@ -9799,6 +9837,8 @@ impl LanguageServerWatchedPathsBuilder { .collect(); LanguageServerWatchedPaths { worktree_paths: self.worktree_paths, + pending_events: HashMap::default(), + flush_timer_task: None, abs_paths, } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 37d08687707bdd98c1e20a2cff32de5243fef099..29e93a89a197936e8b008aa34355e8fdf4311f87 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,8 +1,8 @@ #![allow(clippy::format_collect)] use crate::{ - Event, git_store::StatusEntry, task_inventory::TaskContexts, task_store::TaskSettingsLocation, - *, + Event, git_store::StatusEntry, lsp_store::FS_WATCH_DEBOUNCE_TIMEOUT, + task_inventory::TaskContexts, task_store::TaskSettingsLocation, *, }; use buffer_diff::{ BufferDiffEvent, CALCULATE_DIFF_TASK, DiffHunkSecondaryStatus, DiffHunkStatus, @@ -1190,6 +1190,9 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon // The language server receives events for the FS mutations that match its watch patterns. cx.executor().run_until_parked(); + cx.executor().advance_clock(FS_WATCH_DEBOUNCE_TIMEOUT); + cx.executor().run_until_parked(); + assert_eq!( &*file_changes.lock(), &[