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