1use std::{path::PathBuf, str::FromStr, sync::Arc};
2
3use anyhow::{Context as _, Result, bail};
4
5use async_trait::async_trait;
6use collections::{BTreeMap, IndexSet};
7use fs::Fs;
8use gpui::{
9 App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
10};
11use language::{
12 LanguageName, LanguageRegistry, LanguageToolchainStore, ManifestDelegate, Toolchain,
13 ToolchainList, ToolchainScope,
14};
15use rpc::{
16 AnyProtoClient, TypedEnvelope,
17 proto::{
18 self, ResolveToolchainResponse,
19 resolve_toolchain_response::Response as ResolveResponsePayload,
20 },
21};
22use settings::WorktreeId;
23use task::Shell;
24use util::{ResultExt as _, rel_path::RelPath};
25
26use crate::{
27 ProjectEnvironment, ProjectPath,
28 manifest_tree::{ManifestQueryDelegate, ManifestTree},
29 worktree_store::WorktreeStore,
30};
31
32pub struct ToolchainStore {
33 mode: ToolchainStoreInner,
34 user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,
35 worktree_store: Entity<WorktreeStore>,
36 _sub: Subscription,
37}
38
39enum ToolchainStoreInner {
40 Local(Entity<LocalToolchainStore>),
41 Remote(Entity<RemoteToolchainStore>),
42}
43
44pub struct Toolchains {
45 /// Auto-detected toolchains.
46 pub toolchains: ToolchainList,
47 /// Path of the project root at which we ran the automatic toolchain detection.
48 pub root_path: Arc<RelPath>,
49 pub user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,
50}
51impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
52impl ToolchainStore {
53 pub fn init(client: &AnyProtoClient) {
54 client.add_entity_request_handler(Self::handle_activate_toolchain);
55 client.add_entity_request_handler(Self::handle_list_toolchains);
56 client.add_entity_request_handler(Self::handle_active_toolchain);
57 client.add_entity_request_handler(Self::handle_resolve_toolchain);
58 }
59
60 pub fn local(
61 languages: Arc<LanguageRegistry>,
62 worktree_store: Entity<WorktreeStore>,
63 project_environment: Entity<ProjectEnvironment>,
64 manifest_tree: Entity<ManifestTree>,
65 fs: Arc<dyn Fs>,
66 cx: &mut Context<Self>,
67 ) -> Self {
68 let entity = cx.new(|_| LocalToolchainStore {
69 languages,
70 worktree_store: worktree_store.clone(),
71 project_environment,
72 active_toolchains: Default::default(),
73 manifest_tree,
74 fs,
75 });
76 let _sub = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {
77 cx.emit(e.clone())
78 });
79 Self {
80 mode: ToolchainStoreInner::Local(entity),
81 worktree_store,
82 user_toolchains: Default::default(),
83 _sub,
84 }
85 }
86
87 pub(super) fn remote(
88 project_id: u64,
89 worktree_store: Entity<WorktreeStore>,
90 client: AnyProtoClient,
91 cx: &mut Context<Self>,
92 ) -> Self {
93 let entity = cx.new(|_| RemoteToolchainStore { client, project_id });
94 let _sub = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {
95 cx.emit(e.clone())
96 });
97 Self {
98 mode: ToolchainStoreInner::Remote(entity),
99 user_toolchains: Default::default(),
100 worktree_store,
101 _sub,
102 }
103 }
104 pub(crate) fn activate_toolchain(
105 &self,
106 path: ProjectPath,
107 toolchain: Toolchain,
108 cx: &mut App,
109 ) -> Task<Option<()>> {
110 match &self.mode {
111 ToolchainStoreInner::Local(local) => {
112 local.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
113 }
114 ToolchainStoreInner::Remote(remote) => {
115 remote.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
116 }
117 }
118 }
119
120 pub(crate) fn user_toolchains(&self) -> BTreeMap<ToolchainScope, IndexSet<Toolchain>> {
121 self.user_toolchains.clone()
122 }
123 pub(crate) fn add_toolchain(
124 &mut self,
125 toolchain: Toolchain,
126 scope: ToolchainScope,
127 cx: &mut Context<Self>,
128 ) {
129 let did_insert = self
130 .user_toolchains
131 .entry(scope)
132 .or_default()
133 .insert(toolchain);
134 if did_insert {
135 cx.emit(ToolchainStoreEvent::CustomToolchainsModified);
136 }
137 }
138
139 pub(crate) fn remove_toolchain(
140 &mut self,
141 toolchain: Toolchain,
142 scope: ToolchainScope,
143 cx: &mut Context<Self>,
144 ) {
145 let mut did_remove = false;
146 self.user_toolchains
147 .entry(scope)
148 .and_modify(|toolchains| did_remove = toolchains.shift_remove(&toolchain));
149 if did_remove {
150 cx.emit(ToolchainStoreEvent::CustomToolchainsModified);
151 }
152 }
153
154 pub(crate) fn resolve_toolchain(
155 &self,
156 abs_path: PathBuf,
157 language_name: LanguageName,
158 cx: &mut Context<Self>,
159 ) -> Task<Result<Toolchain>> {
160 debug_assert!(abs_path.is_absolute());
161 match &self.mode {
162 ToolchainStoreInner::Local(local) => local.update(cx, |this, cx| {
163 this.resolve_toolchain(abs_path, language_name, cx)
164 }),
165 ToolchainStoreInner::Remote(remote) => remote.update(cx, |this, cx| {
166 this.resolve_toolchain(abs_path, language_name, cx)
167 }),
168 }
169 }
170 pub(crate) fn list_toolchains(
171 &self,
172 path: ProjectPath,
173 language_name: LanguageName,
174 cx: &mut Context<Self>,
175 ) -> Task<Option<Toolchains>> {
176 let Some(worktree) = self
177 .worktree_store
178 .read(cx)
179 .worktree_for_id(path.worktree_id, cx)
180 else {
181 return Task::ready(None);
182 };
183 let target_root_path = worktree.read_with(cx, |this, _| this.abs_path());
184
185 let user_toolchains = self
186 .user_toolchains
187 .iter()
188 .filter(|(scope, _)| {
189 if let ToolchainScope::Subproject(subproject_root_path, relative_path) = scope {
190 target_root_path == *subproject_root_path
191 && relative_path.starts_with(&path.path)
192 } else {
193 true
194 }
195 })
196 .map(|(scope, toolchains)| {
197 (
198 scope.clone(),
199 toolchains
200 .iter()
201 .filter(|toolchain| toolchain.language_name == language_name)
202 .cloned()
203 .collect::<IndexSet<_>>(),
204 )
205 })
206 .collect::<BTreeMap<_, _>>();
207 let task = match &self.mode {
208 ToolchainStoreInner::Local(local) => {
209 local.update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
210 }
211 ToolchainStoreInner::Remote(remote) => {
212 remote.read(cx).list_toolchains(path, language_name, cx)
213 }
214 };
215 cx.spawn(async move |_, _| {
216 let (mut toolchains, root_path) = task.await?;
217 toolchains.toolchains.retain(|toolchain| {
218 !user_toolchains
219 .values()
220 .any(|toolchains| toolchains.contains(toolchain))
221 });
222
223 Some(Toolchains {
224 toolchains,
225 root_path,
226 user_toolchains,
227 })
228 })
229 }
230
231 pub(crate) fn active_toolchain(
232 &self,
233 path: ProjectPath,
234 language_name: LanguageName,
235 cx: &App,
236 ) -> Task<Option<Toolchain>> {
237 match &self.mode {
238 ToolchainStoreInner::Local(local) => Task::ready(local.read(cx).active_toolchain(
239 path.worktree_id,
240 &path.path,
241 language_name,
242 )),
243 ToolchainStoreInner::Remote(remote) => {
244 remote.read(cx).active_toolchain(path, language_name, cx)
245 }
246 }
247 }
248 async fn handle_activate_toolchain(
249 this: Entity<Self>,
250 envelope: TypedEnvelope<proto::ActivateToolchain>,
251 mut cx: AsyncApp,
252 ) -> Result<proto::Ack> {
253 this.update(&mut cx, |this, cx| {
254 let language_name = LanguageName::from_proto(envelope.payload.language_name);
255 let Some(toolchain) = envelope.payload.toolchain else {
256 bail!("Missing `toolchain` in payload");
257 };
258 let toolchain = Toolchain {
259 name: toolchain.name.into(),
260 // todo(windows)
261 // Do we need to convert path to native string?
262 path: toolchain.path.into(),
263 as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
264 language_name,
265 };
266 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
267 let path = if let Some(path) = envelope.payload.path {
268 RelPath::from_proto(&path)?
269 } else {
270 RelPath::empty().into()
271 };
272 Ok(this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx))
273 })??
274 .await;
275 Ok(proto::Ack {})
276 }
277 async fn handle_active_toolchain(
278 this: Entity<Self>,
279 envelope: TypedEnvelope<proto::ActiveToolchain>,
280 mut cx: AsyncApp,
281 ) -> Result<proto::ActiveToolchainResponse> {
282 let path = RelPath::unix(envelope.payload.path.as_deref().unwrap_or(""))?;
283 let toolchain = this
284 .update(&mut cx, |this, cx| {
285 let language_name = LanguageName::from_proto(envelope.payload.language_name);
286 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
287 this.active_toolchain(
288 ProjectPath {
289 worktree_id,
290 path: Arc::from(path),
291 },
292 language_name,
293 cx,
294 )
295 })?
296 .await;
297
298 Ok(proto::ActiveToolchainResponse {
299 toolchain: toolchain.map(|toolchain| {
300 let path = PathBuf::from(toolchain.path.to_string());
301 proto::Toolchain {
302 name: toolchain.name.into(),
303 path: path.to_string_lossy().into_owned(),
304 raw_json: toolchain.as_json.to_string(),
305 }
306 }),
307 })
308 }
309
310 async fn handle_list_toolchains(
311 this: Entity<Self>,
312 envelope: TypedEnvelope<proto::ListToolchains>,
313 mut cx: AsyncApp,
314 ) -> Result<proto::ListToolchainsResponse> {
315 let toolchains = this
316 .update(&mut cx, |this, cx| {
317 let language_name = LanguageName::from_proto(envelope.payload.language_name);
318 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
319 let path = RelPath::from_proto(envelope.payload.path.as_deref().unwrap_or(""))?;
320 anyhow::Ok(this.list_toolchains(
321 ProjectPath { worktree_id, path },
322 language_name,
323 cx,
324 ))
325 })??
326 .await;
327 let has_values = toolchains.is_some();
328 let groups = if let Some(Toolchains { toolchains, .. }) = &toolchains {
329 toolchains
330 .groups
331 .iter()
332 .filter_map(|group| {
333 Some(proto::ToolchainGroup {
334 start_index: u64::try_from(group.0).ok()?,
335 name: String::from(group.1.as_ref()),
336 })
337 })
338 .collect()
339 } else {
340 vec![]
341 };
342 let (toolchains, relative_path) = if let Some(Toolchains {
343 toolchains,
344 root_path: relative_path,
345 ..
346 }) = toolchains
347 {
348 let toolchains = toolchains
349 .toolchains
350 .into_iter()
351 .map(|toolchain| {
352 let path = PathBuf::from(toolchain.path.to_string());
353 proto::Toolchain {
354 name: toolchain.name.to_string(),
355 path: path.to_string_lossy().into_owned(),
356 raw_json: toolchain.as_json.to_string(),
357 }
358 })
359 .collect::<Vec<_>>();
360 (toolchains, relative_path)
361 } else {
362 (vec![], Arc::from(RelPath::empty()))
363 };
364
365 Ok(proto::ListToolchainsResponse {
366 has_values,
367 toolchains,
368 groups,
369 relative_worktree_path: Some(relative_path.to_proto()),
370 })
371 }
372
373 async fn handle_resolve_toolchain(
374 this: Entity<Self>,
375 envelope: TypedEnvelope<proto::ResolveToolchain>,
376 mut cx: AsyncApp,
377 ) -> Result<proto::ResolveToolchainResponse> {
378 let toolchain = this
379 .update(&mut cx, |this, cx| {
380 let language_name = LanguageName::from_proto(envelope.payload.language_name);
381 let path = PathBuf::from(envelope.payload.abs_path);
382 this.resolve_toolchain(path, language_name, cx)
383 })?
384 .await;
385 let response = match toolchain {
386 Ok(toolchain) => {
387 let toolchain = proto::Toolchain {
388 name: toolchain.name.to_string(),
389 path: toolchain.path.to_string(),
390 raw_json: toolchain.as_json.to_string(),
391 };
392 ResolveResponsePayload::Toolchain(toolchain)
393 }
394 Err(e) => ResolveResponsePayload::Error(e.to_string()),
395 };
396 Ok(ResolveToolchainResponse {
397 response: Some(response),
398 })
399 }
400
401 pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
402 match &self.mode {
403 ToolchainStoreInner::Local(local) => Arc::new(LocalStore(local.downgrade())),
404 ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
405 }
406 }
407 pub fn as_local_store(&self) -> Option<&Entity<LocalToolchainStore>> {
408 match &self.mode {
409 ToolchainStoreInner::Local(local) => Some(local),
410 ToolchainStoreInner::Remote(_) => None,
411 }
412 }
413}
414
415pub struct LocalToolchainStore {
416 languages: Arc<LanguageRegistry>,
417 worktree_store: Entity<WorktreeStore>,
418 project_environment: Entity<ProjectEnvironment>,
419 active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<RelPath>, Toolchain>>,
420 manifest_tree: Entity<ManifestTree>,
421 fs: Arc<dyn Fs>,
422}
423
424#[async_trait(?Send)]
425impl language::LocalLanguageToolchainStore for LocalStore {
426 fn active_toolchain(
427 self: Arc<Self>,
428 worktree_id: WorktreeId,
429 path: &Arc<RelPath>,
430 language_name: LanguageName,
431 cx: &mut AsyncApp,
432 ) -> Option<Toolchain> {
433 self.0
434 .update(cx, |this, _| {
435 this.active_toolchain(worktree_id, path, language_name)
436 })
437 .ok()?
438 }
439}
440
441#[async_trait(?Send)]
442impl language::LanguageToolchainStore for RemoteStore {
443 async fn active_toolchain(
444 self: Arc<Self>,
445 worktree_id: WorktreeId,
446 path: Arc<RelPath>,
447 language_name: LanguageName,
448 cx: &mut AsyncApp,
449 ) -> Option<Toolchain> {
450 self.0
451 .update(cx, |this, cx| {
452 this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)
453 })
454 .ok()?
455 .await
456 }
457}
458
459pub struct EmptyToolchainStore;
460impl language::LocalLanguageToolchainStore for EmptyToolchainStore {
461 fn active_toolchain(
462 self: Arc<Self>,
463 _: WorktreeId,
464 _: &Arc<RelPath>,
465 _: LanguageName,
466 _: &mut AsyncApp,
467 ) -> Option<Toolchain> {
468 None
469 }
470}
471pub(crate) struct LocalStore(WeakEntity<LocalToolchainStore>);
472struct RemoteStore(WeakEntity<RemoteToolchainStore>);
473
474#[derive(Clone)]
475pub enum ToolchainStoreEvent {
476 ToolchainActivated,
477 CustomToolchainsModified,
478}
479
480impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
481
482impl LocalToolchainStore {
483 pub(crate) fn activate_toolchain(
484 &self,
485 path: ProjectPath,
486 toolchain: Toolchain,
487 cx: &mut Context<Self>,
488 ) -> Task<Option<()>> {
489 cx.spawn(async move |this, cx| {
490 this.update(cx, |this, cx| {
491 this.active_toolchains
492 .entry((path.worktree_id, toolchain.language_name.clone()))
493 .or_default()
494 .insert(path.path, toolchain.clone());
495 cx.emit(ToolchainStoreEvent::ToolchainActivated);
496 })
497 .ok();
498 Some(())
499 })
500 }
501 pub(crate) fn list_toolchains(
502 &mut self,
503 path: ProjectPath,
504 language_name: LanguageName,
505 cx: &mut Context<Self>,
506 ) -> Task<Option<(ToolchainList, Arc<RelPath>)>> {
507 let registry = self.languages.clone();
508
509 let manifest_tree = self.manifest_tree.downgrade();
510 let fs = self.fs.clone();
511
512 let environment = self.project_environment.clone();
513 cx.spawn(async move |this, cx| {
514 let language = cx
515 .background_spawn(registry.language_for_name(language_name.as_ref()))
516 .await
517 .ok()?;
518 let toolchains = language.toolchain_lister()?;
519 let manifest_name = toolchains.meta().manifest_name;
520 let (snapshot, worktree) = this
521 .update(cx, |this, cx| {
522 this.worktree_store
523 .read(cx)
524 .worktree_for_id(path.worktree_id, cx)
525 .map(|worktree| (worktree.read(cx).snapshot(), worktree))
526 })
527 .ok()
528 .flatten()?;
529 let worktree_id = snapshot.id();
530 let worktree_root = snapshot.abs_path().to_path_buf();
531 let delegate =
532 Arc::from(ManifestQueryDelegate::new(snapshot)) as Arc<dyn ManifestDelegate>;
533 let relative_path = manifest_tree
534 .update(cx, |this, cx| {
535 this.root_for_path(&path, &manifest_name, &delegate, cx)
536 })
537 .ok()?
538 .unwrap_or_else(|| ProjectPath {
539 path: Arc::from(RelPath::empty()),
540 worktree_id,
541 });
542 let abs_path = worktree
543 .update(cx, |this, _| this.absolutize(&relative_path.path))
544 .ok()?;
545
546 let project_env = environment
547 .update(cx, |environment, cx| {
548 environment.local_directory_environment(
549 &Shell::System,
550 abs_path.as_path().into(),
551 cx,
552 )
553 })
554 .ok()?
555 .await;
556
557 cx.background_spawn(async move {
558 Some((
559 toolchains
560 .list(
561 worktree_root,
562 relative_path.path.clone(),
563 project_env,
564 fs.as_ref(),
565 )
566 .await,
567 relative_path.path,
568 ))
569 })
570 .await
571 })
572 }
573 pub(crate) fn active_toolchain(
574 &self,
575 worktree_id: WorktreeId,
576 relative_path: &Arc<RelPath>,
577 language_name: LanguageName,
578 ) -> Option<Toolchain> {
579 let ancestors = relative_path.ancestors();
580
581 self.active_toolchains
582 .get(&(worktree_id, language_name))
583 .and_then(|paths| {
584 ancestors
585 .into_iter()
586 .find_map(|root_path| paths.get(root_path))
587 })
588 .cloned()
589 }
590
591 fn resolve_toolchain(
592 &self,
593 path: PathBuf,
594 language_name: LanguageName,
595 cx: &mut Context<Self>,
596 ) -> Task<Result<Toolchain>> {
597 let registry = self.languages.clone();
598 let environment = self.project_environment.clone();
599 let fs = self.fs.clone();
600 cx.spawn(async move |_, cx| {
601 let language = cx
602 .background_spawn(registry.language_for_name(&language_name.0))
603 .await
604 .with_context(|| format!("Language {} not found", language_name.0))?;
605 let toolchain_lister = language.toolchain_lister().with_context(|| {
606 format!("Language {} does not support toolchains", language_name.0)
607 })?;
608
609 let project_env = environment
610 .update(cx, |environment, cx| {
611 environment.local_directory_environment(
612 &Shell::System,
613 path.as_path().into(),
614 cx,
615 )
616 })?
617 .await;
618 cx.background_spawn(async move {
619 toolchain_lister
620 .resolve(path, project_env, fs.as_ref())
621 .await
622 })
623 .await
624 })
625 }
626}
627
628impl EventEmitter<ToolchainStoreEvent> for RemoteToolchainStore {}
629struct RemoteToolchainStore {
630 client: AnyProtoClient,
631 project_id: u64,
632}
633
634impl RemoteToolchainStore {
635 pub(crate) fn activate_toolchain(
636 &self,
637 project_path: ProjectPath,
638 toolchain: Toolchain,
639 cx: &mut Context<Self>,
640 ) -> Task<Option<()>> {
641 let project_id = self.project_id;
642 let client = self.client.clone();
643 cx.spawn(async move |this, cx| {
644 let did_activate = cx
645 .background_spawn(async move {
646 let path = PathBuf::from(toolchain.path.to_string());
647 let _ = client
648 .request(proto::ActivateToolchain {
649 project_id,
650 worktree_id: project_path.worktree_id.to_proto(),
651 language_name: toolchain.language_name.into(),
652 toolchain: Some(proto::Toolchain {
653 name: toolchain.name.into(),
654 path: path.to_string_lossy().into_owned(),
655 raw_json: toolchain.as_json.to_string(),
656 }),
657 path: Some(project_path.path.to_proto()),
658 })
659 .await
660 .log_err()?;
661 Some(())
662 })
663 .await;
664 did_activate.and_then(|_| {
665 this.update(cx, |_, cx| {
666 cx.emit(ToolchainStoreEvent::ToolchainActivated);
667 })
668 .ok()
669 })
670 })
671 }
672
673 pub(crate) fn list_toolchains(
674 &self,
675 path: ProjectPath,
676 language_name: LanguageName,
677 cx: &App,
678 ) -> Task<Option<(ToolchainList, Arc<RelPath>)>> {
679 let project_id = self.project_id;
680 let client = self.client.clone();
681 cx.background_spawn(async move {
682 let response = client
683 .request(proto::ListToolchains {
684 project_id,
685 worktree_id: path.worktree_id.to_proto(),
686 language_name: language_name.clone().into(),
687 path: Some(path.path.to_proto()),
688 })
689 .await
690 .log_err()?;
691 if !response.has_values {
692 return None;
693 }
694 let toolchains = response
695 .toolchains
696 .into_iter()
697 .filter_map(|toolchain| {
698 Some(Toolchain {
699 language_name: language_name.clone(),
700 name: toolchain.name.into(),
701 path: toolchain.path.into(),
702 as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
703 })
704 })
705 .collect();
706 let groups = response
707 .groups
708 .into_iter()
709 .filter_map(|group| {
710 Some((usize::try_from(group.start_index).ok()?, group.name.into()))
711 })
712 .collect();
713 let relative_path = RelPath::from_proto(
714 response
715 .relative_worktree_path
716 .as_deref()
717 .unwrap_or_default(),
718 )
719 .log_err()?;
720 Some((
721 ToolchainList {
722 toolchains,
723 default: None,
724 groups,
725 },
726 relative_path,
727 ))
728 })
729 }
730 pub(crate) fn active_toolchain(
731 &self,
732 path: ProjectPath,
733 language_name: LanguageName,
734 cx: &App,
735 ) -> Task<Option<Toolchain>> {
736 let project_id = self.project_id;
737 let client = self.client.clone();
738 cx.background_spawn(async move {
739 let response = client
740 .request(proto::ActiveToolchain {
741 project_id,
742 worktree_id: path.worktree_id.to_proto(),
743 language_name: language_name.clone().into(),
744 path: Some(path.path.to_proto()),
745 })
746 .await
747 .log_err()?;
748
749 response.toolchain.and_then(|toolchain| {
750 Some(Toolchain {
751 language_name: language_name.clone(),
752 name: toolchain.name.into(),
753 path: toolchain.path.into(),
754 as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
755 })
756 })
757 })
758 }
759
760 fn resolve_toolchain(
761 &self,
762 abs_path: PathBuf,
763 language_name: LanguageName,
764 cx: &mut Context<Self>,
765 ) -> Task<Result<Toolchain>> {
766 let project_id = self.project_id;
767 let client = self.client.clone();
768 cx.background_spawn(async move {
769 let response: proto::ResolveToolchainResponse = client
770 .request(proto::ResolveToolchain {
771 project_id,
772 language_name: language_name.clone().into(),
773 abs_path: abs_path.to_string_lossy().into_owned(),
774 })
775 .await?;
776
777 let response = response
778 .response
779 .context("Failed to resolve toolchain via RPC")?;
780 use proto::resolve_toolchain_response::Response;
781 match response {
782 Response::Toolchain(toolchain) => Ok(Toolchain {
783 language_name: language_name.clone(),
784 name: toolchain.name.into(),
785 path: toolchain.path.into(),
786 as_json: serde_json::Value::from_str(&toolchain.raw_json)
787 .context("Deserializing ResolveToolchain LSP response")?,
788 }),
789 Response::Error(error) => {
790 anyhow::bail!("{error}");
791 }
792 }
793 })
794 }
795}