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