дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
fix(lsp): tell language servers about files changed outside the editor (#1135)
Language servers never got workspace/didChangeWatchedFiles, so files changed on disk outside the editor (checkout, pull, another tool) stayed unknown to them; jdtls's incremental build skipped them and Java / Spring Boot launches ran their old classes (e.g. another module of a multi-module project). The project's file watcher now forwards each batch to the running servers: created, changed and removed files of a server's languages under its root. Refs #1015
Цей коміт міститься в:
1 джерело
2f2d3a25ae
коміт
770178966f
3 files changed
+55
No files matched your search
@@ -305,6 +305,7 @@ impl Host {
|
||||
index.update(&changed, &batch.removed);
|
||||
kotlin_bridge.files_changed(changed.iter().chain(&batch.removed));
|
||||
lsp.project_files_changed(batch.created.iter().chain(&batch.removed));
|
||||
lsp.watched_files_changed(&batch.created, &batch.modified, &batch.removed);
|
||||
// Atomic saves by external tools surface as `created`.
|
||||
documents.on_disk_changed(&changed);
|
||||
// A framework's root marker (`angular.json`) created or deleted: its
|
||||
|
||||
@@ -1973,6 +1973,29 @@ impl LspManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Files that changed on disk, outside the editor too (a checkout, another tool): each running
|
||||
/// server hears of those of its languages under its root through `workspace/didChangeWatchedFiles`.
|
||||
/// Without it jdtls's incremental build never sees them, so a launch runs stale classes.
|
||||
pub fn watched_files_changed(&self, created: &[PathBuf], modified: &[PathBuf], removed: &[PathBuf]) {
|
||||
let servers: Vec<Arc<Server>> = self.servers.lock().clone();
|
||||
for s in servers {
|
||||
let Some(run) = s.running() else { continue };
|
||||
let languages = s.languages();
|
||||
let changes: Vec<Value> = [(created, 1), (modified, 2), (removed, 3)]
|
||||
.into_iter()
|
||||
.flat_map(|(paths, kind)| paths.iter().map(move |p| (p, kind)))
|
||||
.filter(|(p, _)| p.starts_with(&s.root) && languages.contains(&self.languages.detect(p, None)))
|
||||
.map(|(p, kind)| json!({ "uri": uri::path_to_uri(p), "type": kind }))
|
||||
.collect();
|
||||
if changes.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = run.peer.notify("workspace/didChangeWatchedFiles", json!({ "changes": changes })) {
|
||||
tracing::debug!("{}: didChangeWatchedFiles: {e}", s.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks initialized capabilities, including dynamic registrations, before choosing a formatter.
|
||||
pub async fn can_format(&self, doc: DocId) -> IdeResult<bool> {
|
||||
let snap = self.snapshot(doc)?;
|
||||
|
||||
@@ -405,6 +405,37 @@ async fn linked_projects_initialize_separate_language_server_roots() {
|
||||
h.mgr.shutdown_all().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn files_changed_on_disk_reach_the_servers_of_their_language() {
|
||||
let h = harness(&[("mock", incremental_caps())]);
|
||||
h.open("a.mk", "x");
|
||||
h.wait_status("mock", ServerStatus::Ready).await;
|
||||
let s = h.launcher.state("mock");
|
||||
let (created, modified, removed) = (h.root.join("new.mk"), h.root.join("a.mk"), h.root.join("old.mk"));
|
||||
// Other languages and files outside the server's root are left out.
|
||||
let other = h.root.join("notes.txt");
|
||||
let outside = tempfile::tempdir().unwrap().path().join("far.mk");
|
||||
h.mgr.watched_files_changed(
|
||||
&[created.clone(), other],
|
||||
&[modified.clone(), outside],
|
||||
std::slice::from_ref(&removed),
|
||||
);
|
||||
eventually(|| !s.notified("workspace/didChangeWatchedFiles").is_empty()).await;
|
||||
assert_eq!(
|
||||
s.notified("workspace/didChangeWatchedFiles")[0]["changes"],
|
||||
json!([
|
||||
{ "uri": uri_of(&created), "type": 1 },
|
||||
{ "uri": uri_of(&modified), "type": 2 },
|
||||
{ "uri": uri_of(&removed), "type": 3 },
|
||||
])
|
||||
);
|
||||
// Nothing of its languages: no notification.
|
||||
h.mgr.watched_files_changed(&[h.root.join("b.txt")], &[], &[]);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
assert_eq!(s.notified("workspace/didChangeWatchedFiles").len(), 1);
|
||||
h.mgr.shutdown_all().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn initialize_and_incremental_sync() {
|
||||
let h = harness(&[("mock", incremental_caps())]);
|
||||
|
||||
Reference in new issue
Заблокувати користувача