]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/world.rs
51824e7a352bda91a6da97d46422f6c632e5f3eb
[rust.git] / crates / ra_lsp_server / src / world.rs
1 //! FIXME: write short doc here
2
3 use std::{
4     path::{Path, PathBuf},
5     sync::Arc,
6 };
7
8 use crossbeam_channel::{unbounded, Receiver};
9 use lsp_server::ErrorCode;
10 use lsp_types::Url;
11 use parking_lot::RwLock;
12 use ra_ide_api::{
13     Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData,
14     SourceRootId,
15 };
16 use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
17 use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
18 use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
19 use relative_path::RelativePathBuf;
20
21 use crate::{
22     main_loop::pending_requests::{CompletedRequest, LatestRequests},
23     LspError, Result,
24 };
25
26 #[derive(Debug, Clone)]
27 pub struct Options {
28     pub publish_decorations: bool,
29     pub supports_location_link: bool,
30     pub line_folding_only: bool,
31 }
32
33 /// `WorldState` is the primary mutable state of the language server
34 ///
35 /// The most interesting components are `vfs`, which stores a consistent
36 /// snapshot of the file systems, and `analysis_host`, which stores our
37 /// incremental salsa database.
38 #[derive(Debug)]
39 pub struct WorldState {
40     pub options: Options,
41     //FIXME: this belongs to `LoopState` rather than to `WorldState`
42     pub roots_to_scan: usize,
43     pub roots: Vec<PathBuf>,
44     pub workspaces: Arc<Vec<ProjectWorkspace>>,
45     pub analysis_host: AnalysisHost,
46     pub vfs: Arc<RwLock<Vfs>>,
47     pub task_receiver: Receiver<VfsTask>,
48     pub latest_requests: Arc<RwLock<LatestRequests>>,
49 }
50
51 /// An immutable snapshot of the world's state at a point in time.
52 pub struct WorldSnapshot {
53     pub options: Options,
54     pub workspaces: Arc<Vec<ProjectWorkspace>>,
55     pub analysis: Analysis,
56     pub vfs: Arc<RwLock<Vfs>>,
57     pub latest_requests: Arc<RwLock<LatestRequests>>,
58 }
59
60 impl WorldState {
61     pub fn new(
62         folder_roots: Vec<PathBuf>,
63         workspaces: Vec<ProjectWorkspace>,
64         lru_capacity: Option<usize>,
65         exclude_globs: &[Glob],
66         watch: Watch,
67         options: Options,
68         feature_flags: FeatureFlags,
69     ) -> WorldState {
70         let mut change = AnalysisChange::new();
71
72         let mut roots = Vec::new();
73         roots.extend(folder_roots.iter().map(|path| {
74             let mut filter = RustPackageFilterBuilder::default().set_member(true);
75             for glob in exclude_globs.iter() {
76                 filter = filter.exclude(glob.clone());
77             }
78             RootEntry::new(path.clone(), filter.into_vfs_filter())
79         }));
80         for ws in workspaces.iter() {
81             roots.extend(ws.to_roots().into_iter().map(|pkg_root| {
82                 let mut filter =
83                     RustPackageFilterBuilder::default().set_member(pkg_root.is_member());
84                 for glob in exclude_globs.iter() {
85                     filter = filter.exclude(glob.clone());
86                 }
87                 RootEntry::new(pkg_root.path().clone(), filter.into_vfs_filter())
88             }));
89         }
90         let (task_sender, task_receiver) = unbounded();
91         let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
92         let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
93         let roots_to_scan = vfs_roots.len();
94         for r in vfs_roots {
95             let vfs_root_path = vfs.root2path(r);
96             let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it));
97             change.add_root(SourceRootId(r.0), is_local);
98             change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string());
99         }
100
101         // FIXME: Read default cfgs from config
102         let default_cfg_options = {
103             let mut opts = get_rustc_cfg_options();
104             opts.insert_atom("test".into());
105             opts.insert_atom("debug_assertion".into());
106             opts
107         };
108
109         // Create crate graph from all the workspaces
110         let mut crate_graph = CrateGraph::default();
111         let mut load = |path: &std::path::Path| {
112             let vfs_file = vfs.load(path);
113             vfs_file.map(|f| FileId(f.0))
114         };
115         for ws in workspaces.iter() {
116             let (graph, crate_names) = ws.to_crate_graph(&default_cfg_options, &mut load);
117             let shift = crate_graph.extend(graph);
118             for (crate_id, name) in crate_names {
119                 change.set_debug_crate_name(crate_id.shift(shift), name)
120             }
121         }
122         change.set_crate_graph(crate_graph);
123
124         let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags);
125         analysis_host.apply_change(change);
126         WorldState {
127             options,
128             roots_to_scan,
129             roots: folder_roots,
130             workspaces: Arc::new(workspaces),
131             analysis_host,
132             vfs: Arc::new(RwLock::new(vfs)),
133             task_receiver,
134             latest_requests: Default::default(),
135         }
136     }
137
138     /// Returns a vec of libraries
139     /// FIXME: better API here
140     pub fn process_changes(
141         &mut self,
142     ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)> {
143         let changes = self.vfs.write().commit_changes();
144         if changes.is_empty() {
145             return Vec::new();
146         }
147         let mut libs = Vec::new();
148         let mut change = AnalysisChange::new();
149         for c in changes {
150             match c {
151                 VfsChange::AddRoot { root, files } => {
152                     let root_path = self.vfs.read().root2path(root);
153                     let is_local = self.roots.iter().any(|r| root_path.starts_with(r));
154                     if is_local {
155                         self.roots_to_scan -= 1;
156                         for (file, path, text) in files {
157                             change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
158                         }
159                     } else {
160                         let files = files
161                             .into_iter()
162                             .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text))
163                             .collect();
164                         libs.push((SourceRootId(root.0), files));
165                     }
166                 }
167                 VfsChange::AddFile { root, file, path, text } => {
168                     change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
169                 }
170                 VfsChange::RemoveFile { root, file, path } => {
171                     change.remove_file(SourceRootId(root.0), FileId(file.0), path)
172                 }
173                 VfsChange::ChangeFile { file, text } => {
174                     change.change_file(FileId(file.0), text);
175                 }
176             }
177         }
178         self.analysis_host.apply_change(change);
179         libs
180     }
181
182     pub fn add_lib(&mut self, data: LibraryData) {
183         self.roots_to_scan -= 1;
184         let mut change = AnalysisChange::new();
185         change.add_library(data);
186         self.analysis_host.apply_change(change);
187     }
188
189     pub fn snapshot(&self) -> WorldSnapshot {
190         WorldSnapshot {
191             options: self.options.clone(),
192             workspaces: Arc::clone(&self.workspaces),
193             analysis: self.analysis_host.analysis(),
194             vfs: Arc::clone(&self.vfs),
195             latest_requests: Arc::clone(&self.latest_requests),
196         }
197     }
198
199     pub fn maybe_collect_garbage(&mut self) {
200         self.analysis_host.maybe_collect_garbage()
201     }
202
203     pub fn collect_garbage(&mut self) {
204         self.analysis_host.collect_garbage()
205     }
206
207     pub fn complete_request(&mut self, request: CompletedRequest) {
208         self.latest_requests.write().record(request)
209     }
210
211     pub fn feature_flags(&self) -> &FeatureFlags {
212         self.analysis_host.feature_flags()
213     }
214 }
215
216 impl WorldSnapshot {
217     pub fn analysis(&self) -> &Analysis {
218         &self.analysis
219     }
220
221     pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> {
222         let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
223         let file = self.vfs.read().path2file(&path).ok_or_else(|| {
224             // Show warning as this file is outside current workspace
225             LspError {
226                 code: ErrorCode::InvalidRequest as i32,
227                 message: "Rust file outside current workspace is not supported yet.".to_string(),
228             }
229         })?;
230         Ok(FileId(file.0))
231     }
232
233     pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
234         let path = self.vfs.read().file2path(VfsFile(id.0));
235         let url = Url::from_file_path(&path)
236             .map_err(|_| format!("can't convert path to url: {}", path.display()))?;
237         Ok(url)
238     }
239
240     pub fn file_line_endings(&self, id: FileId) -> LineEndings {
241         self.vfs.read().file_line_endings(VfsFile(id.0))
242     }
243
244     pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> {
245         let base = self.vfs.read().root2path(VfsRoot(root.0));
246         let path = path.to_path(base);
247         let url = Url::from_file_path(&path)
248             .map_err(|_| format!("can't convert path to url: {}", path.display()))?;
249         Ok(url)
250     }
251
252     pub fn status(&self) -> String {
253         let mut res = String::new();
254         if self.workspaces.is_empty() {
255             res.push_str("no workspaces\n")
256         } else {
257             res.push_str("workspaces:\n");
258             for w in self.workspaces.iter() {
259                 res += &format!("{} packages loaded\n", w.n_packages());
260             }
261         }
262         res.push_str("\nanalysis:\n");
263         res.push_str(
264             &self
265                 .analysis
266                 .status()
267                 .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
268         );
269         res
270     }
271
272     pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
273         let path = self.vfs.read().file2path(VfsFile(file_id.0));
274         self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
275     }
276
277     pub fn feature_flags(&self) -> &FeatureFlags {
278         self.analysis.feature_flags()
279     }
280 }