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