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