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