1use std::{iter, mem, sync::atomic::AtomicUsize};
17
18use hir::{ChangeWithProcMacros, ProcMacrosBuilder, db::DefDatabase};
19use ide_db::{
20 FxHashMap,
21 base_db::{CrateGraphBuilder, ProcMacroLoadingError, ProcMacroPaths, salsa::Durability},
22};
23use itertools::Itertools;
24use load_cargo::{ProjectFolders, load_proc_macro};
25use lsp_types::FileSystemWatcher;
26use paths::Utf8Path;
27use proc_macro_api::ProcMacroClient;
28use project_model::{
29 ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts, project_json,
30};
31use stdx::{format_to, thread::ThreadIntent};
32use triomphe::Arc;
33use vfs::{AbsPath, AbsPathBuf, ChangeKind};
34
35use crate::{
36 config::{Config, FilesWatcher, LinkedProject},
37 flycheck::{FlycheckConfig, FlycheckHandle},
38 global_state::{
39 FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState,
40 },
41 lsp_ext,
42 main_loop::{DiscoverProjectParam, Task},
43 op_queue::Cause,
44};
45use tracing::{debug, info};
46
47#[derive(Debug)]
48pub(crate) enum ProjectWorkspaceProgress {
49 Begin,
50 Report(String),
51 End(Vec<anyhow::Result<ProjectWorkspace>>, bool),
52}
53
54#[derive(Debug)]
55pub(crate) enum BuildDataProgress {
56 Begin,
57 Report(String),
58 End((Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)),
59}
60
61#[derive(Debug)]
62pub(crate) enum ProcMacroProgress {
63 Begin,
64 Report(String),
65 End(ChangeWithProcMacros),
66}
67
68impl GlobalState {
69 pub(crate) fn is_quiescent(&self) -> bool {
74 self.vfs_done
75 && self.fetch_ws_receiver.is_none()
76 && !self.fetch_workspaces_queue.op_in_progress()
77 && !self.fetch_build_data_queue.op_in_progress()
78 && !self.fetch_proc_macros_queue.op_in_progress()
79 && self.discover_jobs_active == 0
80 && self.vfs_progress_config_version >= self.vfs_config_version
81 }
82
83 fn is_fully_ready(&self) -> bool {
89 self.is_quiescent() && !self.prime_caches_queue.op_in_progress()
90 }
91
92 pub(crate) fn update_configuration(&mut self, config: Config) {
93 let _p = tracing::info_span!("GlobalState::update_configuration").entered();
94 let old_config = mem::replace(&mut self.config, Arc::new(config));
95 if self.config.lru_parse_query_capacity() != old_config.lru_parse_query_capacity() {
96 self.analysis_host.update_lru_capacity(self.config.lru_parse_query_capacity());
97 }
98 if self.config.lru_query_capacities_config() != old_config.lru_query_capacities_config() {
99 self.analysis_host.update_lru_capacities(
100 &self.config.lru_query_capacities_config().cloned().unwrap_or_default(),
101 );
102 }
103
104 if self.config.linked_or_discovered_projects() != old_config.linked_or_discovered_projects()
105 {
106 let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
107 self.fetch_workspaces_queue.request_op("discovered projects changed".to_owned(), req)
108 } else if self.config.flycheck(None) != old_config.flycheck(None) {
109 self.reload_flycheck();
110 }
111
112 if self.analysis_host.raw_database().expand_proc_attr_macros()
113 != self.config.expand_proc_attr_macros()
114 {
115 self.analysis_host.raw_database_mut().set_expand_proc_attr_macros_with_durability(
116 self.config.expand_proc_attr_macros(),
117 Durability::HIGH,
118 );
119 }
120
121 if self.config.cargo(None) != old_config.cargo(None) {
122 let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
123 self.fetch_workspaces_queue.request_op("cargo config changed".to_owned(), req)
124 }
125
126 if self.config.cfg_set_test(None) != old_config.cfg_set_test(None) {
127 let req = FetchWorkspaceRequest { path: None, force_crate_graph_reload: false };
128 self.fetch_workspaces_queue.request_op("cfg_set_test config changed".to_owned(), req)
129 }
130 }
131
132 pub(crate) fn current_status(&self) -> lsp_ext::ServerStatusParams {
133 let mut status = lsp_ext::ServerStatusParams {
134 health: lsp_ext::Health::Ok,
135 quiescent: self.is_fully_ready(),
136 message: None,
137 };
138 let mut message = String::new();
139
140 if !self.config.cargo_autoreload_config(None)
141 && self.is_quiescent()
142 && self.fetch_workspaces_queue.op_requested()
143 && self.config.discover_workspace_config().is_none()
144 {
145 status.health |= lsp_ext::Health::Warning;
146 message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n");
147 }
148
149 if self.build_deps_changed {
150 status.health |= lsp_ext::Health::Warning;
151 message.push_str(
152 "Proc-macros and/or build scripts have changed and need to be rebuilt.\n\n",
153 );
154 }
155 if self.fetch_build_data_error().is_err() {
156 status.health |= lsp_ext::Health::Warning;
157 message.push_str("Failed to run build scripts of some packages.\n\n");
158 message.push_str(
159 "Please refer to the language server logs for more details on the errors.",
160 );
161 }
162 if let Some(err) = &self.config_errors {
163 status.health |= lsp_ext::Health::Warning;
164 format_to!(message, "{err}\n");
165 }
166 if let Some(err) = &self.last_flycheck_error {
167 status.health |= lsp_ext::Health::Warning;
168 message.push_str(err);
169 message.push('\n');
170 }
171
172 if self.config.linked_or_discovered_projects().is_empty()
173 && self.config.detached_files().is_empty()
174 {
175 status.health |= lsp_ext::Health::Warning;
176 message.push_str("Failed to discover workspace.\n");
177 message.push_str("Consider adding the `Cargo.toml` of the workspace to the [`linkedProjects`](https://rust-analyzer.github.io/book/configuration.html#linkedProjects) setting.\n\n");
178 }
179 if self.fetch_workspace_error().is_err() {
180 status.health |= lsp_ext::Health::Error;
181 message.push_str("Failed to load workspaces.");
182
183 if self.config.has_linked_projects() {
184 message.push_str(
185 "`rust-analyzer.linkedProjects` have been specified, which may be incorrect. Specified project paths:\n",
186 );
187 message
188 .push_str(&format!(" {}", self.config.linked_manifests().format("\n ")));
189 if self.config.has_linked_project_jsons() {
190 message.push_str("\nAdditionally, one or more project jsons are specified")
191 }
192 }
193 message.push_str("\n\n");
194 }
195
196 if !self.workspaces.is_empty() {
197 self.check_workspaces_msrv().for_each(|e| {
198 status.health |= lsp_ext::Health::Warning;
199 format_to!(message, "{e}");
200 });
201
202 let proc_macro_clients = self.proc_macro_clients.iter().chain(iter::repeat(&None));
203
204 for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) {
205 if let ProjectWorkspaceKind::Cargo { error: Some(error), .. }
206 | ProjectWorkspaceKind::DetachedFile {
207 cargo: Some((_, _, Some(error))), ..
208 } = &ws.kind
209 {
210 status.health |= lsp_ext::Health::Warning;
211 format_to!(
212 message,
213 "Failed to read Cargo metadata with dependencies for `{}`: {:#}\n\n",
214 ws.manifest_or_root(),
215 error
216 );
217 }
218 if let Some(err) = ws.sysroot.error() {
219 status.health |= lsp_ext::Health::Warning;
220 format_to!(
221 message,
222 "Workspace `{}` has sysroot errors: ",
223 ws.manifest_or_root()
224 );
225 message.push_str(err);
226 message.push_str("\n\n");
227 }
228 if let Some(err) = ws.sysroot.metadata_error() {
229 status.health |= lsp_ext::Health::Warning;
230 format_to!(
231 message,
232 "Failed to read Cargo metadata with dependencies for sysroot of `{}`: ",
233 ws.manifest_or_root()
234 );
235 message.push_str(err);
236 message.push_str("\n\n");
237 }
238 if let ProjectWorkspaceKind::Cargo { rustc: Err(Some(err)), .. } = &ws.kind {
239 status.health |= lsp_ext::Health::Warning;
240 format_to!(
241 message,
242 "Failed loading rustc_private crates for workspace `{}`: ",
243 ws.manifest_or_root()
244 );
245 message.push_str(err);
246 message.push_str("\n\n");
247 };
248 match proc_macro_client {
249 Some(Err(err)) => {
250 status.health |= lsp_ext::Health::Warning;
251 format_to!(
252 message,
253 "Failed spawning proc-macro server for workspace `{}`: {err}",
254 ws.manifest_or_root()
255 );
256 message.push_str("\n\n");
257 }
258 Some(Ok(client)) => {
259 if let Some(err) = client.exited() {
260 status.health |= lsp_ext::Health::Warning;
261 format_to!(
262 message,
263 "proc-macro server for workspace `{}` exited: {err}",
264 ws.manifest_or_root()
265 );
266 message.push_str("\n\n");
267 }
268 }
269 None => {}
271 }
272 }
273 }
274
275 if !message.is_empty() {
276 status.message = Some(message.trim_end().to_owned());
277 }
278
279 status
280 }
281
282 pub(crate) fn fetch_workspaces(
283 &mut self,
284 cause: Cause,
285 path: Option<AbsPathBuf>,
286 force_crate_graph_reload: bool,
287 ) {
288 info!(%cause, "will fetch workspaces");
289
290 self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
291 let linked_projects = self.config.linked_or_discovered_projects();
292 let detached_files: Vec<_> = self
293 .config
294 .detached_files()
295 .iter()
296 .cloned()
297 .map(ManifestPath::try_from)
298 .filter_map(Result::ok)
299 .collect();
300 let cargo_config = self.config.cargo(None);
301 let discover_command = self.config.discover_workspace_config().cloned();
302 let is_quiescent = !(self.discover_jobs_active > 0
303 || self.vfs_progress_config_version < self.vfs_config_version
304 || !self.vfs_done);
305
306 move |sender| {
307 let progress = {
308 let sender = sender.clone();
309 move |msg| {
310 sender
311 .send(Task::FetchWorkspace(ProjectWorkspaceProgress::Report(msg)))
312 .unwrap()
313 }
314 };
315
316 sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
317
318 if let (Some(_command), Some(path)) = (&discover_command, &path) {
319 let build = linked_projects.iter().find_map(|project| match project {
320 LinkedProject::InlineProjectJson(it) => it.crate_by_buildfile(path),
321 _ => None,
322 });
323
324 if let Some(build) = build
325 && is_quiescent
326 {
327 let path = AbsPathBuf::try_from(build.build_file)
328 .expect("Unable to convert to an AbsPath");
329 let arg = DiscoverProjectParam::Buildfile(path);
330 sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
331 }
332 }
333
334 let mut workspaces: Vec<_> = linked_projects
335 .iter()
336 .map(|project| match project {
337 LinkedProject::ProjectManifest(manifest) => {
338 debug!(path = %manifest, "loading project from manifest");
339
340 project_model::ProjectWorkspace::load(
341 manifest.clone(),
342 &cargo_config,
343 &progress,
344 )
345 }
346 LinkedProject::InlineProjectJson(it) => {
347 let workspace = project_model::ProjectWorkspace::load_inline(
348 it.clone(),
349 &cargo_config,
350 &progress,
351 );
352 Ok(workspace)
353 }
354 })
355 .collect();
356
357 let mut i = 0;
358 while i < workspaces.len() {
359 if let Ok(w) = &workspaces[i] {
360 let dupes: Vec<_> = workspaces[i + 1..]
361 .iter()
362 .positions(|it| it.as_ref().is_ok_and(|ws| ws.eq_ignore_build_data(w)))
363 .collect();
364 dupes.into_iter().rev().for_each(|d| {
365 _ = workspaces.remove(d + i + 1);
366 });
367 }
368 i += 1;
369 }
370
371 if !detached_files.is_empty() {
372 workspaces.extend(project_model::ProjectWorkspace::load_detached_files(
373 detached_files,
374 &cargo_config,
375 ));
376 }
377
378 info!(?workspaces, "did fetch workspaces");
379 sender
380 .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(
381 workspaces,
382 force_crate_graph_reload,
383 )))
384 .unwrap();
385 }
386 });
387 }
388
389 pub(crate) fn fetch_build_data(&mut self, cause: Cause) {
390 info!(%cause, "will fetch build data");
391 let workspaces = Arc::clone(&self.workspaces);
392 let config = self.config.cargo(None);
393 let root_path = self.config.default_root_path().clone();
394
395 self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
396 sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
397
398 let progress = {
399 let sender = sender.clone();
400 move |msg| {
401 sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
402 }
403 };
404 let res = ProjectWorkspace::run_all_build_scripts(
405 &workspaces,
406 &config,
407 &progress,
408 &root_path,
409 );
410
411 sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap();
412 });
413 }
414
415 pub(crate) fn fetch_proc_macros(
416 &mut self,
417 cause: Cause,
418 mut change: ChangeWithProcMacros,
419 paths: Vec<ProcMacroPaths>,
420 ) {
421 info!(%cause, "will load proc macros");
422 let ignored_proc_macros = self.config.ignored_proc_macros(None).clone();
423 let proc_macro_clients = self.proc_macro_clients.clone();
424
425 self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
426 sender.send(Task::LoadProcMacros(ProcMacroProgress::Begin)).unwrap();
427
428 let ignored_proc_macros = &ignored_proc_macros;
429 let progress = {
430 let sender = sender.clone();
431 &move |msg| {
432 sender.send(Task::LoadProcMacros(ProcMacroProgress::Report(msg))).unwrap()
433 }
434 };
435
436 let mut builder = ProcMacrosBuilder::default();
437 let proc_macro_clients = proc_macro_clients.iter().chain(iter::repeat(&None));
438 for (client, paths) in proc_macro_clients.zip(paths) {
439 for (crate_id, res) in paths.iter() {
440 let expansion_res = match client {
441 Some(Ok(client)) => match res {
442 Ok((crate_name, path)) => {
443 progress(path.to_string());
444 let ignored_proc_macros = ignored_proc_macros
445 .iter()
446 .find_map(|(name, macros)| {
447 eq_ignore_underscore(name, crate_name).then_some(&**macros)
448 })
449 .unwrap_or_default();
450
451 load_proc_macro(client, path, ignored_proc_macros)
452 }
453 Err(e) => Err(e.clone()),
454 },
455 Some(Err(e)) => Err(ProcMacroLoadingError::ProcMacroSrvError(
456 e.to_string().into_boxed_str(),
457 )),
458 None => Err(ProcMacroLoadingError::ProcMacroSrvError(
459 "proc-macro-srv is not running".into(),
460 )),
461 };
462 builder.insert(*crate_id, expansion_res)
463 }
464 }
465
466 change.set_proc_macros(builder);
467 sender.send(Task::LoadProcMacros(ProcMacroProgress::End(change))).unwrap();
468 });
469 }
470
471 pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
472 let _p = tracing::info_span!("GlobalState::switch_workspaces").entered();
473 tracing::info!(%cause, "will switch workspaces");
474
475 let Some(FetchWorkspaceResponse { workspaces, force_crate_graph_reload }) =
476 self.fetch_workspaces_queue.last_op_result()
477 else {
478 return;
479 };
480 let switching_from_empty_workspace = self.workspaces.is_empty();
481
482 info!(%cause, ?force_crate_graph_reload, %switching_from_empty_workspace);
483 if self.fetch_workspace_error().is_err() && !switching_from_empty_workspace {
484 if *force_crate_graph_reload {
485 self.recreate_crate_graph(cause, false);
486 }
487 return;
490 }
491
492 let workspaces =
493 workspaces.iter().filter_map(|res| res.as_ref().ok().cloned()).collect::<Vec<_>>();
494
495 let same_workspaces = workspaces.len() == self.workspaces.len()
496 && workspaces
497 .iter()
498 .zip(self.workspaces.iter())
499 .all(|(l, r)| l.eq_ignore_build_data(r));
500
501 if same_workspaces {
502 if switching_from_empty_workspace {
503 return;
505 }
506 if let Some(FetchBuildDataResponse { workspaces, build_scripts }) =
507 self.fetch_build_data_queue.last_op_result()
508 {
509 if Arc::ptr_eq(workspaces, &self.workspaces) {
510 info!("set build scripts to workspaces");
511
512 let workspaces = workspaces
513 .iter()
514 .cloned()
515 .zip(build_scripts)
516 .map(|(mut ws, bs)| {
517 ws.set_build_scripts(bs.as_ref().ok().cloned().unwrap_or_default());
518 ws
519 })
520 .collect::<Vec<_>>();
521 info!("same workspace, but new build data");
523 self.workspaces = Arc::new(workspaces);
524 } else {
525 info!("build scripts do not match the version of the active workspace");
526 if *force_crate_graph_reload {
527 self.recreate_crate_graph(cause, switching_from_empty_workspace);
528 }
529
530 return;
533 }
534 } else {
535 if *force_crate_graph_reload {
536 self.recreate_crate_graph(cause, switching_from_empty_workspace);
537 }
538
539 return;
541 }
542 } else {
543 info!("abandon build scripts for workspaces");
544
545 self.workspaces = Arc::new(workspaces);
549 self.check_workspaces_msrv().for_each(|message| {
550 self.send_notification::<lsp_types::notification::ShowMessage>(
551 lsp_types::ShowMessageParams { typ: lsp_types::MessageType::WARNING, message },
552 );
553 });
554
555 if self.config.run_build_scripts(None) {
556 self.build_deps_changed = false;
557 self.fetch_build_data_queue.request_op("workspace updated".to_owned(), ());
558
559 if !switching_from_empty_workspace {
560 return;
564 }
565 }
566 }
567
568 if let FilesWatcher::Client = self.config.files().watcher {
569 let filter = self
570 .workspaces
571 .iter()
572 .flat_map(|ws| ws.to_roots())
573 .filter(|it| it.is_local)
574 .map(|it| it.include);
575
576 let mut watchers: Vec<FileSystemWatcher> =
577 if self.config.did_change_watched_files_relative_pattern_support() {
578 filter
580 .flat_map(|include| {
581 include.into_iter().flat_map(|base| {
582 [
583 (base.clone(), "**/*.rs"),
584 (base.clone(), "**/Cargo.{lock,toml}"),
585 (base.clone(), "**/rust-analyzer.toml"),
586 (base, "**/*.md"),
587 ]
588 })
589 })
590 .map(|(base, pat)| lsp_types::FileSystemWatcher {
591 glob_pattern: lsp_types::GlobPattern::Relative(
592 lsp_types::RelativePattern {
593 base_uri: lsp_types::OneOf::Right(
594 lsp_types::Url::from_file_path(base).unwrap(),
595 ),
596 pattern: pat.to_owned(),
597 },
598 ),
599 kind: None,
600 })
601 .collect()
602 } else {
603 filter
605 .flat_map(|include| {
606 include.into_iter().flat_map(|base| {
607 [
608 format!("{base}/**/*.rs"),
609 format!("{base}/**/Cargo.{{toml,lock}}"),
610 format!("{base}/**/rust-analyzer.toml"),
611 format!("{base}/**/*.md"),
612 ]
613 })
614 })
615 .map(|glob_pattern| lsp_types::FileSystemWatcher {
616 glob_pattern: lsp_types::GlobPattern::String(glob_pattern),
617 kind: None,
618 })
619 .collect()
620 };
621
622 for ws in self.workspaces.iter() {
624 if let ProjectWorkspaceKind::Json(project_json) = &ws.kind {
625 for (_, krate) in project_json.crates() {
626 let Some(build) = &krate.build else {
627 continue;
628 };
629 watchers.push(lsp_types::FileSystemWatcher {
630 glob_pattern: lsp_types::GlobPattern::String(
631 build.build_file.to_string(),
632 ),
633 kind: None,
634 });
635 }
636 }
637 }
638
639 watchers.extend(
640 iter::once(Config::user_config_dir_path().as_deref())
641 .chain(self.workspaces.iter().map(|ws| ws.manifest().map(ManifestPath::as_ref)))
642 .flatten()
643 .map(|glob_pattern| lsp_types::FileSystemWatcher {
644 glob_pattern: lsp_types::GlobPattern::String(glob_pattern.to_string()),
645 kind: None,
646 }),
647 );
648
649 let registration_options =
650 lsp_types::DidChangeWatchedFilesRegistrationOptions { watchers };
651 let registration = lsp_types::Registration {
652 id: "workspace/didChangeWatchedFiles".to_owned(),
653 method: "workspace/didChangeWatchedFiles".to_owned(),
654 register_options: Some(serde_json::to_value(registration_options).unwrap()),
655 };
656 self.send_request::<lsp_types::request::RegisterCapability>(
657 lsp_types::RegistrationParams { registrations: vec![registration] },
658 |_, _| (),
659 );
660 }
661
662 let files_config = self.config.files();
663 let project_folders = ProjectFolders::new(
664 &self.workspaces,
665 &files_config.exclude,
666 Config::user_config_dir_path().as_deref(),
667 );
668
669 if (self.proc_macro_clients.len() < self.workspaces.len() || !same_workspaces)
670 && self.config.expand_proc_macros()
671 {
672 info!("Spawning proc-macro servers");
673
674 self.proc_macro_clients = Arc::from_iter(self.workspaces.iter().map(|ws| {
675 let path = match self.config.proc_macro_srv() {
676 Some(path) => path,
677 None => match ws.find_sysroot_proc_macro_srv()? {
678 Ok(path) => path,
679 Err(e) => return Some(Err(e)),
680 },
681 };
682
683 let env: FxHashMap<_, _> = match &ws.kind {
684 ProjectWorkspaceKind::Cargo { cargo, .. }
685 | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, ..)), .. } => cargo
686 .env()
687 .into_iter()
688 .map(|(k, v)| (k.clone(), Some(v.clone())))
689 .chain(
690 self.config.extra_env(None).iter().map(|(k, v)| (k.clone(), v.clone())),
691 )
692 .chain(
693 ws.sysroot
694 .root()
695 .filter(|_| {
696 !self.config.extra_env(None).contains_key("RUSTUP_TOOLCHAIN")
697 && std::env::var_os("RUSTUP_TOOLCHAIN").is_none()
698 })
699 .map(|it| ("RUSTUP_TOOLCHAIN".to_owned(), Some(it.to_string()))),
700 )
701 .collect(),
702
703 _ => Default::default(),
704 };
705 info!("Using proc-macro server at {path}");
706 let num_process = self.config.proc_macro_num_processes();
707
708 Some(
709 ProcMacroClient::spawn(&path, &env, ws.toolchain.as_ref(), num_process)
710 .map_err(|err| {
711 tracing::error!(
712 "Failed to run proc-macro server from path {path}, error: {err:?}",
713 );
714 anyhow::format_err!(
715 "Failed to run proc-macro server from path {path}, error: {err:?}",
716 )
717 }),
718 )
719 }))
720 }
721
722 let watch = match files_config.watcher {
723 FilesWatcher::Client => vec![],
724 FilesWatcher::Server => project_folders.watch,
725 };
726 self.vfs_config_version += 1;
727 self.loader.handle.set_config(vfs::loader::Config {
728 load: project_folders.load,
729 watch,
730 version: self.vfs_config_version,
731 });
732 self.source_root_config = project_folders.source_root_config;
733 self.local_roots_parent_map = Arc::new(self.source_root_config.source_root_parent_map());
734
735 info!(?cause, "recreating the crate graph");
736 self.recreate_crate_graph(cause, switching_from_empty_workspace);
737
738 info!("did switch workspaces");
739 }
740
741 fn recreate_crate_graph(&mut self, cause: String, initial_build: bool) {
742 info!(?cause, "Building Crate Graph");
743 self.report_progress(
744 "Building CrateGraph",
745 crate::lsp::utils::Progress::Begin,
746 None,
747 None,
748 None,
749 );
750
751 self.crate_graph_file_dependencies.clear();
754 self.detached_files = self
755 .workspaces
756 .iter()
757 .filter_map(|ws| match &ws.kind {
758 ProjectWorkspaceKind::DetachedFile { file, .. } => Some(file.clone()),
759 _ => None,
760 })
761 .collect();
762
763 self.incomplete_crate_graph = false;
764 let (crate_graph, proc_macro_paths) = {
765 let vfs = &self.vfs.read().0;
767 let load = |path: &AbsPath| {
768 let vfs_path = vfs::VfsPath::from(path.to_path_buf());
769 self.crate_graph_file_dependencies.insert(vfs_path.clone());
770 let file_id = vfs.file_id(&vfs_path);
771 self.incomplete_crate_graph |= file_id.is_none();
772 file_id.and_then(|(file_id, excluded)| {
773 (excluded == vfs::FileExcluded::No).then_some(file_id)
774 })
775 };
776
777 ws_to_crate_graph(&self.workspaces, self.config.extra_env(None), load)
778 };
779 let mut change = ChangeWithProcMacros::default();
780 if initial_build || !self.config.expand_proc_macros() {
781 if self.config.expand_proc_macros() {
782 change.set_proc_macros(
783 crate_graph
784 .iter()
785 .map(|id| (id, Err(ProcMacroLoadingError::NotYetBuilt)))
786 .collect(),
787 );
788 } else {
789 change.set_proc_macros(
790 crate_graph
791 .iter()
792 .map(|id| (id, Err(ProcMacroLoadingError::Disabled)))
793 .collect(),
794 );
795 }
796
797 change.set_crate_graph(crate_graph);
798 self.analysis_host.apply_change(change);
799
800 self.finish_loading_crate_graph();
801 } else {
802 change.set_crate_graph(crate_graph);
803 self.fetch_proc_macros_queue.request_op(cause, (change, proc_macro_paths));
804 }
805
806 self.report_progress(
807 "Building CrateGraph",
808 crate::lsp::utils::Progress::End,
809 None,
810 None,
811 None,
812 );
813 }
814
815 pub(crate) fn finish_loading_crate_graph(&mut self) {
816 self.process_changes();
817 self.reload_flycheck();
818 }
819
820 pub(super) fn fetch_workspace_error(&self) -> Result<(), String> {
821 let mut buf = String::new();
822
823 let Some(FetchWorkspaceResponse { workspaces, .. }) =
824 self.fetch_workspaces_queue.last_op_result()
825 else {
826 return Ok(());
827 };
828
829 if workspaces.is_empty() && self.config.discover_workspace_config().is_none() {
830 stdx::format_to!(buf, "rust-analyzer failed to fetch workspace");
831 } else {
832 for ws in workspaces {
833 if let Err(err) = ws {
834 stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
835 }
836 }
837 }
838
839 if buf.is_empty() {
840 return Ok(());
841 }
842
843 Err(buf)
844 }
845
846 pub(super) fn fetch_build_data_error(&self) -> Result<(), String> {
847 let mut buf = String::new();
848
849 let Some(FetchBuildDataResponse { build_scripts, .. }) =
850 &self.fetch_build_data_queue.last_op_result()
851 else {
852 return Ok(());
853 };
854
855 for script in build_scripts {
856 match script {
857 Ok(data) => {
858 if let Some(stderr) = data.error() {
859 stdx::format_to!(buf, "{:#}\n", stderr)
860 }
861 }
862 Err(err) => stdx::format_to!(buf, "{:#}\n", err),
864 }
865 }
866
867 if buf.is_empty() { Ok(()) } else { Err(buf) }
868 }
869
870 fn reload_flycheck(&mut self) {
871 let _p = tracing::info_span!("GlobalState::reload_flycheck").entered();
872 let config = self.config.flycheck(None);
873 let sender = &self.flycheck_sender;
874 let invocation_strategy = config.invocation_strategy();
875 let next_gen =
876 self.flycheck.iter().map(FlycheckHandle::generation).max().unwrap_or_default() + 1;
877 let generation = Arc::new(AtomicUsize::new(next_gen));
878
879 self.flycheck = match invocation_strategy {
880 crate::flycheck::InvocationStrategy::Once => {
881 vec![FlycheckHandle::spawn(
882 0,
883 generation.clone(),
884 sender.clone(),
885 config,
886 crate::flycheck::FlycheckConfigJson::default(),
887 None,
888 self.config.default_root_path().clone(),
889 None,
890 None,
891 )]
892 }
893 crate::flycheck::InvocationStrategy::PerWorkspace => {
894 self.workspaces
895 .iter()
896 .enumerate()
897 .filter_map(|(id, ws)| {
898 Some((
899 id,
900 match &ws.kind {
901 ProjectWorkspaceKind::Cargo { cargo, .. }
902 | ProjectWorkspaceKind::DetachedFile {
903 cargo: Some((cargo, _, _)),
904 ..
905 } => (
906 crate::flycheck::FlycheckConfigJson::default(),
907 cargo.workspace_root(),
908 Some(cargo.manifest_path()),
909 Some(cargo.target_directory()),
910 ),
911 ProjectWorkspaceKind::Json(project) => {
912 let config_json = crate::flycheck::FlycheckConfigJson {
913 single_template: project
914 .runnable_template(project_json::RunnableKind::Flycheck)
915 .cloned(),
916 };
917 match config {
920 _ if config_json.any_configured() => {
921 (config_json, project.path(), None, None)
922 }
923 FlycheckConfig::CustomCommand { .. } => {
924 (config_json, project.path(), None, None)
925 }
926 _ => return None,
927 }
928 }
929 ProjectWorkspaceKind::DetachedFile { .. } => return None,
930 },
931 ws.sysroot.root().map(ToOwned::to_owned),
932 ))
933 })
934 .map(|(id, (config_json, root, manifest_path, target_dir), sysroot_root)| {
935 FlycheckHandle::spawn(
936 id,
937 generation.clone(),
938 sender.clone(),
939 config.clone(),
940 config_json,
941 sysroot_root,
942 root.to_path_buf(),
943 manifest_path.map(|it| it.to_path_buf()),
944 target_dir.map(|it| AsRef::<Utf8Path>::as_ref(it).to_path_buf()),
945 )
946 })
947 .collect()
948 }
949 }
950 .into();
951 self.flycheck_formatted_commands = vec![];
952 }
953}
954
955pub fn ws_to_crate_graph(
957 workspaces: &[ProjectWorkspace],
958 extra_env: &FxHashMap<String, Option<String>>,
959 mut load: impl FnMut(&AbsPath) -> Option<vfs::FileId>,
960) -> (CrateGraphBuilder, Vec<ProcMacroPaths>) {
961 let mut crate_graph = CrateGraphBuilder::default();
962 let mut proc_macro_paths = Vec::default();
963 for ws in workspaces {
964 let (other, mut crate_proc_macros) = ws.to_crate_graph(&mut load, extra_env);
965
966 crate_graph.extend(other, &mut crate_proc_macros);
967 proc_macro_paths.push(crate_proc_macros);
968 }
969
970 crate_graph.shrink_to_fit();
971 proc_macro_paths.shrink_to_fit();
972 (crate_graph, proc_macro_paths)
973}
974
975pub(crate) fn should_refresh_for_change(
976 path: &AbsPath,
977 change_kind: ChangeKind,
978 additional_paths: &[&str],
979) -> bool {
980 const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
982 const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
983
984 let file_name = match path.file_name() {
985 Some(it) => it,
986 None => return false,
987 };
988
989 if let "Cargo.toml" | "Cargo.lock" = file_name {
990 return true;
991 }
992
993 if additional_paths.contains(&file_name) {
994 return true;
995 }
996
997 if matches!(file_name, "config.toml" | "config")
999 && path.parent().map(|parent| parent.as_str().ends_with(".cargo")).unwrap_or(false)
1000 {
1001 return true;
1002 }
1003
1004 if change_kind == ChangeKind::Modify {
1006 return false;
1007 }
1008
1009 if path.extension().unwrap_or_default() != "rs" {
1010 return false;
1011 }
1012
1013 if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_str().ends_with(it)) {
1014 return true;
1015 }
1016 let parent = match path.parent() {
1017 Some(it) => it,
1018 None => return false,
1019 };
1020 if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_str().ends_with(it)) {
1021 return true;
1022 }
1023 if file_name == "main.rs" {
1024 let grand_parent = match parent.parent() {
1025 Some(it) => it,
1026 None => return false,
1027 };
1028 if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_str().ends_with(it)) {
1029 return true;
1030 }
1031 }
1032 false
1033}
1034
1035fn eq_ignore_underscore(s1: &str, s2: &str) -> bool {
1038 if s1.len() != s2.len() {
1039 return false;
1040 }
1041
1042 s1.as_bytes().iter().zip(s2.as_bytes()).all(|(c1, c2)| {
1043 let c1_underscore = c1 == &b'_' || c1 == &b'-';
1044 let c2_underscore = c2 == &b'_' || c2 == &b'-';
1045
1046 c1 == c2 || (c1_underscore && c2_underscore)
1047 })
1048}