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