From 66e8ef53a0ed018d03340577a0443030a193f773 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 12:21:38 +0100 Subject: Initial implementation of cargo check watching --- crates/ra_lsp_server/Cargo.toml | 1 + crates/ra_lsp_server/src/caps.rs | 4 +- crates/ra_lsp_server/src/cargo_check.rs | 533 +++++++++++++++++++++++++ crates/ra_lsp_server/src/lib.rs | 1 + crates/ra_lsp_server/src/main_loop.rs | 27 +- crates/ra_lsp_server/src/main_loop/handlers.rs | 28 +- crates/ra_lsp_server/src/world.rs | 8 + 7 files changed, 598 insertions(+), 4 deletions(-) create mode 100644 crates/ra_lsp_server/src/cargo_check.rs (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 030e9033c..aa1acdc33 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -27,6 +27,7 @@ ra_project_model = { path = "../ra_project_model" } ra_prof = { path = "../ra_prof" } ra_vfs_glob = { path = "../ra_vfs_glob" } env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } +cargo_metadata = "0.9.1" [dev-dependencies] tempfile = "3" diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index ceb4c4259..0f84e7a34 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -6,7 +6,7 @@ use lsp_types::{ ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, - TypeDefinitionProviderCapability, WorkDoneProgressOptions, + TypeDefinitionProviderCapability, WorkDoneProgressOptions, SaveOptions }; pub fn server_capabilities() -> ServerCapabilities { @@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities { change: Some(TextDocumentSyncKind::Full), will_save: None, will_save_wait_until: None, - save: None, + save: Some(SaveOptions::default()), })), hover_provider: Some(true), completion_provider: Some(CompletionOptions { diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs new file mode 100644 index 000000000..d5ff02154 --- /dev/null +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -0,0 +1,533 @@ +use cargo_metadata::{ + diagnostic::{ + Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, + DiagnosticSpanMacroExpansion, + }, + Message, +}; +use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError}; +use lsp_types::{ + Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, + NumberOrString, Position, Range, Url, +}; +use parking_lot::RwLock; +use std::{ + collections::HashMap, + fmt::Write, + path::PathBuf, + process::{Command, Stdio}, + sync::Arc, + thread::JoinHandle, + time::Instant, +}; + +#[derive(Debug)] +pub struct CheckWatcher { + pub task_recv: Receiver, + pub cmd_send: Sender, + pub shared: Arc>, + handle: JoinHandle<()>, +} + +impl CheckWatcher { + pub fn new(workspace_root: PathBuf) -> CheckWatcher { + let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new())); + + let (task_send, task_recv) = unbounded::(); + let (cmd_send, cmd_recv) = unbounded::(); + let shared_ = shared.clone(); + let handle = std::thread::spawn(move || { + let mut check = CheckWatcherState::new(shared_, workspace_root); + check.run(&task_send, &cmd_recv); + }); + + CheckWatcher { task_recv, cmd_send, handle, shared } + } + + pub fn update(&self) { + self.cmd_send.send(CheckCommand::Update).unwrap(); + } +} + +pub struct CheckWatcherState { + workspace_root: PathBuf, + running: bool, + watcher: WatchThread, + last_update_req: Option, + shared: Arc>, +} + +#[derive(Debug)] +pub struct CheckWatcherSharedState { + diagnostic_collection: HashMap>, + suggested_fix_collection: HashMap>, +} + +impl CheckWatcherSharedState { + fn new() -> CheckWatcherSharedState { + CheckWatcherSharedState { + diagnostic_collection: HashMap::new(), + suggested_fix_collection: HashMap::new(), + } + } + + pub fn clear(&mut self, task_send: &Sender) { + let cleared_files: Vec = self.diagnostic_collection.keys().cloned().collect(); + + self.diagnostic_collection.clear(); + self.suggested_fix_collection.clear(); + + for uri in cleared_files { + task_send.send(CheckTask::Update(uri.clone())).unwrap(); + } + } + + pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> { + self.diagnostic_collection.get(uri).map(|d| d.as_slice()) + } + + pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> { + self.suggested_fix_collection.get(uri).map(|d| d.as_slice()) + } + + fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) { + let diagnostics = self.diagnostic_collection.entry(file_uri).or_default(); + + // If we're building multiple targets it's possible we've already seen this diagnostic + let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic)); + if is_duplicate { + return; + } + + diagnostics.push(diagnostic); + } + + fn add_suggested_fix_for_diagnostic( + &mut self, + mut suggested_fix: SuggestedFix, + diagnostic: &Diagnostic, + ) { + let file_uri = suggested_fix.location.uri.clone(); + let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default(); + + let existing_suggestion: Option<&mut SuggestedFix> = + file_suggestions.iter_mut().find(|s| s == &&suggested_fix); + if let Some(existing_suggestion) = existing_suggestion { + // The existing suggestion also applies to this new diagnostic + existing_suggestion.diagnostics.push(diagnostic.clone()); + } else { + // We haven't seen this suggestion before + suggested_fix.diagnostics.push(diagnostic.clone()); + file_suggestions.push(suggested_fix); + } + } +} + +#[derive(Debug)] +pub enum CheckTask { + Update(Url), +} + +pub enum CheckCommand { + Update, +} + +impl CheckWatcherState { + pub fn new( + shared: Arc>, + workspace_root: PathBuf, + ) -> CheckWatcherState { + let watcher = WatchThread::new(&workspace_root); + CheckWatcherState { workspace_root, running: false, watcher, last_update_req: None, shared } + } + + pub fn run(&mut self, task_send: &Sender, cmd_recv: &Receiver) { + self.running = true; + while self.running { + select! { + recv(&cmd_recv) -> cmd => match cmd { + Ok(cmd) => self.handle_command(cmd), + Err(RecvError) => { + // Command channel has closed, so shut down + self.running = false; + }, + }, + recv(self.watcher.message_recv) -> msg => match msg { + Ok(msg) => self.handle_message(msg, task_send), + Err(RecvError) => {}, + } + }; + + if self.should_recheck() { + self.last_update_req.take(); + self.shared.write().clear(task_send); + + self.watcher.cancel(); + self.watcher = WatchThread::new(&self.workspace_root); + } + } + } + + fn should_recheck(&mut self) -> bool { + if let Some(_last_update_req) = &self.last_update_req { + // We currently only request an update on save, as we need up to + // date source on disk for cargo check to do it's magic, so we + // don't really need to debounce the requests at this point. + return true; + } + false + } + + fn handle_command(&mut self, cmd: CheckCommand) { + match cmd { + CheckCommand::Update => self.last_update_req = Some(Instant::now()), + } + } + + fn handle_message(&mut self, msg: cargo_metadata::Message, task_send: &Sender) { + match msg { + Message::CompilerArtifact(_msg) => { + // TODO: Status display + } + + Message::CompilerMessage(msg) => { + let map_result = + match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) { + Some(map_result) => map_result, + None => return, + }; + + let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result; + let file_uri = location.uri.clone(); + + if !suggested_fixes.is_empty() { + for suggested_fix in suggested_fixes { + self.shared + .write() + .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic); + } + } + self.shared.write().add_diagnostic(file_uri, diagnostic); + + task_send.send(CheckTask::Update(location.uri)).unwrap(); + } + + Message::BuildScriptExecuted(_msg) => {} + Message::Unknown => {} + } + } +} + +/// WatchThread exists to wrap around the communication needed to be able to +/// run `cargo check` without blocking. Currently the Rust standard library +/// doesn't provide a way to read sub-process output without blocking, so we +/// have to wrap sub-processes output handling in a thread and pass messages +/// back over a channel. +struct WatchThread { + message_recv: Receiver, + cancel_send: Sender<()>, +} + +impl WatchThread { + fn new(workspace_root: &PathBuf) -> WatchThread { + let manifest_path = format!("{}/Cargo.toml", workspace_root.to_string_lossy()); + let (message_send, message_recv) = unbounded(); + let (cancel_send, cancel_recv) = unbounded(); + std::thread::spawn(move || { + let mut command = Command::new("cargo") + .args(&["check", "--message-format=json", "--manifest-path", &manifest_path]) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .expect("couldn't launch cargo"); + + for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { + match cancel_recv.try_recv() { + Ok(()) | Err(TryRecvError::Disconnected) => { + command.kill().expect("couldn't kill command"); + } + Err(TryRecvError::Empty) => (), + } + + message_send.send(message.unwrap()).unwrap(); + } + }); + WatchThread { message_recv, cancel_send } + } + + fn cancel(&self) { + let _ = self.cancel_send.send(()); + } +} + +/// Converts a Rust level string to a LSP severity +fn map_level_to_severity(val: DiagnosticLevel) -> Option { + match val { + DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), + DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), + DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), + DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), + DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), + DiagnosticLevel::Unknown => None, + } +} + +/// Check whether a file name is from macro invocation +fn is_from_macro(file_name: &str) -> bool { + file_name.starts_with('<') && file_name.ends_with('>') +} + +/// Converts a Rust macro span to a LSP location recursively +fn map_macro_span_to_location( + span_macro: &DiagnosticSpanMacroExpansion, + workspace_root: &PathBuf, +) -> Option { + if !is_from_macro(&span_macro.span.file_name) { + return Some(map_span_to_location(&span_macro.span, workspace_root)); + } + + if let Some(expansion) = &span_macro.span.expansion { + return map_macro_span_to_location(&expansion, workspace_root); + } + + None +} + +/// Converts a Rust span to a LSP location +fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { + if is_from_macro(&span.file_name) && span.expansion.is_some() { + let expansion = span.expansion.as_ref().unwrap(); + if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { + return macro_range; + } + } + + let mut file_name = workspace_root.clone(); + file_name.push(&span.file_name); + let uri = Url::from_file_path(file_name).unwrap(); + + let range = Range::new( + Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), + Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), + ); + + Location { uri, range } +} + +/// Converts a secondary Rust span to a LSP related information +/// +/// If the span is unlabelled this will return `None`. +fn map_secondary_span_to_related( + span: &DiagnosticSpan, + workspace_root: &PathBuf, +) -> Option { + if let Some(label) = &span.label { + let location = map_span_to_location(span, workspace_root); + Some(DiagnosticRelatedInformation { location, message: label.clone() }) + } else { + // Nothing to label this with + None + } +} + +/// Determines if diagnostic is related to unused code +fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { + if let Some(code) = &rd.code { + match code.code.as_str() { + "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" + | "unused_imports" | "unused_macros" | "unused_variables" => true, + _ => false, + } + } else { + false + } +} + +/// Determines if diagnostic is related to deprecated code +fn is_deprecated(rd: &RustDiagnostic) -> bool { + if let Some(code) = &rd.code { + match code.code.as_str() { + "deprecated" => true, + _ => false, + } + } else { + false + } +} + +#[derive(Debug)] +pub struct SuggestedFix { + pub title: String, + pub location: Location, + pub replacement: String, + pub applicability: Applicability, + pub diagnostics: Vec, +} + +impl std::cmp::PartialEq for SuggestedFix { + fn eq(&self, other: &SuggestedFix) -> bool { + if self.title == other.title + && self.location == other.location + && self.replacement == other.replacement + { + // Applicability doesn't impl PartialEq... + match (&self.applicability, &other.applicability) { + (Applicability::MachineApplicable, Applicability::MachineApplicable) => true, + (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true, + (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true, + (Applicability::Unspecified, Applicability::Unspecified) => true, + _ => false, + } + } else { + false + } + } +} + +enum MappedRustChildDiagnostic { + Related(DiagnosticRelatedInformation), + SuggestedFix(SuggestedFix), + MessageLine(String), +} + +fn map_rust_child_diagnostic( + rd: &RustDiagnostic, + workspace_root: &PathBuf, +) -> MappedRustChildDiagnostic { + let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) { + Some(span) => span, + None => { + // `rustc` uses these spanless children as a way to print multi-line + // messages + return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); + } + }; + + // If we have a primary span use its location, otherwise use the parent + let location = map_span_to_location(&span, workspace_root); + + if let Some(suggested_replacement) = &span.suggested_replacement { + // Include our replacement in the title unless it's empty + let title = if !suggested_replacement.is_empty() { + format!("{}: '{}'", rd.message, suggested_replacement) + } else { + rd.message.clone() + }; + + MappedRustChildDiagnostic::SuggestedFix(SuggestedFix { + title, + location, + replacement: suggested_replacement.clone(), + applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown), + diagnostics: vec![], + }) + } else { + MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { + location, + message: rd.message.clone(), + }) + } +} + +struct MappedRustDiagnostic { + location: Location, + diagnostic: Diagnostic, + suggested_fixes: Vec, +} + +/// Converts a Rust root diagnostic to LSP form +/// +/// This flattens the Rust diagnostic by: +/// +/// 1. Creating a LSP diagnostic with the root message and primary span. +/// 2. Adding any labelled secondary spans to `relatedInformation` +/// 3. Categorising child diagnostics as either `SuggestedFix`es, +/// `relatedInformation` or additional message lines. +/// +/// If the diagnostic has no primary span this will return `None` +fn map_rust_diagnostic_to_lsp( + rd: &RustDiagnostic, + workspace_root: &PathBuf, +) -> Option { + let primary_span = rd.spans.iter().find(|s| s.is_primary)?; + + let location = map_span_to_location(&primary_span, workspace_root); + + let severity = map_level_to_severity(rd.level); + let mut primary_span_label = primary_span.label.as_ref(); + + let mut source = String::from("rustc"); + let mut code = rd.code.as_ref().map(|c| c.code.clone()); + if let Some(code_val) = &code { + // See if this is an RFC #2103 scoped lint (e.g. from Clippy) + let scoped_code: Vec<&str> = code_val.split("::").collect(); + if scoped_code.len() == 2 { + source = String::from(scoped_code[0]); + code = Some(String::from(scoped_code[1])); + } + } + + let mut related_information = vec![]; + let mut tags = vec![]; + + for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { + let related = map_secondary_span_to_related(secondary_span, workspace_root); + if let Some(related) = related { + related_information.push(related); + } + } + + let mut suggested_fixes = vec![]; + let mut message = rd.message.clone(); + for child in &rd.children { + let child = map_rust_child_diagnostic(&child, workspace_root); + match child { + MappedRustChildDiagnostic::Related(related) => related_information.push(related), + MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => { + suggested_fixes.push(suggested_fix) + } + MappedRustChildDiagnostic::MessageLine(message_line) => { + write!(&mut message, "\n{}", message_line).unwrap(); + + // These secondary messages usually duplicate the content of the + // primary span label. + primary_span_label = None; + } + } + } + + if let Some(primary_span_label) = primary_span_label { + write!(&mut message, "\n{}", primary_span_label).unwrap(); + } + + if is_unused_or_unnecessary(rd) { + tags.push(DiagnosticTag::Unnecessary); + } + + if is_deprecated(rd) { + tags.push(DiagnosticTag::Deprecated); + } + + let diagnostic = Diagnostic { + range: location.range, + severity, + code: code.map(NumberOrString::String), + source: Some(source), + message: rd.message.clone(), + related_information: if !related_information.is_empty() { + Some(related_information) + } else { + None + }, + tags: if !tags.is_empty() { Some(tags) } else { None }, + }; + + Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes }) +} + +fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { + left.source == right.source + && left.severity == right.severity + && left.range == right.range + && left.message == right.message +} diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 2ca149fd5..2811231fa 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -22,6 +22,7 @@ macro_rules! print { } mod caps; +mod cargo_check; mod cargo_target_spec; mod conv; mod main_loop; diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index dda318e43..943d38943 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -19,6 +19,7 @@ use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; use crate::{ + cargo_check::CheckTask, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, @@ -176,7 +177,8 @@ pub fn main_loop( Ok(task) => Event::Vfs(task), Err(RecvError) => Err("vfs died")?, }, - recv(libdata_receiver) -> data => Event::Lib(data.unwrap()) + recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), + recv(world_state.check_watcher.task_recv) -> task => Event::CheckWatcher(task.unwrap()) }; if let Event::Msg(Message::Request(req)) = &event { if connection.handle_shutdown(&req)? { @@ -222,6 +224,7 @@ enum Event { Task(Task), Vfs(VfsTask), Lib(LibraryData), + CheckWatcher(CheckTask), } impl fmt::Debug for Event { @@ -259,6 +262,7 @@ impl fmt::Debug for Event { Event::Task(it) => fmt::Debug::fmt(it, f), Event::Vfs(it) => fmt::Debug::fmt(it, f), Event::Lib(it) => fmt::Debug::fmt(it, f), + Event::CheckWatcher(it) => fmt::Debug::fmt(it, f), } } } @@ -318,6 +322,20 @@ fn loop_turn( world_state.maybe_collect_garbage(); loop_state.in_flight_libraries -= 1; } + Event::CheckWatcher(task) => match task { + CheckTask::Update(uri) => { + // We manually send a diagnostic update when the watcher asks + // us to, to avoid the issue of having to change the file to + // receive updated diagnostics. + let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; + if let Some(file_id) = world_state.vfs.read().path2file(&path) { + let params = + handlers::publish_diagnostics(&world_state.snapshot(), FileId(file_id.0))?; + let not = notification_new::(params); + task_sender.send(Task::Notify(not)).unwrap(); + } + } + }, Event::Msg(msg) => match msg { Message::Request(req) => on_request( world_state, @@ -517,6 +535,13 @@ fn on_notification( } Err(not) => not, }; + let not = match notification_cast::(not) { + Ok(_params) => { + state.check_watcher.update(); + return Ok(()); + } + Err(not) => not, + }; let not = match notification_cast::(not) { Ok(params) => { let uri = params.text_document.uri; diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 39eb3df3e..331beab13 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -654,6 +654,29 @@ pub fn handle_code_action( res.push(action.into()); } + for fix in world.check_watcher.read().fixes_for(¶ms.text_document.uri).into_iter().flatten() + { + let fix_range = fix.location.range.conv_with(&line_index); + if fix_range.intersection(&range).is_none() { + continue; + } + + let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())]; + let mut edit_map = std::collections::HashMap::new(); + edit_map.insert(fix.location.uri.clone(), edits); + let edit = WorkspaceEdit::new(edit_map); + + let action = CodeAction { + title: fix.title.clone(), + kind: Some("quickfix".to_string()), + diagnostics: Some(fix.diagnostics.clone()), + edit: Some(edit), + command: None, + is_preferred: None, + }; + res.push(action.into()); + } + for assist in assists { let title = assist.change.label.clone(); let edit = assist.change.try_conv_with(&world)?; @@ -820,7 +843,7 @@ pub fn publish_diagnostics( let _p = profile("publish_diagnostics"); let uri = world.file_id_to_uri(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; - let diagnostics = world + let mut diagnostics: Vec = world .analysis() .diagnostics(file_id)? .into_iter() @@ -834,6 +857,9 @@ pub fn publish_diagnostics( tags: None, }) .collect(); + if let Some(check_diags) = world.check_watcher.read().diagnostics_for(&uri) { + diagnostics.extend(check_diags.iter().cloned()); + } Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) } diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 79431e7e6..8e9380ca0 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -24,6 +24,7 @@ use std::path::{Component, Prefix}; use crate::{ main_loop::pending_requests::{CompletedRequest, LatestRequests}, + cargo_check::{CheckWatcher, CheckWatcherSharedState}, LspError, Result, }; use std::str::FromStr; @@ -52,6 +53,7 @@ pub struct WorldState { pub vfs: Arc>, pub task_receiver: Receiver, pub latest_requests: Arc>, + pub check_watcher: CheckWatcher, } /// An immutable snapshot of the world's state at a point in time. @@ -61,6 +63,7 @@ pub struct WorldSnapshot { pub analysis: Analysis, pub vfs: Arc>, pub latest_requests: Arc>, + pub check_watcher: Arc>, } impl WorldState { @@ -127,6 +130,9 @@ impl WorldState { } change.set_crate_graph(crate_graph); + // FIXME: Figure out the multi-workspace situation + let check_watcher = CheckWatcher::new(folder_roots.first().cloned().unwrap()); + let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); analysis_host.apply_change(change); WorldState { @@ -138,6 +144,7 @@ impl WorldState { vfs: Arc::new(RwLock::new(vfs)), task_receiver, latest_requests: Default::default(), + check_watcher, } } @@ -199,6 +206,7 @@ impl WorldState { analysis: self.analysis_host.analysis(), vfs: Arc::clone(&self.vfs), latest_requests: Arc::clone(&self.latest_requests), + check_watcher: self.check_watcher.shared.clone(), } } -- cgit v1.2.3