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