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