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