1use std::{
2 path::{Path, PathBuf},
3 str::FromStr,
4 sync::Arc,
5};
6
7use anyhow::{Result, bail};
8
9use async_trait::async_trait;
10use collections::BTreeMap;
11use gpui::{
12 App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
13};
14use language::{
15 LanguageName, LanguageRegistry, LanguageToolchainStore, ManifestDelegate, Toolchain,
16 ToolchainList,
17};
18use rpc::{
19 AnyProtoClient, TypedEnvelope,
20 proto::{self, FromProto, ToProto},
21};
22use settings::WorktreeId;
23use util::ResultExt as _;
24
25use crate::{
26 ProjectEnvironment, ProjectPath,
27 manifest_tree::{ManifestQueryDelegate, ManifestTree},
28 worktree_store::WorktreeStore,
29};
30
31pub struct ToolchainStore(ToolchainStoreInner);
32enum ToolchainStoreInner {
33 Local(
34 Entity<LocalToolchainStore>,
35 #[allow(dead_code)] Subscription,
36 ),
37 Remote(
38 Entity<RemoteToolchainStore>,
39 #[allow(dead_code)] Subscription,
40 ),
41}
42
43impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
44impl ToolchainStore {
45 pub fn init(client: &AnyProtoClient) {
46 client.add_entity_request_handler(Self::handle_activate_toolchain);
47 client.add_entity_request_handler(Self::handle_list_toolchains);
48 client.add_entity_request_handler(Self::handle_active_toolchain);
49 }
50
51 pub fn local(
52 languages: Arc<LanguageRegistry>,
53 worktree_store: Entity<WorktreeStore>,
54 project_environment: Entity<ProjectEnvironment>,
55 manifest_tree: Entity<ManifestTree>,
56 cx: &mut Context<Self>,
57 ) -> Self {
58 let entity = cx.new(|_| LocalToolchainStore {
59 languages,
60 worktree_store,
61 project_environment,
62 active_toolchains: Default::default(),
63 manifest_tree,
64 });
65 let subscription = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {
66 cx.emit(e.clone())
67 });
68 Self(ToolchainStoreInner::Local(entity, subscription))
69 }
70
71 pub(super) fn remote(project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) -> Self {
72 let entity = cx.new(|_| RemoteToolchainStore { client, project_id });
73 let _subscription = cx.subscribe(&entity, |_, _, e: &ToolchainStoreEvent, cx| {
74 cx.emit(e.clone())
75 });
76 Self(ToolchainStoreInner::Remote(entity, _subscription))
77 }
78 pub(crate) fn activate_toolchain(
79 &self,
80 path: ProjectPath,
81 toolchain: Toolchain,
82 cx: &mut App,
83 ) -> Task<Option<()>> {
84 match &self.0 {
85 ToolchainStoreInner::Local(local, _) => {
86 local.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
87 }
88 ToolchainStoreInner::Remote(remote, _) => {
89 remote.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
90 }
91 }
92 }
93 pub(crate) fn list_toolchains(
94 &self,
95 path: ProjectPath,
96 language_name: LanguageName,
97 cx: &mut Context<Self>,
98 ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
99 match &self.0 {
100 ToolchainStoreInner::Local(local, _) => {
101 local.update(cx, |this, cx| this.list_toolchains(path, language_name, cx))
102 }
103 ToolchainStoreInner::Remote(remote, _) => {
104 remote.read(cx).list_toolchains(path, language_name, cx)
105 }
106 }
107 }
108 pub(crate) fn active_toolchain(
109 &self,
110 path: ProjectPath,
111 language_name: LanguageName,
112 cx: &App,
113 ) -> Task<Option<Toolchain>> {
114 match &self.0 {
115 ToolchainStoreInner::Local(local, _) => Task::ready(local.read(cx).active_toolchain(
116 path.worktree_id,
117 &path.path,
118 language_name,
119 )),
120 ToolchainStoreInner::Remote(remote, _) => {
121 remote.read(cx).active_toolchain(path, language_name, cx)
122 }
123 }
124 }
125 async fn handle_activate_toolchain(
126 this: Entity<Self>,
127 envelope: TypedEnvelope<proto::ActivateToolchain>,
128 mut cx: AsyncApp,
129 ) -> Result<proto::Ack> {
130 this.update(&mut cx, |this, cx| {
131 let language_name = LanguageName::from_proto(envelope.payload.language_name);
132 let Some(toolchain) = envelope.payload.toolchain else {
133 bail!("Missing `toolchain` in payload");
134 };
135 let toolchain = Toolchain {
136 name: toolchain.name.into(),
137 // todo(windows)
138 // Do we need to convert path to native string?
139 path: PathBuf::from(toolchain.path).to_proto().into(),
140 as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
141 language_name,
142 };
143 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
144 let path: Arc<Path> = if let Some(path) = envelope.payload.path {
145 Arc::from(path.as_ref())
146 } else {
147 Arc::from("".as_ref())
148 };
149 Ok(this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx))
150 })??
151 .await;
152 Ok(proto::Ack {})
153 }
154 async fn handle_active_toolchain(
155 this: Entity<Self>,
156 envelope: TypedEnvelope<proto::ActiveToolchain>,
157 mut cx: AsyncApp,
158 ) -> Result<proto::ActiveToolchainResponse> {
159 let toolchain = this
160 .update(&mut cx, |this, cx| {
161 let language_name = LanguageName::from_proto(envelope.payload.language_name);
162 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
163 this.active_toolchain(
164 ProjectPath {
165 worktree_id,
166 path: Arc::from(envelope.payload.path.as_deref().unwrap_or("").as_ref()),
167 },
168 language_name,
169 cx,
170 )
171 })?
172 .await;
173
174 Ok(proto::ActiveToolchainResponse {
175 toolchain: toolchain.map(|toolchain| {
176 let path = PathBuf::from(toolchain.path.to_string());
177 proto::Toolchain {
178 name: toolchain.name.into(),
179 path: path.to_proto(),
180 raw_json: toolchain.as_json.to_string(),
181 }
182 }),
183 })
184 }
185
186 async fn handle_list_toolchains(
187 this: Entity<Self>,
188 envelope: TypedEnvelope<proto::ListToolchains>,
189 mut cx: AsyncApp,
190 ) -> Result<proto::ListToolchainsResponse> {
191 let toolchains = this
192 .update(&mut cx, |this, cx| {
193 let language_name = LanguageName::from_proto(envelope.payload.language_name);
194 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
195 let path = Arc::from(envelope.payload.path.as_deref().unwrap_or("").as_ref());
196 this.list_toolchains(ProjectPath { worktree_id, path }, language_name, cx)
197 })?
198 .await;
199 let has_values = toolchains.is_some();
200 let groups = if let Some((toolchains, _)) = &toolchains {
201 toolchains
202 .groups
203 .iter()
204 .filter_map(|group| {
205 Some(proto::ToolchainGroup {
206 start_index: u64::try_from(group.0).ok()?,
207 name: String::from(group.1.as_ref()),
208 })
209 })
210 .collect()
211 } else {
212 vec![]
213 };
214 let (toolchains, relative_path) = if let Some((toolchains, relative_path)) = toolchains {
215 let toolchains = toolchains
216 .toolchains
217 .into_iter()
218 .map(|toolchain| {
219 let path = PathBuf::from(toolchain.path.to_string());
220 proto::Toolchain {
221 name: toolchain.name.to_string(),
222 path: path.to_proto(),
223 raw_json: toolchain.as_json.to_string(),
224 }
225 })
226 .collect::<Vec<_>>();
227 (toolchains, relative_path)
228 } else {
229 (vec![], Arc::from(Path::new("")))
230 };
231
232 Ok(proto::ListToolchainsResponse {
233 has_values,
234 toolchains,
235 groups,
236 relative_worktree_path: Some(relative_path.to_string_lossy().into_owned()),
237 })
238 }
239 pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
240 match &self.0 {
241 ToolchainStoreInner::Local(local, _) => Arc::new(LocalStore(local.downgrade())),
242 ToolchainStoreInner::Remote(remote, _) => Arc::new(RemoteStore(remote.downgrade())),
243 }
244 }
245 pub fn as_local_store(&self) -> Option<&Entity<LocalToolchainStore>> {
246 match &self.0 {
247 ToolchainStoreInner::Local(local, _) => Some(local),
248 ToolchainStoreInner::Remote(_, _) => None,
249 }
250 }
251}
252
253pub struct LocalToolchainStore {
254 languages: Arc<LanguageRegistry>,
255 worktree_store: Entity<WorktreeStore>,
256 project_environment: Entity<ProjectEnvironment>,
257 active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<Path>, Toolchain>>,
258 manifest_tree: Entity<ManifestTree>,
259}
260
261#[async_trait(?Send)]
262impl language::LocalLanguageToolchainStore for LocalStore {
263 fn active_toolchain(
264 self: Arc<Self>,
265 worktree_id: WorktreeId,
266 path: &Arc<Path>,
267 language_name: LanguageName,
268 cx: &mut AsyncApp,
269 ) -> Option<Toolchain> {
270 self.0
271 .update(cx, |this, _| {
272 this.active_toolchain(worktree_id, path, language_name)
273 })
274 .ok()?
275 }
276}
277
278#[async_trait(?Send)]
279impl language::LanguageToolchainStore for RemoteStore {
280 async fn active_toolchain(
281 self: Arc<Self>,
282 worktree_id: WorktreeId,
283 path: Arc<Path>,
284 language_name: LanguageName,
285 cx: &mut AsyncApp,
286 ) -> Option<Toolchain> {
287 self.0
288 .update(cx, |this, cx| {
289 this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)
290 })
291 .ok()?
292 .await
293 }
294}
295
296pub struct EmptyToolchainStore;
297impl language::LocalLanguageToolchainStore for EmptyToolchainStore {
298 fn active_toolchain(
299 self: Arc<Self>,
300 _: WorktreeId,
301 _: &Arc<Path>,
302 _: LanguageName,
303 _: &mut AsyncApp,
304 ) -> Option<Toolchain> {
305 None
306 }
307}
308pub(crate) struct LocalStore(WeakEntity<LocalToolchainStore>);
309struct RemoteStore(WeakEntity<RemoteToolchainStore>);
310
311#[derive(Clone)]
312pub enum ToolchainStoreEvent {
313 ToolchainActivated,
314}
315
316impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
317
318impl LocalToolchainStore {
319 pub(crate) fn activate_toolchain(
320 &self,
321 path: ProjectPath,
322 toolchain: Toolchain,
323 cx: &mut Context<Self>,
324 ) -> Task<Option<()>> {
325 cx.spawn(async move |this, cx| {
326 this.update(cx, |this, cx| {
327 this.active_toolchains
328 .entry((path.worktree_id, toolchain.language_name.clone()))
329 .or_default()
330 .insert(path.path, toolchain.clone());
331 cx.emit(ToolchainStoreEvent::ToolchainActivated);
332 })
333 .ok();
334 Some(())
335 })
336 }
337 pub(crate) fn list_toolchains(
338 &mut self,
339 path: ProjectPath,
340 language_name: LanguageName,
341 cx: &mut Context<Self>,
342 ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
343 let registry = self.languages.clone();
344
345 let manifest_tree = self.manifest_tree.downgrade();
346
347 let environment = self.project_environment.clone();
348 cx.spawn(async move |this, cx| {
349 let language = cx
350 .background_spawn(registry.language_for_name(language_name.as_ref()))
351 .await
352 .ok()?;
353 let toolchains = language.toolchain_lister()?;
354 let manifest_name = toolchains.manifest_name();
355 let (snapshot, worktree) = this
356 .update(cx, |this, cx| {
357 this.worktree_store
358 .read(cx)
359 .worktree_for_id(path.worktree_id, cx)
360 .map(|worktree| (worktree.read(cx).snapshot(), worktree))
361 })
362 .ok()
363 .flatten()?;
364 let worktree_id = snapshot.id();
365 let worktree_root = snapshot.abs_path().to_path_buf();
366 let delegate =
367 Arc::from(ManifestQueryDelegate::new(snapshot)) as Arc<dyn ManifestDelegate>;
368 let relative_path = manifest_tree
369 .update(cx, |this, cx| {
370 this.root_for_path(&path, &manifest_name, &delegate, cx)
371 })
372 .ok()?
373 .unwrap_or_else(|| ProjectPath {
374 path: Arc::from(Path::new("")),
375 worktree_id,
376 });
377 let abs_path = worktree
378 .update(cx, |this, _| this.absolutize(&relative_path.path).ok())
379 .ok()
380 .flatten()?;
381
382 let project_env = environment
383 .update(cx, |environment, cx| {
384 environment.get_directory_environment(abs_path.as_path().into(), cx)
385 })
386 .ok()?
387 .await;
388
389 cx.background_spawn(async move {
390 Some((
391 toolchains
392 .list(worktree_root, relative_path.path.clone(), project_env)
393 .await,
394 relative_path.path,
395 ))
396 })
397 .await
398 })
399 }
400 pub(crate) fn active_toolchain(
401 &self,
402 worktree_id: WorktreeId,
403 relative_path: &Arc<Path>,
404 language_name: LanguageName,
405 ) -> Option<Toolchain> {
406 let ancestors = relative_path.ancestors();
407
408 self.active_toolchains
409 .get(&(worktree_id, language_name))
410 .and_then(|paths| {
411 ancestors
412 .into_iter()
413 .find_map(|root_path| paths.get(root_path))
414 })
415 .cloned()
416 }
417}
418
419impl EventEmitter<ToolchainStoreEvent> for RemoteToolchainStore {}
420struct RemoteToolchainStore {
421 client: AnyProtoClient,
422 project_id: u64,
423}
424
425impl RemoteToolchainStore {
426 pub(crate) fn activate_toolchain(
427 &self,
428 project_path: ProjectPath,
429 toolchain: Toolchain,
430 cx: &mut Context<Self>,
431 ) -> Task<Option<()>> {
432 let project_id = self.project_id;
433 let client = self.client.clone();
434 cx.spawn(async move |this, cx| {
435 let did_activate = cx
436 .background_spawn(async move {
437 let path = PathBuf::from(toolchain.path.to_string());
438 let _ = client
439 .request(proto::ActivateToolchain {
440 project_id,
441 worktree_id: project_path.worktree_id.to_proto(),
442 language_name: toolchain.language_name.into(),
443 toolchain: Some(proto::Toolchain {
444 name: toolchain.name.into(),
445 path: path.to_proto(),
446 raw_json: toolchain.as_json.to_string(),
447 }),
448 path: Some(project_path.path.to_string_lossy().into_owned()),
449 })
450 .await
451 .log_err()?;
452 Some(())
453 })
454 .await;
455 did_activate.and_then(|_| {
456 this.update(cx, |_, cx| {
457 cx.emit(ToolchainStoreEvent::ToolchainActivated);
458 })
459 .ok()
460 })
461 })
462 }
463
464 pub(crate) fn list_toolchains(
465 &self,
466 path: ProjectPath,
467 language_name: LanguageName,
468 cx: &App,
469 ) -> Task<Option<(ToolchainList, Arc<Path>)>> {
470 let project_id = self.project_id;
471 let client = self.client.clone();
472 cx.background_spawn(async move {
473 let response = client
474 .request(proto::ListToolchains {
475 project_id,
476 worktree_id: path.worktree_id.to_proto(),
477 language_name: language_name.clone().into(),
478 path: Some(path.path.to_string_lossy().into_owned()),
479 })
480 .await
481 .log_err()?;
482 if !response.has_values {
483 return None;
484 }
485 let toolchains = response
486 .toolchains
487 .into_iter()
488 .filter_map(|toolchain| {
489 Some(Toolchain {
490 language_name: language_name.clone(),
491 name: toolchain.name.into(),
492 // todo(windows)
493 // Do we need to convert path to native string?
494 path: PathBuf::from_proto(toolchain.path)
495 .to_string_lossy()
496 .to_string()
497 .into(),
498 as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
499 })
500 })
501 .collect();
502 let groups = response
503 .groups
504 .into_iter()
505 .filter_map(|group| {
506 Some((usize::try_from(group.start_index).ok()?, group.name.into()))
507 })
508 .collect();
509 let relative_path = Arc::from(Path::new(
510 response
511 .relative_worktree_path
512 .as_deref()
513 .unwrap_or_default(),
514 ));
515 Some((
516 ToolchainList {
517 toolchains,
518 default: None,
519 groups,
520 },
521 relative_path,
522 ))
523 })
524 }
525 pub(crate) fn active_toolchain(
526 &self,
527 path: ProjectPath,
528 language_name: LanguageName,
529 cx: &App,
530 ) -> Task<Option<Toolchain>> {
531 let project_id = self.project_id;
532 let client = self.client.clone();
533 cx.background_spawn(async move {
534 let response = client
535 .request(proto::ActiveToolchain {
536 project_id,
537 worktree_id: path.worktree_id.to_proto(),
538 language_name: language_name.clone().into(),
539 path: Some(path.path.to_string_lossy().into_owned()),
540 })
541 .await
542 .log_err()?;
543
544 response.toolchain.and_then(|toolchain| {
545 Some(Toolchain {
546 language_name: language_name.clone(),
547 name: toolchain.name.into(),
548 // todo(windows)
549 // Do we need to convert path to native string?
550 path: PathBuf::from_proto(toolchain.path)
551 .to_string_lossy()
552 .to_string()
553 .into(),
554 as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
555 })
556 })
557 })
558 }
559}