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