diff options
author | Aleksey Kladov <[email protected]> | 2020-06-25 22:44:58 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-06-25 23:27:57 +0100 |
commit | 72fb712dff63a518e5b607030266e30ea7542756 (patch) | |
tree | 151e9acae2c8c3e409adbdc987edf04869a3c7ea /crates | |
parent | 3d0f78213879be78064d70a54411e40a6392a224 (diff) |
Add new module for project loading stuff
Diffstat (limited to 'crates')
-rw-r--r-- | crates/rust-analyzer/src/cli/load_cargo.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 10 | ||||
-rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 244 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/reload.rs | 241 |
5 files changed, 260 insertions, 238 deletions
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index c5cf5ff27..b1250f2fe 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs | |||
@@ -9,7 +9,7 @@ use ra_ide::{AnalysisChange, AnalysisHost}; | |||
9 | use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; | 9 | use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; |
10 | use vfs::{loader::Handle, AbsPath}; | 10 | use vfs::{loader::Handle, AbsPath}; |
11 | 11 | ||
12 | use crate::global_state::{ProjectFolders, SourceRootConfig}; | 12 | use crate::reload::{ProjectFolders, SourceRootConfig}; |
13 | 13 | ||
14 | pub fn load_cargo( | 14 | pub fn load_cargo( |
15 | root: &Path, | 15 | root: &Path, |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 435bbbb6b..6b17ce18b 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -30,7 +30,7 @@ pub struct Config { | |||
30 | 30 | ||
31 | pub cargo: CargoConfig, | 31 | pub cargo: CargoConfig, |
32 | pub rustfmt: RustfmtConfig, | 32 | pub rustfmt: RustfmtConfig, |
33 | pub check: Option<FlycheckConfig>, | 33 | pub flycheck: Option<FlycheckConfig>, |
34 | 34 | ||
35 | pub inlay_hints: InlayHintsConfig, | 35 | pub inlay_hints: InlayHintsConfig, |
36 | pub completion: CompletionConfig, | 36 | pub completion: CompletionConfig, |
@@ -147,7 +147,7 @@ impl Config { | |||
147 | 147 | ||
148 | cargo: CargoConfig::default(), | 148 | cargo: CargoConfig::default(), |
149 | rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, | 149 | rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, |
150 | check: Some(FlycheckConfig::CargoCommand { | 150 | flycheck: Some(FlycheckConfig::CargoCommand { |
151 | command: "check".to_string(), | 151 | command: "check".to_string(), |
152 | all_targets: true, | 152 | all_targets: true, |
153 | all_features: false, | 153 | all_features: false, |
@@ -227,14 +227,14 @@ impl Config { | |||
227 | 227 | ||
228 | if let Some(false) = get(value, "/checkOnSave/enable") { | 228 | if let Some(false) = get(value, "/checkOnSave/enable") { |
229 | // check is disabled | 229 | // check is disabled |
230 | self.check = None; | 230 | self.flycheck = None; |
231 | } else { | 231 | } else { |
232 | // check is enabled | 232 | // check is enabled |
233 | match get::<Vec<String>>(value, "/checkOnSave/overrideCommand") { | 233 | match get::<Vec<String>>(value, "/checkOnSave/overrideCommand") { |
234 | // first see if the user has completely overridden the command | 234 | // first see if the user has completely overridden the command |
235 | Some(mut args) if !args.is_empty() => { | 235 | Some(mut args) if !args.is_empty() => { |
236 | let command = args.remove(0); | 236 | let command = args.remove(0); |
237 | self.check = Some(FlycheckConfig::CustomCommand { | 237 | self.flycheck = Some(FlycheckConfig::CustomCommand { |
238 | command, | 238 | command, |
239 | args, | 239 | args, |
240 | }); | 240 | }); |
@@ -242,7 +242,7 @@ impl Config { | |||
242 | // otherwise configure command customizations | 242 | // otherwise configure command customizations |
243 | _ => { | 243 | _ => { |
244 | if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features, features }) | 244 | if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features, features }) |
245 | = &mut self.check | 245 | = &mut self.flycheck |
246 | { | 246 | { |
247 | set(value, "/checkOnSave/extraArgs", extra_args); | 247 | set(value, "/checkOnSave/extraArgs", extra_args); |
248 | set(value, "/checkOnSave/command", command); | 248 | set(value, "/checkOnSave/command", command); |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index ca4a248f1..8486da627 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs | |||
@@ -3,24 +3,25 @@ | |||
3 | //! | 3 | //! |
4 | //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`. | 4 | //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`. |
5 | 5 | ||
6 | use std::{convert::TryFrom, sync::Arc}; | 6 | use std::sync::Arc; |
7 | 7 | ||
8 | use crossbeam_channel::{unbounded, Receiver, Sender}; | 8 | use crossbeam_channel::{unbounded, Receiver, Sender}; |
9 | use flycheck::{FlycheckConfig, FlycheckHandle}; | 9 | use flycheck::FlycheckHandle; |
10 | use lsp_types::{request::Request, Url}; | 10 | use lsp_types::Url; |
11 | use parking_lot::RwLock; | 11 | use parking_lot::RwLock; |
12 | use ra_db::{CrateId, SourceRoot, VfsPath}; | 12 | use ra_db::{CrateId, VfsPath}; |
13 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId}; | 13 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; |
14 | use ra_project_model::{CargoWorkspace, PackageRoot, ProcMacroClient, ProjectWorkspace, Target}; | 14 | use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; |
15 | use stdx::format_to; | 15 | use stdx::format_to; |
16 | use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf}; | 16 | use vfs::loader::Handle; |
17 | 17 | ||
18 | use crate::{ | 18 | use crate::{ |
19 | config::{Config, FilesWatcher, LinkedProject}, | 19 | config::Config, |
20 | diagnostics::{CheckFixes, DiagnosticCollection}, | 20 | diagnostics::{CheckFixes, DiagnosticCollection}, |
21 | from_proto, | 21 | from_proto, |
22 | line_endings::LineEndings, | 22 | line_endings::LineEndings, |
23 | main_loop::{ReqQueue, Task}, | 23 | main_loop::{ReqQueue, Task}, |
24 | reload::SourceRootConfig, | ||
24 | request_metrics::{LatestRequests, RequestMetrics}, | 25 | request_metrics::{LatestRequests, RequestMetrics}, |
25 | show_message, | 26 | show_message, |
26 | thread_pool::TaskPool, | 27 | thread_pool::TaskPool, |
@@ -29,26 +30,6 @@ use crate::{ | |||
29 | }; | 30 | }; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 31 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | 32 | ||
32 | fn create_flycheck( | ||
33 | workspaces: &[ProjectWorkspace], | ||
34 | config: &FlycheckConfig, | ||
35 | ) -> Option<(FlycheckHandle, Receiver<flycheck::Message>)> { | ||
36 | // FIXME: Figure out the multi-workspace situation | ||
37 | workspaces.iter().find_map(move |w| match w { | ||
38 | ProjectWorkspace::Cargo { cargo, .. } => { | ||
39 | let (sender, receiver) = unbounded(); | ||
40 | let sender = Box::new(move |msg| sender.send(msg).unwrap()); | ||
41 | let cargo_project_root = cargo.workspace_root().to_path_buf(); | ||
42 | let flycheck = FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into()); | ||
43 | Some((flycheck, receiver)) | ||
44 | } | ||
45 | ProjectWorkspace::Json { .. } => { | ||
46 | log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); | ||
47 | None | ||
48 | } | ||
49 | }) | ||
50 | } | ||
51 | |||
52 | #[derive(Eq, PartialEq)] | 33 | #[derive(Eq, PartialEq)] |
53 | pub(crate) enum Status { | 34 | pub(crate) enum Status { |
54 | Loading, | 35 | Loading, |
@@ -79,10 +60,10 @@ pub(crate) struct GlobalState { | |||
79 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | 60 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, |
80 | pub(crate) status: Status, | 61 | pub(crate) status: Status, |
81 | pub(crate) req_queue: ReqQueue, | 62 | pub(crate) req_queue: ReqQueue, |
63 | pub(crate) source_root_config: SourceRootConfig, | ||
64 | pub(crate) proc_macro_client: ProcMacroClient, | ||
65 | pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, | ||
82 | latest_requests: Arc<RwLock<LatestRequests>>, | 66 | latest_requests: Arc<RwLock<LatestRequests>>, |
83 | source_root_config: SourceRootConfig, | ||
84 | proc_macro_client: ProcMacroClient, | ||
85 | workspaces: Arc<Vec<ProjectWorkspace>>, | ||
86 | } | 67 | } |
87 | 68 | ||
88 | /// An immutable snapshot of the world's state at a point in time. | 69 | /// An immutable snapshot of the world's state at a point in time. |
@@ -135,132 +116,6 @@ impl GlobalState { | |||
135 | } | 116 | } |
136 | } | 117 | } |
137 | 118 | ||
138 | pub(crate) fn reload(&mut self) { | ||
139 | let workspaces = { | ||
140 | if self.config.linked_projects.is_empty() | ||
141 | && self.config.notifications.cargo_toml_not_found | ||
142 | { | ||
143 | self.show_message( | ||
144 | lsp_types::MessageType::Error, | ||
145 | "rust-analyzer failed to discover workspace".to_string(), | ||
146 | ); | ||
147 | }; | ||
148 | |||
149 | self.config | ||
150 | .linked_projects | ||
151 | .iter() | ||
152 | .filter_map(|project| match project { | ||
153 | LinkedProject::ProjectManifest(manifest) => { | ||
154 | ra_project_model::ProjectWorkspace::load( | ||
155 | manifest.clone(), | ||
156 | &self.config.cargo, | ||
157 | self.config.with_sysroot, | ||
158 | ) | ||
159 | .map_err(|err| { | ||
160 | log::error!("failed to load workspace: {:#}", err); | ||
161 | self.show_message( | ||
162 | lsp_types::MessageType::Error, | ||
163 | format!("rust-analyzer failed to load workspace: {:#}", err), | ||
164 | ); | ||
165 | }) | ||
166 | .ok() | ||
167 | } | ||
168 | LinkedProject::InlineJsonProject(it) => { | ||
169 | Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) | ||
170 | } | ||
171 | }) | ||
172 | .collect::<Vec<_>>() | ||
173 | }; | ||
174 | |||
175 | if let FilesWatcher::Client = self.config.files.watcher { | ||
176 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { | ||
177 | watchers: workspaces | ||
178 | .iter() | ||
179 | .flat_map(ProjectWorkspace::to_roots) | ||
180 | .filter(PackageRoot::is_member) | ||
181 | .map(|root| format!("{}/**/*.rs", root.path().display())) | ||
182 | .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None }) | ||
183 | .collect(), | ||
184 | }; | ||
185 | let registration = lsp_types::Registration { | ||
186 | id: "file-watcher".to_string(), | ||
187 | method: "workspace/didChangeWatchedFiles".to_string(), | ||
188 | register_options: Some(serde_json::to_value(registration_options).unwrap()), | ||
189 | }; | ||
190 | let params = lsp_types::RegistrationParams { registrations: vec![registration] }; | ||
191 | let request = self.req_queue.outgoing.register( | ||
192 | lsp_types::request::RegisterCapability::METHOD.to_string(), | ||
193 | params, | ||
194 | |_, _| (), | ||
195 | ); | ||
196 | self.send(request.into()); | ||
197 | } | ||
198 | |||
199 | let mut change = AnalysisChange::new(); | ||
200 | |||
201 | let project_folders = ProjectFolders::new(&workspaces); | ||
202 | |||
203 | self.proc_macro_client = match &self.config.proc_macro_srv { | ||
204 | None => ProcMacroClient::dummy(), | ||
205 | Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) { | ||
206 | Ok(it) => it, | ||
207 | Err(err) => { | ||
208 | log::error!( | ||
209 | "Failed to run ra_proc_macro_srv from path {}, error: {:?}", | ||
210 | path.display(), | ||
211 | err | ||
212 | ); | ||
213 | ProcMacroClient::dummy() | ||
214 | } | ||
215 | }, | ||
216 | }; | ||
217 | let watch = match self.config.files.watcher { | ||
218 | FilesWatcher::Client => vec![], | ||
219 | FilesWatcher::Notify => project_folders.watch, | ||
220 | }; | ||
221 | self.loader.set_config(vfs::loader::Config { load: project_folders.load, watch }); | ||
222 | |||
223 | // Create crate graph from all the workspaces | ||
224 | let crate_graph = { | ||
225 | let mut crate_graph = CrateGraph::default(); | ||
226 | let vfs = &mut self.vfs.write().0; | ||
227 | let loader = &mut self.loader; | ||
228 | let mut load = |path: &AbsPath| { | ||
229 | let contents = loader.load_sync(path); | ||
230 | let path = vfs::VfsPath::from(path.to_path_buf()); | ||
231 | vfs.set_file_contents(path.clone(), contents); | ||
232 | vfs.file_id(&path) | ||
233 | }; | ||
234 | for ws in workspaces.iter() { | ||
235 | crate_graph.extend(ws.to_crate_graph( | ||
236 | self.config.cargo.target.as_deref(), | ||
237 | &self.proc_macro_client, | ||
238 | &mut load, | ||
239 | )); | ||
240 | } | ||
241 | |||
242 | crate_graph | ||
243 | }; | ||
244 | change.set_crate_graph(crate_graph); | ||
245 | |||
246 | self.flycheck = self.config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); | ||
247 | self.source_root_config = project_folders.source_root_config; | ||
248 | self.workspaces = Arc::new(workspaces); | ||
249 | |||
250 | self.analysis_host.apply_change(change); | ||
251 | self.process_changes(); | ||
252 | } | ||
253 | |||
254 | pub(crate) fn update_configuration(&mut self, config: Config) { | ||
255 | self.analysis_host.update_lru_capacity(config.lru_capacity); | ||
256 | if config.check != self.config.check { | ||
257 | self.flycheck = | ||
258 | config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); | ||
259 | } | ||
260 | |||
261 | self.config = config; | ||
262 | } | ||
263 | |||
264 | pub(crate) fn process_changes(&mut self) -> bool { | 119 | pub(crate) fn process_changes(&mut self) -> bool { |
265 | let change = { | 120 | let change = { |
266 | let mut change = AnalysisChange::new(); | 121 | let mut change = AnalysisChange::new(); |
@@ -408,78 +263,3 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url { | |||
408 | let path = path.as_path().unwrap(); | 263 | let path = path.as_path().unwrap(); |
409 | url_from_abs_path(&path) | 264 | url_from_abs_path(&path) |
410 | } | 265 | } |
411 | |||
412 | #[derive(Default)] | ||
413 | pub(crate) struct ProjectFolders { | ||
414 | pub(crate) load: Vec<vfs::loader::Entry>, | ||
415 | pub(crate) watch: Vec<usize>, | ||
416 | pub(crate) source_root_config: SourceRootConfig, | ||
417 | } | ||
418 | |||
419 | impl ProjectFolders { | ||
420 | pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders { | ||
421 | let mut res = ProjectFolders::default(); | ||
422 | let mut fsc = FileSetConfig::builder(); | ||
423 | let mut local_filesets = vec![]; | ||
424 | |||
425 | for root in workspaces.iter().flat_map(|it| it.to_roots()) { | ||
426 | let path = root.path().to_owned(); | ||
427 | |||
428 | let mut file_set_roots: Vec<VfsPath> = vec![]; | ||
429 | |||
430 | let entry = if root.is_member() { | ||
431 | vfs::loader::Entry::local_cargo_package(path.to_path_buf()) | ||
432 | } else { | ||
433 | vfs::loader::Entry::cargo_package_dependency(path.to_path_buf()) | ||
434 | }; | ||
435 | res.load.push(entry); | ||
436 | if root.is_member() { | ||
437 | res.watch.push(res.load.len() - 1); | ||
438 | } | ||
439 | |||
440 | if let Some(out_dir) = root.out_dir() { | ||
441 | let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap(); | ||
442 | res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone())); | ||
443 | if root.is_member() { | ||
444 | res.watch.push(res.load.len() - 1); | ||
445 | } | ||
446 | file_set_roots.push(out_dir.into()); | ||
447 | } | ||
448 | file_set_roots.push(path.to_path_buf().into()); | ||
449 | |||
450 | if root.is_member() { | ||
451 | local_filesets.push(fsc.len()); | ||
452 | } | ||
453 | fsc.add_file_set(file_set_roots) | ||
454 | } | ||
455 | |||
456 | let fsc = fsc.build(); | ||
457 | res.source_root_config = SourceRootConfig { fsc, local_filesets }; | ||
458 | |||
459 | res | ||
460 | } | ||
461 | } | ||
462 | |||
463 | #[derive(Default, Debug)] | ||
464 | pub(crate) struct SourceRootConfig { | ||
465 | pub(crate) fsc: FileSetConfig, | ||
466 | pub(crate) local_filesets: Vec<usize>, | ||
467 | } | ||
468 | |||
469 | impl SourceRootConfig { | ||
470 | pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> { | ||
471 | self.fsc | ||
472 | .partition(vfs) | ||
473 | .into_iter() | ||
474 | .enumerate() | ||
475 | .map(|(idx, file_set)| { | ||
476 | let is_local = self.local_filesets.contains(&idx); | ||
477 | if is_local { | ||
478 | SourceRoot::new_local(file_set) | ||
479 | } else { | ||
480 | SourceRoot::new_library(file_set) | ||
481 | } | ||
482 | }) | ||
483 | .collect() | ||
484 | } | ||
485 | } | ||
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index d503fe96e..a24dfe58c 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs | |||
@@ -18,6 +18,7 @@ macro_rules! eprintln { | |||
18 | } | 18 | } |
19 | 19 | ||
20 | mod global_state; | 20 | mod global_state; |
21 | mod reload; | ||
21 | mod main_loop; | 22 | mod main_loop; |
22 | mod dispatch; | 23 | mod dispatch; |
23 | mod handlers; | 24 | mod handlers; |
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs new file mode 100644 index 000000000..1981a97da --- /dev/null +++ b/crates/rust-analyzer/src/reload.rs | |||
@@ -0,0 +1,241 @@ | |||
1 | //! Project loading & configuration updates | ||
2 | use std::sync::Arc; | ||
3 | |||
4 | use crossbeam_channel::unbounded; | ||
5 | use flycheck::FlycheckHandle; | ||
6 | use lsp_types::request::Request; | ||
7 | use ra_db::{CrateGraph, SourceRoot, VfsPath}; | ||
8 | use ra_ide::AnalysisChange; | ||
9 | use ra_project_model::{PackageRoot, ProcMacroClient, ProjectWorkspace}; | ||
10 | use vfs::{file_set::FileSetConfig, AbsPath}; | ||
11 | |||
12 | use crate::{ | ||
13 | config::{Config, FilesWatcher, LinkedProject}, | ||
14 | global_state::GlobalState, | ||
15 | }; | ||
16 | |||
17 | impl GlobalState { | ||
18 | pub(crate) fn update_configuration(&mut self, new_config: Config) { | ||
19 | self.analysis_host.update_lru_capacity(new_config.lru_capacity); | ||
20 | if new_config.flycheck != self.config.flycheck { | ||
21 | self.reload_flycheck(); | ||
22 | } | ||
23 | self.config = new_config; | ||
24 | } | ||
25 | pub(crate) fn reload(&mut self) { | ||
26 | let workspaces = { | ||
27 | if self.config.linked_projects.is_empty() | ||
28 | && self.config.notifications.cargo_toml_not_found | ||
29 | { | ||
30 | self.show_message( | ||
31 | lsp_types::MessageType::Error, | ||
32 | "rust-analyzer failed to discover workspace".to_string(), | ||
33 | ); | ||
34 | }; | ||
35 | |||
36 | self.config | ||
37 | .linked_projects | ||
38 | .iter() | ||
39 | .filter_map(|project| match project { | ||
40 | LinkedProject::ProjectManifest(manifest) => { | ||
41 | ra_project_model::ProjectWorkspace::load( | ||
42 | manifest.clone(), | ||
43 | &self.config.cargo, | ||
44 | self.config.with_sysroot, | ||
45 | ) | ||
46 | .map_err(|err| { | ||
47 | log::error!("failed to load workspace: {:#}", err); | ||
48 | self.show_message( | ||
49 | lsp_types::MessageType::Error, | ||
50 | format!("rust-analyzer failed to load workspace: {:#}", err), | ||
51 | ); | ||
52 | }) | ||
53 | .ok() | ||
54 | } | ||
55 | LinkedProject::InlineJsonProject(it) => { | ||
56 | Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) | ||
57 | } | ||
58 | }) | ||
59 | .collect::<Vec<_>>() | ||
60 | }; | ||
61 | |||
62 | if let FilesWatcher::Client = self.config.files.watcher { | ||
63 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { | ||
64 | watchers: workspaces | ||
65 | .iter() | ||
66 | .flat_map(ProjectWorkspace::to_roots) | ||
67 | .filter(PackageRoot::is_member) | ||
68 | .map(|root| format!("{}/**/*.rs", root.path().display())) | ||
69 | .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None }) | ||
70 | .collect(), | ||
71 | }; | ||
72 | let registration = lsp_types::Registration { | ||
73 | id: "file-watcher".to_string(), | ||
74 | method: "workspace/didChangeWatchedFiles".to_string(), | ||
75 | register_options: Some(serde_json::to_value(registration_options).unwrap()), | ||
76 | }; | ||
77 | let params = lsp_types::RegistrationParams { registrations: vec![registration] }; | ||
78 | let request = self.req_queue.outgoing.register( | ||
79 | lsp_types::request::RegisterCapability::METHOD.to_string(), | ||
80 | params, | ||
81 | |_, _| (), | ||
82 | ); | ||
83 | self.send(request.into()); | ||
84 | } | ||
85 | |||
86 | let mut change = AnalysisChange::new(); | ||
87 | |||
88 | let project_folders = ProjectFolders::new(&workspaces); | ||
89 | |||
90 | self.proc_macro_client = match &self.config.proc_macro_srv { | ||
91 | None => ProcMacroClient::dummy(), | ||
92 | Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) { | ||
93 | Ok(it) => it, | ||
94 | Err(err) => { | ||
95 | log::error!( | ||
96 | "Failed to run ra_proc_macro_srv from path {}, error: {:?}", | ||
97 | path.display(), | ||
98 | err | ||
99 | ); | ||
100 | ProcMacroClient::dummy() | ||
101 | } | ||
102 | }, | ||
103 | }; | ||
104 | let watch = match self.config.files.watcher { | ||
105 | FilesWatcher::Client => vec![], | ||
106 | FilesWatcher::Notify => project_folders.watch, | ||
107 | }; | ||
108 | self.loader.set_config(vfs::loader::Config { load: project_folders.load, watch }); | ||
109 | |||
110 | // Create crate graph from all the workspaces | ||
111 | let crate_graph = { | ||
112 | let mut crate_graph = CrateGraph::default(); | ||
113 | let vfs = &mut self.vfs.write().0; | ||
114 | let loader = &mut self.loader; | ||
115 | let mut load = |path: &AbsPath| { | ||
116 | let contents = loader.load_sync(path); | ||
117 | let path = vfs::VfsPath::from(path.to_path_buf()); | ||
118 | vfs.set_file_contents(path.clone(), contents); | ||
119 | vfs.file_id(&path) | ||
120 | }; | ||
121 | for ws in workspaces.iter() { | ||
122 | crate_graph.extend(ws.to_crate_graph( | ||
123 | self.config.cargo.target.as_deref(), | ||
124 | &self.proc_macro_client, | ||
125 | &mut load, | ||
126 | )); | ||
127 | } | ||
128 | |||
129 | crate_graph | ||
130 | }; | ||
131 | change.set_crate_graph(crate_graph); | ||
132 | |||
133 | self.source_root_config = project_folders.source_root_config; | ||
134 | self.workspaces = Arc::new(workspaces); | ||
135 | |||
136 | self.analysis_host.apply_change(change); | ||
137 | self.process_changes(); | ||
138 | self.reload_flycheck(); | ||
139 | } | ||
140 | |||
141 | fn reload_flycheck(&mut self) { | ||
142 | let config = match self.config.flycheck.clone() { | ||
143 | Some(it) => it, | ||
144 | None => { | ||
145 | self.flycheck = None; | ||
146 | return; | ||
147 | } | ||
148 | }; | ||
149 | |||
150 | // FIXME: Figure out the multi-workspace situation | ||
151 | self.flycheck = self.workspaces.iter().find_map(move |w| match w { | ||
152 | ProjectWorkspace::Cargo { cargo, .. } => { | ||
153 | let (sender, receiver) = unbounded(); | ||
154 | let sender = Box::new(move |msg| sender.send(msg).unwrap()); | ||
155 | let cargo_project_root = cargo.workspace_root().to_path_buf(); | ||
156 | let flycheck = | ||
157 | FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into()); | ||
158 | Some((flycheck, receiver)) | ||
159 | } | ||
160 | ProjectWorkspace::Json { .. } => { | ||
161 | log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); | ||
162 | None | ||
163 | } | ||
164 | }) | ||
165 | } | ||
166 | } | ||
167 | |||
168 | #[derive(Default)] | ||
169 | pub(crate) struct ProjectFolders { | ||
170 | pub(crate) load: Vec<vfs::loader::Entry>, | ||
171 | pub(crate) watch: Vec<usize>, | ||
172 | pub(crate) source_root_config: SourceRootConfig, | ||
173 | } | ||
174 | |||
175 | impl ProjectFolders { | ||
176 | pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders { | ||
177 | let mut res = ProjectFolders::default(); | ||
178 | let mut fsc = FileSetConfig::builder(); | ||
179 | let mut local_filesets = vec![]; | ||
180 | |||
181 | for root in workspaces.iter().flat_map(|it| it.to_roots()) { | ||
182 | let path = root.path().to_owned(); | ||
183 | |||
184 | let mut file_set_roots: Vec<VfsPath> = vec![]; | ||
185 | |||
186 | let entry = if root.is_member() { | ||
187 | vfs::loader::Entry::local_cargo_package(path.to_path_buf()) | ||
188 | } else { | ||
189 | vfs::loader::Entry::cargo_package_dependency(path.to_path_buf()) | ||
190 | }; | ||
191 | res.load.push(entry); | ||
192 | if root.is_member() { | ||
193 | res.watch.push(res.load.len() - 1); | ||
194 | } | ||
195 | |||
196 | if let Some(out_dir) = root.out_dir() { | ||
197 | let out_dir = out_dir.to_path_buf(); | ||
198 | res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone())); | ||
199 | if root.is_member() { | ||
200 | res.watch.push(res.load.len() - 1); | ||
201 | } | ||
202 | file_set_roots.push(out_dir.into()); | ||
203 | } | ||
204 | file_set_roots.push(path.to_path_buf().into()); | ||
205 | |||
206 | if root.is_member() { | ||
207 | local_filesets.push(fsc.len()); | ||
208 | } | ||
209 | fsc.add_file_set(file_set_roots) | ||
210 | } | ||
211 | |||
212 | let fsc = fsc.build(); | ||
213 | res.source_root_config = SourceRootConfig { fsc, local_filesets }; | ||
214 | |||
215 | res | ||
216 | } | ||
217 | } | ||
218 | |||
219 | #[derive(Default, Debug)] | ||
220 | pub(crate) struct SourceRootConfig { | ||
221 | pub(crate) fsc: FileSetConfig, | ||
222 | pub(crate) local_filesets: Vec<usize>, | ||
223 | } | ||
224 | |||
225 | impl SourceRootConfig { | ||
226 | pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> { | ||
227 | self.fsc | ||
228 | .partition(vfs) | ||
229 | .into_iter() | ||
230 | .enumerate() | ||
231 | .map(|(idx, file_set)| { | ||
232 | let is_local = self.local_filesets.contains(&idx); | ||
233 | if is_local { | ||
234 | SourceRoot::new_local(file_set) | ||
235 | } else { | ||
236 | SourceRoot::new_library(file_set) | ||
237 | } | ||
238 | }) | ||
239 | .collect() | ||
240 | } | ||
241 | } | ||