toolchain_store.rs

  1use std::{
  2    path::{Path, PathBuf},
  3    str::FromStr,
  4    sync::Arc,
  5};
  6
  7use anyhow::{Context as _, Result, bail};
  8
  9use async_trait::async_trait;
 10use collections::{BTreeMap, IndexSet};
 11use gpui::{
 12    App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
 13};
 14use language::{
 15    LanguageName, LanguageRegistry, LanguageToolchainStore, ManifestDelegate, Toolchain,
 16    ToolchainList, ToolchainScope,
 17};
 18use rpc::{
 19    AnyProtoClient, TypedEnvelope,
 20    proto::{
 21        self, FromProto, ResolveToolchainResponse, ToProto,
 22        resolve_toolchain_response::Response as ResolveResponsePayload,
 23    },
 24};
 25use settings::WorktreeId;
 26use util::ResultExt as _;
 27
 28use crate::{
 29    ProjectEnvironment, ProjectPath,
 30    manifest_tree::{ManifestQueryDelegate, ManifestTree},
 31    worktree_store::WorktreeStore,
 32};
 33
 34pub struct ToolchainStore {
 35    mode: ToolchainStoreInner,
 36    user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,
 37    _sub: Subscription,
 38}
 39
 40enum ToolchainStoreInner {
 41    Local(Entity<LocalToolchainStore>),
 42    Remote(Entity<RemoteToolchainStore>),
 43}
 44
 45pub struct Toolchains {
 46    /// Auto-detected toolchains.
 47    pub toolchains: ToolchainList,
 48    /// Path of the project root at which we ran the automatic toolchain detection.
 49    pub root_path: Arc<Path>,
 50    pub user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,
 51}
 52impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
 53impl ToolchainStore {
 54    pub fn init(client: &AnyProtoClient) {
 55        client.add_entity_request_handler(Self::handle_activate_toolchain);
 56        client.add_entity_request_handler(Self::handle_list_toolchains);
 57        client.add_entity_request_handler(Self::handle_active_toolchain);
 58        client.add_entity_request_handler(Self::handle_resolve_toolchain);
 59    }
 60
 61    pub fn local(
 62        languages: Arc<LanguageRegistry>,
 63        worktree_store: Entity<WorktreeStore>,
 64        project_environment: Entity<ProjectEnvironment>,
 65        manifest_tree: Entity<ManifestTree>,
 66        cx: &mut Context<Self>,
 67    ) -> Self {
 68        let entity = cx.new(|_| LocalToolchainStore {
 69            languages,
 70            worktree_store,
 71            project_environment,
 72            active_toolchains: Default::default(),
 73            manifest_tree,
 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: PathBuf::from(toolchain.path).to_proto().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: Arc<Path> = if let Some(path) = envelope.payload.path {
250                Arc::from(path.as_ref())
251            } else {
252                Arc::from("".as_ref())
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 toolchain = this
265            .update(&mut cx, |this, cx| {
266                let language_name = LanguageName::from_proto(envelope.payload.language_name);
267                let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
268                this.active_toolchain(
269                    ProjectPath {
270                        worktree_id,
271                        path: Arc::from(envelope.payload.path.as_deref().unwrap_or("").as_ref()),
272                    },
273                    language_name,
274                    cx,
275                )
276            })?
277            .await;
278
279        Ok(proto::ActiveToolchainResponse {
280            toolchain: toolchain.map(|toolchain| {
281                let path = PathBuf::from(toolchain.path.to_string());
282                proto::Toolchain {
283                    name: toolchain.name.into(),
284                    path: path.to_proto(),
285                    raw_json: toolchain.as_json.to_string(),
286                }
287            }),
288        })
289    }
290
291    async fn handle_list_toolchains(
292        this: Entity<Self>,
293        envelope: TypedEnvelope<proto::ListToolchains>,
294        mut cx: AsyncApp,
295    ) -> Result<proto::ListToolchainsResponse> {
296        let toolchains = this
297            .update(&mut cx, |this, cx| {
298                let language_name = LanguageName::from_proto(envelope.payload.language_name);
299                let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
300                let path = Arc::from(envelope.payload.path.as_deref().unwrap_or("").as_ref());
301                this.list_toolchains(ProjectPath { worktree_id, path }, language_name, cx)
302            })?
303            .await;
304        let has_values = toolchains.is_some();
305        let groups = if let Some(Toolchains { toolchains, .. }) = &toolchains {
306            toolchains
307                .groups
308                .iter()
309                .filter_map(|group| {
310                    Some(proto::ToolchainGroup {
311                        start_index: u64::try_from(group.0).ok()?,
312                        name: String::from(group.1.as_ref()),
313                    })
314                })
315                .collect()
316        } else {
317            vec![]
318        };
319        let (toolchains, relative_path) = if let Some(Toolchains {
320            toolchains,
321            root_path: relative_path,
322            ..
323        }) = toolchains
324        {
325            let toolchains = toolchains
326                .toolchains
327                .into_iter()
328                .map(|toolchain| {
329                    let path = PathBuf::from(toolchain.path.to_string());
330                    proto::Toolchain {
331                        name: toolchain.name.to_string(),
332                        path: path.to_proto(),
333                        raw_json: toolchain.as_json.to_string(),
334                    }
335                })
336                .collect::<Vec<_>>();
337            (toolchains, relative_path)
338        } else {
339            (vec![], Arc::from(Path::new("")))
340        };
341
342        Ok(proto::ListToolchainsResponse {
343            has_values,
344            toolchains,
345            groups,
346            relative_worktree_path: Some(relative_path.to_string_lossy().into_owned()),
347        })
348    }
349
350    async fn handle_resolve_toolchain(
351        this: Entity<Self>,
352        envelope: TypedEnvelope<proto::ResolveToolchain>,
353        mut cx: AsyncApp,
354    ) -> Result<proto::ResolveToolchainResponse> {
355        let toolchain = this
356            .update(&mut cx, |this, cx| {
357                let language_name = LanguageName::from_proto(envelope.payload.language_name);
358                let path = PathBuf::from(envelope.payload.abs_path);
359                this.resolve_toolchain(path, language_name, cx)
360            })?
361            .await;
362        let response = match toolchain {
363            Ok(toolchain) => {
364                let toolchain = proto::Toolchain {
365                    name: toolchain.name.to_string(),
366                    path: toolchain.path.to_string(),
367                    raw_json: toolchain.as_json.to_string(),
368                };
369                ResolveResponsePayload::Toolchain(toolchain)
370            }
371            Err(e) => ResolveResponsePayload::Error(e.to_string()),
372        };
373        Ok(ResolveToolchainResponse {
374            response: Some(response),
375        })
376    }
377
378    pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
379        match &self.mode {
380            ToolchainStoreInner::Local(local) => Arc::new(LocalStore(local.downgrade())),
381            ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
382        }
383    }
384    pub fn as_local_store(&self) -> Option<&Entity<LocalToolchainStore>> {
385        match &self.mode {
386            ToolchainStoreInner::Local(local) => Some(local),
387            ToolchainStoreInner::Remote(_) => None,
388        }
389    }
390}
391
392pub struct LocalToolchainStore {
393    languages: Arc<LanguageRegistry>,
394    worktree_store: Entity<WorktreeStore>,
395    project_environment: Entity<ProjectEnvironment>,
396    active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<Path>, Toolchain>>,
397    manifest_tree: Entity<ManifestTree>,
398}
399
400#[async_trait(?Send)]
401impl language::LocalLanguageToolchainStore for LocalStore {
402    fn active_toolchain(
403        self: Arc<Self>,
404        worktree_id: WorktreeId,
405        path: &Arc<Path>,
406        language_name: LanguageName,
407        cx: &mut AsyncApp,
408    ) -> Option<Toolchain> {
409        self.0
410            .update(cx, |this, _| {
411                this.active_toolchain(worktree_id, path, language_name)
412            })
413            .ok()?
414    }
415}
416
417#[async_trait(?Send)]
418impl language::LanguageToolchainStore for RemoteStore {
419    async fn active_toolchain(
420        self: Arc<Self>,
421        worktree_id: WorktreeId,
422        path: Arc<Path>,
423        language_name: LanguageName,
424        cx: &mut AsyncApp,
425    ) -> Option<Toolchain> {
426        self.0
427            .update(cx, |this, cx| {
428                this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)
429            })
430            .ok()?
431            .await
432    }
433}
434
435pub struct EmptyToolchainStore;
436impl language::LocalLanguageToolchainStore for EmptyToolchainStore {
437    fn active_toolchain(
438        self: Arc<Self>,
439        _: WorktreeId,
440        _: &Arc<Path>,
441        _: LanguageName,
442        _: &mut AsyncApp,
443    ) -> Option<Toolchain> {
444        None
445    }
446}
447pub(crate) struct LocalStore(WeakEntity<LocalToolchainStore>);
448struct RemoteStore(WeakEntity<RemoteToolchainStore>);
449
450#[derive(Clone)]
451pub enum ToolchainStoreEvent {
452    ToolchainActivated,
453    CustomToolchainsModified,
454}
455
456impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
457
458impl LocalToolchainStore {
459    pub(crate) fn activate_toolchain(
460        &self,
461        path: ProjectPath,
462        toolchain: Toolchain,
463        cx: &mut Context<Self>,
464    ) -> Task<Option<()>> {
465        cx.spawn(async move |this, cx| {
466            this.update(cx, |this, cx| {
467                this.active_toolchains
468                    .entry((path.worktree_id, toolchain.language_name.clone()))
469                    .or_default()
470                    .insert(path.path, toolchain.clone());
471                cx.emit(ToolchainStoreEvent::ToolchainActivated);
472            })
473            .ok();
474            Some(())
475        })
476    }
477    pub(crate) fn list_toolchains(
478        &mut self,
479        path: ProjectPath,
480        language_name: LanguageName,
481        cx: &mut Context<Self>,
482    ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
483        let registry = self.languages.clone();
484
485        let manifest_tree = self.manifest_tree.downgrade();
486
487        let environment = self.project_environment.clone();
488        cx.spawn(async move |this, cx| {
489            let language = cx
490                .background_spawn(registry.language_for_name(language_name.as_ref()))
491                .await
492                .ok()?;
493            let toolchains = language.toolchain_lister()?;
494            let manifest_name = toolchains.meta().manifest_name;
495            let (snapshot, worktree) = this
496                .update(cx, |this, cx| {
497                    this.worktree_store
498                        .read(cx)
499                        .worktree_for_id(path.worktree_id, cx)
500                        .map(|worktree| (worktree.read(cx).snapshot(), worktree))
501                })
502                .ok()
503                .flatten()?;
504            let worktree_id = snapshot.id();
505            let worktree_root = snapshot.abs_path().to_path_buf();
506            let delegate =
507                Arc::from(ManifestQueryDelegate::new(snapshot)) as Arc<dyn ManifestDelegate>;
508            let relative_path = manifest_tree
509                .update(cx, |this, cx| {
510                    this.root_for_path(&path, &manifest_name, &delegate, cx)
511                })
512                .ok()?
513                .unwrap_or_else(|| ProjectPath {
514                    path: Arc::from(Path::new("")),
515                    worktree_id,
516                });
517            let abs_path = worktree
518                .update(cx, |this, _| this.absolutize(&relative_path.path).ok())
519                .ok()
520                .flatten()?;
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<Path>,
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_proto(),
613                                raw_json: toolchain.as_json.to_string(),
614                            }),
615                            path: Some(project_path.path.to_string_lossy().into_owned()),
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<Path>)>> {
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_string_lossy().into_owned()),
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                        // todo(windows)
660                        // Do we need to convert path to native string?
661                        path: PathBuf::from_proto(toolchain.path)
662                            .to_string_lossy()
663                            .to_string()
664                            .into(),
665                        as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
666                    })
667                })
668                .collect();
669            let groups = response
670                .groups
671                .into_iter()
672                .filter_map(|group| {
673                    Some((usize::try_from(group.start_index).ok()?, group.name.into()))
674                })
675                .collect();
676            let relative_path = Arc::from(Path::new(
677                response
678                    .relative_worktree_path
679                    .as_deref()
680                    .unwrap_or_default(),
681            ));
682            Some((
683                ToolchainList {
684                    toolchains,
685                    default: None,
686                    groups,
687                },
688                relative_path,
689            ))
690        })
691    }
692    pub(crate) fn active_toolchain(
693        &self,
694        path: ProjectPath,
695        language_name: LanguageName,
696        cx: &App,
697    ) -> Task<Option<Toolchain>> {
698        let project_id = self.project_id;
699        let client = self.client.clone();
700        cx.background_spawn(async move {
701            let response = client
702                .request(proto::ActiveToolchain {
703                    project_id,
704                    worktree_id: path.worktree_id.to_proto(),
705                    language_name: language_name.clone().into(),
706                    path: Some(path.path.to_string_lossy().into_owned()),
707                })
708                .await
709                .log_err()?;
710
711            response.toolchain.and_then(|toolchain| {
712                Some(Toolchain {
713                    language_name: language_name.clone(),
714                    name: toolchain.name.into(),
715                    // todo(windows)
716                    // Do we need to convert path to native string?
717                    path: PathBuf::from_proto(toolchain.path)
718                        .to_string_lossy()
719                        .to_string()
720                        .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) => {
750                    Ok(Toolchain {
751                        language_name: language_name.clone(),
752                        name: toolchain.name.into(),
753                        // todo(windows)
754                        // Do we need to convert path to native string?
755                        path: PathBuf::from_proto(toolchain.path)
756                            .to_string_lossy()
757                            .to_string()
758                            .into(),
759                        as_json: serde_json::Value::from_str(&toolchain.raw_json)
760                            .context("Deserializing ResolveToolchain LSP response")?,
761                    })
762                }
763                Response::Error(error) => {
764                    anyhow::bail!("{error}");
765                }
766            }
767        })
768    }
769}