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.update(cx, |this, _| this.absolutize(&relative_path.path));
543
544 let project_env = environment
545 .update(cx, |environment, cx| {
546 environment.local_directory_environment(
547 &Shell::System,
548 abs_path.as_path().into(),
549 cx,
550 )
551 })
552 .await;
553
554 cx.background_spawn(async move {
555 Some((
556 toolchains
557 .list(
558 worktree_root,
559 relative_path.path.clone(),
560 project_env,
561 fs.as_ref(),
562 )
563 .await,
564 relative_path.path,
565 ))
566 })
567 .await
568 })
569 }
570 pub(crate) fn active_toolchain(
571 &self,
572 worktree_id: WorktreeId,
573 relative_path: &Arc<RelPath>,
574 language_name: LanguageName,
575 ) -> Option<Toolchain> {
576 let ancestors = relative_path.ancestors();
577
578 self.active_toolchains
579 .get(&(worktree_id, language_name))
580 .and_then(|paths| {
581 ancestors
582 .into_iter()
583 .find_map(|root_path| paths.get(root_path))
584 })
585 .cloned()
586 }
587
588 fn resolve_toolchain(
589 &self,
590 path: PathBuf,
591 language_name: LanguageName,
592 cx: &mut Context<Self>,
593 ) -> Task<Result<Toolchain>> {
594 let registry = self.languages.clone();
595 let environment = self.project_environment.clone();
596 let fs = self.fs.clone();
597 cx.spawn(async move |_, cx| {
598 let language = cx
599 .background_spawn(registry.language_for_name(&language_name.0))
600 .await
601 .with_context(|| format!("Language {} not found", language_name.0))?;
602 let toolchain_lister = language.toolchain_lister().with_context(|| {
603 format!("Language {} does not support toolchains", language_name.0)
604 })?;
605
606 let project_env = environment
607 .update(cx, |environment, cx| {
608 environment.local_directory_environment(
609 &Shell::System,
610 path.as_path().into(),
611 cx,
612 )
613 })
614 .await;
615 cx.background_spawn(async move {
616 toolchain_lister
617 .resolve(path, project_env, fs.as_ref())
618 .await
619 })
620 .await
621 })
622 }
623}
624
625impl EventEmitter<ToolchainStoreEvent> for RemoteToolchainStore {}
626struct RemoteToolchainStore {
627 client: AnyProtoClient,
628 project_id: u64,
629}
630
631impl RemoteToolchainStore {
632 pub(crate) fn activate_toolchain(
633 &self,
634 project_path: ProjectPath,
635 toolchain: Toolchain,
636 cx: &mut Context<Self>,
637 ) -> Task<Option<()>> {
638 let project_id = self.project_id;
639 let client = self.client.clone();
640 cx.spawn(async move |this, cx| {
641 let did_activate = cx
642 .background_spawn(async move {
643 let path = PathBuf::from(toolchain.path.to_string());
644 let _ = client
645 .request(proto::ActivateToolchain {
646 project_id,
647 worktree_id: project_path.worktree_id.to_proto(),
648 language_name: toolchain.language_name.into(),
649 toolchain: Some(proto::Toolchain {
650 name: toolchain.name.into(),
651 path: path.to_string_lossy().into_owned(),
652 raw_json: toolchain.as_json.to_string(),
653 }),
654 path: Some(project_path.path.to_proto()),
655 })
656 .await
657 .log_err()?;
658 Some(())
659 })
660 .await;
661 did_activate.and_then(|_| {
662 this.update(cx, |_, cx| {
663 cx.emit(ToolchainStoreEvent::ToolchainActivated);
664 })
665 .ok()
666 })
667 })
668 }
669
670 pub(crate) fn list_toolchains(
671 &self,
672 path: ProjectPath,
673 language_name: LanguageName,
674 cx: &App,
675 ) -> Task<Option<(ToolchainList, Arc<RelPath>)>> {
676 let project_id = self.project_id;
677 let client = self.client.clone();
678 cx.background_spawn(async move {
679 let response = client
680 .request(proto::ListToolchains {
681 project_id,
682 worktree_id: path.worktree_id.to_proto(),
683 language_name: language_name.clone().into(),
684 path: Some(path.path.to_proto()),
685 })
686 .await
687 .log_err()?;
688 if !response.has_values {
689 return None;
690 }
691 let toolchains = response
692 .toolchains
693 .into_iter()
694 .filter_map(|toolchain| {
695 Some(Toolchain {
696 language_name: language_name.clone(),
697 name: toolchain.name.into(),
698 path: toolchain.path.into(),
699 as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
700 })
701 })
702 .collect();
703 let groups = response
704 .groups
705 .into_iter()
706 .filter_map(|group| {
707 Some((usize::try_from(group.start_index).ok()?, group.name.into()))
708 })
709 .collect();
710 let relative_path = RelPath::from_proto(
711 response
712 .relative_worktree_path
713 .as_deref()
714 .unwrap_or_default(),
715 )
716 .log_err()?;
717 Some((
718 ToolchainList {
719 toolchains,
720 default: None,
721 groups,
722 },
723 relative_path,
724 ))
725 })
726 }
727 pub(crate) fn active_toolchain(
728 &self,
729 path: ProjectPath,
730 language_name: LanguageName,
731 cx: &App,
732 ) -> Task<Option<Toolchain>> {
733 let project_id = self.project_id;
734 let client = self.client.clone();
735 cx.background_spawn(async move {
736 let response = client
737 .request(proto::ActiveToolchain {
738 project_id,
739 worktree_id: path.worktree_id.to_proto(),
740 language_name: language_name.clone().into(),
741 path: Some(path.path.to_proto()),
742 })
743 .await
744 .log_err()?;
745
746 response.toolchain.and_then(|toolchain| {
747 Some(Toolchain {
748 language_name: language_name.clone(),
749 name: toolchain.name.into(),
750 path: toolchain.path.into(),
751 as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
752 })
753 })
754 })
755 }
756
757 fn resolve_toolchain(
758 &self,
759 abs_path: PathBuf,
760 language_name: LanguageName,
761 cx: &mut Context<Self>,
762 ) -> Task<Result<Toolchain>> {
763 let project_id = self.project_id;
764 let client = self.client.clone();
765 cx.background_spawn(async move {
766 let response: proto::ResolveToolchainResponse = client
767 .request(proto::ResolveToolchain {
768 project_id,
769 language_name: language_name.clone().into(),
770 abs_path: abs_path.to_string_lossy().into_owned(),
771 })
772 .await?;
773
774 let response = response
775 .response
776 .context("Failed to resolve toolchain via RPC")?;
777 use proto::resolve_toolchain_response::Response;
778 match response {
779 Response::Toolchain(toolchain) => Ok(Toolchain {
780 language_name: language_name.clone(),
781 name: toolchain.name.into(),
782 path: toolchain.path.into(),
783 as_json: serde_json::Value::from_str(&toolchain.raw_json)
784 .context("Deserializing ResolveToolchain LSP response")?,
785 }),
786 Response::Error(error) => {
787 anyhow::bail!("{error}");
788 }
789 }
790 })
791 }
792}