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