1use std::{
7 ops::Not as _,
8 panic::AssertUnwindSafe,
9 time::{Duration, Instant},
10};
11
12use crossbeam_channel::{Receiver, Sender, unbounded};
13use hir::ChangeWithProcMacros;
14use ide::{Analysis, AnalysisHost, Cancellable, FileId, SourceRootId};
15use ide_db::{
16 MiniCore,
17 base_db::{Crate, ProcMacroPaths, SourceDatabase, salsa::Revision},
18};
19use itertools::Itertools;
20use load_cargo::SourceRootConfig;
21use lsp_types::{SemanticTokens, Url};
22use parking_lot::{
23 MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard,
24 RwLockWriteGuard,
25};
26use proc_macro_api::ProcMacroClient;
27use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts};
28use rustc_hash::{FxHashMap, FxHashSet};
29use stdx::thread;
30use tracing::{Level, span, trace};
31use triomphe::Arc;
32use vfs::{AbsPathBuf, AnchoredPathBuf, ChangeKind, Vfs, VfsPath};
33
34use crate::{
35 config::{Config, ConfigChange, ConfigErrors, RatomlFileKind},
36 diagnostics::{CheckFixes, DiagnosticCollection},
37 discover,
38 flycheck::{FlycheckHandle, FlycheckMessage, PackageSpecifier},
39 line_index::{LineEndings, LineIndex},
40 lsp::{from_proto, to_proto::url_from_abs_path},
41 lsp_ext,
42 main_loop::Task,
43 mem_docs::MemDocs,
44 op_queue::{Cause, OpQueue},
45 reload,
46 target_spec::{CargoTargetSpec, ProjectJsonTargetSpec, TargetSpec},
47 task_pool::{DeferredTaskQueue, TaskPool},
48 test_runner::{CargoTestHandle, CargoTestMessage},
49};
50
51#[derive(Debug)]
52pub(crate) struct FetchWorkspaceRequest {
53 pub(crate) path: Option<AbsPathBuf>,
54 pub(crate) force_crate_graph_reload: bool,
55}
56
57pub(crate) struct FetchWorkspaceResponse {
58 pub(crate) workspaces: Vec<anyhow::Result<ProjectWorkspace>>,
59 pub(crate) force_crate_graph_reload: bool,
60}
61
62pub(crate) struct FetchBuildDataResponse {
63 pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
64 pub(crate) build_scripts: Vec<anyhow::Result<WorkspaceBuildScripts>>,
65}
66
67pub(crate) struct Handle<H, C> {
69 pub(crate) handle: H,
70 pub(crate) receiver: C,
71}
72
73pub(crate) type ReqHandler = fn(&mut GlobalState, lsp_server::Response);
74type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
75
76#[doc(alias = "GlobalMess")]
84pub(crate) struct GlobalState {
85 sender: Sender<lsp_server::Message>,
86 req_queue: ReqQueue,
87
88 pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
89 pub(crate) fmt_pool: Handle<TaskPool<Task>, Receiver<Task>>,
90 pub(crate) cancellation_pool: thread::Pool,
91
92 pub(crate) config: Arc<Config>,
93 pub(crate) config_errors: Option<ConfigErrors>,
94 pub(crate) analysis_host: AnalysisHost,
95 pub(crate) diagnostics: DiagnosticCollection,
96 pub(crate) mem_docs: MemDocs,
97 pub(crate) source_root_config: SourceRootConfig,
98 pub(crate) local_roots_parent_map: Arc<FxHashMap<SourceRootId, SourceRootId>>,
100 pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
101
102 pub(crate) shutdown_requested: bool,
104 pub(crate) last_reported_status: lsp_ext::ServerStatusParams,
105
106 pub(crate) proc_macro_clients: Arc<[Option<anyhow::Result<ProcMacroClient>>]>,
108 pub(crate) build_deps_changed: bool,
109
110 pub(crate) flycheck: Arc<[FlycheckHandle]>,
112 pub(crate) flycheck_sender: Sender<FlycheckMessage>,
113 pub(crate) flycheck_receiver: Receiver<FlycheckMessage>,
114 pub(crate) last_flycheck_error: Option<String>,
115 pub(crate) flycheck_formatted_commands: Vec<String>,
116
117 pub(crate) test_run_session: Option<Vec<CargoTestHandle>>,
119 pub(crate) test_run_sender: Sender<CargoTestMessage>,
120 pub(crate) test_run_receiver: Receiver<CargoTestMessage>,
121 pub(crate) test_run_remaining_jobs: usize,
122
123 pub(crate) discover_handles: Vec<discover::DiscoverHandle>,
125 pub(crate) discover_sender: Sender<discover::DiscoverProjectMessage>,
126 pub(crate) discover_receiver: Receiver<discover::DiscoverProjectMessage>,
127 pub(crate) discover_jobs_active: u32,
128
129 pub(crate) fetch_ws_receiver: Option<(Receiver<Instant>, FetchWorkspaceRequest)>,
133
134 pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
136 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
137 pub(crate) vfs_config_version: u32,
138 pub(crate) vfs_progress_config_version: u32,
139 pub(crate) vfs_done: bool,
140 pub(crate) vfs_span: Option<tracing::span::EnteredSpan>,
143 pub(crate) wants_to_switch: Option<Cause>,
144
145 pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
171 pub(crate) crate_graph_file_dependencies: FxHashSet<vfs::VfsPath>,
172 pub(crate) detached_files: FxHashSet<ManifestPath>,
173
174 pub(crate) fetch_workspaces_queue: OpQueue<FetchWorkspaceRequest, FetchWorkspaceResponse>,
176 pub(crate) fetch_build_data_queue: OpQueue<(), FetchBuildDataResponse>,
177 pub(crate) fetch_proc_macros_queue: OpQueue<(ChangeWithProcMacros, Vec<ProcMacroPaths>), bool>,
178 pub(crate) prime_caches_queue: OpQueue,
179
180 pub(crate) deferred_task_queue: DeferredTaskQueue,
190
191 pub(crate) incomplete_crate_graph: bool,
195
196 pub(crate) minicore: MiniCoreRustAnalyzerInternalOnly,
197 pub(crate) last_gc_revision: Revision,
198}
199
200#[derive(Debug, Clone, Default)]
202pub(crate) struct MiniCoreRustAnalyzerInternalOnly {
203 pub(crate) minicore_text: Option<Arc<str>>,
204}
205
206pub(crate) struct GlobalStateSnapshot {
208 pub(crate) config: Arc<Config>,
209 pub(crate) analysis: Analysis,
210 pub(crate) check_fixes: CheckFixes,
211 mem_docs: MemDocs,
212 pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>,
213 vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
214 pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
215 pub(crate) proc_macros_loaded: bool,
219 pub(crate) flycheck: Arc<[FlycheckHandle]>,
220 minicore: MiniCoreRustAnalyzerInternalOnly,
221}
222
223impl std::panic::UnwindSafe for GlobalStateSnapshot {}
224
225impl GlobalState {
226 pub(crate) fn new(sender: Sender<lsp_server::Message>, config: Config) -> GlobalState {
227 let loader = {
228 let (sender, receiver) = unbounded::<vfs::loader::Message>();
229 let handle: vfs_notify::NotifyHandle = vfs::loader::Handle::spawn(sender);
230 let handle = Box::new(handle) as Box<dyn vfs::loader::Handle>;
231 Handle { handle, receiver }
232 };
233
234 let task_pool = {
235 let (sender, receiver) = unbounded();
236 let handle = TaskPool::new_with_threads(sender, config.main_loop_num_threads());
237 Handle { handle, receiver }
238 };
239 let fmt_pool = {
240 let (sender, receiver) = unbounded();
241 let handle = TaskPool::new_with_threads(sender, 1);
242 Handle { handle, receiver }
243 };
244 let cancellation_pool = thread::Pool::new(1);
245
246 let deferred_task_queue = {
247 let (sender, receiver) = unbounded();
248 DeferredTaskQueue { sender, receiver }
249 };
250
251 let mut analysis_host = AnalysisHost::new(config.lru_parse_query_capacity());
252 if let Some(capacities) = config.lru_query_capacities_config() {
253 analysis_host.update_lru_capacities(capacities);
254 }
255 let (flycheck_sender, flycheck_receiver) = unbounded();
256 let (test_run_sender, test_run_receiver) = unbounded();
257
258 let (discover_sender, discover_receiver) = unbounded();
259
260 let last_gc_revision = analysis_host.raw_database().nonce_and_revision().1;
261
262 let mut this = GlobalState {
263 sender,
264 req_queue: ReqQueue::default(),
265 task_pool,
266 fmt_pool,
267 cancellation_pool,
268 loader,
269 config: Arc::new(config.clone()),
270 analysis_host,
271 diagnostics: Default::default(),
272 mem_docs: MemDocs::default(),
273 semantic_tokens_cache: Arc::new(Default::default()),
274 shutdown_requested: false,
275 last_reported_status: lsp_ext::ServerStatusParams {
276 health: lsp_ext::Health::Ok,
277 quiescent: true,
278 message: None,
279 },
280 source_root_config: SourceRootConfig::default(),
281 local_roots_parent_map: Arc::new(FxHashMap::default()),
282 config_errors: Default::default(),
283
284 proc_macro_clients: Arc::from_iter([]),
285
286 build_deps_changed: false,
287
288 flycheck: Arc::from_iter([]),
289 flycheck_sender,
290 flycheck_receiver,
291 last_flycheck_error: None,
292 flycheck_formatted_commands: vec![],
293
294 test_run_session: None,
295 test_run_sender,
296 test_run_receiver,
297 test_run_remaining_jobs: 0,
298
299 discover_handles: vec![],
300 discover_sender,
301 discover_receiver,
302 discover_jobs_active: 0,
303
304 fetch_ws_receiver: None,
305
306 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), Default::default()))),
307 vfs_config_version: 0,
308 vfs_progress_config_version: 0,
309 vfs_span: None,
310 vfs_done: true,
311 wants_to_switch: None,
312
313 workspaces: Arc::from(Vec::new()),
314 crate_graph_file_dependencies: FxHashSet::default(),
315 detached_files: FxHashSet::default(),
316 fetch_workspaces_queue: OpQueue::default(),
317 fetch_build_data_queue: OpQueue::default(),
318 fetch_proc_macros_queue: OpQueue::default(),
319
320 prime_caches_queue: OpQueue::default(),
321
322 deferred_task_queue,
323 incomplete_crate_graph: false,
324
325 minicore: MiniCoreRustAnalyzerInternalOnly::default(),
326 last_gc_revision,
327 };
328 this.update_configuration(config);
330 this
331 }
332
333 pub(crate) fn process_changes(&mut self) -> bool {
334 let _p = span!(Level::INFO, "GlobalState::process_changes").entered();
335 let mut modified_ratoml_files: FxHashMap<FileId, (ChangeKind, vfs::VfsPath)> =
340 FxHashMap::default();
341
342 let mut change = ChangeWithProcMacros::default();
343 let mut guard = self.vfs.write();
344 let changed_files = guard.0.take_changes();
345 if changed_files.is_empty() {
346 return false;
347 }
348
349 let (change, modified_rust_files, workspace_structure_change) =
350 self.cancellation_pool.scoped(|s| {
351 let analysis_host = AssertUnwindSafe(&mut self.analysis_host);
354 s.spawn(thread::ThreadIntent::LatencySensitive, || {
355 { analysis_host }.0.trigger_cancellation()
356 });
357
358 let guard = RwLockWriteGuard::downgrade_to_upgradable(guard);
360 let vfs: &Vfs = &guard.0;
361
362 let mut workspace_structure_change = None;
363 let mut has_structure_changes = false;
365 let mut bytes = vec![];
366 let mut modified_rust_files = vec![];
367 for file in changed_files.into_values() {
368 let vfs_path = vfs.file_path(file.file_id);
369 if let Some(("rust-analyzer", Some("toml"))) = vfs_path.name_and_extension() {
370 modified_ratoml_files.insert(file.file_id, (file.kind(), vfs_path.clone()));
372 }
373
374 if let Some(path) = vfs_path.as_path() {
375 has_structure_changes |= file.is_created_or_deleted();
376
377 if file.is_modified() && path.extension() == Some("rs") {
378 modified_rust_files.push(file.file_id);
379 }
380
381 let additional_files = self
382 .config
383 .discover_workspace_config()
384 .map(|cfg| {
385 cfg.files_to_watch.iter().map(String::as_str).collect::<Vec<&str>>()
386 })
387 .unwrap_or_default();
388
389 let path = path.to_path_buf();
390 if file.is_created_or_deleted() {
391 workspace_structure_change.get_or_insert((path, false)).1 |=
392 self.crate_graph_file_dependencies.contains(vfs_path);
393 } else if reload::should_refresh_for_change(
394 &path,
395 file.kind(),
396 &additional_files,
397 ) {
398 trace!(?path, kind = ?file.kind(), "refreshing for a change");
399 workspace_structure_change.get_or_insert((path.clone(), false));
400 }
401 }
402
403 if !file.exists() {
405 self.diagnostics.clear_native_for(file.file_id);
406 }
407
408 let text = if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) =
409 file.change
410 {
411 String::from_utf8(v).ok().map(|text| {
412 let (text, line_endings) = LineEndings::normalize(text);
415 (text, line_endings)
416 })
417 } else {
418 None
419 };
420 bytes.push((file.file_id, text));
423 }
424 let (vfs, line_endings_map) = &mut *RwLockUpgradableReadGuard::upgrade(guard);
425 bytes.into_iter().for_each(|(file_id, text)| {
426 let text = match text {
427 None => None,
428 Some((text, line_endings)) => {
429 line_endings_map.insert(file_id, line_endings);
430 Some(text)
431 }
432 };
433 change.change_file(file_id, text);
434 });
435 if has_structure_changes {
436 let roots = self.source_root_config.partition(vfs);
437 change.set_roots(roots);
438 }
439 (change, modified_rust_files, workspace_structure_change)
440 });
441
442 self.analysis_host.apply_change(change);
443
444 if !modified_ratoml_files.is_empty()
445 || !self.config.same_source_root_parent_map(&self.local_roots_parent_map)
446 {
447 let config_change = {
448 let _p = span!(Level::INFO, "GlobalState::process_changes/config_change").entered();
449 let user_config_path = (|| {
450 let mut p = Config::user_config_dir_path()?;
451 p.push("rust-analyzer.toml");
452 Some(p)
453 })();
454
455 let user_config_abs_path = user_config_path.as_deref();
456
457 let mut change = ConfigChange::default();
458 let db = self.analysis_host.raw_database();
459
460 let workspace_ratoml_paths = self
463 .workspaces
464 .iter()
465 .map(|ws| {
466 VfsPath::from({
467 let mut p = ws.workspace_root().to_owned();
468 p.push("rust-analyzer.toml");
469 p
470 })
471 })
472 .collect_vec();
473
474 for (file_id, (change_kind, vfs_path)) in modified_ratoml_files {
475 tracing::info!(%vfs_path, ?change_kind, "Processing rust-analyzer.toml changes");
476 if vfs_path.as_path() == user_config_abs_path {
477 tracing::info!(%vfs_path, ?change_kind, "Use config rust-analyzer.toml changes");
478 change.change_user_config(Some(db.file_text(file_id).text(db).clone()));
479 }
480
481 let source_root_id = db.file_source_root(file_id).source_root_id(db);
484 let source_root = db.source_root(source_root_id).source_root(db);
485
486 if !source_root.is_library {
487 let entry = if workspace_ratoml_paths.contains(&vfs_path) {
488 tracing::info!(%vfs_path, ?source_root_id, "workspace rust-analyzer.toml changes");
489 change.change_workspace_ratoml(
490 source_root_id,
491 vfs_path.clone(),
492 Some(db.file_text(file_id).text(db).clone()),
493 )
494 } else {
495 tracing::info!(%vfs_path, ?source_root_id, "crate rust-analyzer.toml changes");
496 change.change_ratoml(
497 source_root_id,
498 vfs_path.clone(),
499 Some(db.file_text(file_id).text(db).clone()),
500 )
501 };
502
503 if let Some((kind, old_path, old_text)) = entry {
504 if old_path < vfs_path {
506 tracing::error!(
507 "Two `rust-analyzer.toml` files were found inside the same crate. {vfs_path} has no effect."
508 );
509 match kind {
511 RatomlFileKind::Crate => {
512 change.change_ratoml(source_root_id, old_path, old_text);
513 }
514 RatomlFileKind::Workspace => {
515 change.change_workspace_ratoml(
516 source_root_id,
517 old_path,
518 old_text,
519 );
520 }
521 }
522 }
523 }
524 } else {
525 tracing::info!(%vfs_path, "Ignoring library rust-analyzer.toml");
526 }
527 }
528 change.change_source_root_parent_map(self.local_roots_parent_map.clone());
529 change
530 };
531
532 let (config, e, should_update) = self.config.apply_change(config_change);
533 self.config_errors = e.is_empty().not().then_some(e);
534
535 if should_update {
536 self.update_configuration(config);
537 } else {
538 self.config = Arc::new(config);
540 }
541 }
542
543 {
549 if !matches!(&workspace_structure_change, Some((.., true))) {
550 _ = self.deferred_task_queue.sender.send(
551 crate::main_loop::DeferredTask::CheckProcMacroSources(modified_rust_files),
552 );
553 }
554 if let Some((path, force_crate_graph_reload)) = workspace_structure_change {
558 let _p = span!(Level::INFO, "GlobalState::process_changes/ws_structure_change")
559 .entered();
560 self.enqueue_workspace_fetch(path, force_crate_graph_reload);
561 }
562 }
563
564 true
565 }
566
567 pub(crate) fn snapshot(&self) -> GlobalStateSnapshot {
568 GlobalStateSnapshot {
569 config: Arc::clone(&self.config),
570 workspaces: Arc::clone(&self.workspaces),
571 analysis: self.analysis_host.analysis(),
572 vfs: Arc::clone(&self.vfs),
573 minicore: self.minicore.clone(),
574 check_fixes: Arc::clone(&self.diagnostics.check_fixes),
575 mem_docs: self.mem_docs.clone(),
576 semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
577 proc_macros_loaded: !self.config.expand_proc_macros()
578 || self.fetch_proc_macros_queue.last_op_result().copied().unwrap_or(false),
579 flycheck: self.flycheck.clone(),
580 }
581 }
582
583 pub(crate) fn send_request<R: lsp_types::request::Request>(
584 &mut self,
585 params: R::Params,
586 handler: ReqHandler,
587 ) {
588 let request = self.req_queue.outgoing.register(R::METHOD.to_owned(), params, handler);
589 self.send(request.into());
590 }
591
592 pub(crate) fn complete_request(&mut self, response: lsp_server::Response) {
593 let handler = self
594 .req_queue
595 .outgoing
596 .complete(response.id.clone())
597 .expect("received response for unknown request");
598 handler(self, response)
599 }
600
601 pub(crate) fn send_notification<N: lsp_types::notification::Notification>(
602 &self,
603 params: N::Params,
604 ) {
605 let not = lsp_server::Notification::new(N::METHOD.to_owned(), params);
606 self.send(not.into());
607 }
608
609 pub(crate) fn register_request(
610 &mut self,
611 request: &lsp_server::Request,
612 request_received: Instant,
613 ) {
614 self.req_queue
615 .incoming
616 .register(request.id.clone(), (request.method.clone(), request_received));
617 }
618
619 pub(crate) fn respond(&mut self, response: lsp_server::Response) {
620 if let Some((method, start)) = self.req_queue.incoming.complete(&response.id) {
621 if let Some(err) = &response.error
622 && err.message.starts_with("server panicked")
623 {
624 self.poke_rust_analyzer_developer(format!("{}, check the log", err.message));
625 }
626
627 let duration = start.elapsed();
628 tracing::debug!(name: "message response", method, %response.id, duration = format_args!("{:0.2?}", duration));
629 self.send(response.into());
630 }
631 }
632
633 pub(crate) fn cancel(&mut self, request_id: lsp_server::RequestId) {
634 if let Some(response) = self.req_queue.incoming.cancel(request_id) {
635 self.send(response.into());
636 }
637 }
638
639 pub(crate) fn is_completed(&self, request: &lsp_server::Request) -> bool {
640 self.req_queue.incoming.is_completed(&request.id)
641 }
642
643 #[track_caller]
644 fn send(&self, message: lsp_server::Message) {
645 self.sender.send(message).unwrap();
646 }
647
648 pub(crate) fn publish_diagnostics(
649 &mut self,
650 uri: Url,
651 version: Option<i32>,
652 mut diagnostics: Vec<lsp_types::Diagnostic>,
653 ) {
654 self.task_pool.handle.spawn_with_sender(stdx::thread::ThreadIntent::Worker, {
656 let sender = self.sender.clone();
657 move |_| {
658 let patch_empty = |message: &mut String| {
668 if message.is_empty() {
669 " ".clone_into(message);
670 }
671 };
672
673 for d in &mut diagnostics {
674 patch_empty(&mut d.message);
675 if let Some(dri) = &mut d.related_information {
676 for dri in dri {
677 patch_empty(&mut dri.message);
678 }
679 }
680 }
681
682 let not = lsp_server::Notification::new(
683 <lsp_types::notification::PublishDiagnostics as lsp_types::notification::Notification>::METHOD.to_owned(),
684 lsp_types::PublishDiagnosticsParams { uri, diagnostics, version },
685 );
686 _ = sender.send(not.into());
687 }
688 });
689 }
690
691 pub(crate) fn check_workspaces_msrv(&self) -> impl Iterator<Item = String> + '_ {
692 self.workspaces.iter().filter_map(|ws| {
693 if let Some(toolchain) = &ws.toolchain
694 && *toolchain < crate::MINIMUM_SUPPORTED_TOOLCHAIN_VERSION
695 {
696 return Some(format!(
697 "Workspace `{}` is using an outdated toolchain version `{}` but \
698 rust-analyzer only supports `{}` and higher.\n\
699 Consider using the rust-analyzer rustup component for your toolchain or
700 upgrade your toolchain to a supported version.\n\n",
701 ws.manifest_or_root(),
702 toolchain,
703 crate::MINIMUM_SUPPORTED_TOOLCHAIN_VERSION,
704 ));
705 }
706 None
707 })
708 }
709
710 fn enqueue_workspace_fetch(&mut self, path: AbsPathBuf, force_crate_graph_reload: bool) {
711 let already_requested = self.fetch_workspaces_queue.op_requested()
712 && !self.fetch_workspaces_queue.op_in_progress();
713 if self.fetch_ws_receiver.is_none() && already_requested {
714 return;
720 }
721
722 self.fetch_ws_receiver = Some((
723 crossbeam_channel::after(Duration::from_millis(100)),
724 FetchWorkspaceRequest { path: Some(path), force_crate_graph_reload },
725 ));
726 }
727
728 pub(crate) fn debounce_workspace_fetch(&mut self) {
729 if let Some((fetch_receiver, _)) = &mut self.fetch_ws_receiver {
730 *fetch_receiver = crossbeam_channel::after(Duration::from_millis(100));
731 }
732 }
733}
734
735impl Drop for GlobalState {
736 fn drop(&mut self) {
737 self.analysis_host.trigger_cancellation();
738 }
739}
740
741impl GlobalStateSnapshot {
742 fn vfs_read(&self) -> MappedRwLockReadGuard<'_, vfs::Vfs> {
743 RwLockReadGuard::map(self.vfs.read(), |(it, _)| it)
744 }
745
746 pub(crate) fn url_to_file_id(&self, url: &Url) -> anyhow::Result<Option<FileId>> {
748 url_to_file_id(&self.vfs_read(), url)
749 }
750
751 pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
752 file_id_to_url(&self.vfs_read(), id)
753 }
754
755 pub(crate) fn vfs_path_to_file_id(&self, vfs_path: &VfsPath) -> anyhow::Result<Option<FileId>> {
757 vfs_path_to_file_id(&self.vfs_read(), vfs_path)
758 }
759
760 pub(crate) fn file_line_index(&self, file_id: FileId) -> Cancellable<LineIndex> {
761 let endings = self.vfs.read().1[&file_id];
762 let index = self.analysis.file_line_index(file_id)?;
763 let res = LineIndex { index, endings, encoding: self.config.caps().negotiated_encoding() };
764 Ok(res)
765 }
766
767 pub(crate) fn file_version(&self, file_id: FileId) -> Option<i32> {
768 Some(self.mem_docs.get(self.vfs_read().file_path(file_id))?.version)
769 }
770
771 pub(crate) fn url_file_version(&self, url: &Url) -> Option<i32> {
772 let path = from_proto::vfs_path(url).ok()?;
773 Some(self.mem_docs.get(&path)?.version)
774 }
775
776 pub(crate) fn anchored_path(&self, path: &AnchoredPathBuf) -> Url {
777 let mut base = self.vfs_read().file_path(path.anchor).clone();
778 base.pop();
779 let path = base.join(&path.path).unwrap();
780 let path = path.as_path().unwrap();
781 url_from_abs_path(path)
782 }
783
784 pub(crate) fn file_id_to_file_path(&self, file_id: FileId) -> vfs::VfsPath {
785 self.vfs_read().file_path(file_id).clone()
786 }
787
788 pub(crate) fn target_spec_for_crate(&self, crate_id: Crate) -> Option<TargetSpec> {
789 let file_id = self.analysis.crate_root(crate_id).ok()?;
790 self.target_spec_for_file(file_id, crate_id)
791 }
792
793 pub(crate) fn target_spec_for_file(
794 &self,
795 file_id: FileId,
796 crate_id: Crate,
797 ) -> Option<TargetSpec> {
798 let path = self.vfs_read().file_path(file_id).clone();
799 let path = path.as_path()?;
800
801 for workspace in self.workspaces.iter() {
802 match &workspace.kind {
803 ProjectWorkspaceKind::Cargo { cargo, .. }
804 | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => {
805 let Some(target_idx) = cargo.target_by_root(path) else {
806 continue;
807 };
808
809 let target_data = &cargo[target_idx];
810 let package_data = &cargo[target_data.package];
811
812 return Some(TargetSpec::Cargo(CargoTargetSpec {
813 workspace_root: cargo.workspace_root().to_path_buf(),
814 cargo_toml: package_data.manifest.clone(),
815 crate_id,
816 package: cargo.package_flag(package_data),
817 package_id: package_data.id.clone(),
818 target: target_data.name.clone(),
819 target_kind: target_data.kind,
820 required_features: target_data.required_features.clone(),
821 features: package_data.features.keys().cloned().collect(),
822 sysroot_root: workspace.sysroot.root().map(ToOwned::to_owned),
823 }));
824 }
825 ProjectWorkspaceKind::Json(project) => {
826 let Some(krate) = project.crate_by_root(path) else {
827 continue;
828 };
829 let Some(build) = krate.build.clone() else {
830 continue;
831 };
832
833 return Some(TargetSpec::ProjectJson(ProjectJsonTargetSpec {
834 label: build.label,
835 target_kind: build.target_kind,
836 shell_runnables: project.runnables().to_owned(),
837 project_root: project.project_root().to_owned(),
838 }));
839 }
840 ProjectWorkspaceKind::DetachedFile { .. } => {}
841 };
842 }
843
844 None
845 }
846
847 pub(crate) fn all_workspace_dependencies_for_package(
848 &self,
849 package: &PackageSpecifier,
850 ) -> Option<FxHashSet<PackageSpecifier>> {
851 match package {
852 PackageSpecifier::Cargo { package_id } => {
853 self.workspaces.iter().find_map(|workspace| match &workspace.kind {
854 ProjectWorkspaceKind::Cargo { cargo, .. }
855 | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. } => {
856 let package = cargo.packages().find(|p| cargo[*p].id == *package_id)?;
857
858 cargo[package].all_member_deps.as_ref().map(|deps| {
859 deps.iter()
860 .map(|dep| cargo[*dep].id.clone())
861 .map(|p| PackageSpecifier::Cargo { package_id: p })
862 .collect()
863 })
864 }
865 _ => None,
866 })
867 }
868 PackageSpecifier::BuildInfo { label } => {
869 self.workspaces.iter().find_map(|workspace| match &workspace.kind {
870 ProjectWorkspaceKind::Json(p) => {
871 let krate = p.crate_by_label(label)?;
872 Some(
873 krate
874 .iter_deps()
875 .filter_map(|dep| p[dep].build.as_ref())
876 .map(|build| PackageSpecifier::BuildInfo {
877 label: build.label.clone(),
878 })
879 .collect(),
880 )
881 }
882 _ => None,
883 })
884 }
885 }
886 }
887
888 pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
889 self.vfs.read().0.exists(file_id)
890 }
891
892 #[inline]
893 pub(crate) fn minicore(&self) -> MiniCore<'_> {
894 match &self.minicore.minicore_text {
895 Some(minicore) => MiniCore::new(minicore),
896 None => MiniCore::default(),
897 }
898 }
899}
900
901pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
902 let path = vfs.file_path(id);
903 let path = path.as_path().unwrap();
904 url_from_abs_path(path)
905}
906
907pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> anyhow::Result<Option<FileId>> {
909 let path = from_proto::vfs_path(url)?;
910 vfs_path_to_file_id(vfs, &path)
911}
912
913pub(crate) fn vfs_path_to_file_id(
915 vfs: &vfs::Vfs,
916 vfs_path: &VfsPath,
917) -> anyhow::Result<Option<FileId>> {
918 let (file_id, excluded) =
919 vfs.file_id(vfs_path).ok_or_else(|| anyhow::format_err!("file not found: {vfs_path}"))?;
920 match excluded {
921 vfs::FileExcluded::Yes => Ok(None),
922 vfs::FileExcluded::No => Ok(Some(file_id)),
923 }
924}