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, 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 language_name,
123 };
124 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
125 Ok(this.activate_toolchain(worktree_id, toolchain, cx))
126 })??
127 .await;
128 Ok(proto::Ack {})
129 }
130 async fn handle_active_toolchain(
131 this: Model<Self>,
132 envelope: TypedEnvelope<proto::ActiveToolchain>,
133 mut cx: AsyncAppContext,
134 ) -> Result<proto::ActiveToolchainResponse> {
135 let toolchain = this
136 .update(&mut cx, |this, cx| {
137 let language_name = LanguageName::from_proto(envelope.payload.language_name);
138 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
139 this.active_toolchain(worktree_id, language_name, cx)
140 })?
141 .await;
142
143 Ok(proto::ActiveToolchainResponse {
144 toolchain: toolchain.map(|toolchain| proto::Toolchain {
145 name: toolchain.name.into(),
146 path: toolchain.path.into(),
147 }),
148 })
149 }
150
151 async fn handle_list_toolchains(
152 this: Model<Self>,
153 envelope: TypedEnvelope<proto::ListToolchains>,
154 mut cx: AsyncAppContext,
155 ) -> Result<proto::ListToolchainsResponse> {
156 let toolchains = this
157 .update(&mut cx, |this, cx| {
158 let language_name = LanguageName::from_proto(envelope.payload.language_name);
159 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
160 this.list_toolchains(worktree_id, language_name, cx)
161 })?
162 .await;
163 let has_values = toolchains.is_some();
164 let groups = if let Some(toolchains) = &toolchains {
165 toolchains
166 .groups
167 .iter()
168 .filter_map(|group| {
169 Some(proto::ToolchainGroup {
170 start_index: u64::try_from(group.0).ok()?,
171 name: String::from(group.1.as_ref()),
172 })
173 })
174 .collect()
175 } else {
176 vec![]
177 };
178 let toolchains = if let Some(toolchains) = toolchains {
179 toolchains
180 .toolchains
181 .into_iter()
182 .map(|toolchain| proto::Toolchain {
183 name: toolchain.name.to_string(),
184 path: toolchain.path.to_string(),
185 })
186 .collect::<Vec<_>>()
187 } else {
188 vec![]
189 };
190
191 Ok(proto::ListToolchainsResponse {
192 has_values,
193 toolchains,
194 groups,
195 })
196 }
197 pub fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
198 match &self.0 {
199 ToolchainStoreInner::Local(local, _) => Arc::new(LocalStore(local.downgrade())),
200 ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
201 }
202 }
203}
204
205struct LocalToolchainStore {
206 languages: Arc<LanguageRegistry>,
207 worktree_store: Model<WorktreeStore>,
208 project_environment: Model<ProjectEnvironment>,
209 active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
210}
211
212#[async_trait(?Send)]
213impl language::LanguageToolchainStore for LocalStore {
214 async fn active_toolchain(
215 self: Arc<Self>,
216 worktree_id: WorktreeId,
217 language_name: LanguageName,
218 cx: &mut AsyncAppContext,
219 ) -> Option<Toolchain> {
220 self.0
221 .update(cx, |this, cx| {
222 this.active_toolchain(worktree_id, language_name, cx)
223 })
224 .ok()?
225 .await
226 }
227}
228
229#[async_trait(?Send)]
230impl language::LanguageToolchainStore for RemoteStore {
231 async fn active_toolchain(
232 self: Arc<Self>,
233 worktree_id: WorktreeId,
234 language_name: LanguageName,
235 cx: &mut AsyncAppContext,
236 ) -> Option<Toolchain> {
237 self.0
238 .update(cx, |this, cx| {
239 this.active_toolchain(worktree_id, language_name, cx)
240 })
241 .ok()?
242 .await
243 }
244}
245
246pub(crate) struct EmptyToolchainStore;
247#[async_trait(?Send)]
248impl language::LanguageToolchainStore for EmptyToolchainStore {
249 async fn active_toolchain(
250 self: Arc<Self>,
251 _: WorktreeId,
252 _: LanguageName,
253 _: &mut AsyncAppContext,
254 ) -> Option<Toolchain> {
255 None
256 }
257}
258struct LocalStore(WeakModel<LocalToolchainStore>);
259struct RemoteStore(WeakModel<RemoteToolchainStore>);
260
261#[derive(Clone)]
262pub(crate) enum ToolchainStoreEvent {
263 ToolchainActivated,
264}
265
266impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
267
268impl LocalToolchainStore {
269 pub(crate) fn activate_toolchain(
270 &self,
271 worktree_id: WorktreeId,
272 toolchain: Toolchain,
273 cx: &mut ModelContext<Self>,
274 ) -> Task<Option<()>> {
275 cx.spawn(move |this, mut cx| async move {
276 this.update(&mut cx, |this, cx| {
277 this.active_toolchains.insert(
278 (worktree_id, toolchain.language_name.clone()),
279 toolchain.clone(),
280 );
281 cx.emit(ToolchainStoreEvent::ToolchainActivated);
282 })
283 .ok();
284 Some(())
285 })
286 }
287 pub(crate) fn list_toolchains(
288 &self,
289 worktree_id: WorktreeId,
290 language_name: LanguageName,
291 cx: &AppContext,
292 ) -> Task<Option<ToolchainList>> {
293 let registry = self.languages.clone();
294 let Some(root) = self
295 .worktree_store
296 .read(cx)
297 .worktree_for_id(worktree_id, cx)
298 .map(|worktree| worktree.read(cx).abs_path())
299 else {
300 return Task::ready(None);
301 };
302
303 let environment = self.project_environment.clone();
304 cx.spawn(|mut cx| async move {
305 let project_env = environment
306 .update(&mut cx, |environment, cx| {
307 environment.get_environment(Some(worktree_id), Some(root.clone()), cx)
308 })
309 .ok()?
310 .await;
311 let language = registry.language_for_name(&language_name.0).await.ok()?;
312 let toolchains = language
313 .toolchain_lister()?
314 .list(root.to_path_buf(), project_env)
315 .await;
316 Some(toolchains)
317 })
318 }
319 pub(crate) fn active_toolchain(
320 &self,
321 worktree_id: WorktreeId,
322 language_name: LanguageName,
323 _: &AppContext,
324 ) -> Task<Option<Toolchain>> {
325 Task::ready(
326 self.active_toolchains
327 .get(&(worktree_id, language_name))
328 .cloned(),
329 )
330 }
331}
332struct RemoteToolchainStore {
333 client: AnyProtoClient,
334 project_id: u64,
335}
336
337impl RemoteToolchainStore {
338 pub(crate) fn activate_toolchain(
339 &self,
340 worktree_id: WorktreeId,
341 toolchain: Toolchain,
342 cx: &AppContext,
343 ) -> Task<Option<()>> {
344 let project_id = self.project_id;
345 let client = self.client.clone();
346 cx.spawn(move |_| async move {
347 let _ = client
348 .request(proto::ActivateToolchain {
349 project_id,
350 worktree_id: worktree_id.to_proto(),
351 language_name: toolchain.language_name.into(),
352 toolchain: Some(proto::Toolchain {
353 name: toolchain.name.into(),
354 path: toolchain.path.into(),
355 }),
356 })
357 .await
358 .log_err()?;
359 Some(())
360 })
361 }
362
363 pub(crate) fn list_toolchains(
364 &self,
365 worktree_id: WorktreeId,
366 language_name: LanguageName,
367 cx: &AppContext,
368 ) -> Task<Option<ToolchainList>> {
369 let project_id = self.project_id;
370 let client = self.client.clone();
371 cx.spawn(move |_| async move {
372 let response = client
373 .request(proto::ListToolchains {
374 project_id,
375 worktree_id: worktree_id.to_proto(),
376 language_name: language_name.clone().into(),
377 })
378 .await
379 .log_err()?;
380 if !response.has_values {
381 return None;
382 }
383 let toolchains = response
384 .toolchains
385 .into_iter()
386 .map(|toolchain| Toolchain {
387 language_name: language_name.clone(),
388 name: toolchain.name.into(),
389 path: toolchain.path.into(),
390 })
391 .collect();
392 let groups = response
393 .groups
394 .into_iter()
395 .filter_map(|group| {
396 Some((usize::try_from(group.start_index).ok()?, group.name.into()))
397 })
398 .collect();
399 Some(ToolchainList {
400 toolchains,
401 default: None,
402 groups,
403 })
404 })
405 }
406 pub(crate) fn active_toolchain(
407 &self,
408 worktree_id: WorktreeId,
409 language_name: LanguageName,
410 cx: &AppContext,
411 ) -> Task<Option<Toolchain>> {
412 let project_id = self.project_id;
413 let client = self.client.clone();
414 cx.spawn(move |_| async move {
415 let response = client
416 .request(proto::ActiveToolchain {
417 project_id,
418 worktree_id: worktree_id.to_proto(),
419 language_name: language_name.clone().into(),
420 })
421 .await
422 .log_err()?;
423
424 response.toolchain.map(|toolchain| Toolchain {
425 language_name: language_name.clone(),
426 name: toolchain.name.into(),
427 path: toolchain.path.into(),
428 })
429 })
430 }
431}