diff options
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 326 |
1 files changed, 166 insertions, 160 deletions
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index c2f43df1d..94b9c0ca2 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -126,6 +126,45 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
126 | Ok(()) | 126 | Ok(()) |
127 | } | 127 | } |
128 | 128 | ||
129 | enum Event { | ||
130 | Lsp(lsp_server::Message), | ||
131 | Task(Task), | ||
132 | Vfs(vfs::loader::Message), | ||
133 | Flycheck(flycheck::Message), | ||
134 | } | ||
135 | |||
136 | impl fmt::Debug for Event { | ||
137 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
138 | let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| { | ||
139 | f.debug_struct("Notification").field("method", ¬.method).finish() | ||
140 | }; | ||
141 | |||
142 | match self { | ||
143 | Event::Lsp(lsp_server::Message::Notification(not)) => { | ||
144 | if notification_is::<lsp_types::notification::DidOpenTextDocument>(not) | ||
145 | || notification_is::<lsp_types::notification::DidChangeTextDocument>(not) | ||
146 | { | ||
147 | return debug_verbose_not(not, f); | ||
148 | } | ||
149 | } | ||
150 | Event::Task(Task::Respond(resp)) => { | ||
151 | return f | ||
152 | .debug_struct("Response") | ||
153 | .field("id", &resp.id) | ||
154 | .field("error", &resp.error) | ||
155 | .finish(); | ||
156 | } | ||
157 | _ => (), | ||
158 | } | ||
159 | match self { | ||
160 | Event::Lsp(it) => fmt::Debug::fmt(it, f), | ||
161 | Event::Task(it) => fmt::Debug::fmt(it, f), | ||
162 | Event::Vfs(it) => fmt::Debug::fmt(it, f), | ||
163 | Event::Flycheck(it) => fmt::Debug::fmt(it, f), | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | |||
129 | impl GlobalState { | 168 | impl GlobalState { |
130 | fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> { | 169 | fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> { |
131 | select! { | 170 | select! { |
@@ -145,97 +184,150 @@ impl GlobalState { | |||
145 | 184 | ||
146 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { | 185 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { |
147 | while let Some(event) = self.next_event(&inbox) { | 186 | while let Some(event) = self.next_event(&inbox) { |
148 | let loop_start = Instant::now(); | 187 | if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { |
149 | // NOTE: don't count blocking select! call as a loop-turn time | 188 | if not.method == lsp_types::notification::Exit::METHOD { |
150 | let _p = profile("main_loop_inner/loop-turn"); | 189 | return Ok(()); |
151 | 190 | } | |
152 | log::info!("loop turn = {:?}", event); | ||
153 | let queue_count = self.task_pool.0.len(); | ||
154 | if queue_count > 0 { | ||
155 | log::info!("queued count = {}", queue_count); | ||
156 | } | 191 | } |
192 | self.loop_turn(event)? | ||
193 | } | ||
194 | Err("client exited without proper shutdown sequence")? | ||
195 | } | ||
157 | 196 | ||
158 | let mut became_ready = false; | 197 | fn loop_turn(&mut self, event: Event) -> Result<()> { |
159 | match event { | 198 | let loop_start = Instant::now(); |
160 | Event::Lsp(msg) => match msg { | 199 | // NOTE: don't count blocking select! call as a loop-turn time |
161 | lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, | 200 | let _p = profile("main_loop_inner/loop-turn"); |
162 | lsp_server::Message::Notification(not) => { | 201 | |
163 | if not.method == lsp_types::notification::Exit::METHOD { | 202 | log::info!("loop turn = {:?}", event); |
164 | return Ok(()); | 203 | let queue_count = self.task_pool.0.len(); |
204 | if queue_count > 0 { | ||
205 | log::info!("queued count = {}", queue_count); | ||
206 | } | ||
207 | |||
208 | let mut became_ready = false; | ||
209 | match event { | ||
210 | Event::Lsp(msg) => match msg { | ||
211 | lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, | ||
212 | lsp_server::Message::Notification(not) => { | ||
213 | self.on_notification(not)?; | ||
214 | } | ||
215 | lsp_server::Message::Response(resp) => { | ||
216 | let handler = self.req_queue.outgoing.complete(resp.id.clone()); | ||
217 | handler(self, resp) | ||
218 | } | ||
219 | }, | ||
220 | Event::Task(task) => { | ||
221 | self.on_task(task); | ||
222 | self.maybe_collect_garbage(); | ||
223 | } | ||
224 | Event::Vfs(task) => match task { | ||
225 | vfs::loader::Message::Loaded { files } => { | ||
226 | let vfs = &mut self.vfs.write().0; | ||
227 | for (path, contents) in files { | ||
228 | let path = VfsPath::from(path); | ||
229 | if !self.mem_docs.contains(&path) { | ||
230 | vfs.set_file_contents(path, contents) | ||
165 | } | 231 | } |
166 | self.on_notification(not)?; | ||
167 | } | 232 | } |
168 | lsp_server::Message::Response(resp) => { | ||
169 | let handler = self.req_queue.outgoing.complete(resp.id.clone()); | ||
170 | handler(&mut self, resp) | ||
171 | } | ||
172 | }, | ||
173 | Event::Task(task) => { | ||
174 | self.on_task(task); | ||
175 | self.maybe_collect_garbage(); | ||
176 | } | 233 | } |
177 | Event::Vfs(task) => match task { | 234 | vfs::loader::Message::Progress { n_total, n_done } => { |
178 | vfs::loader::Message::Loaded { files } => { | 235 | let state = if n_done == 0 { |
179 | let vfs = &mut self.vfs.write().0; | 236 | Progress::Begin |
180 | for (path, contents) in files { | 237 | } else if n_done < n_total { |
181 | let path = VfsPath::from(path); | 238 | Progress::Report |
182 | if !self.mem_docs.contains(&path) { | 239 | } else { |
183 | vfs.set_file_contents(path, contents) | 240 | assert_eq!(n_done, n_total); |
241 | self.status = Status::Ready; | ||
242 | became_ready = true; | ||
243 | Progress::End | ||
244 | }; | ||
245 | report_progress( | ||
246 | self, | ||
247 | "roots scanned", | ||
248 | state, | ||
249 | Some(format!("{}/{}", n_done, n_total)), | ||
250 | Some(percentage(n_done, n_total)), | ||
251 | ) | ||
252 | } | ||
253 | }, | ||
254 | Event::Flycheck(task) => match task { | ||
255 | flycheck::Message::ClearDiagnostics => { | ||
256 | on_diagnostic_task(DiagnosticTask::ClearCheck, self) | ||
257 | } | ||
258 | |||
259 | flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { | ||
260 | let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( | ||
261 | &self.config.diagnostics, | ||
262 | &diagnostic, | ||
263 | &workspace_root, | ||
264 | ); | ||
265 | for diag in diagnostics { | ||
266 | let path = from_proto::vfs_path(&diag.location.uri)?; | ||
267 | let file_id = match self.vfs.read().0.file_id(&path) { | ||
268 | Some(file) => FileId(file.0), | ||
269 | None => { | ||
270 | log::error!( | ||
271 | "File with cargo diagnostic not found in VFS: {}", | ||
272 | path | ||
273 | ); | ||
274 | return Ok(()); | ||
184 | } | 275 | } |
185 | } | ||
186 | } | ||
187 | vfs::loader::Message::Progress { n_total, n_done } => { | ||
188 | let state = if n_done == 0 { | ||
189 | Progress::Begin | ||
190 | } else if n_done < n_total { | ||
191 | Progress::Report | ||
192 | } else { | ||
193 | assert_eq!(n_done, n_total); | ||
194 | self.status = Status::Ready; | ||
195 | became_ready = true; | ||
196 | Progress::End | ||
197 | }; | 276 | }; |
198 | report_progress( | 277 | |
199 | &mut self, | 278 | on_diagnostic_task( |
200 | "roots scanned", | 279 | DiagnosticTask::AddCheck( |
201 | state, | 280 | file_id, |
202 | Some(format!("{}/{}", n_done, n_total)), | 281 | diag.diagnostic, |
203 | Some(percentage(n_done, n_total)), | 282 | diag.fixes.into_iter().map(|it| it.into()).collect(), |
283 | ), | ||
284 | self, | ||
204 | ) | 285 | ) |
205 | } | 286 | } |
206 | }, | 287 | } |
207 | Event::Flycheck(task) => on_check_task(task, &mut self)?, | ||
208 | } | ||
209 | 288 | ||
210 | let state_changed = self.process_changes(); | 289 | flycheck::Message::Progress(status) => { |
211 | if became_ready { | 290 | let (state, message) = match status { |
212 | if let Some(flycheck) = &self.flycheck { | 291 | flycheck::Progress::Being => (Progress::Begin, None), |
213 | flycheck.0.update(); | 292 | flycheck::Progress::DidCheckCrate(target) => { |
293 | (Progress::Report, Some(target)) | ||
294 | } | ||
295 | flycheck::Progress::End => (Progress::End, None), | ||
296 | }; | ||
297 | |||
298 | report_progress(self, "cargo check", state, message, None); | ||
214 | } | 299 | } |
300 | }, | ||
301 | } | ||
302 | |||
303 | let state_changed = self.process_changes(); | ||
304 | if became_ready { | ||
305 | if let Some(flycheck) = &self.flycheck { | ||
306 | flycheck.0.update(); | ||
215 | } | 307 | } |
308 | } | ||
216 | 309 | ||
217 | if self.status == Status::Ready && (state_changed || became_ready) { | 310 | if self.status == Status::Ready && (state_changed || became_ready) { |
218 | let subscriptions = self | 311 | let subscriptions = self |
219 | .mem_docs | 312 | .mem_docs |
220 | .iter() | 313 | .iter() |
221 | .map(|path| self.vfs.read().0.file_id(&path).unwrap()) | 314 | .map(|path| self.vfs.read().0.file_id(&path).unwrap()) |
222 | .collect::<Vec<_>>(); | 315 | .collect::<Vec<_>>(); |
223 | 316 | ||
224 | self.update_file_notifications_on_threadpool(subscriptions); | 317 | self.update_file_notifications_on_threadpool(subscriptions); |
225 | } | 318 | } |
226 | 319 | ||
227 | let loop_duration = loop_start.elapsed(); | 320 | let loop_duration = loop_start.elapsed(); |
228 | if loop_duration > Duration::from_millis(100) { | 321 | if loop_duration > Duration::from_millis(100) { |
229 | log::error!("overly long loop turn: {:?}", loop_duration); | 322 | log::error!("overly long loop turn: {:?}", loop_duration); |
230 | if env::var("RA_PROFILE").is_ok() { | 323 | if env::var("RA_PROFILE").is_ok() { |
231 | self.show_message( | 324 | self.show_message( |
232 | lsp_types::MessageType::Error, | 325 | lsp_types::MessageType::Error, |
233 | format!("overly long loop turn: {:?}", loop_duration), | 326 | format!("overly long loop turn: {:?}", loop_duration), |
234 | ) | 327 | ) |
235 | } | ||
236 | } | 328 | } |
237 | } | 329 | } |
238 | Err("client exited without proper shutdown sequence")? | 330 | Ok(()) |
239 | } | 331 | } |
240 | 332 | ||
241 | fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { | 333 | fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { |
@@ -461,96 +553,10 @@ pub(crate) enum Task { | |||
461 | Unit, | 553 | Unit, |
462 | } | 554 | } |
463 | 555 | ||
464 | enum Event { | ||
465 | Lsp(lsp_server::Message), | ||
466 | Task(Task), | ||
467 | Vfs(vfs::loader::Message), | ||
468 | Flycheck(flycheck::Message), | ||
469 | } | ||
470 | |||
471 | impl fmt::Debug for Event { | ||
472 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
473 | let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| { | ||
474 | f.debug_struct("Notification").field("method", ¬.method).finish() | ||
475 | }; | ||
476 | |||
477 | match self { | ||
478 | Event::Lsp(lsp_server::Message::Notification(not)) => { | ||
479 | if notification_is::<lsp_types::notification::DidOpenTextDocument>(not) | ||
480 | || notification_is::<lsp_types::notification::DidChangeTextDocument>(not) | ||
481 | { | ||
482 | return debug_verbose_not(not, f); | ||
483 | } | ||
484 | } | ||
485 | Event::Task(Task::Respond(resp)) => { | ||
486 | return f | ||
487 | .debug_struct("Response") | ||
488 | .field("id", &resp.id) | ||
489 | .field("error", &resp.error) | ||
490 | .finish(); | ||
491 | } | ||
492 | _ => (), | ||
493 | } | ||
494 | match self { | ||
495 | Event::Lsp(it) => fmt::Debug::fmt(it, f), | ||
496 | Event::Task(it) => fmt::Debug::fmt(it, f), | ||
497 | Event::Vfs(it) => fmt::Debug::fmt(it, f), | ||
498 | Event::Flycheck(it) => fmt::Debug::fmt(it, f), | ||
499 | } | ||
500 | } | ||
501 | } | ||
502 | |||
503 | pub(crate) type ReqHandler = fn(&mut GlobalState, Response); | 556 | pub(crate) type ReqHandler = fn(&mut GlobalState, Response); |
504 | pub(crate) type ReqQueue = lsp_server::ReqQueue<(&'static str, Instant), ReqHandler>; | 557 | pub(crate) type ReqQueue = lsp_server::ReqQueue<(&'static str, Instant), ReqHandler>; |
505 | const DO_NOTHING: ReqHandler = |_, _| (); | 558 | const DO_NOTHING: ReqHandler = |_, _| (); |
506 | 559 | ||
507 | fn on_check_task(task: flycheck::Message, global_state: &mut GlobalState) -> Result<()> { | ||
508 | match task { | ||
509 | flycheck::Message::ClearDiagnostics => { | ||
510 | on_diagnostic_task(DiagnosticTask::ClearCheck, global_state) | ||
511 | } | ||
512 | |||
513 | flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { | ||
514 | let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( | ||
515 | &global_state.config.diagnostics, | ||
516 | &diagnostic, | ||
517 | &workspace_root, | ||
518 | ); | ||
519 | for diag in diagnostics { | ||
520 | let path = from_proto::vfs_path(&diag.location.uri)?; | ||
521 | let file_id = match global_state.vfs.read().0.file_id(&path) { | ||
522 | Some(file) => FileId(file.0), | ||
523 | None => { | ||
524 | log::error!("File with cargo diagnostic not found in VFS: {}", path); | ||
525 | return Ok(()); | ||
526 | } | ||
527 | }; | ||
528 | |||
529 | on_diagnostic_task( | ||
530 | DiagnosticTask::AddCheck( | ||
531 | file_id, | ||
532 | diag.diagnostic, | ||
533 | diag.fixes.into_iter().map(|it| it.into()).collect(), | ||
534 | ), | ||
535 | global_state, | ||
536 | ) | ||
537 | } | ||
538 | } | ||
539 | |||
540 | flycheck::Message::Progress(status) => { | ||
541 | let (state, message) = match status { | ||
542 | flycheck::Progress::Being => (Progress::Begin, None), | ||
543 | flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)), | ||
544 | flycheck::Progress::End => (Progress::End, None), | ||
545 | }; | ||
546 | |||
547 | report_progress(global_state, "cargo check", state, message, None); | ||
548 | } | ||
549 | }; | ||
550 | |||
551 | Ok(()) | ||
552 | } | ||
553 | |||
554 | fn on_diagnostic_task(task: DiagnosticTask, global_state: &mut GlobalState) { | 560 | fn on_diagnostic_task(task: DiagnosticTask, global_state: &mut GlobalState) { |
555 | let subscriptions = global_state.diagnostics.handle_task(task); | 561 | let subscriptions = global_state.diagnostics.handle_task(task); |
556 | 562 | ||