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