diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-06-25 23:28:48 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-25 23:28:48 +0100 |
commit | 6eb0349a7b2dd71eae6f1541cdd26e1ac50363d0 (patch) | |
tree | fe3b2a9c5e1facf36807730129be1a66a827b79a | |
parent | 3615347fcebebaa58a5cbf675ae062aae149d9c3 (diff) | |
parent | e70f7dc10c622e0ffec4b264235ad203b4047171 (diff) |
Merge #5068
5068: Prep dynamic reload r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
-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/dispatch.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/global_state.rs | 227 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 115 | ||||
-rw-r--r-- | crates/rust-analyzer/src/reload.rs | 241 |
7 files changed, 309 insertions, 289 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/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs index 03b373dee..891fdb96d 100644 --- a/crates/rust-analyzer/src/dispatch.rs +++ b/crates/rust-analyzer/src/dispatch.rs | |||
@@ -59,7 +59,7 @@ impl<'a> RequestDispatcher<'a> { | |||
59 | } | 59 | } |
60 | }; | 60 | }; |
61 | 61 | ||
62 | self.global_state.task_pool.0.spawn({ | 62 | self.global_state.task_pool.handle.spawn({ |
63 | let world = self.global_state.snapshot(); | 63 | let world = self.global_state.snapshot(); |
64 | move || { | 64 | move || { |
65 | let result = f(world, params); | 65 | let result = f(world, params); |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 149b1b5f9..17de2a075 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::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, 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 as _; |
17 | 17 | ||
18 | use crate::{ | 18 | use crate::{ |
19 | config::{Config, FilesWatcher}, | 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, |
@@ -61,28 +42,35 @@ impl Default for Status { | |||
61 | } | 42 | } |
62 | } | 43 | } |
63 | 44 | ||
45 | // Enforces drop order | ||
46 | pub(crate) struct Handle<H, C> { | ||
47 | pub(crate) handle: H, | ||
48 | pub(crate) receiver: C, | ||
49 | } | ||
50 | |||
64 | /// `GlobalState` is the primary mutable state of the language server | 51 | /// `GlobalState` is the primary mutable state of the language server |
65 | /// | 52 | /// |
66 | /// The most interesting components are `vfs`, which stores a consistent | 53 | /// The most interesting components are `vfs`, which stores a consistent |
67 | /// snapshot of the file systems, and `analysis_host`, which stores our | 54 | /// snapshot of the file systems, and `analysis_host`, which stores our |
68 | /// incremental salsa database. | 55 | /// incremental salsa database. |
56 | /// | ||
57 | /// Note that this struct has more than on impl in various modules! | ||
69 | pub(crate) struct GlobalState { | 58 | pub(crate) struct GlobalState { |
70 | sender: Sender<lsp_server::Message>, | 59 | sender: Sender<lsp_server::Message>, |
60 | pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>, | ||
61 | pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>, | ||
62 | pub(crate) flycheck: Option<Handle<FlycheckHandle, Receiver<flycheck::Message>>>, | ||
71 | pub(crate) config: Config, | 63 | pub(crate) config: Config, |
72 | pub(crate) task_pool: (TaskPool<Task>, Receiver<Task>), | ||
73 | pub(crate) analysis_host: AnalysisHost, | 64 | pub(crate) analysis_host: AnalysisHost, |
74 | pub(crate) loader: Box<dyn vfs::loader::Handle>, | ||
75 | pub(crate) task_receiver: Receiver<vfs::loader::Message>, | ||
76 | pub(crate) flycheck: Option<(FlycheckHandle, Receiver<flycheck::Message>)>, | ||
77 | pub(crate) diagnostics: DiagnosticCollection, | 65 | pub(crate) diagnostics: DiagnosticCollection, |
78 | pub(crate) mem_docs: FxHashSet<VfsPath>, | 66 | pub(crate) mem_docs: FxHashSet<VfsPath>, |
79 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | 67 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, |
80 | pub(crate) status: Status, | 68 | pub(crate) status: Status, |
81 | pub(crate) req_queue: ReqQueue, | 69 | pub(crate) req_queue: ReqQueue, |
70 | pub(crate) source_root_config: SourceRootConfig, | ||
71 | pub(crate) proc_macro_client: ProcMacroClient, | ||
72 | pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, | ||
82 | latest_requests: Arc<RwLock<LatestRequests>>, | 73 | latest_requests: Arc<RwLock<LatestRequests>>, |
83 | source_root_config: SourceRootConfig, | ||
84 | _proc_macro_client: ProcMacroClient, | ||
85 | workspaces: Arc<Vec<ProjectWorkspace>>, | ||
86 | } | 74 | } |
87 | 75 | ||
88 | /// An immutable snapshot of the world's state at a point in time. | 76 | /// An immutable snapshot of the world's state at a point in time. |
@@ -98,102 +86,40 @@ pub(crate) struct GlobalStateSnapshot { | |||
98 | impl GlobalState { | 86 | impl GlobalState { |
99 | pub(crate) fn new( | 87 | pub(crate) fn new( |
100 | sender: Sender<lsp_server::Message>, | 88 | sender: Sender<lsp_server::Message>, |
101 | workspaces: Vec<ProjectWorkspace>, | ||
102 | lru_capacity: Option<usize>, | 89 | lru_capacity: Option<usize>, |
103 | config: Config, | 90 | config: Config, |
104 | req_queue: ReqQueue, | ||
105 | ) -> GlobalState { | 91 | ) -> GlobalState { |
106 | let mut change = AnalysisChange::new(); | 92 | let loader = { |
107 | 93 | let (sender, receiver) = unbounded::<vfs::loader::Message>(); | |
108 | let project_folders = ProjectFolders::new(&workspaces); | 94 | let handle = |
109 | 95 | vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap())); | |
110 | let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>(); | 96 | let handle = Box::new(handle) as Box<dyn vfs::loader::Handle>; |
111 | let mut vfs = vfs::Vfs::default(); | 97 | Handle { handle, receiver } |
112 | |||
113 | let proc_macro_client = match &config.proc_macro_srv { | ||
114 | None => ProcMacroClient::dummy(), | ||
115 | Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) { | ||
116 | Ok(it) => it, | ||
117 | Err(err) => { | ||
118 | log::error!( | ||
119 | "Failed to run ra_proc_macro_srv from path {}, error: {:?}", | ||
120 | path.display(), | ||
121 | err | ||
122 | ); | ||
123 | ProcMacroClient::dummy() | ||
124 | } | ||
125 | }, | ||
126 | }; | 98 | }; |
127 | 99 | ||
128 | let mut loader = { | ||
129 | let loader = vfs_notify::NotifyHandle::spawn(Box::new(move |msg| { | ||
130 | task_sender.send(msg).unwrap() | ||
131 | })); | ||
132 | Box::new(loader) | ||
133 | }; | ||
134 | let watch = match config.files.watcher { | ||
135 | FilesWatcher::Client => vec![], | ||
136 | FilesWatcher::Notify => project_folders.watch, | ||
137 | }; | ||
138 | loader.set_config(vfs::loader::Config { load: project_folders.load, watch }); | ||
139 | |||
140 | // Create crate graph from all the workspaces | ||
141 | let mut crate_graph = CrateGraph::default(); | ||
142 | let mut load = |path: &AbsPath| { | ||
143 | let contents = loader.load_sync(path); | ||
144 | let path = vfs::VfsPath::from(path.to_path_buf()); | ||
145 | vfs.set_file_contents(path.clone(), contents); | ||
146 | vfs.file_id(&path) | ||
147 | }; | ||
148 | for ws in workspaces.iter() { | ||
149 | crate_graph.extend(ws.to_crate_graph( | ||
150 | config.cargo.target.as_deref(), | ||
151 | &proc_macro_client, | ||
152 | &mut load, | ||
153 | )); | ||
154 | } | ||
155 | change.set_crate_graph(crate_graph); | ||
156 | |||
157 | let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); | ||
158 | |||
159 | let mut analysis_host = AnalysisHost::new(lru_capacity); | ||
160 | analysis_host.apply_change(change); | ||
161 | |||
162 | let task_pool = { | 100 | let task_pool = { |
163 | let (sender, receiver) = unbounded(); | 101 | let (sender, receiver) = unbounded(); |
164 | (TaskPool::new(sender), receiver) | 102 | let handle = TaskPool::new(sender); |
103 | Handle { handle, receiver } | ||
165 | }; | 104 | }; |
166 | 105 | ||
167 | let mut res = GlobalState { | 106 | GlobalState { |
168 | sender, | 107 | sender, |
169 | config, | ||
170 | task_pool, | 108 | task_pool, |
171 | analysis_host, | ||
172 | loader, | 109 | loader, |
173 | task_receiver, | 110 | config, |
174 | flycheck, | 111 | analysis_host: AnalysisHost::new(lru_capacity), |
112 | flycheck: None, | ||
175 | diagnostics: Default::default(), | 113 | diagnostics: Default::default(), |
176 | mem_docs: FxHashSet::default(), | 114 | mem_docs: FxHashSet::default(), |
177 | vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))), | 115 | vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), |
178 | status: Status::default(), | 116 | status: Status::default(), |
179 | req_queue, | 117 | req_queue: ReqQueue::default(), |
118 | source_root_config: SourceRootConfig::default(), | ||
119 | proc_macro_client: ProcMacroClient::dummy(), | ||
120 | workspaces: Arc::new(Vec::new()), | ||
180 | latest_requests: Default::default(), | 121 | latest_requests: Default::default(), |
181 | source_root_config: project_folders.source_root_config, | ||
182 | _proc_macro_client: proc_macro_client, | ||
183 | workspaces: Arc::new(workspaces), | ||
184 | }; | ||
185 | res.process_changes(); | ||
186 | res | ||
187 | } | ||
188 | |||
189 | pub(crate) fn update_configuration(&mut self, config: Config) { | ||
190 | self.analysis_host.update_lru_capacity(config.lru_capacity); | ||
191 | if config.check != self.config.check { | ||
192 | self.flycheck = | ||
193 | config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); | ||
194 | } | 122 | } |
195 | |||
196 | self.config = config; | ||
197 | } | 123 | } |
198 | 124 | ||
199 | pub(crate) fn process_changes(&mut self) -> bool { | 125 | pub(crate) fn process_changes(&mut self) -> bool { |
@@ -266,7 +192,7 @@ impl GlobalState { | |||
266 | self.send(response.into()); | 192 | self.send(response.into()); |
267 | } | 193 | } |
268 | } | 194 | } |
269 | pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) { | 195 | pub(crate) fn show_message(&self, typ: lsp_types::MessageType, message: String) { |
270 | show_message(typ, message, &self.sender) | 196 | show_message(typ, message, &self.sender) |
271 | } | 197 | } |
272 | } | 198 | } |
@@ -343,78 +269,3 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url { | |||
343 | let path = path.as_path().unwrap(); | 269 | let path = path.as_path().unwrap(); |
344 | url_from_abs_path(&path) | 270 | url_from_abs_path(&path) |
345 | } | 271 | } |
346 | |||
347 | #[derive(Default)] | ||
348 | pub(crate) struct ProjectFolders { | ||
349 | pub(crate) load: Vec<vfs::loader::Entry>, | ||
350 | pub(crate) watch: Vec<usize>, | ||
351 | pub(crate) source_root_config: SourceRootConfig, | ||
352 | } | ||
353 | |||
354 | impl ProjectFolders { | ||
355 | pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders { | ||
356 | let mut res = ProjectFolders::default(); | ||
357 | let mut fsc = FileSetConfig::builder(); | ||
358 | let mut local_filesets = vec![]; | ||
359 | |||
360 | for root in workspaces.iter().flat_map(|it| it.to_roots()) { | ||
361 | let path = root.path().to_owned(); | ||
362 | |||
363 | let mut file_set_roots: Vec<VfsPath> = vec![]; | ||
364 | |||
365 | let entry = if root.is_member() { | ||
366 | vfs::loader::Entry::local_cargo_package(path.to_path_buf()) | ||
367 | } else { | ||
368 | vfs::loader::Entry::cargo_package_dependency(path.to_path_buf()) | ||
369 | }; | ||
370 | res.load.push(entry); | ||
371 | if root.is_member() { | ||
372 | res.watch.push(res.load.len() - 1); | ||
373 | } | ||
374 | |||
375 | if let Some(out_dir) = root.out_dir() { | ||
376 | let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap(); | ||
377 | res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone())); | ||
378 | if root.is_member() { | ||
379 | res.watch.push(res.load.len() - 1); | ||
380 | } | ||
381 | file_set_roots.push(out_dir.into()); | ||
382 | } | ||
383 | file_set_roots.push(path.to_path_buf().into()); | ||
384 | |||
385 | if root.is_member() { | ||
386 | local_filesets.push(fsc.len()); | ||
387 | } | ||
388 | fsc.add_file_set(file_set_roots) | ||
389 | } | ||
390 | |||
391 | let fsc = fsc.build(); | ||
392 | res.source_root_config = SourceRootConfig { fsc, local_filesets }; | ||
393 | |||
394 | res | ||
395 | } | ||
396 | } | ||
397 | |||
398 | #[derive(Default, Debug)] | ||
399 | pub(crate) struct SourceRootConfig { | ||
400 | pub(crate) fsc: FileSetConfig, | ||
401 | pub(crate) local_filesets: Vec<usize>, | ||
402 | } | ||
403 | |||
404 | impl SourceRootConfig { | ||
405 | pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> { | ||
406 | self.fsc | ||
407 | .partition(vfs) | ||
408 | .into_iter() | ||
409 | .enumerate() | ||
410 | .map(|(idx, file_set)| { | ||
411 | let is_local = self.local_filesets.contains(&idx); | ||
412 | if is_local { | ||
413 | SourceRoot::new_local(file_set) | ||
414 | } else { | ||
415 | SourceRoot::new_library(file_set) | ||
416 | } | ||
417 | }) | ||
418 | .collect() | ||
419 | } | ||
420 | } | ||
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/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 1bd9d6389..d4879283d 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -11,17 +11,14 @@ use lsp_types::{notification::Notification as _, request::Request as _}; | |||
11 | use ra_db::VfsPath; | 11 | use ra_db::VfsPath; |
12 | use ra_ide::{Canceled, FileId}; | 12 | use ra_ide::{Canceled, FileId}; |
13 | use ra_prof::profile; | 13 | use ra_prof::profile; |
14 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | ||
15 | 14 | ||
16 | use crate::{ | 15 | use crate::{ |
17 | config::{Config, FilesWatcher, LinkedProject}, | 16 | config::Config, |
18 | dispatch::{NotificationDispatcher, RequestDispatcher}, | 17 | dispatch::{NotificationDispatcher, RequestDispatcher}, |
19 | from_proto, | 18 | from_proto, |
20 | global_state::{file_id_to_url, GlobalState, Status}, | 19 | global_state::{file_id_to_url, GlobalState, Status}, |
21 | handlers, lsp_ext, | 20 | handlers, lsp_ext, |
22 | lsp_utils::{ | 21 | lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new}, |
23 | apply_document_changes, is_canceled, notification_is, notification_new, show_message, | ||
24 | }, | ||
25 | Result, | 22 | Result, |
26 | }; | 23 | }; |
27 | 24 | ||
@@ -47,81 +44,8 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
47 | SetThreadPriority(thread, thread_priority_above_normal); | 44 | SetThreadPriority(thread, thread_priority_above_normal); |
48 | } | 45 | } |
49 | 46 | ||
50 | let global_state = { | 47 | GlobalState::new(connection.sender.clone(), config.lru_capacity, config) |
51 | let workspaces = { | 48 | .run(connection.receiver) |
52 | if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found { | ||
53 | show_message( | ||
54 | lsp_types::MessageType::Error, | ||
55 | "rust-analyzer failed to discover workspace".to_string(), | ||
56 | &connection.sender, | ||
57 | ); | ||
58 | }; | ||
59 | |||
60 | config | ||
61 | .linked_projects | ||
62 | .iter() | ||
63 | .filter_map(|project| match project { | ||
64 | LinkedProject::ProjectManifest(manifest) => { | ||
65 | ra_project_model::ProjectWorkspace::load( | ||
66 | manifest.clone(), | ||
67 | &config.cargo, | ||
68 | config.with_sysroot, | ||
69 | ) | ||
70 | .map_err(|err| { | ||
71 | log::error!("failed to load workspace: {:#}", err); | ||
72 | show_message( | ||
73 | lsp_types::MessageType::Error, | ||
74 | format!("rust-analyzer failed to load workspace: {:#}", err), | ||
75 | &connection.sender, | ||
76 | ); | ||
77 | }) | ||
78 | .ok() | ||
79 | } | ||
80 | LinkedProject::InlineJsonProject(it) => { | ||
81 | Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) | ||
82 | } | ||
83 | }) | ||
84 | .collect::<Vec<_>>() | ||
85 | }; | ||
86 | |||
87 | let mut req_queue = ReqQueue::default(); | ||
88 | |||
89 | if let FilesWatcher::Client = config.files.watcher { | ||
90 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { | ||
91 | watchers: workspaces | ||
92 | .iter() | ||
93 | .flat_map(ProjectWorkspace::to_roots) | ||
94 | .filter(PackageRoot::is_member) | ||
95 | .map(|root| format!("{}/**/*.rs", root.path().display())) | ||
96 | .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None }) | ||
97 | .collect(), | ||
98 | }; | ||
99 | let registration = lsp_types::Registration { | ||
100 | id: "file-watcher".to_string(), | ||
101 | method: "workspace/didChangeWatchedFiles".to_string(), | ||
102 | register_options: Some(serde_json::to_value(registration_options).unwrap()), | ||
103 | }; | ||
104 | let params = lsp_types::RegistrationParams { registrations: vec![registration] }; | ||
105 | let request = req_queue.outgoing.register( | ||
106 | lsp_types::request::RegisterCapability::METHOD.to_string(), | ||
107 | params, | ||
108 | DO_NOTHING, | ||
109 | ); | ||
110 | connection.sender.send(request.into()).unwrap(); | ||
111 | } | ||
112 | |||
113 | GlobalState::new( | ||
114 | connection.sender.clone(), | ||
115 | workspaces, | ||
116 | config.lru_capacity, | ||
117 | config, | ||
118 | req_queue, | ||
119 | ) | ||
120 | }; | ||
121 | |||
122 | log::info!("server initialized, serving requests"); | ||
123 | global_state.run(connection.receiver)?; | ||
124 | Ok(()) | ||
125 | } | 49 | } |
126 | 50 | ||
127 | enum Event { | 51 | enum Event { |
@@ -176,36 +100,39 @@ impl GlobalState { | |||
176 | recv(inbox) -> msg => | 100 | recv(inbox) -> msg => |
177 | msg.ok().map(Event::Lsp), | 101 | msg.ok().map(Event::Lsp), |
178 | 102 | ||
179 | recv(self.task_pool.1) -> task => | 103 | recv(self.task_pool.receiver) -> task => |
180 | Some(Event::Task(task.unwrap())), | 104 | Some(Event::Task(task.unwrap())), |
181 | 105 | ||
182 | recv(self.task_receiver) -> task => | 106 | recv(self.loader.receiver) -> task => |
183 | Some(Event::Vfs(task.unwrap())), | 107 | Some(Event::Vfs(task.unwrap())), |
184 | 108 | ||
185 | recv(self.flycheck.as_ref().map_or(&never(), |it| &it.1)) -> task => | 109 | recv(self.flycheck.as_ref().map_or(&never(), |it| &it.receiver)) -> task => |
186 | Some(Event::Flycheck(task.unwrap())), | 110 | Some(Event::Flycheck(task.unwrap())), |
187 | } | 111 | } |
188 | } | 112 | } |
189 | 113 | ||
190 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { | 114 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { |
115 | self.reload(); | ||
116 | |||
191 | while let Some(event) = self.next_event(&inbox) { | 117 | while let Some(event) = self.next_event(&inbox) { |
192 | if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { | 118 | if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { |
193 | if not.method == lsp_types::notification::Exit::METHOD { | 119 | if not.method == lsp_types::notification::Exit::METHOD { |
194 | return Ok(()); | 120 | return Ok(()); |
195 | } | 121 | } |
196 | } | 122 | } |
197 | self.loop_turn(event)? | 123 | self.handle_event(event)? |
198 | } | 124 | } |
125 | |||
199 | Err("client exited without proper shutdown sequence")? | 126 | Err("client exited without proper shutdown sequence")? |
200 | } | 127 | } |
201 | 128 | ||
202 | fn loop_turn(&mut self, event: Event) -> Result<()> { | 129 | fn handle_event(&mut self, event: Event) -> Result<()> { |
203 | let loop_start = Instant::now(); | 130 | let loop_start = Instant::now(); |
204 | // NOTE: don't count blocking select! call as a loop-turn time | 131 | // NOTE: don't count blocking select! call as a loop-turn time |
205 | let _p = profile("main_loop_inner/loop-turn"); | 132 | let _p = profile("GlobalState::handle_event"); |
206 | 133 | ||
207 | log::info!("loop turn = {:?}", event); | 134 | log::info!("handle_event({:?})", event); |
208 | let queue_count = self.task_pool.0.len(); | 135 | let queue_count = self.task_pool.handle.len(); |
209 | if queue_count > 0 { | 136 | if queue_count > 0 { |
210 | log::info!("queued count = {}", queue_count); | 137 | log::info!("queued count = {}", queue_count); |
211 | } | 138 | } |
@@ -306,7 +233,7 @@ impl GlobalState { | |||
306 | let state_changed = self.process_changes(); | 233 | let state_changed = self.process_changes(); |
307 | if became_ready { | 234 | if became_ready { |
308 | if let Some(flycheck) = &self.flycheck { | 235 | if let Some(flycheck) = &self.flycheck { |
309 | flycheck.0.update(); | 236 | flycheck.handle.update(); |
310 | } | 237 | } |
311 | } | 238 | } |
312 | 239 | ||
@@ -443,7 +370,7 @@ impl GlobalState { | |||
443 | log::error!("orphan DidCloseTextDocument: {}", path) | 370 | log::error!("orphan DidCloseTextDocument: {}", path) |
444 | } | 371 | } |
445 | if let Some(path) = path.as_path() { | 372 | if let Some(path) = path.as_path() { |
446 | this.loader.invalidate(path.to_path_buf()); | 373 | this.loader.handle.invalidate(path.to_path_buf()); |
447 | } | 374 | } |
448 | } | 375 | } |
449 | let params = lsp_types::PublishDiagnosticsParams { | 376 | let params = lsp_types::PublishDiagnosticsParams { |
@@ -457,7 +384,7 @@ impl GlobalState { | |||
457 | })? | 384 | })? |
458 | .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { | 385 | .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { |
459 | if let Some(flycheck) = &this.flycheck { | 386 | if let Some(flycheck) = &this.flycheck { |
460 | flycheck.0.update(); | 387 | flycheck.handle.update(); |
461 | } | 388 | } |
462 | Ok(()) | 389 | Ok(()) |
463 | })? | 390 | })? |
@@ -500,7 +427,7 @@ impl GlobalState { | |||
500 | .on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| { | 427 | .on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| { |
501 | for change in params.changes { | 428 | for change in params.changes { |
502 | if let Ok(path) = from_proto::abs_path(&change.uri) { | 429 | if let Ok(path) = from_proto::abs_path(&change.uri) { |
503 | this.loader.invalidate(path); | 430 | this.loader.handle.invalidate(path); |
504 | } | 431 | } |
505 | } | 432 | } |
506 | Ok(()) | 433 | Ok(()) |
@@ -513,7 +440,7 @@ impl GlobalState { | |||
513 | if self.config.publish_diagnostics { | 440 | if self.config.publish_diagnostics { |
514 | let snapshot = self.snapshot(); | 441 | let snapshot = self.snapshot(); |
515 | let subscriptions = subscriptions.clone(); | 442 | let subscriptions = subscriptions.clone(); |
516 | self.task_pool.0.spawn(move || { | 443 | self.task_pool.handle.spawn(move || { |
517 | let diagnostics = subscriptions | 444 | let diagnostics = subscriptions |
518 | .into_iter() | 445 | .into_iter() |
519 | .filter_map(|file_id| { | 446 | .filter_map(|file_id| { |
@@ -531,7 +458,7 @@ impl GlobalState { | |||
531 | Task::Diagnostics(diagnostics) | 458 | Task::Diagnostics(diagnostics) |
532 | }) | 459 | }) |
533 | } | 460 | } |
534 | self.task_pool.0.spawn({ | 461 | self.task_pool.handle.spawn({ |
535 | let subs = subscriptions; | 462 | let subs = subscriptions; |
536 | let snap = self.snapshot(); | 463 | let snap = self.snapshot(); |
537 | move || { | 464 | move || { |
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs new file mode 100644 index 000000000..a22d3e262 --- /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, Handle}, | ||
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.handle.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.handle.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 handle = | ||
157 | FlycheckHandle::spawn(sender, config.clone(), cargo_project_root.into()); | ||
158 | Some(Handle { handle, 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 | } | ||