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