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            let language = registry.language_for_name(&language_name.0).await.ok()?;
315            let toolchains = language
316                .toolchain_lister()?
317                .list(root.to_path_buf(), project_env)
318                .await;
319            Some(toolchains)
320        })
321    }
322    pub(crate) fn active_toolchain(
323        &self,
324        worktree_id: WorktreeId,
325        language_name: LanguageName,
326        _: &AppContext,
327    ) -> Task<Option<Toolchain>> {
328        Task::ready(
329            self.active_toolchains
330                .get(&(worktree_id, language_name))
331                .cloned(),
332        )
333    }
334}
335struct RemoteToolchainStore {
336    client: AnyProtoClient,
337    project_id: u64,
338}
339
340impl RemoteToolchainStore {
341    pub(crate) fn activate_toolchain(
342        &self,
343        worktree_id: WorktreeId,
344        toolchain: Toolchain,
345        cx: &AppContext,
346    ) -> Task<Option<()>> {
347        let project_id = self.project_id;
348        let client = self.client.clone();
349        cx.spawn(move |_| async move {
350            let _ = client
351                .request(proto::ActivateToolchain {
352                    project_id,
353                    worktree_id: worktree_id.to_proto(),
354                    language_name: toolchain.language_name.into(),
355                    toolchain: Some(proto::Toolchain {
356                        name: toolchain.name.into(),
357                        path: toolchain.path.into(),
358                        raw_json: toolchain.as_json.to_string(),
359                    }),
360                })
361                .await
362                .log_err()?;
363            Some(())
364        })
365    }
366
367    pub(crate) fn list_toolchains(
368        &self,
369        worktree_id: WorktreeId,
370        language_name: LanguageName,
371        cx: &AppContext,
372    ) -> Task<Option<ToolchainList>> {
373        let project_id = self.project_id;
374        let client = self.client.clone();
375        cx.spawn(move |_| async move {
376            let response = client
377                .request(proto::ListToolchains {
378                    project_id,
379                    worktree_id: worktree_id.to_proto(),
380                    language_name: language_name.clone().into(),
381                })
382                .await
383                .log_err()?;
384            if !response.has_values {
385                return None;
386            }
387            let toolchains = response
388                .toolchains
389                .into_iter()
390                .filter_map(|toolchain| {
391                    Some(Toolchain {
392                        language_name: language_name.clone(),
393                        name: toolchain.name.into(),
394                        path: toolchain.path.into(),
395                        as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
396                    })
397                })
398                .collect();
399            let groups = response
400                .groups
401                .into_iter()
402                .filter_map(|group| {
403                    Some((usize::try_from(group.start_index).ok()?, group.name.into()))
404                })
405                .collect();
406            Some(ToolchainList {
407                toolchains,
408                default: None,
409                groups,
410            })
411        })
412    }
413    pub(crate) fn active_toolchain(
414        &self,
415        worktree_id: WorktreeId,
416        language_name: LanguageName,
417        cx: &AppContext,
418    ) -> Task<Option<Toolchain>> {
419        let project_id = self.project_id;
420        let client = self.client.clone();
421        cx.spawn(move |_| async move {
422            let response = client
423                .request(proto::ActiveToolchain {
424                    project_id,
425                    worktree_id: worktree_id.to_proto(),
426                    language_name: language_name.clone().into(),
427                })
428                .await
429                .log_err()?;
430
431            response.toolchain.and_then(|toolchain| {
432                Some(Toolchain {
433                    language_name: language_name.clone(),
434                    name: toolchain.name.into(),
435                    path: toolchain.path.into(),
436                    as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
437                })
438            })
439        })
440    }
441}