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