]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/global_state.rs
Merge #4927
[rust.git] / crates / rust-analyzer / src / global_state.rs
1 //! The context or environment in which the language server functions. In our
2 //! server implementation this is know as the `WorldState`.
3 //!
4 //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
5
6 use std::{
7     path::{Path, PathBuf},
8     sync::Arc,
9 };
10
11 use crossbeam_channel::{unbounded, Receiver};
12 use lsp_types::Url;
13 use parking_lot::RwLock;
14 use ra_flycheck::{Flycheck, FlycheckConfig};
15 use ra_ide::{
16     Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
17 };
18 use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
19 use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsTask, Watch};
20 use relative_path::RelativePathBuf;
21 use stdx::format_to;
22
23 use crate::{
24     config::Config,
25     diagnostics::{CheckFixes, DiagnosticCollection},
26     main_loop::pending_requests::{CompletedRequest, LatestRequests},
27     to_proto::url_from_abs_path,
28     vfs_glob::{Glob, RustPackageFilterBuilder},
29     LspError, Result,
30 };
31 use ra_db::{CrateId, ExternSourceId};
32 use rustc_hash::{FxHashMap, FxHashSet};
33
34 fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
35     // FIXME: Figure out the multi-workspace situation
36     workspaces
37         .iter()
38         .find_map(|w| match w {
39             ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
40             ProjectWorkspace::Json { .. } => None,
41         })
42         .map(|cargo| {
43             let cargo_project_root = cargo.workspace_root().to_path_buf();
44             Some(Flycheck::new(config.clone(), cargo_project_root))
45         })
46         .unwrap_or_else(|| {
47             log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
48             None
49         })
50 }
51
52 /// `GlobalState` is the primary mutable state of the language server
53 ///
54 /// The most interesting components are `vfs`, which stores a consistent
55 /// snapshot of the file systems, and `analysis_host`, which stores our
56 /// incremental salsa database.
57 #[derive(Debug)]
58 pub struct GlobalState {
59     pub config: Config,
60     pub local_roots: Vec<PathBuf>,
61     pub workspaces: Arc<Vec<ProjectWorkspace>>,
62     pub analysis_host: AnalysisHost,
63     pub vfs: Arc<RwLock<Vfs>>,
64     pub task_receiver: Receiver<VfsTask>,
65     pub latest_requests: Arc<RwLock<LatestRequests>>,
66     pub flycheck: Option<Flycheck>,
67     pub diagnostics: DiagnosticCollection,
68     pub proc_macro_client: ProcMacroClient,
69 }
70
71 /// An immutable snapshot of the world's state at a point in time.
72 pub struct GlobalStateSnapshot {
73     pub config: Config,
74     pub workspaces: Arc<Vec<ProjectWorkspace>>,
75     pub analysis: Analysis,
76     pub latest_requests: Arc<RwLock<LatestRequests>>,
77     pub check_fixes: CheckFixes,
78     vfs: Arc<RwLock<Vfs>>,
79 }
80
81 impl GlobalState {
82     pub fn new(
83         workspaces: Vec<ProjectWorkspace>,
84         lru_capacity: Option<usize>,
85         exclude_globs: &[Glob],
86         watch: Watch,
87         config: Config,
88     ) -> GlobalState {
89         let mut change = AnalysisChange::new();
90
91         let mut extern_dirs: FxHashSet<PathBuf> = FxHashSet::default();
92
93         let mut local_roots = Vec::new();
94         let roots: Vec<_> = {
95             let create_filter = |is_member| {
96                 RustPackageFilterBuilder::default()
97                     .set_member(is_member)
98                     .exclude(exclude_globs.iter().cloned())
99                     .into_vfs_filter()
100             };
101             let mut roots = Vec::new();
102             for root in workspaces.iter().flat_map(ProjectWorkspace::to_roots) {
103                 let path = root.path().to_owned();
104                 if root.is_member() {
105                     local_roots.push(path.clone());
106                 }
107                 roots.push(RootEntry::new(path, create_filter(root.is_member())));
108                 if let Some(out_dir) = root.out_dir() {
109                     extern_dirs.insert(out_dir.to_path_buf());
110                     roots.push(RootEntry::new(
111                         out_dir.to_path_buf(),
112                         create_filter(root.is_member()),
113                     ))
114                 }
115             }
116             roots
117         };
118
119         let (task_sender, task_receiver) = unbounded();
120         let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
121         let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
122
123         let mut extern_source_roots = FxHashMap::default();
124         for r in vfs_roots {
125             let vfs_root_path = vfs.root2path(r);
126             let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it));
127             change.add_root(SourceRootId(r.0), is_local);
128
129             // FIXME: add path2root in vfs to simpily this logic
130             if extern_dirs.contains(&vfs_root_path) {
131                 extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0));
132             }
133         }
134
135         let proc_macro_client = match &config.proc_macro_srv {
136             None => ProcMacroClient::dummy(),
137             Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
138                 Ok(it) => it,
139                 Err(err) => {
140                     log::error!(
141                         "Failed to run ra_proc_macro_srv from path {}, error: {:?}",
142                         path.display(),
143                         err
144                     );
145                     ProcMacroClient::dummy()
146                 }
147             },
148         };
149
150         // Create crate graph from all the workspaces
151         let mut crate_graph = CrateGraph::default();
152         let mut load = |path: &Path| {
153             // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs
154             let path = path.canonicalize().ok()?;
155             let vfs_file = vfs.load(&path);
156             vfs_file.map(|f| FileId(f.0))
157         };
158         for ws in workspaces.iter() {
159             crate_graph.extend(ws.to_crate_graph(
160                 config.cargo.target.as_deref(),
161                 &extern_source_roots,
162                 &proc_macro_client,
163                 &mut load,
164             ));
165         }
166         change.set_crate_graph(crate_graph);
167
168         let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
169
170         let mut analysis_host = AnalysisHost::new(lru_capacity);
171         analysis_host.apply_change(change);
172         GlobalState {
173             config,
174             local_roots,
175             workspaces: Arc::new(workspaces),
176             analysis_host,
177             vfs: Arc::new(RwLock::new(vfs)),
178             task_receiver,
179             latest_requests: Default::default(),
180             flycheck,
181             diagnostics: Default::default(),
182             proc_macro_client,
183         }
184     }
185
186     pub fn update_configuration(&mut self, config: Config) {
187         self.analysis_host.update_lru_capacity(config.lru_capacity);
188         if config.check != self.config.check {
189             self.flycheck =
190                 config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
191         }
192
193         self.config = config;
194     }
195
196     /// Returns a vec of libraries
197     /// FIXME: better API here
198     pub fn process_changes(
199         &mut self,
200         roots_scanned: &mut usize,
201     ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
202         let changes = self.vfs.write().commit_changes();
203         if changes.is_empty() {
204             return None;
205         }
206         let mut libs = Vec::new();
207         let mut change = AnalysisChange::new();
208         for c in changes {
209             match c {
210                 VfsChange::AddRoot { root, files } => {
211                     let root_path = self.vfs.read().root2path(root);
212                     let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r));
213                     if is_local {
214                         *roots_scanned += 1;
215                         for (file, path, text) in files {
216                             change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
217                         }
218                     } else {
219                         let files = files
220                             .into_iter()
221                             .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text))
222                             .collect();
223                         libs.push((SourceRootId(root.0), files));
224                     }
225                 }
226                 VfsChange::AddFile { root, file, path, text } => {
227                     change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
228                 }
229                 VfsChange::RemoveFile { root, file, path } => {
230                     change.remove_file(SourceRootId(root.0), FileId(file.0), path)
231                 }
232                 VfsChange::ChangeFile { file, text } => {
233                     change.change_file(FileId(file.0), text);
234                 }
235             }
236         }
237         self.analysis_host.apply_change(change);
238         Some(libs)
239     }
240
241     pub fn add_lib(&mut self, data: LibraryData) {
242         let mut change = AnalysisChange::new();
243         change.add_library(data);
244         self.analysis_host.apply_change(change);
245     }
246
247     pub fn snapshot(&self) -> GlobalStateSnapshot {
248         GlobalStateSnapshot {
249             config: self.config.clone(),
250             workspaces: Arc::clone(&self.workspaces),
251             analysis: self.analysis_host.analysis(),
252             vfs: Arc::clone(&self.vfs),
253             latest_requests: Arc::clone(&self.latest_requests),
254             check_fixes: Arc::clone(&self.diagnostics.check_fixes),
255         }
256     }
257
258     pub fn maybe_collect_garbage(&mut self) {
259         self.analysis_host.maybe_collect_garbage()
260     }
261
262     pub fn collect_garbage(&mut self) {
263         self.analysis_host.collect_garbage()
264     }
265
266     pub fn complete_request(&mut self, request: CompletedRequest) {
267         self.latest_requests.write().record(request)
268     }
269 }
270
271 impl GlobalStateSnapshot {
272     pub fn analysis(&self) -> &Analysis {
273         &self.analysis
274     }
275
276     pub fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
277         let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
278         let file = self.vfs.read().path2file(&path).ok_or_else(|| {
279             // Show warning as this file is outside current workspace
280             // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`.
281             LspError {
282                 code: LspError::UNKNOWN_FILE,
283                 message: "Rust file outside current workspace is not supported yet.".to_string(),
284             }
285         })?;
286         Ok(FileId(file.0))
287     }
288
289     pub fn file_id_to_url(&self, id: FileId) -> Url {
290         file_id_to_url(&self.vfs.read(), id)
291     }
292
293     pub fn file_line_endings(&self, id: FileId) -> LineEndings {
294         self.vfs.read().file_line_endings(VfsFile(id.0))
295     }
296
297     pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
298         let mut base = self.vfs.read().file2path(VfsFile(file_id.0));
299         base.pop();
300         let path = base.join(path);
301         url_from_abs_path(&path)
302     }
303
304     pub(crate) fn cargo_target_for_crate_root(
305         &self,
306         crate_id: CrateId,
307     ) -> Option<(&CargoWorkspace, Target)> {
308         let file_id = self.analysis().crate_root(crate_id).ok()?;
309         let path = self.vfs.read().file2path(VfsFile(file_id.0));
310         self.workspaces.iter().find_map(|ws| match ws {
311             ProjectWorkspace::Cargo { cargo, .. } => {
312                 cargo.target_by_root(&path).map(|it| (cargo, it))
313             }
314             ProjectWorkspace::Json { .. } => None,
315         })
316     }
317
318     pub fn status(&self) -> String {
319         let mut buf = String::new();
320         if self.workspaces.is_empty() {
321             buf.push_str("no workspaces\n")
322         } else {
323             buf.push_str("workspaces:\n");
324             for w in self.workspaces.iter() {
325                 format_to!(buf, "{} packages loaded\n", w.n_packages());
326             }
327         }
328         buf.push_str("\nanalysis:\n");
329         buf.push_str(
330             &self
331                 .analysis
332                 .status()
333                 .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
334         );
335         buf
336     }
337
338     pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
339         let path = self.vfs.read().file2path(VfsFile(file_id.0));
340         self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
341     }
342 }
343
344 pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url {
345     let path = vfs.file2path(VfsFile(id.0));
346     url_from_abs_path(&path)
347 }