1 //! Project loading & configuration updates
2 use std::{mem, sync::Arc};
4 use always_assert::always;
5 use flycheck::{FlycheckConfig, FlycheckHandle};
6 use hir::db::DefDatabase;
8 use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
9 use project_model::{ProcMacroClient, ProjectWorkspace, WorkspaceBuildScripts};
10 use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
13 config::{Config, FilesWatcher, LinkedProject},
14 global_state::GlobalState,
20 pub(crate) enum ProjectWorkspaceProgress {
23 End(Vec<anyhow::Result<ProjectWorkspace>>),
27 pub(crate) enum BuildDataProgress {
30 End(Vec<anyhow::Result<WorkspaceBuildScripts>>),
34 pub(crate) fn is_quiescent(&self) -> bool {
35 !(self.fetch_workspaces_queue.op_in_progress()
36 || self.fetch_build_data_queue.op_in_progress()
37 || self.vfs_progress_config_version < self.vfs_config_version
38 || self.vfs_progress_n_done < self.vfs_progress_n_total)
41 pub(crate) fn update_configuration(&mut self, config: Config) {
42 let _p = profile::span("GlobalState::update_configuration");
43 let old_config = mem::replace(&mut self.config, Arc::new(config));
44 if self.config.lru_capacity() != old_config.lru_capacity() {
45 self.analysis_host.update_lru_capacity(self.config.lru_capacity());
47 if self.config.linked_projects() != old_config.linked_projects() {
48 self.fetch_workspaces_request()
49 } else if self.config.flycheck() != old_config.flycheck() {
50 self.reload_flycheck();
53 // Apply experimental feature flags.
56 .set_enable_proc_attr_macros(self.config.expand_proc_attr_macros());
58 pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
59 if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
63 "Requesting workspace reload because of the following changes: {}",
67 .filter(|(path, kind)| is_interesting(path, *kind))
68 .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
72 self.fetch_workspaces_request();
74 fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
75 const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
76 const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
77 let file_name = path.file_name().unwrap_or_default();
79 if file_name == "Cargo.toml" || file_name == "Cargo.lock" {
82 if change_kind == ChangeKind::Modify {
85 if path.extension().unwrap_or_default() != "rs" {
88 if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) {
91 let parent = match path.parent() {
95 if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_ref().ends_with(it)) {
98 if file_name == "main.rs" {
99 let grand_parent = match parent.parent() {
101 None => return false,
103 if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_ref().ends_with(it)) {
110 pub(crate) fn report_new_status_if_needed(&mut self) {
111 let mut status = lsp_ext::ServerStatusParams {
112 health: lsp_ext::Health::Ok,
113 quiescent: self.is_quiescent(),
117 if let Some(error) = self.build_data_error() {
118 status.health = lsp_ext::Health::Warning;
119 status.message = Some(error)
121 if !self.config.cargo_autoreload()
122 && self.is_quiescent()
123 && self.fetch_workspaces_queue.op_requested()
125 status.health = lsp_ext::Health::Warning;
126 status.message = Some("Workspace reload required".to_string())
129 if let Some(error) = self.fetch_workspace_error() {
130 status.health = lsp_ext::Health::Error;
131 status.message = Some(error)
134 if self.last_reported_status.as_ref() != Some(&status) {
135 self.last_reported_status = Some(status.clone());
137 if let (lsp_ext::Health::Error, Some(message)) = (status.health, &status.message) {
138 self.show_message(lsp_types::MessageType::Error, message.clone());
141 if self.config.server_status_notification() {
142 self.send_notification::<lsp_ext::ServerStatusNotification>(status);
147 pub(crate) fn fetch_workspaces_request(&mut self) {
148 self.fetch_workspaces_queue.request_op()
150 pub(crate) fn fetch_workspaces_if_needed(&mut self) {
151 if !self.fetch_workspaces_queue.should_start_op() {
154 log::info!("will fetch workspaces");
156 self.task_pool.handle.spawn_with_sender({
157 let linked_projects = self.config.linked_projects();
158 let detached_files = self.config.detached_files().to_vec();
159 let cargo_config = self.config.cargo();
163 let sender = sender.clone();
166 .send(Task::FetchWorkspace(ProjectWorkspaceProgress::Report(msg)))
171 sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
173 let mut workspaces = linked_projects
175 .map(|project| match project {
176 LinkedProject::ProjectManifest(manifest) => {
177 project_model::ProjectWorkspace::load(
183 LinkedProject::InlineJsonProject(it) => {
184 project_model::ProjectWorkspace::load_inline(
186 cargo_config.target.as_deref(),
190 .collect::<Vec<_>>();
192 if !detached_files.is_empty() {
194 .push(project_model::ProjectWorkspace::load_detached_files(detached_files));
197 log::info!("did fetch workspaces {:?}", workspaces);
199 .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(workspaces)))
204 pub(crate) fn fetch_workspaces_completed(
206 workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
208 self.fetch_workspaces_queue.op_completed(workspaces)
211 pub(crate) fn fetch_build_data_request(&mut self) {
212 self.fetch_build_data_queue.request_op();
214 pub(crate) fn fetch_build_data_if_needed(&mut self) {
215 if !self.fetch_build_data_queue.should_start_op() {
219 let workspaces = Arc::clone(&self.workspaces);
220 let config = self.config.cargo();
221 self.task_pool.handle.spawn_with_sender(move |sender| {
222 sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
225 let sender = sender.clone();
227 sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
230 let mut res = Vec::new();
231 for ws in workspaces.iter() {
233 ProjectWorkspace::Cargo { cargo, .. } => cargo,
234 ProjectWorkspace::DetachedFiles { .. } | ProjectWorkspace::Json { .. } => {
235 res.push(Ok(WorkspaceBuildScripts::default()));
239 res.push(WorkspaceBuildScripts::run(&config, ws, &progress))
241 sender.send(Task::FetchBuildData(BuildDataProgress::End(res))).unwrap();
244 pub(crate) fn fetch_build_data_completed(
246 build_data: Vec<anyhow::Result<WorkspaceBuildScripts>>,
248 self.fetch_build_data_queue.op_completed(build_data)
251 pub(crate) fn switch_workspaces(&mut self) {
252 let _p = profile::span("GlobalState::switch_workspaces");
253 log::info!("will switch workspaces");
255 if let Some(error_message) = self.fetch_workspace_error() {
256 log::error!("failed to switch workspaces: {}", error_message);
257 if !self.workspaces.is_empty() {
262 if let Some(error_message) = self.build_data_error() {
263 log::error!("failed to switch build data: {}", error_message);
266 let workspaces = self
267 .fetch_workspaces_queue
270 .filter_map(|res| res.as_ref().ok().cloned())
271 .collect::<Vec<_>>();
273 let mut build_scripts = self
274 .fetch_build_data_queue
277 .map(|res| res.as_ref().ok().cloned().unwrap_or_default())
278 .collect::<Vec<_>>();
280 // FIXME: This is not even remotely correct. I do hope that this is
281 // eventually consistent though. We need to figure a better way to map
282 // `cargo metadata` to `cargo check` in the future.
284 // I *think* what we need here is an extra field on `ProjectWorkspace`,
285 // and a workflow to set it, once build data is ready.
286 build_scripts.resize_with(workspaces.len(), WorkspaceBuildScripts::default);
288 if *self.workspaces == workspaces && self.workspace_build_data == build_scripts {
292 if let FilesWatcher::Client = self.config.files().watcher {
293 if self.config.did_change_watched_files_dynamic_registration() {
294 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
298 .flat_map(|(ws, bs)| ws.to_roots(bs))
299 .filter(|it| it.is_member)
301 root.include.into_iter().flat_map(|it| {
303 format!("{}/**/*.rs", it.display()),
304 format!("{}/**/Cargo.toml", it.display()),
305 format!("{}/**/Cargo.lock", it.display()),
309 .map(|glob_pattern| lsp_types::FileSystemWatcher {
315 let registration = lsp_types::Registration {
316 id: "workspace/didChangeWatchedFiles".to_string(),
317 method: "workspace/didChangeWatchedFiles".to_string(),
318 register_options: Some(serde_json::to_value(registration_options).unwrap()),
320 self.send_request::<lsp_types::request::RegisterCapability>(
321 lsp_types::RegistrationParams { registrations: vec![registration] },
327 let mut change = Change::new();
329 let files_config = self.config.files();
330 let project_folders =
331 ProjectFolders::new(&workspaces, &build_scripts, &files_config.exclude);
333 if self.proc_macro_client.is_none() {
334 self.proc_macro_client = match self.config.proc_macro_srv() {
336 Some((path, args)) => match ProcMacroClient::extern_process(path.clone(), args) {
340 "Failed to run proc_macro_srv from path {}, error: {:?}",
350 let watch = match files_config.watcher {
351 FilesWatcher::Client => vec![],
352 FilesWatcher::Notify => project_folders.watch,
354 self.vfs_config_version += 1;
355 self.loader.handle.set_config(vfs::loader::Config {
356 load: project_folders.load,
358 version: self.vfs_config_version,
361 // Create crate graph from all the workspaces
363 let mut crate_graph = CrateGraph::default();
364 let vfs = &mut self.vfs.write().0;
365 let loader = &mut self.loader;
366 let mem_docs = &self.mem_docs;
367 let mut load = |path: &AbsPath| {
368 let _p = profile::span("GlobalState::load");
369 let vfs_path = vfs::VfsPath::from(path.to_path_buf());
370 if !mem_docs.contains_key(&vfs_path) {
371 let contents = loader.handle.load_sync(path);
372 vfs.set_file_contents(vfs_path.clone(), contents);
374 let res = vfs.file_id(&vfs_path);
376 log::warn!("failed to load {}", path.display())
380 for (ws, bs) in workspaces.iter().zip(&build_scripts) {
381 crate_graph.extend(ws.to_crate_graph(
383 self.proc_macro_client.as_ref(),
390 change.set_crate_graph(crate_graph);
392 self.source_root_config = project_folders.source_root_config;
393 self.workspaces = Arc::new(workspaces);
394 self.workspace_build_data = build_scripts;
396 self.analysis_host.apply_change(change);
397 self.process_changes();
398 self.reload_flycheck();
399 log::info!("did switch workspaces");
402 fn fetch_workspace_error(&self) -> Option<String> {
403 let mut buf = String::new();
405 for ws in self.fetch_workspaces_queue.last_op_result() {
406 if let Err(err) = ws {
407 stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
418 fn build_data_error(&self) -> Option<String> {
419 let mut buf = String::new();
421 for ws in self.fetch_build_data_queue.last_op_result() {
422 if let Err(err) = ws {
423 stdx::format_to!(buf, "rust-analyzer failed to run custom build: {:#}\n", err);
434 fn reload_flycheck(&mut self) {
435 let _p = profile::span("GlobalState::reload_flycheck");
436 let config = match self.config.flycheck() {
439 self.flycheck = Vec::new();
444 let sender = self.flycheck_sender.clone();
449 .filter_map(|(id, w)| match w {
450 ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
451 ProjectWorkspace::Json { project, .. } => {
452 // Enable flychecks for json projects if a custom flycheck command was supplied
453 // in the workspace configuration.
455 FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
459 ProjectWorkspace::DetachedFiles { .. } => None,
462 let sender = sender.clone();
463 FlycheckHandle::spawn(
465 Box::new(move |msg| sender.send(msg).unwrap()),
467 root.to_path_buf().into(),
475 pub(crate) struct ProjectFolders {
476 pub(crate) load: Vec<vfs::loader::Entry>,
477 pub(crate) watch: Vec<usize>,
478 pub(crate) source_root_config: SourceRootConfig,
481 impl ProjectFolders {
483 workspaces: &[ProjectWorkspace],
484 build_scripts: &[WorkspaceBuildScripts],
485 global_excludes: &[AbsPathBuf],
486 ) -> ProjectFolders {
487 always!(workspaces.len() == build_scripts.len());
488 let mut res = ProjectFolders::default();
489 let mut fsc = FileSetConfig::builder();
490 let mut local_filesets = vec![];
492 for root in workspaces.iter().zip(build_scripts).flat_map(|(ws, bs)| ws.to_roots(bs)) {
493 let file_set_roots: Vec<VfsPath> =
494 root.include.iter().cloned().map(VfsPath::from).collect();
497 let mut dirs = vfs::loader::Directories::default();
498 dirs.extensions.push("rs".into());
499 dirs.include.extend(root.include);
500 dirs.exclude.extend(root.exclude);
501 for excl in global_excludes {
505 .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
507 dirs.exclude.push(excl.clone());
511 vfs::loader::Entry::Directories(dirs)
515 res.watch.push(res.load.len());
517 res.load.push(entry);
520 local_filesets.push(fsc.len());
522 fsc.add_file_set(file_set_roots)
525 let fsc = fsc.build();
526 res.source_root_config = SourceRootConfig { fsc, local_filesets };
532 #[derive(Default, Debug)]
533 pub(crate) struct SourceRootConfig {
534 pub(crate) fsc: FileSetConfig,
535 pub(crate) local_filesets: Vec<usize>,
538 impl SourceRootConfig {
539 pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
540 let _p = profile::span("SourceRootConfig::partition");
545 .map(|(idx, file_set)| {
546 let is_local = self.local_filesets.contains(&idx);
548 SourceRoot::new_local(file_set)
550 SourceRoot::new_library(file_set)