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