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