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