toolchain_store.rs

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