]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/reload.rs
internal: reduce crate interdependence
[rust.git] / crates / rust-analyzer / src / reload.rs
1 //! Project loading & configuration updates
2 use std::{mem, sync::Arc};
3
4 use flycheck::{FlycheckConfig, FlycheckHandle};
5 use hir::db::DefDatabase;
6 use ide::Change;
7 use ide_db::base_db::{CrateGraph, SourceRoot, VfsPath};
8 use proc_macro_api::ProcMacroClient;
9 use project_model::{ProjectWorkspace, WorkspaceBuildScripts};
10 use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
11
12 use crate::{
13     config::{Config, FilesWatcher, LinkedProject},
14     global_state::GlobalState,
15     lsp_ext,
16     main_loop::Task,
17 };
18
19 #[derive(Debug)]
20 pub(crate) enum ProjectWorkspaceProgress {
21     Begin,
22     Report(String),
23     End(Vec<anyhow::Result<ProjectWorkspace>>),
24 }
25
26 #[derive(Debug)]
27 pub(crate) enum BuildDataProgress {
28     Begin,
29     Report(String),
30     End((Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>)),
31 }
32
33 impl GlobalState {
34     pub(crate) fn is_quiescent(&self) -> bool {
35         !(self.fetch_workspaces_queue.op_in_progress()
36             || self.fetch_build_data_queue.op_in_progress()
37             || self.vfs_progress_config_version < self.vfs_config_version
38             || self.vfs_progress_n_done < self.vfs_progress_n_total)
39     }
40
41     pub(crate) fn update_configuration(&mut self, config: Config) {
42         let _p = profile::span("GlobalState::update_configuration");
43         let old_config = mem::replace(&mut self.config, Arc::new(config));
44         if self.config.lru_capacity() != old_config.lru_capacity() {
45             self.analysis_host.update_lru_capacity(self.config.lru_capacity());
46         }
47         if self.config.linked_projects() != old_config.linked_projects() {
48             self.fetch_workspaces_request()
49         } else if self.config.flycheck() != old_config.flycheck() {
50             self.reload_flycheck();
51         }
52
53         // Apply experimental feature flags.
54         self.analysis_host
55             .raw_database_mut()
56             .set_enable_proc_attr_macros(self.config.expand_proc_attr_macros());
57     }
58     pub(crate) fn maybe_refresh(&mut self, changes: &[(AbsPathBuf, ChangeKind)]) {
59         if !changes.iter().any(|(path, kind)| is_interesting(path, *kind)) {
60             return;
61         }
62         log::info!(
63             "Requesting workspace reload because of the following changes: {}",
64             itertools::join(
65                 changes
66                     .iter()
67                     .filter(|(path, kind)| is_interesting(path, *kind))
68                     .map(|(path, kind)| format!("{}: {:?}", path.display(), kind)),
69                 ", "
70             )
71         );
72         self.fetch_workspaces_request();
73
74         fn is_interesting(path: &AbsPath, change_kind: ChangeKind) -> bool {
75             const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"];
76             const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"];
77             let file_name = path.file_name().unwrap_or_default();
78
79             if file_name == "Cargo.toml" || file_name == "Cargo.lock" {
80                 return true;
81             }
82             if change_kind == ChangeKind::Modify {
83                 return false;
84             }
85             if path.extension().unwrap_or_default() != "rs" {
86                 return false;
87             }
88             if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) {
89                 return true;
90             }
91             let parent = match path.parent() {
92                 Some(it) => it,
93                 None => return false,
94             };
95             if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_ref().ends_with(it)) {
96                 return true;
97             }
98             if file_name == "main.rs" {
99                 let grand_parent = match parent.parent() {
100                     Some(it) => it,
101                     None => return false,
102                 };
103                 if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_ref().ends_with(it)) {
104                     return true;
105                 }
106             }
107             false
108         }
109     }
110     pub(crate) fn report_new_status_if_needed(&mut self) {
111         let mut status = lsp_ext::ServerStatusParams {
112             health: lsp_ext::Health::Ok,
113             quiescent: self.is_quiescent(),
114             message: None,
115         };
116
117         if let Some(error) = self.fetch_build_data_error() {
118             status.health = lsp_ext::Health::Warning;
119             status.message = Some(error)
120         }
121         if !self.config.cargo_autoreload()
122             && self.is_quiescent()
123             && self.fetch_workspaces_queue.op_requested()
124         {
125             status.health = lsp_ext::Health::Warning;
126             status.message = Some("Workspace reload required".to_string())
127         }
128
129         if let Some(error) = self.fetch_workspace_error() {
130             status.health = lsp_ext::Health::Error;
131             status.message = Some(error)
132         }
133
134         if self.last_reported_status.as_ref() != Some(&status) {
135             self.last_reported_status = Some(status.clone());
136
137             if let (lsp_ext::Health::Error, Some(message)) = (status.health, &status.message) {
138                 self.show_message(lsp_types::MessageType::Error, message.clone());
139             }
140
141             if self.config.server_status_notification() {
142                 self.send_notification::<lsp_ext::ServerStatusNotification>(status);
143             }
144         }
145     }
146
147     pub(crate) fn fetch_workspaces_request(&mut self) {
148         self.fetch_workspaces_queue.request_op()
149     }
150     pub(crate) fn fetch_workspaces_if_needed(&mut self) {
151         if !self.fetch_workspaces_queue.should_start_op() {
152             return;
153         }
154         log::info!("will fetch workspaces");
155
156         self.task_pool.handle.spawn_with_sender({
157             let linked_projects = self.config.linked_projects();
158             let detached_files = self.config.detached_files().to_vec();
159             let cargo_config = self.config.cargo();
160
161             move |sender| {
162                 let progress = {
163                     let sender = sender.clone();
164                     move |msg| {
165                         sender
166                             .send(Task::FetchWorkspace(ProjectWorkspaceProgress::Report(msg)))
167                             .unwrap()
168                     }
169                 };
170
171                 sender.send(Task::FetchWorkspace(ProjectWorkspaceProgress::Begin)).unwrap();
172
173                 let mut workspaces = linked_projects
174                     .iter()
175                     .map(|project| match project {
176                         LinkedProject::ProjectManifest(manifest) => {
177                             project_model::ProjectWorkspace::load(
178                                 manifest.clone(),
179                                 &cargo_config,
180                                 &progress,
181                             )
182                         }
183                         LinkedProject::InlineJsonProject(it) => {
184                             project_model::ProjectWorkspace::load_inline(
185                                 it.clone(),
186                                 cargo_config.target.as_deref(),
187                             )
188                         }
189                     })
190                     .collect::<Vec<_>>();
191
192                 if !detached_files.is_empty() {
193                     workspaces
194                         .push(project_model::ProjectWorkspace::load_detached_files(detached_files));
195                 }
196
197                 log::info!("did fetch workspaces {:?}", workspaces);
198                 sender
199                     .send(Task::FetchWorkspace(ProjectWorkspaceProgress::End(workspaces)))
200                     .unwrap();
201             }
202         });
203     }
204     pub(crate) fn fetch_workspaces_completed(
205         &mut self,
206         workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
207     ) {
208         self.fetch_workspaces_queue.op_completed(workspaces)
209     }
210
211     pub(crate) fn fetch_build_data_request(&mut self) {
212         self.fetch_build_data_queue.request_op();
213     }
214     pub(crate) fn fetch_build_data_if_needed(&mut self) {
215         if !self.fetch_build_data_queue.should_start_op() {
216             return;
217         }
218
219         let workspaces = Arc::clone(&self.workspaces);
220         let config = self.config.cargo();
221         self.task_pool.handle.spawn_with_sender(move |sender| {
222             sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap();
223
224             let progress = {
225                 let sender = sender.clone();
226                 move |msg| {
227                     sender.send(Task::FetchBuildData(BuildDataProgress::Report(msg))).unwrap()
228                 }
229             };
230             let mut res = Vec::new();
231             for ws in workspaces.iter() {
232                 res.push(ws.run_build_scripts(&config, &progress));
233                 let ws = match ws {
234                     ProjectWorkspace::Cargo { cargo, .. } => cargo,
235                     ProjectWorkspace::DetachedFiles { .. } | ProjectWorkspace::Json { .. } => {
236                         res.push(Ok(WorkspaceBuildScripts::default()));
237                         continue;
238                     }
239                 };
240                 res.push(WorkspaceBuildScripts::run(&config, ws, &progress))
241             }
242             sender.send(Task::FetchBuildData(BuildDataProgress::End((workspaces, res)))).unwrap();
243         });
244     }
245     pub(crate) fn fetch_build_data_completed(
246         &mut self,
247         build_data: (Arc<Vec<ProjectWorkspace>>, Vec<anyhow::Result<WorkspaceBuildScripts>>),
248     ) {
249         self.fetch_build_data_queue.op_completed(build_data)
250     }
251
252     pub(crate) fn switch_workspaces(&mut self) {
253         let _p = profile::span("GlobalState::switch_workspaces");
254         log::info!("will switch workspaces");
255
256         if let Some(error_message) = self.fetch_workspace_error() {
257             log::error!("failed to switch workspaces: {}", error_message);
258             if !self.workspaces.is_empty() {
259                 // It only makes sense to switch to a partially broken workspace
260                 // if we don't have any workspace at all yet.
261                 return;
262             }
263         }
264
265         if let Some(error_message) = self.fetch_build_data_error() {
266             log::error!("failed to switch build data: {}", error_message);
267         }
268
269         let workspaces = self
270             .fetch_workspaces_queue
271             .last_op_result()
272             .iter()
273             .filter_map(|res| res.as_ref().ok().cloned())
274             .collect::<Vec<_>>();
275
276         fn eq_ignore_build_data<'a>(
277             left: &'a ProjectWorkspace,
278             right: &'a ProjectWorkspace,
279         ) -> bool {
280             let key = |p: &'a ProjectWorkspace| match p {
281                 ProjectWorkspace::Cargo {
282                     cargo,
283                     sysroot,
284                     rustc,
285                     rustc_cfg,
286                     cfg_overrides,
287
288                     build_scripts: _,
289                 } => Some((cargo, sysroot, rustc, rustc_cfg, cfg_overrides)),
290                 _ => None,
291             };
292             match (key(left), key(right)) {
293                 (Some(lk), Some(rk)) => lk == rk,
294                 _ => left == right,
295             }
296         }
297
298         let same_workspaces = workspaces.len() == self.workspaces.len()
299             && workspaces
300                 .iter()
301                 .zip(self.workspaces.iter())
302                 .all(|(l, r)| eq_ignore_build_data(l, r));
303
304         if same_workspaces {
305             let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result();
306             if Arc::ptr_eq(&workspaces, &self.workspaces) {
307                 let workspaces = workspaces
308                     .iter()
309                     .cloned()
310                     .zip(build_scripts)
311                     .map(|(mut ws, bs)| {
312                         ws.set_build_scripts(bs.as_ref().ok().cloned().unwrap_or_default());
313                         ws
314                     })
315                     .collect::<Vec<_>>();
316
317                 // Workspaces are the same, but we've updated build data.
318                 self.workspaces = Arc::new(workspaces);
319             } else {
320                 // Current build scripts do not match the version of the active
321                 // workspace, so there's nothing for us to update.
322                 return;
323             }
324         } else {
325             // Here, we completely changed the workspace (Cargo.toml edit), so
326             // we don't care about build-script results, they are stale.
327             self.workspaces = Arc::new(workspaces)
328         }
329
330         if let FilesWatcher::Client = self.config.files().watcher {
331             if self.config.did_change_watched_files_dynamic_registration() {
332                 let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
333                     watchers: self
334                         .workspaces
335                         .iter()
336                         .flat_map(|ws| ws.to_roots())
337                         .filter(|it| it.is_member)
338                         .flat_map(|root| {
339                             root.include.into_iter().flat_map(|it| {
340                                 [
341                                     format!("{}/**/*.rs", it.display()),
342                                     format!("{}/**/Cargo.toml", it.display()),
343                                     format!("{}/**/Cargo.lock", it.display()),
344                                 ]
345                             })
346                         })
347                         .map(|glob_pattern| lsp_types::FileSystemWatcher {
348                             glob_pattern,
349                             kind: None,
350                         })
351                         .collect(),
352                 };
353                 let registration = lsp_types::Registration {
354                     id: "workspace/didChangeWatchedFiles".to_string(),
355                     method: "workspace/didChangeWatchedFiles".to_string(),
356                     register_options: Some(serde_json::to_value(registration_options).unwrap()),
357                 };
358                 self.send_request::<lsp_types::request::RegisterCapability>(
359                     lsp_types::RegistrationParams { registrations: vec![registration] },
360                     |_, _| (),
361                 );
362             }
363         }
364
365         let mut change = Change::new();
366
367         let files_config = self.config.files();
368         let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude);
369
370         if self.proc_macro_client.is_none() {
371             self.proc_macro_client = match self.config.proc_macro_srv() {
372                 None => None,
373                 Some((path, args)) => match ProcMacroClient::extern_process(path.clone(), args) {
374                     Ok(it) => Some(it),
375                     Err(err) => {
376                         log::error!(
377                             "Failed to run proc_macro_srv from path {}, error: {:?}",
378                             path.display(),
379                             err
380                         );
381                         None
382                     }
383                 },
384             };
385         }
386
387         let watch = match files_config.watcher {
388             FilesWatcher::Client => vec![],
389             FilesWatcher::Notify => project_folders.watch,
390         };
391         self.vfs_config_version += 1;
392         self.loader.handle.set_config(vfs::loader::Config {
393             load: project_folders.load,
394             watch,
395             version: self.vfs_config_version,
396         });
397
398         // Create crate graph from all the workspaces
399         let crate_graph = {
400             let proc_macro_client = self.proc_macro_client.as_ref();
401             let mut load_proc_macro = move |path: &AbsPath| {
402                 proc_macro_client.map(|it| it.by_dylib_path(path)).unwrap_or_default()
403             };
404
405             let vfs = &mut self.vfs.write().0;
406             let loader = &mut self.loader;
407             let mem_docs = &self.mem_docs;
408             let mut load = move |path: &AbsPath| {
409                 let _p = profile::span("GlobalState::load");
410                 let vfs_path = vfs::VfsPath::from(path.to_path_buf());
411                 if !mem_docs.contains(&vfs_path) {
412                     let contents = loader.handle.load_sync(path);
413                     vfs.set_file_contents(vfs_path.clone(), contents);
414                 }
415                 let res = vfs.file_id(&vfs_path);
416                 if res.is_none() {
417                     log::warn!("failed to load {}", path.display())
418                 }
419                 res
420             };
421
422             let mut crate_graph = CrateGraph::default();
423             for ws in self.workspaces.iter() {
424                 crate_graph.extend(ws.to_crate_graph(&mut load_proc_macro, &mut load));
425             }
426             crate_graph
427         };
428         change.set_crate_graph(crate_graph);
429
430         self.source_root_config = project_folders.source_root_config;
431
432         self.analysis_host.apply_change(change);
433         self.process_changes();
434         self.reload_flycheck();
435         log::info!("did switch workspaces");
436     }
437
438     fn fetch_workspace_error(&self) -> Option<String> {
439         let mut buf = String::new();
440
441         for ws in self.fetch_workspaces_queue.last_op_result() {
442             if let Err(err) = ws {
443                 stdx::format_to!(buf, "rust-analyzer failed to load workspace: {:#}\n", err);
444             }
445         }
446
447         if buf.is_empty() {
448             return None;
449         }
450
451         Some(buf)
452     }
453
454     fn fetch_build_data_error(&self) -> Option<String> {
455         let mut buf = String::new();
456
457         for ws in &self.fetch_build_data_queue.last_op_result().1 {
458             if let Err(err) = ws {
459                 stdx::format_to!(buf, "rust-analyzer failed to run custom build: {:#}\n", err);
460             }
461         }
462
463         if buf.is_empty() {
464             return None;
465         }
466
467         Some(buf)
468     }
469
470     fn reload_flycheck(&mut self) {
471         let _p = profile::span("GlobalState::reload_flycheck");
472         let config = match self.config.flycheck() {
473             Some(it) => it,
474             None => {
475                 self.flycheck = Vec::new();
476                 return;
477             }
478         };
479
480         let sender = self.flycheck_sender.clone();
481         self.flycheck = self
482             .workspaces
483             .iter()
484             .enumerate()
485             .filter_map(|(id, w)| match w {
486                 ProjectWorkspace::Cargo { cargo, .. } => Some((id, cargo.workspace_root())),
487                 ProjectWorkspace::Json { project, .. } => {
488                     // Enable flychecks for json projects if a custom flycheck command was supplied
489                     // in the workspace configuration.
490                     match config {
491                         FlycheckConfig::CustomCommand { .. } => Some((id, project.path())),
492                         _ => None,
493                     }
494                 }
495                 ProjectWorkspace::DetachedFiles { .. } => None,
496             })
497             .map(|(id, root)| {
498                 let sender = sender.clone();
499                 FlycheckHandle::spawn(
500                     id,
501                     Box::new(move |msg| sender.send(msg).unwrap()),
502                     config.clone(),
503                     root.to_path_buf().into(),
504                 )
505             })
506             .collect();
507     }
508 }
509
510 #[derive(Default)]
511 pub(crate) struct ProjectFolders {
512     pub(crate) load: Vec<vfs::loader::Entry>,
513     pub(crate) watch: Vec<usize>,
514     pub(crate) source_root_config: SourceRootConfig,
515 }
516
517 impl ProjectFolders {
518     pub(crate) fn new(
519         workspaces: &[ProjectWorkspace],
520         global_excludes: &[AbsPathBuf],
521     ) -> ProjectFolders {
522         let mut res = ProjectFolders::default();
523         let mut fsc = FileSetConfig::builder();
524         let mut local_filesets = vec![];
525
526         for root in workspaces.iter().flat_map(|ws| ws.to_roots()) {
527             let file_set_roots: Vec<VfsPath> =
528                 root.include.iter().cloned().map(VfsPath::from).collect();
529
530             let entry = {
531                 let mut dirs = vfs::loader::Directories::default();
532                 dirs.extensions.push("rs".into());
533                 dirs.include.extend(root.include);
534                 dirs.exclude.extend(root.exclude);
535                 for excl in global_excludes {
536                     if dirs
537                         .include
538                         .iter()
539                         .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
540                     {
541                         dirs.exclude.push(excl.clone());
542                     }
543                 }
544
545                 vfs::loader::Entry::Directories(dirs)
546             };
547
548             if root.is_member {
549                 res.watch.push(res.load.len());
550             }
551             res.load.push(entry);
552
553             if root.is_member {
554                 local_filesets.push(fsc.len());
555             }
556             fsc.add_file_set(file_set_roots)
557         }
558
559         let fsc = fsc.build();
560         res.source_root_config = SourceRootConfig { fsc, local_filesets };
561
562         res
563     }
564 }
565
566 #[derive(Default, Debug)]
567 pub(crate) struct SourceRootConfig {
568     pub(crate) fsc: FileSetConfig,
569     pub(crate) local_filesets: Vec<usize>,
570 }
571
572 impl SourceRootConfig {
573     pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
574         let _p = profile::span("SourceRootConfig::partition");
575         self.fsc
576             .partition(vfs)
577             .into_iter()
578             .enumerate()
579             .map(|(idx, file_set)| {
580                 let is_local = self.local_filesets.contains(&idx);
581                 if is_local {
582                     SourceRoot::new_local(file_set)
583                 } else {
584                     SourceRoot::new_library(file_set)
585                 }
586             })
587             .collect()
588     }
589 }