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