ra_ap_rust_analyzer/
reload.rs

1//! Project loading & configuration updates.
2//!
3//! This is quite tricky. The main problem is time and changes -- there's no
4//! fixed "project" rust-analyzer is working with, "current project" is itself
5//! mutable state. For example, when the user edits `Cargo.toml` by adding a new
6//! dependency, project model changes. What's more, switching project model is
7//! not instantaneous -- it takes time to run `cargo metadata` and (for proc
8//! macros) `cargo check`.
9//!
10//! The main guiding principle here is, as elsewhere in rust-analyzer,
11//! robustness. We try not to assume that the project model exists or is
12//! correct. Instead, we try to provide a best-effort service. Even if the
13//! project is currently loading and we don't have a full project model, we
14//! still want to respond to various  requests.
15// FIXME: This is a mess that needs some untangling work
16use 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    /// Is the server quiescent?
68    ///
69    /// This indicates that we've fully loaded the projects and
70    /// are ready to do semantic work.
71    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    /// Is the server ready to respond to analysis dependent LSP requests?
81    ///
82    /// Unlike `is_quiescent`, this returns false when we're indexing
83    /// the project, because we're holding the salsa lock and cannot
84    /// respond to LSP requests that depend on salsa data.
85    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            // It only makes sense to switch to a partially broken workspace
468            // if we don't have any workspace at all yet.
469            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                // Workspaces are the same, but we've updated build data.
502                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                // Current build scripts do not match the version of the active
511                // workspace, so there's nothing for us to update.
512                return;
513            }
514        } else {
515            info!("abandon build scripts for workspaces");
516
517            // Here, we completely changed the workspace (Cargo.toml edit), so
518            // we don't care about build-script results, they are stale.
519            // FIXME: can we abort the build scripts here if they are already running?
520            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                    // When relative patterns are supported by the client, prefer using them
544                    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                    // When they're not, integrate the base to make them into absolute patterns
568                    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            // Also explicitly watch any build files configured in JSON project files.
586            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        // crate graph construction relies on these paths, record them so when one of them gets
702        // deleted or created we trigger a reconstruction of the crate graph
703        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            // Create crate graph from all the workspaces
715            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                // io errors
800                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                                    // Enable flychecks for json projects if a custom flycheck command was supplied
850                                    // in the workspace configuration.
851                                    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
880// FIXME: Move this into load-cargo?
881pub 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        // Populate the side tables for the newly merged crates
895        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    // .cargo/config{.toml}
933    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
961/// Similar to [`str::eq_ignore_ascii_case`] but instead of ignoring
962/// case, we say that `-` and `_` are equal.
963fn 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}