From 50c77daa0b1f9769e01e4d0e0307a785f1a61c17 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 25 Oct 2021 21:42:30 -0700 Subject: [PATCH] Start work on a test for worktree handling LSP diagnostics --- Cargo.lock | 1 + crates/project/Cargo.toml | 1 + crates/project/src/lib.rs | 6 +- crates/project/src/worktree.rs | 103 ++++++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0504fafb62..3a9736c564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3833,6 +3833,7 @@ dependencies = [ "sum_tree", "tempdir", "toml 0.5.8", + "unindent", "util", ] diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index a0ca5e6e75..bb00812251 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -41,3 +41,4 @@ util = { path = "../util", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } rand = "0.8.3" tempdir = { version = "0.3.7" } +unindent = "0.1.7" diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 3e129c8fb8..a98b89db5a 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -8,6 +8,7 @@ use futures::Future; use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task}; use language::LanguageRegistry; +use lsp::LanguageServer; use std::{ path::Path, sync::{atomic::AtomicBool, Arc}, @@ -73,8 +74,11 @@ impl Project { let rpc = self.client.clone(); let languages = self.languages.clone(); let path = Arc::from(abs_path); + let language_server = LanguageServer::rust(&path, cx); cx.spawn(|this, mut cx| async move { - let worktree = Worktree::open_local(rpc, path, fs, languages, &mut cx).await?; + let worktree = + Worktree::open_local(rpc, path, fs, languages, Some(language_server?), &mut cx) + .await?; this.update(&mut cx, |this, cx| { this.add_worktree(worktree.clone(), cx); }); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 500e9d502f..426115bfd4 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -98,10 +98,11 @@ impl Worktree { path: impl Into>, fs: Arc, languages: Arc, + language_server: Option>, cx: &mut AsyncAppContext, ) -> Result> { let (tree, scan_states_tx) = - LocalWorktree::new(rpc, path, fs.clone(), languages, cx).await?; + LocalWorktree::new(rpc, path, fs.clone(), languages, language_server, cx).await?; tree.update(cx, |tree, cx| { let tree = tree.as_local_mut().unwrap(); let abs_path = tree.snapshot.abs_path.clone(); @@ -671,7 +672,7 @@ pub struct LocalWorktree { queued_operations: Vec<(u64, Operation)>, rpc: Arc, fs: Arc, - language_server: Arc, + language_server: Option>, } #[derive(Default, Deserialize)] @@ -685,6 +686,7 @@ impl LocalWorktree { path: impl Into>, fs: Arc, languages: Arc, + language_server: Option>, cx: &mut AsyncAppContext, ) -> Result<(ModelHandle, Sender)> { let abs_path = path.into(); @@ -709,7 +711,6 @@ impl LocalWorktree { let (scan_states_tx, scan_states_rx) = smol::channel::unbounded(); let (mut last_scan_state_tx, last_scan_state_rx) = watch::channel_with(ScanState::Scanning); let tree = cx.add_model(move |cx: &mut ModelContext| { - let language_server = LanguageServer::rust(&abs_path, cx).unwrap(); let mut snapshot = Snapshot { id: cx.model_id(), scan_id: 0, @@ -815,6 +816,28 @@ impl LocalWorktree { }) .detach(); + if let Some(language_server) = &tree.language_server { + let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + language_server + .on_notification::(move |params| { + smol::block_on(diagnostics_tx.send(params)).ok(); + }) + .detach(); + cx.spawn_weak(|this, mut cx| async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + let this = this.as_local_mut().unwrap(); + this.update_diagnostics(diagnostics, cx); + }); + } else { + break; + } + } + }) + .detach(); + } + Worktree::Local(tree) }); @@ -1161,6 +1184,14 @@ impl LocalWorktree { }) }) } + + fn update_diagnostics( + &mut self, + diagnostics: lsp::PublishDiagnosticsParams, + cx: &mut ModelContext, + ) { + // + } } fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result { @@ -2804,6 +2835,8 @@ mod tests { use anyhow::Result; use client::test::FakeServer; use fs::RealFs; + use language::Point; + use lsp::Url; use rand::prelude::*; use serde_json::json; use std::{cell::RefCell, rc::Rc}; @@ -2812,6 +2845,7 @@ mod tests { fmt::Write, time::{SystemTime, UNIX_EPOCH}, }; + use unindent::Unindent as _; use util::test::temp_tree; #[gpui::test] @@ -2834,6 +2868,7 @@ mod tests { Arc::from(Path::new("/root")), Arc::new(fs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -2866,6 +2901,7 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -2896,6 +2932,7 @@ mod tests { file_path.clone(), Arc::new(RealFs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -2942,6 +2979,7 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -3102,6 +3140,7 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -3156,6 +3195,7 @@ mod tests { "/path/to/the-dir".as_ref(), fs, Default::default(), + None, &mut cx.to_async(), ) .await @@ -3204,6 +3244,7 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -3337,6 +3378,7 @@ mod tests { dir.path(), Arc::new(RealFs), Default::default(), + None, &mut cx.to_async(), ) .await @@ -3425,6 +3467,61 @@ mod tests { .await; } + #[gpui::test] + async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { + let (language_server, mut fake_lsp) = LanguageServer::fake(&cx.background()).await; + let dir = temp_tree(json!({ + "a.rs": " + fn a() { A } + fn b() { BB } + ".unindent(), + "b.rs": " + const y: i32 = 1 + ".unindent(), + })); + + let tree = Worktree::open_local( + Client::new(), + dir.path(), + Arc::new(RealFs), + Default::default(), + Some(language_server), + &mut cx.to_async(), + ) + .await + .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + fake_lsp + .notify::(lsp::PublishDiagnosticsParams { + uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(), + version: None, + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "undefined variable 'A'".to_string(), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(2, 11)), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "undefined variable 'BB'".to_string(), + ..Default::default() + }, + ], + }) + .await; + + let buffer = tree + .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx)) + .await + .unwrap(); + + // Check buffer's diagnostics + } + #[gpui::test(iterations = 100)] fn test_random(mut rng: StdRng) { let operations = env::var("OPERATIONS")