toolchain_store.rs

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