toolchain_store.rs

  1use std::{str::FromStr, sync::Arc};
  2
  3use anyhow::{bail, Result};
  4
  5use async_trait::async_trait;
  6use collections::BTreeMap;
  7use gpui::{
  8    App, AppContext as _, AsyncAppContext, Context, Entity, EventEmitter, Subscription, Task,
  9    WeakEntity,
 10};
 11use language::{LanguageName, LanguageRegistry, LanguageToolchainStore, Toolchain, ToolchainList};
 12use rpc::{proto, AnyProtoClient, TypedEnvelope};
 13use settings::WorktreeId;
 14use util::ResultExt as _;
 15
 16use crate::{worktree_store::WorktreeStore, ProjectEnvironment};
 17
 18pub struct ToolchainStore(ToolchainStoreInner);
 19enum ToolchainStoreInner {
 20    Local(
 21        Entity<LocalToolchainStore>,
 22        #[allow(dead_code)] Subscription,
 23    ),
 24    Remote(Entity<RemoteToolchainStore>),
 25}
 26
 27impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
 28impl ToolchainStore {
 29    pub fn init(client: &AnyProtoClient) {
 30        client.add_model_request_handler(Self::handle_activate_toolchain);
 31        client.add_model_request_handler(Self::handle_list_toolchains);
 32        client.add_model_request_handler(Self::handle_active_toolchain);
 33    }
 34
 35    pub fn local(
 36        languages: Arc<LanguageRegistry>,
 37        worktree_store: Entity<WorktreeStore>,
 38        project_environment: Entity<ProjectEnvironment>,
 39        cx: &mut Context<Self>,
 40    ) -> Self {
 41        let model = cx.new(|_| LocalToolchainStore {
 42            languages,
 43            worktree_store,
 44            project_environment,
 45            active_toolchains: Default::default(),
 46        });
 47        let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| {
 48            cx.emit(e.clone())
 49        });
 50        Self(ToolchainStoreInner::Local(model, subscription))
 51    }
 52    pub(super) fn remote(project_id: u64, client: AnyProtoClient, cx: &mut App) -> Self {
 53        Self(ToolchainStoreInner::Remote(
 54            cx.new(|_| RemoteToolchainStore { client, project_id }),
 55        ))
 56    }
 57    pub(crate) fn activate_toolchain(
 58        &self,
 59        worktree_id: WorktreeId,
 60        toolchain: Toolchain,
 61        cx: &mut App,
 62    ) -> Task<Option<()>> {
 63        match &self.0 {
 64            ToolchainStoreInner::Local(local, _) => local.update(cx, |this, cx| {
 65                this.activate_toolchain(worktree_id, toolchain, cx)
 66            }),
 67            ToolchainStoreInner::Remote(remote) => {
 68                remote
 69                    .read(cx)
 70                    .activate_toolchain(worktree_id, toolchain, cx)
 71            }
 72        }
 73    }
 74    pub(crate) fn list_toolchains(
 75        &self,
 76        worktree_id: WorktreeId,
 77        language_name: LanguageName,
 78        cx: &App,
 79    ) -> Task<Option<ToolchainList>> {
 80        match &self.0 {
 81            ToolchainStoreInner::Local(local, _) => {
 82                local
 83                    .read(cx)
 84                    .list_toolchains(worktree_id, language_name, cx)
 85            }
 86            ToolchainStoreInner::Remote(remote) => {
 87                remote
 88                    .read(cx)
 89                    .list_toolchains(worktree_id, language_name, cx)
 90            }
 91        }
 92    }
 93    pub(crate) fn active_toolchain(
 94        &self,
 95        worktree_id: WorktreeId,
 96        language_name: LanguageName,
 97        cx: &App,
 98    ) -> Task<Option<Toolchain>> {
 99        match &self.0 {
100            ToolchainStoreInner::Local(local, _) => {
101                local
102                    .read(cx)
103                    .active_toolchain(worktree_id, language_name, cx)
104            }
105            ToolchainStoreInner::Remote(remote) => {
106                remote
107                    .read(cx)
108                    .active_toolchain(worktree_id, language_name, cx)
109            }
110        }
111    }
112    async fn handle_activate_toolchain(
113        this: Entity<Self>,
114        envelope: TypedEnvelope<proto::ActivateToolchain>,
115        mut cx: AsyncAppContext,
116    ) -> Result<proto::Ack> {
117        this.update(&mut cx, |this, cx| {
118            let language_name = LanguageName::from_proto(envelope.payload.language_name);
119            let Some(toolchain) = envelope.payload.toolchain else {
120                bail!("Missing `toolchain` in payload");
121            };
122            let toolchain = Toolchain {
123                name: toolchain.name.into(),
124                path: toolchain.path.into(),
125                as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
126                language_name,
127            };
128            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
129            Ok(this.activate_toolchain(worktree_id, toolchain, cx))
130        })??
131        .await;
132        Ok(proto::Ack {})
133    }
134    async fn handle_active_toolchain(
135        this: Entity<Self>,
136        envelope: TypedEnvelope<proto::ActiveToolchain>,
137        mut cx: AsyncAppContext,
138    ) -> Result<proto::ActiveToolchainResponse> {
139        let toolchain = this
140            .update(&mut cx, |this, cx| {
141                let language_name = LanguageName::from_proto(envelope.payload.language_name);
142                let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
143                this.active_toolchain(worktree_id, language_name, cx)
144            })?
145            .await;
146
147        Ok(proto::ActiveToolchainResponse {
148            toolchain: toolchain.map(|toolchain| proto::Toolchain {
149                name: toolchain.name.into(),
150                path: toolchain.path.into(),
151                raw_json: toolchain.as_json.to_string(),
152            }),
153        })
154    }
155
156    async fn handle_list_toolchains(
157        this: Entity<Self>,
158        envelope: TypedEnvelope<proto::ListToolchains>,
159        mut cx: AsyncAppContext,
160    ) -> Result<proto::ListToolchainsResponse> {
161        let toolchains = this
162            .update(&mut cx, |this, cx| {
163                let language_name = LanguageName::from_proto(envelope.payload.language_name);
164                let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
165                this.list_toolchains(worktree_id, language_name, cx)
166            })?
167            .await;
168        let has_values = toolchains.is_some();
169        let groups = if let Some(toolchains) = &toolchains {
170            toolchains
171                .groups
172                .iter()
173                .filter_map(|group| {
174                    Some(proto::ToolchainGroup {
175                        start_index: u64::try_from(group.0).ok()?,
176                        name: String::from(group.1.as_ref()),
177                    })
178                })
179                .collect()
180        } else {
181            vec![]
182        };
183        let toolchains = if let Some(toolchains) = toolchains {
184            toolchains
185                .toolchains
186                .into_iter()
187                .map(|toolchain| proto::Toolchain {
188                    name: toolchain.name.to_string(),
189                    path: toolchain.path.to_string(),
190                    raw_json: toolchain.as_json.to_string(),
191                })
192                .collect::<Vec<_>>()
193        } else {
194            vec![]
195        };
196
197        Ok(proto::ListToolchainsResponse {
198            has_values,
199            toolchains,
200            groups,
201        })
202    }
203    pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
204        match &self.0 {
205            ToolchainStoreInner::Local(local, _) => Arc::new(LocalStore(local.downgrade())),
206            ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
207        }
208    }
209}
210
211struct LocalToolchainStore {
212    languages: Arc<LanguageRegistry>,
213    worktree_store: Entity<WorktreeStore>,
214    project_environment: Entity<ProjectEnvironment>,
215    active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
216}
217
218#[async_trait(?Send)]
219impl language::LanguageToolchainStore for LocalStore {
220    async fn active_toolchain(
221        self: Arc<Self>,
222        worktree_id: WorktreeId,
223        language_name: LanguageName,
224        cx: &mut AsyncAppContext,
225    ) -> Option<Toolchain> {
226        self.0
227            .update(cx, |this, cx| {
228                this.active_toolchain(worktree_id, language_name, cx)
229            })
230            .ok()?
231            .await
232    }
233}
234
235#[async_trait(?Send)]
236impl language::LanguageToolchainStore for RemoteStore {
237    async fn active_toolchain(
238        self: Arc<Self>,
239        worktree_id: WorktreeId,
240        language_name: LanguageName,
241        cx: &mut AsyncAppContext,
242    ) -> Option<Toolchain> {
243        self.0
244            .update(cx, |this, cx| {
245                this.active_toolchain(worktree_id, language_name, cx)
246            })
247            .ok()?
248            .await
249    }
250}
251
252pub(crate) struct EmptyToolchainStore;
253#[async_trait(?Send)]
254impl language::LanguageToolchainStore for EmptyToolchainStore {
255    async fn active_toolchain(
256        self: Arc<Self>,
257        _: WorktreeId,
258        _: LanguageName,
259        _: &mut AsyncAppContext,
260    ) -> Option<Toolchain> {
261        None
262    }
263}
264struct LocalStore(WeakEntity<LocalToolchainStore>);
265struct RemoteStore(WeakEntity<RemoteToolchainStore>);
266
267#[derive(Clone)]
268pub(crate) enum ToolchainStoreEvent {
269    ToolchainActivated,
270}
271
272impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
273
274impl LocalToolchainStore {
275    pub(crate) fn activate_toolchain(
276        &self,
277        worktree_id: WorktreeId,
278        toolchain: Toolchain,
279        cx: &mut Context<Self>,
280    ) -> Task<Option<()>> {
281        cx.spawn(move |this, mut cx| async move {
282            this.update(&mut cx, |this, cx| {
283                this.active_toolchains.insert(
284                    (worktree_id, toolchain.language_name.clone()),
285                    toolchain.clone(),
286                );
287                cx.emit(ToolchainStoreEvent::ToolchainActivated);
288            })
289            .ok();
290            Some(())
291        })
292    }
293    pub(crate) fn list_toolchains(
294        &self,
295        worktree_id: WorktreeId,
296        language_name: LanguageName,
297        cx: &App,
298    ) -> Task<Option<ToolchainList>> {
299        let registry = self.languages.clone();
300        let Some(root) = self
301            .worktree_store
302            .read(cx)
303            .worktree_for_id(worktree_id, cx)
304            .map(|worktree| worktree.read(cx).abs_path())
305        else {
306            return Task::ready(None);
307        };
308
309        let environment = self.project_environment.clone();
310        cx.spawn(|mut cx| async move {
311            let project_env = environment
312                .update(&mut cx, |environment, cx| {
313                    environment.get_environment(Some(worktree_id), Some(root.clone()), cx)
314                })
315                .ok()?
316                .await;
317
318            cx.background_executor()
319                .spawn(async move {
320                    let language = registry.language_for_name(&language_name.0).await.ok()?;
321                    let toolchains = language.toolchain_lister()?;
322                    Some(toolchains.list(root.to_path_buf(), project_env).await)
323                })
324                .await
325        })
326    }
327    pub(crate) fn active_toolchain(
328        &self,
329        worktree_id: WorktreeId,
330        language_name: LanguageName,
331        _: &App,
332    ) -> Task<Option<Toolchain>> {
333        Task::ready(
334            self.active_toolchains
335                .get(&(worktree_id, language_name))
336                .cloned(),
337        )
338    }
339}
340struct RemoteToolchainStore {
341    client: AnyProtoClient,
342    project_id: u64,
343}
344
345impl RemoteToolchainStore {
346    pub(crate) fn activate_toolchain(
347        &self,
348        worktree_id: WorktreeId,
349        toolchain: Toolchain,
350        cx: &App,
351    ) -> Task<Option<()>> {
352        let project_id = self.project_id;
353        let client = self.client.clone();
354        cx.spawn(move |_| async move {
355            let _ = client
356                .request(proto::ActivateToolchain {
357                    project_id,
358                    worktree_id: worktree_id.to_proto(),
359                    language_name: toolchain.language_name.into(),
360                    toolchain: Some(proto::Toolchain {
361                        name: toolchain.name.into(),
362                        path: toolchain.path.into(),
363                        raw_json: toolchain.as_json.to_string(),
364                    }),
365                })
366                .await
367                .log_err()?;
368            Some(())
369        })
370    }
371
372    pub(crate) fn list_toolchains(
373        &self,
374        worktree_id: WorktreeId,
375        language_name: LanguageName,
376        cx: &App,
377    ) -> Task<Option<ToolchainList>> {
378        let project_id = self.project_id;
379        let client = self.client.clone();
380        cx.spawn(move |_| async move {
381            let response = client
382                .request(proto::ListToolchains {
383                    project_id,
384                    worktree_id: worktree_id.to_proto(),
385                    language_name: language_name.clone().into(),
386                })
387                .await
388                .log_err()?;
389            if !response.has_values {
390                return None;
391            }
392            let toolchains = response
393                .toolchains
394                .into_iter()
395                .filter_map(|toolchain| {
396                    Some(Toolchain {
397                        language_name: language_name.clone(),
398                        name: toolchain.name.into(),
399                        path: toolchain.path.into(),
400                        as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
401                    })
402                })
403                .collect();
404            let groups = response
405                .groups
406                .into_iter()
407                .filter_map(|group| {
408                    Some((usize::try_from(group.start_index).ok()?, group.name.into()))
409                })
410                .collect();
411            Some(ToolchainList {
412                toolchains,
413                default: None,
414                groups,
415            })
416        })
417    }
418    pub(crate) fn active_toolchain(
419        &self,
420        worktree_id: WorktreeId,
421        language_name: LanguageName,
422        cx: &App,
423    ) -> Task<Option<Toolchain>> {
424        let project_id = self.project_id;
425        let client = self.client.clone();
426        cx.spawn(move |_| async move {
427            let response = client
428                .request(proto::ActiveToolchain {
429                    project_id,
430                    worktree_id: worktree_id.to_proto(),
431                    language_name: language_name.clone().into(),
432                })
433                .await
434                .log_err()?;
435
436            response.toolchain.and_then(|toolchain| {
437                Some(Toolchain {
438                    language_name: language_name.clone(),
439                    name: toolchain.name.into(),
440                    path: toolchain.path.into(),
441                    as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
442                })
443            })
444        })
445    }
446}