1use std::{
5 fmt,
6 ops::Div as _,
7 panic::AssertUnwindSafe,
8 time::{Duration, Instant},
9};
10
11use crossbeam_channel::{Receiver, never, select};
12use ide_db::base_db::{SourceDatabase, VfsPath, salsa::Database as _};
13use lsp_server::{Connection, Notification, Request};
14use lsp_types::{TextDocumentIdentifier, notification::Notification as _};
15use stdx::thread::ThreadIntent;
16use tracing::{Level, error, span};
17use vfs::{AbsPathBuf, FileId, loader::LoadingProgress};
18
19use crate::{
20 config::Config,
21 diagnostics::{DiagnosticsGeneration, NativeDiagnosticsFetchKind, fetch_native_diagnostics},
22 discover::{DiscoverArgument, DiscoverCommand, DiscoverProjectMessage},
23 flycheck::{self, ClearDiagnosticsKind, FlycheckMessage},
24 global_state::{
25 FetchBuildDataResponse, FetchWorkspaceRequest, FetchWorkspaceResponse, GlobalState,
26 file_id_to_url, url_to_file_id,
27 },
28 handlers::{
29 dispatch::{NotificationDispatcher, RequestDispatcher},
30 request::empty_diagnostic_report,
31 },
32 lsp::{
33 from_proto, to_proto,
34 utils::{Progress, notification_is},
35 },
36 lsp_ext,
37 reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
38 test_runner::{CargoTestMessage, CargoTestOutput, TestState},
39};
40
41pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
42 tracing::info!("initial config: {:#?}", config);
43
44 #[cfg(windows)]
56 unsafe {
57 use windows_sys::Win32::System::Threading::*;
58 let thread = GetCurrentThread();
59 let thread_priority_above_normal = 1;
60 SetThreadPriority(thread, thread_priority_above_normal);
61 }
62
63 GlobalState::new(connection.sender, config).run(connection.receiver)
64}
65
66enum Event {
67 Lsp(lsp_server::Message),
68 Task(Task),
69 QueuedTask(QueuedTask),
70 Vfs(vfs::loader::Message),
71 Flycheck(FlycheckMessage),
72 TestResult(CargoTestMessage),
73 DiscoverProject(DiscoverProjectMessage),
74 FetchWorkspaces(FetchWorkspaceRequest),
75}
76
77impl fmt::Display for Event {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Event::Lsp(_) => write!(f, "Event::Lsp"),
81 Event::Task(_) => write!(f, "Event::Task"),
82 Event::Vfs(_) => write!(f, "Event::Vfs"),
83 Event::Flycheck(_) => write!(f, "Event::Flycheck"),
84 Event::QueuedTask(_) => write!(f, "Event::QueuedTask"),
85 Event::TestResult(_) => write!(f, "Event::TestResult"),
86 Event::DiscoverProject(_) => write!(f, "Event::DiscoverProject"),
87 Event::FetchWorkspaces(_) => write!(f, "Event::SwitchWorkspaces"),
88 }
89 }
90}
91
92#[derive(Debug)]
93pub(crate) enum QueuedTask {
94 CheckIfIndexed(lsp_types::Url),
95 CheckProcMacroSources(Vec<FileId>),
96}
97
98#[derive(Debug)]
99pub(crate) enum DiagnosticsTaskKind {
100 Syntax(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
101 Semantic(DiagnosticsGeneration, Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
102}
103
104#[derive(Debug)]
105pub(crate) enum Task {
106 Response(lsp_server::Response),
107 DiscoverLinkedProjects(DiscoverProjectParam),
108 Retry(lsp_server::Request),
109 Diagnostics(DiagnosticsTaskKind),
110 DiscoverTest(lsp_ext::DiscoverTestResults),
111 PrimeCaches(PrimeCachesProgress),
112 FetchWorkspace(ProjectWorkspaceProgress),
113 FetchBuildData(BuildDataProgress),
114 LoadProcMacros(ProcMacroProgress),
115 BuildDepsHaveChanged,
117}
118
119#[derive(Debug)]
120pub(crate) enum DiscoverProjectParam {
121 Buildfile(AbsPathBuf),
122 Path(AbsPathBuf),
123}
124
125#[derive(Debug)]
126pub(crate) enum PrimeCachesProgress {
127 Begin,
128 Report(ide::ParallelPrimeCachesProgress),
129 End { cancelled: bool },
130}
131
132impl fmt::Debug for Event {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 let debug_non_verbose = |not: &Notification, f: &mut fmt::Formatter<'_>| {
135 f.debug_struct("Notification").field("method", ¬.method).finish()
136 };
137
138 match self {
139 Event::Lsp(lsp_server::Message::Notification(not)) => {
140 if notification_is::<lsp_types::notification::DidOpenTextDocument>(not)
141 || notification_is::<lsp_types::notification::DidChangeTextDocument>(not)
142 {
143 return debug_non_verbose(not, f);
144 }
145 }
146 Event::Task(Task::Response(resp)) => {
147 return f
148 .debug_struct("Response")
149 .field("id", &resp.id)
150 .field("error", &resp.error)
151 .finish();
152 }
153 _ => (),
154 }
155
156 match self {
157 Event::Lsp(it) => fmt::Debug::fmt(it, f),
158 Event::Task(it) => fmt::Debug::fmt(it, f),
159 Event::QueuedTask(it) => fmt::Debug::fmt(it, f),
160 Event::Vfs(it) => fmt::Debug::fmt(it, f),
161 Event::Flycheck(it) => fmt::Debug::fmt(it, f),
162 Event::TestResult(it) => fmt::Debug::fmt(it, f),
163 Event::DiscoverProject(it) => fmt::Debug::fmt(it, f),
164 Event::FetchWorkspaces(it) => fmt::Debug::fmt(it, f),
165 }
166 }
167}
168
169impl GlobalState {
170 fn run(mut self, inbox: Receiver<lsp_server::Message>) -> anyhow::Result<()> {
171 self.update_status_or_notify();
172
173 if self.config.did_save_text_document_dynamic_registration() {
174 let additional_patterns = self
175 .config
176 .discover_workspace_config()
177 .map(|cfg| cfg.files_to_watch.clone().into_iter())
178 .into_iter()
179 .flatten()
180 .map(|f| format!("**/{f}"));
181 self.register_did_save_capability(additional_patterns);
182 }
183
184 if self.config.discover_workspace_config().is_none() {
185 self.fetch_workspaces_queue.request_op(
186 "startup".to_owned(),
187 FetchWorkspaceRequest { path: None, force_crate_graph_reload: false },
188 );
189 if let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
190 self.fetch_workspaces_queue.should_start_op()
191 {
192 self.fetch_workspaces(cause, path, force_crate_graph_reload);
193 }
194 }
195
196 while let Ok(event) = self.next_event(&inbox) {
197 let Some(event) = event else {
198 anyhow::bail!("client exited without proper shutdown sequence");
199 };
200 if matches!(
201 &event,
202 Event::Lsp(lsp_server::Message::Notification(Notification { method, .. }))
203 if method == lsp_types::notification::Exit::METHOD
204 ) {
205 return Ok(());
206 }
207 self.handle_event(event);
208 }
209
210 Err(anyhow::anyhow!("A receiver has been dropped, something panicked!"))
211 }
212
213 fn register_did_save_capability(&mut self, additional_patterns: impl Iterator<Item = String>) {
214 let additional_filters = additional_patterns.map(|pattern| lsp_types::DocumentFilter {
215 language: None,
216 scheme: None,
217 pattern: (Some(pattern)),
218 });
219
220 let mut selectors = vec![
221 lsp_types::DocumentFilter {
222 language: None,
223 scheme: None,
224 pattern: Some("**/*.rs".into()),
225 },
226 lsp_types::DocumentFilter {
227 language: None,
228 scheme: None,
229 pattern: Some("**/Cargo.toml".into()),
230 },
231 lsp_types::DocumentFilter {
232 language: None,
233 scheme: None,
234 pattern: Some("**/Cargo.lock".into()),
235 },
236 ];
237 selectors.extend(additional_filters);
238
239 let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions {
240 include_text: Some(false),
241 text_document_registration_options: lsp_types::TextDocumentRegistrationOptions {
242 document_selector: Some(selectors),
243 },
244 };
245
246 let registration = lsp_types::Registration {
247 id: "textDocument/didSave".to_owned(),
248 method: "textDocument/didSave".to_owned(),
249 register_options: Some(serde_json::to_value(save_registration_options).unwrap()),
250 };
251 self.send_request::<lsp_types::request::RegisterCapability>(
252 lsp_types::RegistrationParams { registrations: vec![registration] },
253 |_, _| (),
254 );
255 }
256
257 fn next_event(
258 &mut self,
259 inbox: &Receiver<lsp_server::Message>,
260 ) -> Result<Option<Event>, crossbeam_channel::RecvError> {
261 if let Ok(task) = self.fmt_pool.receiver.try_recv() {
263 return Ok(Some(Event::Task(task)));
264 }
265
266 select! {
267 recv(inbox) -> msg =>
268 return Ok(msg.ok().map(Event::Lsp)),
269
270 recv(self.task_pool.receiver) -> task =>
271 task.map(Event::Task),
272
273 recv(self.deferred_task_queue.receiver) -> task =>
274 task.map(Event::QueuedTask),
275
276 recv(self.fmt_pool.receiver) -> task =>
277 task.map(Event::Task),
278
279 recv(self.loader.receiver) -> task =>
280 task.map(Event::Vfs),
281
282 recv(self.flycheck_receiver) -> task =>
283 task.map(Event::Flycheck),
284
285 recv(self.test_run_receiver) -> task =>
286 task.map(Event::TestResult),
287
288 recv(self.discover_receiver) -> task =>
289 task.map(Event::DiscoverProject),
290
291 recv(self.fetch_ws_receiver.as_ref().map_or(&never(), |(chan, _)| chan)) -> _instant => {
292 Ok(Event::FetchWorkspaces(self.fetch_ws_receiver.take().unwrap().1))
293 },
294 }
295 .map(Some)
296 }
297
298 fn handle_event(&mut self, event: Event) {
299 let loop_start = Instant::now();
300 let _p = tracing::info_span!("GlobalState::handle_event", event = %event).entered();
301
302 let event_dbg_msg = format!("{event:?}");
303 tracing::debug!(?loop_start, ?event, "handle_event");
304 if tracing::enabled!(tracing::Level::INFO) {
305 let task_queue_len = self.task_pool.handle.len();
306 if task_queue_len > 0 {
307 tracing::info!("task queue len: {}", task_queue_len);
308 }
309 }
310
311 let was_quiescent = self.is_quiescent();
312 match event {
313 Event::Lsp(msg) => match msg {
314 lsp_server::Message::Request(req) => self.on_new_request(loop_start, req),
315 lsp_server::Message::Notification(not) => self.on_notification(not),
316 lsp_server::Message::Response(resp) => self.complete_request(resp),
317 },
318 Event::QueuedTask(task) => {
319 let _p = tracing::info_span!("GlobalState::handle_event/queued_task").entered();
320 self.handle_queued_task(task);
321 while let Ok(task) = self.deferred_task_queue.receiver.try_recv() {
323 self.handle_queued_task(task);
324 }
325 }
326 Event::Task(task) => {
327 let _p = tracing::info_span!("GlobalState::handle_event/task").entered();
328 let mut prime_caches_progress = Vec::new();
329
330 self.handle_task(&mut prime_caches_progress, task);
331 while let Ok(task) = self.task_pool.receiver.try_recv() {
333 self.handle_task(&mut prime_caches_progress, task);
334 }
335
336 for progress in prime_caches_progress {
337 let (state, message, fraction, title);
338 match progress {
339 PrimeCachesProgress::Begin => {
340 state = Progress::Begin;
341 message = None;
342 fraction = 0.0;
343 title = "Indexing";
344 }
345 PrimeCachesProgress::Report(report) => {
346 state = Progress::Report;
347 title = report.work_type;
348
349 message = match &*report.crates_currently_indexing {
350 [crate_name] => Some(format!(
351 "{}/{} ({})",
352 report.crates_done,
353 report.crates_total,
354 crate_name.as_str(),
355 )),
356 [crate_name, rest @ ..] => Some(format!(
357 "{}/{} ({} + {} more)",
358 report.crates_done,
359 report.crates_total,
360 crate_name.as_str(),
361 rest.len()
362 )),
363 _ => None,
364 };
365
366 fraction = Progress::fraction(report.crates_done, report.crates_total);
367 }
368 PrimeCachesProgress::End { cancelled } => {
369 state = Progress::End;
370 message = None;
371 fraction = 1.0;
372 title = "Indexing";
373
374 self.analysis_host.raw_database_mut().trigger_lru_eviction();
375 self.prime_caches_queue.op_completed(());
376 if cancelled {
377 self.prime_caches_queue
378 .request_op("restart after cancellation".to_owned(), ());
379 }
380 }
381 };
382
383 self.report_progress(
384 title,
385 state,
386 message,
387 Some(fraction),
388 Some("rustAnalyzer/cachePriming".to_owned()),
389 );
390 }
391 }
392 Event::Vfs(message) => {
393 let _p = tracing::info_span!("GlobalState::handle_event/vfs").entered();
394 self.handle_vfs_msg(message);
395 while let Ok(message) = self.loader.receiver.try_recv() {
397 self.handle_vfs_msg(message);
398 }
399 }
400 Event::Flycheck(message) => {
401 let _p = tracing::info_span!("GlobalState::handle_event/flycheck").entered();
402 self.handle_flycheck_msg(message);
403 while let Ok(message) = self.flycheck_receiver.try_recv() {
405 self.handle_flycheck_msg(message);
406 }
407 }
408 Event::TestResult(message) => {
409 let _p = tracing::info_span!("GlobalState::handle_event/test_result").entered();
410 self.handle_cargo_test_msg(message);
411 while let Ok(message) = self.test_run_receiver.try_recv() {
413 self.handle_cargo_test_msg(message);
414 }
415 }
416 Event::DiscoverProject(message) => {
417 self.handle_discover_msg(message);
418 while let Ok(message) = self.discover_receiver.try_recv() {
420 self.handle_discover_msg(message);
421 }
422 }
423 Event::FetchWorkspaces(req) => {
424 self.fetch_workspaces_queue.request_op("project structure change".to_owned(), req)
425 }
426 }
427 let event_handling_duration = loop_start.elapsed();
428 let (state_changed, memdocs_added_or_removed) = if self.vfs_done {
429 if let Some(cause) = self.wants_to_switch.take() {
430 self.switch_workspaces(cause);
431 }
432 (self.process_changes(), self.mem_docs.take_changes())
433 } else {
434 (false, false)
435 };
436
437 if self.is_quiescent() {
438 let became_quiescent = !was_quiescent;
439 if became_quiescent {
440 if self.config.check_on_save(None)
441 && self.config.flycheck_workspace(None)
442 && !self.fetch_build_data_queue.op_requested()
443 {
444 self.flycheck.iter().for_each(|flycheck| flycheck.restart_workspace(None));
446 }
447 if self.config.prefill_caches() {
448 self.prime_caches_queue.request_op("became quiescent".to_owned(), ());
449 }
450 }
451
452 let client_refresh = became_quiescent || state_changed;
453 if client_refresh {
454 if self.config.semantic_tokens_refresh() {
456 self.semantic_tokens_cache.lock().clear();
457 self.send_request::<lsp_types::request::SemanticTokensRefresh>((), |_, _| ());
458 }
459
460 if self.config.code_lens_refresh() {
462 self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ());
463 }
464
465 if self.config.inlay_hints_refresh() {
467 self.send_request::<lsp_types::request::InlayHintRefreshRequest>((), |_, _| ());
468 }
469
470 if self.config.diagnostics_refresh() {
471 self.send_request::<lsp_types::request::WorkspaceDiagnosticRefresh>(
472 (),
473 |_, _| (),
474 );
475 }
476 }
477
478 let project_or_mem_docs_changed =
479 became_quiescent || state_changed || memdocs_added_or_removed;
480 if project_or_mem_docs_changed
481 && !self.config.text_document_diagnostic()
482 && self.config.publish_diagnostics(None)
483 {
484 self.update_diagnostics();
485 }
486 if project_or_mem_docs_changed && self.config.test_explorer() {
487 self.update_tests();
488 }
489 }
490
491 if let Some(diagnostic_changes) = self.diagnostics.take_changes() {
492 for file_id in diagnostic_changes {
493 let uri = file_id_to_url(&self.vfs.read().0, file_id);
494 let version = from_proto::vfs_path(&uri)
495 .ok()
496 .and_then(|path| self.mem_docs.get(&path).map(|it| it.version));
497
498 let diagnostics =
499 self.diagnostics.diagnostics_for(file_id).cloned().collect::<Vec<_>>();
500 self.publish_diagnostics(uri, version, diagnostics);
501 }
502 }
503
504 if (self.config.cargo_autoreload_config(None)
505 || self.config.discover_workspace_config().is_some())
506 && let Some((cause, FetchWorkspaceRequest { path, force_crate_graph_reload })) =
507 self.fetch_workspaces_queue.should_start_op()
508 {
509 self.fetch_workspaces(cause, path, force_crate_graph_reload);
510 }
511
512 if !self.fetch_workspaces_queue.op_in_progress() {
513 if let Some((cause, ())) = self.fetch_build_data_queue.should_start_op() {
514 self.fetch_build_data(cause);
515 } else if let Some((cause, (change, paths))) =
516 self.fetch_proc_macros_queue.should_start_op()
517 {
518 self.fetch_proc_macros(cause, change, paths);
519 }
520 }
521
522 if let Some((cause, ())) = self.prime_caches_queue.should_start_op() {
523 self.prime_caches(cause);
524 }
525
526 self.update_status_or_notify();
527
528 let loop_duration = loop_start.elapsed();
529 if loop_duration > Duration::from_millis(100) && was_quiescent {
530 tracing::warn!(
531 "overly long loop turn took {loop_duration:?} (event handling took {event_handling_duration:?}): {event_dbg_msg}"
532 );
533 self.poke_rust_analyzer_developer(format!(
534 "overly long loop turn took {loop_duration:?} (event handling took {event_handling_duration:?}): {event_dbg_msg}"
535 ));
536 }
537 }
538
539 fn prime_caches(&mut self, cause: String) {
540 tracing::debug!(%cause, "will prime caches");
541 let num_worker_threads = self.config.prime_caches_num_threads();
542
543 self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, {
544 let analysis = AssertUnwindSafe(self.snapshot().analysis);
545 move |sender| {
546 sender.send(Task::PrimeCaches(PrimeCachesProgress::Begin)).unwrap();
547 let res = analysis.parallel_prime_caches(num_worker_threads, |progress| {
548 let report = PrimeCachesProgress::Report(progress);
549 sender.send(Task::PrimeCaches(report)).unwrap();
550 });
551 sender
552 .send(Task::PrimeCaches(PrimeCachesProgress::End { cancelled: res.is_err() }))
553 .unwrap();
554 }
555 });
556 }
557
558 fn update_diagnostics(&mut self) {
559 let db = self.analysis_host.raw_database();
560 let generation = self.diagnostics.next_generation();
561 let subscriptions = {
562 let vfs = &self.vfs.read().0;
563 self.mem_docs
564 .iter()
565 .map(|path| vfs.file_id(path).unwrap())
566 .filter_map(|(file_id, excluded)| {
567 (excluded == vfs::FileExcluded::No).then_some(file_id)
568 })
569 .filter(|&file_id| {
570 let source_root_id = db.file_source_root(file_id).source_root_id(db);
571 let source_root = db.source_root(source_root_id).source_root(db);
572 !source_root.is_library
578 })
579 .collect::<std::sync::Arc<_>>()
580 };
581 tracing::trace!("updating notifications for {:?}", subscriptions);
582 let max_tasks = self.config.main_loop_num_threads().div(4).max(1);
585 let chunk_length = subscriptions.len() / max_tasks;
586 let remainder = subscriptions.len() % max_tasks;
587
588 let mut start = 0;
589 for task_idx in 0..max_tasks {
590 let extra = if task_idx < remainder { 1 } else { 0 };
591 let end = start + chunk_length + extra;
592 let slice = start..end;
593 if slice.is_empty() {
594 break;
595 }
596 let snapshot = self.snapshot();
599 self.task_pool.handle.spawn_with_sender(ThreadIntent::LatencySensitive, {
600 let subscriptions = subscriptions.clone();
601 let fetch_semantic =
604 self.vfs_done && self.fetch_workspaces_queue.last_op_result().is_some();
605 move |sender| {
606 let snapshot = AssertUnwindSafe(&snapshot);
608 let Ok(diags) = std::panic::catch_unwind(|| {
609 fetch_native_diagnostics(
610 &snapshot,
611 subscriptions.clone(),
612 slice.clone(),
613 NativeDiagnosticsFetchKind::Syntax,
614 )
615 }) else {
616 return;
617 };
618 sender
619 .send(Task::Diagnostics(DiagnosticsTaskKind::Syntax(generation, diags)))
620 .unwrap();
621
622 if fetch_semantic {
623 let Ok(diags) = std::panic::catch_unwind(|| {
624 fetch_native_diagnostics(
625 &snapshot,
626 subscriptions.clone(),
627 slice.clone(),
628 NativeDiagnosticsFetchKind::Semantic,
629 )
630 }) else {
631 return;
632 };
633 sender
634 .send(Task::Diagnostics(DiagnosticsTaskKind::Semantic(
635 generation, diags,
636 )))
637 .unwrap();
638 }
639 }
640 });
641 start = end;
642 }
643 }
644
645 fn update_tests(&mut self) {
646 if !self.vfs_done {
647 return;
648 }
649 let db = self.analysis_host.raw_database();
650 let subscriptions = self
651 .mem_docs
652 .iter()
653 .map(|path| self.vfs.read().0.file_id(path).unwrap())
654 .filter_map(|(file_id, excluded)| {
655 (excluded == vfs::FileExcluded::No).then_some(file_id)
656 })
657 .filter(|&file_id| {
658 let source_root_id = db.file_source_root(file_id).source_root_id(db);
659 let source_root = db.source_root(source_root_id).source_root(db);
660 !source_root.is_library
661 })
662 .collect::<Vec<_>>();
663 tracing::trace!("updating tests for {:?}", subscriptions);
664
665 self.task_pool.handle.spawn(ThreadIntent::LatencySensitive, {
668 let snapshot = self.snapshot();
669 move || {
670 let tests = subscriptions
671 .iter()
672 .copied()
673 .filter_map(|f| snapshot.analysis.discover_tests_in_file(f).ok())
674 .flatten()
675 .collect::<Vec<_>>();
676
677 Task::DiscoverTest(lsp_ext::DiscoverTestResults {
678 tests: tests
679 .into_iter()
680 .filter_map(|t| {
681 let line_index = t.file.and_then(|f| snapshot.file_line_index(f).ok());
682 to_proto::test_item(&snapshot, t, line_index.as_ref())
683 })
684 .collect(),
685 scope: None,
686 scope_file: Some(
687 subscriptions
688 .into_iter()
689 .map(|f| TextDocumentIdentifier { uri: to_proto::url(&snapshot, f) })
690 .collect(),
691 ),
692 })
693 }
694 });
695 }
696
697 fn update_status_or_notify(&mut self) {
698 let status = self.current_status();
699 if self.last_reported_status != status {
700 self.last_reported_status = status.clone();
701
702 if self.config.server_status_notification() {
703 self.send_notification::<lsp_ext::ServerStatusNotification>(status);
704 } else if let (
705 health @ (lsp_ext::Health::Warning | lsp_ext::Health::Error),
706 Some(message),
707 ) = (status.health, &status.message)
708 {
709 let open_log_button = tracing::enabled!(tracing::Level::ERROR)
710 && (self.fetch_build_data_error().is_err()
711 || self.fetch_workspace_error().is_err());
712 self.show_message(
713 match health {
714 lsp_ext::Health::Ok => lsp_types::MessageType::INFO,
715 lsp_ext::Health::Warning => lsp_types::MessageType::WARNING,
716 lsp_ext::Health::Error => lsp_types::MessageType::ERROR,
717 },
718 message.clone(),
719 open_log_button,
720 );
721 }
722 }
723 }
724
725 fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) {
726 match task {
727 Task::Response(response) => self.respond(response),
728 Task::Retry(req) if !self.is_completed(&req) => self.on_request(req),
730 Task::Retry(_) => (),
731 Task::Diagnostics(kind) => {
732 self.diagnostics.set_native_diagnostics(kind);
733 }
734 Task::PrimeCaches(progress) => match progress {
735 PrimeCachesProgress::Begin => prime_caches_progress.push(progress),
736 PrimeCachesProgress::Report(_) => {
737 match prime_caches_progress.last_mut() {
738 Some(last @ PrimeCachesProgress::Report(_)) => {
739 *last = progress;
741 }
742 _ => prime_caches_progress.push(progress),
743 }
744 }
745 PrimeCachesProgress::End { .. } => prime_caches_progress.push(progress),
746 },
747 Task::FetchWorkspace(progress) => {
748 let (state, msg) = match progress {
749 ProjectWorkspaceProgress::Begin => (Progress::Begin, None),
750 ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)),
751 ProjectWorkspaceProgress::End(workspaces, force_crate_graph_reload) => {
752 let resp = FetchWorkspaceResponse { workspaces, force_crate_graph_reload };
753 self.fetch_workspaces_queue.op_completed(resp);
754 if let Err(e) = self.fetch_workspace_error() {
755 error!("FetchWorkspaceError: {e}");
756 }
757 self.wants_to_switch = Some("fetched workspace".to_owned());
758 self.diagnostics.clear_check_all();
759 (Progress::End, None)
760 }
761 };
762
763 self.report_progress("Fetching", state, msg, None, None);
764 }
765 Task::DiscoverLinkedProjects(arg) => {
766 if let Some(cfg) = self.config.discover_workspace_config()
767 && !self.discover_workspace_queue.op_in_progress()
768 {
769 let title = &cfg.progress_label.clone();
772 let command = cfg.command.clone();
773 let discover = DiscoverCommand::new(self.discover_sender.clone(), command);
774
775 self.report_progress(title, Progress::Begin, None, None, None);
776 self.discover_workspace_queue
777 .request_op("Discovering workspace".to_owned(), ());
778 let _ = self.discover_workspace_queue.should_start_op();
779
780 let arg = match arg {
781 DiscoverProjectParam::Buildfile(it) => DiscoverArgument::Buildfile(it),
782 DiscoverProjectParam::Path(it) => DiscoverArgument::Path(it),
783 };
784
785 let handle = discover.spawn(
786 arg,
787 &std::env::current_dir()
788 .expect("Failed to get cwd during project discovery"),
789 );
790 self.discover_handle = Some(handle.unwrap_or_else(|e| {
791 panic!("Failed to spawn project discovery command: {e}")
792 }));
793 }
794 }
795 Task::FetchBuildData(progress) => {
796 let (state, msg) = match progress {
797 BuildDataProgress::Begin => (Some(Progress::Begin), None),
798 BuildDataProgress::Report(msg) => (Some(Progress::Report), Some(msg)),
799 BuildDataProgress::End((workspaces, build_scripts)) => {
800 let resp = FetchBuildDataResponse { workspaces, build_scripts };
801 self.fetch_build_data_queue.op_completed(resp);
802
803 if let Err(e) = self.fetch_build_data_error() {
804 error!("FetchBuildDataError: {e}");
805 }
806
807 if self.wants_to_switch.is_none() {
808 self.wants_to_switch = Some("fetched build data".to_owned());
809 }
810 (Some(Progress::End), None)
811 }
812 };
813
814 if let Some(state) = state {
815 self.report_progress("Building compile-time-deps", state, msg, None, None);
816 }
817 }
818 Task::LoadProcMacros(progress) => {
819 let (state, msg) = match progress {
820 ProcMacroProgress::Begin => (Some(Progress::Begin), None),
821 ProcMacroProgress::Report(msg) => (Some(Progress::Report), Some(msg)),
822 ProcMacroProgress::End(change) => {
823 self.fetch_proc_macros_queue.op_completed(true);
824 self.analysis_host.apply_change(change);
825 self.finish_loading_crate_graph();
826 (Some(Progress::End), None)
827 }
828 };
829
830 if let Some(state) = state {
831 self.report_progress("Loading proc-macros", state, msg, None, None);
832 }
833 }
834 Task::BuildDepsHaveChanged => self.build_deps_changed = true,
835 Task::DiscoverTest(tests) => {
836 self.send_notification::<lsp_ext::DiscoveredTests>(tests);
837 }
838 }
839 }
840
841 fn handle_vfs_msg(&mut self, message: vfs::loader::Message) {
842 let _p = tracing::info_span!("GlobalState::handle_vfs_msg").entered();
843 let is_changed = matches!(message, vfs::loader::Message::Changed { .. });
844 match message {
845 vfs::loader::Message::Changed { files } | vfs::loader::Message::Loaded { files } => {
846 let _p = tracing::info_span!("GlobalState::handle_vfs_msg{changed/load}").entered();
847 self.debounce_workspace_fetch();
848 let vfs = &mut self.vfs.write().0;
849 for (path, contents) in files {
850 let path = VfsPath::from(path);
851 if !self.mem_docs.contains(&path)
854 && (is_changed || vfs.file_id(&path).is_none())
855 {
856 vfs.set_file_contents(path, contents);
857 }
858 }
859 }
860 vfs::loader::Message::Progress { n_total, n_done, dir, config_version } => {
861 let _p = span!(Level::INFO, "GlobalState::handle_vfs_msg/progress").entered();
862 stdx::always!(config_version <= self.vfs_config_version);
863
864 let (n_done, state) = match n_done {
865 LoadingProgress::Started => {
866 self.vfs_span =
867 Some(span!(Level::INFO, "vfs_load", total = n_total).entered());
868 (0, Progress::Begin)
869 }
870 LoadingProgress::Progress(n_done) => (n_done.min(n_total), Progress::Report),
871 LoadingProgress::Finished => {
872 self.vfs_span = None;
873 (n_total, Progress::End)
874 }
875 };
876
877 self.vfs_progress_config_version = config_version;
878 self.vfs_done = state == Progress::End;
879
880 let mut message = format!("{n_done}/{n_total}");
881 if let Some(dir) = dir {
882 message += &format!(
883 ": {}",
884 match dir.strip_prefix(self.config.root_path()) {
885 Some(relative_path) => relative_path.as_utf8_path(),
886 None => dir.as_ref(),
887 }
888 );
889 }
890
891 self.report_progress(
892 "Roots Scanned",
893 state,
894 Some(message),
895 Some(Progress::fraction(n_done, n_total)),
896 None,
897 );
898 }
899 }
900 }
901
902 fn handle_queued_task(&mut self, task: QueuedTask) {
903 match task {
904 QueuedTask::CheckIfIndexed(uri) => {
905 let snap = self.snapshot();
906
907 self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
908 let _p = tracing::info_span!("GlobalState::check_if_indexed").entered();
909 tracing::debug!(?uri, "handling uri");
910 let Some(id) = from_proto::file_id(&snap, &uri).expect("unable to get FileId")
911 else {
912 return;
913 };
914 if let Ok(crates) = &snap.analysis.crates_for(id) {
915 if crates.is_empty() {
916 if snap.config.discover_workspace_config().is_some() {
917 let path =
918 from_proto::abs_path(&uri).expect("Unable to get AbsPath");
919 let arg = DiscoverProjectParam::Path(path);
920 sender.send(Task::DiscoverLinkedProjects(arg)).unwrap();
921 }
922 } else {
923 tracing::debug!(?uri, "is indexed");
924 }
925 }
926 });
927 }
928 QueuedTask::CheckProcMacroSources(modified_rust_files) => {
929 let analysis = AssertUnwindSafe(self.snapshot().analysis);
930 self.task_pool.handle.spawn_with_sender(stdx::thread::ThreadIntent::Worker, {
931 move |sender| {
932 if modified_rust_files.into_iter().any(|file_id| {
933 match analysis.crates_for(file_id) {
935 Ok(crates) => crates.iter().any(|&krate| {
936 analysis.is_proc_macro_crate(krate).is_ok_and(|it| it)
937 }),
938 _ => false,
939 }
940 }) {
941 sender.send(Task::BuildDepsHaveChanged).unwrap();
942 }
943 }
944 });
945 }
946 }
947 }
948
949 fn handle_discover_msg(&mut self, message: DiscoverProjectMessage) {
950 let title = self
951 .config
952 .discover_workspace_config()
953 .map(|cfg| cfg.progress_label.clone())
954 .expect("No title could be found; this is a bug");
955 match message {
956 DiscoverProjectMessage::Finished { project, buildfile } => {
957 self.discover_handle = None;
958 self.report_progress(&title, Progress::End, None, None, None);
959 self.discover_workspace_queue.op_completed(());
960
961 let mut config = Config::clone(&*self.config);
962 config.add_discovered_project_from_command(project, buildfile);
963 self.update_configuration(config);
964 }
965 DiscoverProjectMessage::Progress { message } => {
966 self.report_progress(&title, Progress::Report, Some(message), None, None)
967 }
968 DiscoverProjectMessage::Error { error, source } => {
969 self.discover_handle = None;
970 let message = format!("Project discovery failed: {error}");
971 self.discover_workspace_queue.op_completed(());
972 self.show_and_log_error(message.clone(), source);
973 self.report_progress(&title, Progress::End, Some(message), None, None)
974 }
975 }
976 }
977
978 fn handle_cargo_test_msg(&mut self, message: CargoTestMessage) {
979 match message.output {
980 CargoTestOutput::Test { name, state } => {
981 let state = match state {
982 TestState::Started => lsp_ext::TestState::Started,
983 TestState::Ignored => lsp_ext::TestState::Skipped,
984 TestState::Ok => lsp_ext::TestState::Passed,
985 TestState::Failed { stdout } => lsp_ext::TestState::Failed { message: stdout },
986 };
987
988 let test_id = format!("{}::{name}", message.target.target.replace('-', "_"));
990
991 self.send_notification::<lsp_ext::ChangeTestState>(
992 lsp_ext::ChangeTestStateParams { test_id, state },
993 );
994 }
995 CargoTestOutput::Suite => (),
996 CargoTestOutput::Finished => {
997 self.test_run_remaining_jobs = self.test_run_remaining_jobs.saturating_sub(1);
998 if self.test_run_remaining_jobs == 0 {
999 self.send_notification::<lsp_ext::EndRunTest>(());
1000 self.test_run_session = None;
1001 }
1002 }
1003 CargoTestOutput::Custom { text } => {
1004 self.send_notification::<lsp_ext::AppendOutputToRunTest>(text);
1005 }
1006 }
1007 }
1008
1009 fn handle_flycheck_msg(&mut self, message: FlycheckMessage) {
1010 match message {
1011 FlycheckMessage::AddDiagnostic {
1012 id,
1013 generation,
1014 workspace_root,
1015 diagnostic,
1016 package_id,
1017 } => {
1018 let snap = self.snapshot();
1019 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
1020 &self.config.diagnostics_map(None),
1021 &diagnostic,
1022 &workspace_root,
1023 &snap,
1024 );
1025 for diag in diagnostics {
1026 match url_to_file_id(&self.vfs.read().0, &diag.url) {
1027 Ok(Some(file_id)) => self.diagnostics.add_check_diagnostic(
1028 id,
1029 generation,
1030 &package_id,
1031 file_id,
1032 diag.diagnostic,
1033 diag.fix,
1034 ),
1035 Ok(None) => {}
1036 Err(err) => {
1037 error!(
1038 "flycheck {id}: File with cargo diagnostic not found in VFS: {}",
1039 err
1040 );
1041 }
1042 };
1043 }
1044 }
1045 FlycheckMessage::ClearDiagnostics { id, kind: ClearDiagnosticsKind::All } => {
1046 self.diagnostics.clear_check(id)
1047 }
1048 FlycheckMessage::ClearDiagnostics {
1049 id,
1050 kind: ClearDiagnosticsKind::OlderThan(generation),
1051 } => self.diagnostics.clear_check_older_than(id, generation),
1052 FlycheckMessage::ClearDiagnostics {
1053 id,
1054 kind: ClearDiagnosticsKind::Package(package_id),
1055 } => self.diagnostics.clear_check_for_package(id, package_id),
1056 FlycheckMessage::Progress { id, progress } => {
1057 let (state, message) = match progress {
1058 flycheck::Progress::DidStart => (Progress::Begin, None),
1059 flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)),
1060 flycheck::Progress::DidCancel => {
1061 self.last_flycheck_error = None;
1062 (Progress::End, None)
1063 }
1064 flycheck::Progress::DidFailToRestart(err) => {
1065 self.last_flycheck_error =
1066 Some(format!("cargo check failed to start: {err}"));
1067 return;
1068 }
1069 flycheck::Progress::DidFinish(result) => {
1070 self.last_flycheck_error =
1071 result.err().map(|err| format!("cargo check failed to start: {err}"));
1072 (Progress::End, None)
1073 }
1074 };
1075
1076 let title = if self.flycheck.len() == 1 {
1079 format!("{}", self.config.flycheck(None))
1080 } else {
1081 format!("{} (#{})", self.config.flycheck(None), id + 1)
1082 };
1083 self.report_progress(
1084 &title,
1085 state,
1086 message,
1087 None,
1088 Some(format!("rust-analyzer/flycheck/{id}")),
1089 );
1090 }
1091 }
1092 }
1093
1094 fn on_new_request(&mut self, request_received: Instant, req: Request) {
1096 let _p =
1097 span!(Level::INFO, "GlobalState::on_new_request", req.method = ?req.method).entered();
1098 self.register_request(&req, request_received);
1099 self.on_request(req);
1100 }
1101
1102 fn on_request(&mut self, req: Request) {
1104 let mut dispatcher = RequestDispatcher { req: Some(req), global_state: self };
1105 dispatcher.on_sync_mut::<lsp_types::request::Shutdown>(|s, ()| {
1106 s.shutdown_requested = true;
1107 Ok(())
1108 });
1109
1110 match &mut dispatcher {
1111 RequestDispatcher { req: Some(req), global_state: this } if this.shutdown_requested => {
1112 this.respond(lsp_server::Response::new_err(
1113 req.id.clone(),
1114 lsp_server::ErrorCode::InvalidRequest as i32,
1115 "Shutdown already requested.".to_owned(),
1116 ));
1117 return;
1118 }
1119 _ => (),
1120 }
1121
1122 use crate::handlers::request as handlers;
1123 use lsp_types::request as lsp_request;
1124
1125 const RETRY: bool = true;
1126 const NO_RETRY: bool = false;
1127
1128 #[rustfmt::skip]
1129 dispatcher
1130 .on_sync_mut::<lsp_ext::ReloadWorkspace>(handlers::handle_workspace_reload)
1133 .on_sync_mut::<lsp_ext::RebuildProcMacros>(handlers::handle_proc_macros_rebuild)
1134 .on_sync_mut::<lsp_ext::MemoryUsage>(handlers::handle_memory_usage)
1135 .on_sync_mut::<lsp_ext::RunTest>(handlers::handle_run_test)
1136 .on_sync::<lsp_ext::JoinLines>(handlers::handle_join_lines)
1139 .on_sync::<lsp_ext::OnEnter>(handlers::handle_on_enter)
1140 .on_sync::<lsp_request::SelectionRangeRequest>(handlers::handle_selection_range)
1141 .on_sync::<lsp_ext::MatchingBrace>(handlers::handle_matching_brace)
1142 .on_sync::<lsp_ext::OnTypeFormatting>(handlers::handle_on_type_formatting)
1143 .on_fmt_thread::<lsp_request::Formatting>(handlers::handle_formatting)
1148 .on_fmt_thread::<lsp_request::RangeFormatting>(handlers::handle_range_formatting)
1149 .on_latency_sensitive::<RETRY, lsp_request::Completion>(handlers::handle_completion)
1155 .on_latency_sensitive::<RETRY, lsp_request::ResolveCompletionItem>(handlers::handle_completion_resolve)
1157 .on_latency_sensitive::<RETRY, lsp_request::SemanticTokensFullRequest>(handlers::handle_semantic_tokens_full)
1158 .on_latency_sensitive::<RETRY, lsp_request::SemanticTokensFullDeltaRequest>(handlers::handle_semantic_tokens_full_delta)
1159 .on_latency_sensitive::<NO_RETRY, lsp_request::SemanticTokensRangeRequest>(handlers::handle_semantic_tokens_range)
1160 .on_with_vfs_default::<lsp_request::DocumentDiagnosticRequest>(handlers::handle_document_diagnostics, empty_diagnostic_report, || lsp_server::ResponseError {
1163 code: lsp_server::ErrorCode::ServerCancelled as i32,
1164 message: "server cancelled the request".to_owned(),
1165 data: serde_json::to_value(lsp_types::DiagnosticServerCancellationData {
1166 retrigger_request: true
1167 }).ok(),
1168 })
1169 .on::<RETRY, lsp_request::DocumentSymbolRequest>(handlers::handle_document_symbol)
1170 .on::<RETRY, lsp_request::FoldingRangeRequest>(handlers::handle_folding_range)
1171 .on::<NO_RETRY, lsp_request::SignatureHelpRequest>(handlers::handle_signature_help)
1172 .on::<RETRY, lsp_request::WillRenameFiles>(handlers::handle_will_rename_files)
1173 .on::<NO_RETRY, lsp_request::GotoDefinition>(handlers::handle_goto_definition)
1174 .on::<NO_RETRY, lsp_request::GotoDeclaration>(handlers::handle_goto_declaration)
1175 .on::<NO_RETRY, lsp_request::GotoImplementation>(handlers::handle_goto_implementation)
1176 .on::<NO_RETRY, lsp_request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
1177 .on::<NO_RETRY, lsp_request::InlayHintRequest>(handlers::handle_inlay_hints)
1178 .on_identity::<NO_RETRY, lsp_request::InlayHintResolveRequest, _>(handlers::handle_inlay_hints_resolve)
1179 .on::<NO_RETRY, lsp_request::CodeLensRequest>(handlers::handle_code_lens)
1180 .on_identity::<NO_RETRY, lsp_request::CodeLensResolve, _>(handlers::handle_code_lens_resolve)
1181 .on::<NO_RETRY, lsp_request::PrepareRenameRequest>(handlers::handle_prepare_rename)
1182 .on::<NO_RETRY, lsp_request::Rename>(handlers::handle_rename)
1183 .on::<NO_RETRY, lsp_request::References>(handlers::handle_references)
1184 .on::<NO_RETRY, lsp_request::DocumentHighlightRequest>(handlers::handle_document_highlight)
1185 .on::<NO_RETRY, lsp_request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
1186 .on::<NO_RETRY, lsp_request::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming)
1187 .on::<NO_RETRY, lsp_request::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing)
1188 .on::<RETRY, lsp_ext::FetchDependencyList>(handlers::fetch_dependency_list)
1190 .on::<RETRY, lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
1191 .on::<RETRY, lsp_ext::ViewFileText>(handlers::handle_view_file_text)
1192 .on::<RETRY, lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
1193 .on::<RETRY, lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)
1194 .on::<RETRY, lsp_ext::DiscoverTest>(handlers::handle_discover_test)
1195 .on::<RETRY, lsp_ext::WorkspaceSymbol>(handlers::handle_workspace_symbol)
1196 .on::<NO_RETRY, lsp_ext::Ssr>(handlers::handle_ssr)
1197 .on::<NO_RETRY, lsp_ext::ViewRecursiveMemoryLayout>(handlers::handle_view_recursive_memory_layout)
1198 .on::<NO_RETRY, lsp_ext::ViewSyntaxTree>(handlers::handle_view_syntax_tree)
1199 .on::<NO_RETRY, lsp_ext::ViewHir>(handlers::handle_view_hir)
1200 .on::<NO_RETRY, lsp_ext::ViewMir>(handlers::handle_view_mir)
1201 .on::<NO_RETRY, lsp_ext::InterpretFunction>(handlers::handle_interpret_function)
1202 .on::<NO_RETRY, lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
1203 .on::<NO_RETRY, lsp_ext::ParentModule>(handlers::handle_parent_module)
1204 .on::<NO_RETRY, lsp_ext::ChildModules>(handlers::handle_child_modules)
1205 .on::<NO_RETRY, lsp_ext::Runnables>(handlers::handle_runnables)
1206 .on::<NO_RETRY, lsp_ext::RelatedTests>(handlers::handle_related_tests)
1207 .on::<NO_RETRY, lsp_ext::CodeActionRequest>(handlers::handle_code_action)
1208 .on_identity::<RETRY, lsp_ext::CodeActionResolveRequest, _>(handlers::handle_code_action_resolve)
1209 .on::<NO_RETRY, lsp_ext::HoverRequest>(handlers::handle_hover)
1210 .on::<NO_RETRY, lsp_ext::ExternalDocs>(handlers::handle_open_docs)
1211 .on::<NO_RETRY, lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
1212 .on::<NO_RETRY, lsp_ext::MoveItem>(handlers::handle_move_item)
1213 .on::<NO_RETRY, lsp_ext::InternalTestingFetchConfig>(handlers::internal_testing_fetch_config)
1215 .finish();
1216 }
1217
1218 fn on_notification(&mut self, not: Notification) {
1220 let _p =
1221 span!(Level::INFO, "GlobalState::on_notification", not.method = ?not.method).entered();
1222 use crate::handlers::notification as handlers;
1223 use lsp_types::notification as notifs;
1224
1225 NotificationDispatcher { not: Some(not), global_state: self }
1226 .on_sync_mut::<notifs::Cancel>(handlers::handle_cancel)
1227 .on_sync_mut::<notifs::WorkDoneProgressCancel>(
1228 handlers::handle_work_done_progress_cancel,
1229 )
1230 .on_sync_mut::<notifs::DidOpenTextDocument>(handlers::handle_did_open_text_document)
1231 .on_sync_mut::<notifs::DidChangeTextDocument>(handlers::handle_did_change_text_document)
1232 .on_sync_mut::<notifs::DidCloseTextDocument>(handlers::handle_did_close_text_document)
1233 .on_sync_mut::<notifs::DidSaveTextDocument>(handlers::handle_did_save_text_document)
1234 .on_sync_mut::<notifs::DidChangeConfiguration>(
1235 handlers::handle_did_change_configuration,
1236 )
1237 .on_sync_mut::<notifs::DidChangeWorkspaceFolders>(
1238 handlers::handle_did_change_workspace_folders,
1239 )
1240 .on_sync_mut::<notifs::DidChangeWatchedFiles>(handlers::handle_did_change_watched_files)
1241 .on_sync_mut::<lsp_ext::CancelFlycheck>(handlers::handle_cancel_flycheck)
1242 .on_sync_mut::<lsp_ext::ClearFlycheck>(handlers::handle_clear_flycheck)
1243 .on_sync_mut::<lsp_ext::RunFlycheck>(handlers::handle_run_flycheck)
1244 .on_sync_mut::<lsp_ext::AbortRunTest>(handlers::handle_abort_run_test)
1245 .finish();
1246 }
1247}