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