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