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}