diff --git a/crates/ide-lsp/src/lib.rs b/crates/ide-lsp/src/lib.rs index b5ce6898c..0eaa9467c 100644 --- a/crates/ide-lsp/src/lib.rs +++ b/crates/ide-lsp/src/lib.rs @@ -2584,6 +2584,10 @@ impl LspManager { let watchers = run.watchers.read(); let roots = self.server_workspace_roots(&s); let changes: Vec<(&PathBuf, Change)> = if watchers.is_empty() { + // Dart watches the filesystem itself and rejects this unregistered notification. + if s.name == "dart" { + continue; + } let languages = s.languages(); all.filter(|(p, change)| { roots.iter().any(|root| p.starts_with(root)) diff --git a/crates/ide-lsp/tests/mock_server.rs b/crates/ide-lsp/tests/mock_server.rs index 7d9dbbfcb..357c618c7 100644 --- a/crates/ide-lsp/tests/mock_server.rs +++ b/crates/ide-lsp/tests/mock_server.rs @@ -420,6 +420,45 @@ fn change(range: Range, text: &str) -> AppliedChange { AppliedChange { range, text: text.into() } } +#[tokio::test(flavor = "multi_thread")] +async fn dart_file_watching_requires_an_explicit_registration() { + for (language, extension, name, fallback) in + [("dart", "dart", "dart", false), ("dart", "dart", "alternative-dart", true), ("java", "java", "jdtls", true)] + { + let h = harness_for(language, extension, &[(name, incremental_caps())], &[]); + let (doc, _) = h.open(&format!("open.{extension}"), "x"); + h.wait_status(name, ServerStatus::Ready).await; + let s = h.launcher.state(name); + let path = h.root.join(format!("closed.{extension}")); + let notify = || h.mgr.watched_files_changed(&[], std::slice::from_ref(&path), &[], &[]); + notify(); + h.mgr.hover(doc, Pos::new(0, 0), Default::default()).await.unwrap(); + assert_eq!(s.notified("workspace/didChangeWatchedFiles").len(), usize::from(fallback), "{name}"); + s.peer() + .request::<_, Value>( + "client/registerCapability", + json!({ "registrations": [{ "id": "watch", "method": "workspace/didChangeWatchedFiles", "registerOptions": { "watchers": [{ "globPattern": "**/*" }] } }] }), + None, + ) + .await + .unwrap(); + notify(); + eventually(|| s.notified("workspace/didChangeWatchedFiles").len() == usize::from(fallback) + 1).await; + s.peer() + .request::<_, Value>( + "client/unregisterCapability", + json!({ "unregisterations": [{ "id": "watch", "method": "workspace/didChangeWatchedFiles" }] }), + None, + ) + .await + .unwrap(); + notify(); + h.mgr.hover(doc, Pos::new(0, 0), Default::default()).await.unwrap(); + assert_eq!(s.notified("workspace/didChangeWatchedFiles").len(), usize::from(fallback) * 2 + 1, "{name}"); + h.mgr.shutdown_all().await; + } +} + // ------------------------------------------------------------------ tests #[tokio::test(flavor = "multi_thread")]