1use std::{path::Path, sync::Arc};
2
3use editor::Editor;
4use gpui::{
5 AsyncWindowContext, Context, Entity, IntoElement, ParentElement, Render, Subscription, Task,
6 WeakEntity, Window, div,
7};
8use language::{Buffer, BufferEvent, LanguageName, Toolchain};
9use project::{Project, ProjectPath, WorktreeId, toolchain_store::ToolchainStoreEvent};
10use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, SharedString, Tooltip};
11use util::maybe;
12use workspace::{StatusItemView, Workspace, item::ItemHandle};
13
14use crate::ToolchainSelector;
15
16pub struct ActiveToolchain {
17 active_toolchain: Option<Toolchain>,
18 term: SharedString,
19 workspace: WeakEntity<Workspace>,
20 active_buffer: Option<(WorktreeId, WeakEntity<Buffer>, Subscription)>,
21 _update_toolchain_task: Task<Option<()>>,
22}
23
24impl ActiveToolchain {
25 pub fn new(workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>) -> Self {
26 if let Some(store) = workspace.project().read(cx).toolchain_store() {
27 cx.subscribe_in(
28 &store,
29 window,
30 |this, _, _: &ToolchainStoreEvent, window, cx| {
31 let editor = this
32 .workspace
33 .update(cx, |workspace, cx| {
34 workspace
35 .active_item(cx)
36 .and_then(|item| item.downcast::<Editor>())
37 })
38 .ok()
39 .flatten();
40 if let Some(editor) = editor {
41 this.active_toolchain.take();
42 this.update_lister(editor, window, cx);
43 }
44 },
45 )
46 .detach();
47 }
48 Self {
49 active_toolchain: None,
50 active_buffer: None,
51 term: SharedString::new_static("Toolchain"),
52 workspace: workspace.weak_handle(),
53
54 _update_toolchain_task: Self::spawn_tracker_task(window, cx),
55 }
56 }
57 fn spawn_tracker_task(window: &mut Window, cx: &mut Context<Self>) -> Task<Option<()>> {
58 cx.spawn_in(window, async move |this, cx| {
59 let did_set_toolchain = maybe!(async {
60 let active_file = this
61 .read_with(cx, |this, _| {
62 this.active_buffer
63 .as_ref()
64 .map(|(_, buffer, _)| buffer.clone())
65 })
66 .ok()
67 .flatten()?;
68 let workspace = this.read_with(cx, |this, _| this.workspace.clone()).ok()?;
69 let language_name = active_file
70 .read_with(cx, |this, _| Some(this.language()?.name()))
71 .ok()
72 .flatten()?;
73 let term = workspace
74 .update(cx, |workspace, cx| {
75 let languages = workspace.project().read(cx).languages();
76 Project::toolchain_term(languages.clone(), language_name.clone())
77 })
78 .ok()?
79 .await?;
80 let _ = this.update(cx, |this, cx| {
81 this.term = term;
82 cx.notify();
83 });
84 let (worktree_id, path) = active_file
85 .update(cx, |this, cx| {
86 this.file().and_then(|file| {
87 Some((
88 file.worktree_id(cx),
89 Arc::<Path>::from(file.path().parent()?),
90 ))
91 })
92 })
93 .ok()
94 .flatten()?;
95 let toolchain =
96 Self::active_toolchain(workspace, worktree_id, path, language_name, cx).await?;
97 this.update(cx, |this, cx| {
98 this.active_toolchain = Some(toolchain);
99
100 cx.notify();
101 })
102 .ok()
103 })
104 .await
105 .is_some();
106 if !did_set_toolchain {
107 this.update(cx, |this, cx| {
108 this.active_toolchain = None;
109 cx.notify();
110 })
111 .ok();
112 }
113 did_set_toolchain.then_some(())
114 })
115 }
116
117 fn update_lister(
118 &mut self,
119 editor: Entity<Editor>,
120 window: &mut Window,
121 cx: &mut Context<Self>,
122 ) {
123 let editor = editor.read(cx);
124 if let Some((_, buffer, _)) = editor.active_excerpt(cx)
125 && let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx))
126 {
127 if self
128 .active_buffer
129 .as_ref()
130 .is_some_and(|(old_worktree_id, old_buffer, _)| {
131 (old_worktree_id, old_buffer.entity_id()) == (&worktree_id, buffer.entity_id())
132 })
133 {
134 return;
135 }
136
137 let subscription = cx.subscribe_in(
138 &buffer,
139 window,
140 |this, _, event: &BufferEvent, window, cx| {
141 if matches!(event, BufferEvent::LanguageChanged) {
142 this._update_toolchain_task = Self::spawn_tracker_task(window, cx);
143 }
144 },
145 );
146 self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription));
147 self._update_toolchain_task = Self::spawn_tracker_task(window, cx);
148 }
149
150 cx.notify();
151 }
152
153 fn active_toolchain(
154 workspace: WeakEntity<Workspace>,
155 worktree_id: WorktreeId,
156 relative_path: Arc<Path>,
157 language_name: LanguageName,
158 cx: &mut AsyncWindowContext,
159 ) -> Task<Option<Toolchain>> {
160 cx.spawn(async move |cx| {
161 let workspace_id = workspace
162 .read_with(cx, |this, _| this.database_id())
163 .ok()
164 .flatten()?;
165 let selected_toolchain = workspace
166 .update(cx, |this, cx| {
167 this.project().read(cx).active_toolchain(
168 ProjectPath {
169 worktree_id,
170 path: relative_path.clone(),
171 },
172 language_name.clone(),
173 cx,
174 )
175 })
176 .ok()?
177 .await;
178 if let Some(toolchain) = selected_toolchain {
179 Some(toolchain)
180 } else {
181 let project = workspace
182 .read_with(cx, |this, _| this.project().clone())
183 .ok()?;
184 let (toolchains, relative_path) = cx
185 .update(|_, cx| {
186 project.read(cx).available_toolchains(
187 ProjectPath {
188 worktree_id,
189 path: relative_path.clone(),
190 },
191 language_name,
192 cx,
193 )
194 })
195 .ok()?
196 .await?;
197 if let Some(toolchain) = toolchains.toolchains.first() {
198 // Since we don't have a selected toolchain, pick one for user here.
199 workspace::WORKSPACE_DB
200 .set_toolchain(
201 workspace_id,
202 worktree_id,
203 relative_path.to_string_lossy().into_owned(),
204 toolchain.clone(),
205 )
206 .await
207 .ok()?;
208 project
209 .update(cx, |this, cx| {
210 this.activate_toolchain(
211 ProjectPath {
212 worktree_id,
213 path: relative_path,
214 },
215 toolchain.clone(),
216 cx,
217 )
218 })
219 .ok()?
220 .await;
221 }
222
223 toolchains.toolchains.first().cloned()
224 }
225 })
226 }
227}
228
229impl Render for ActiveToolchain {
230 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
231 div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
232 let term = self.term.clone();
233 el.child(
234 Button::new("change-toolchain", active_toolchain.name.clone())
235 .label_size(LabelSize::Small)
236 .on_click(cx.listener(|this, _, window, cx| {
237 if let Some(workspace) = this.workspace.upgrade() {
238 workspace.update(cx, |workspace, cx| {
239 ToolchainSelector::toggle(workspace, window, cx)
240 });
241 }
242 }))
243 .tooltip(Tooltip::text(format!("Select {}", &term))),
244 )
245 })
246 }
247}
248
249impl StatusItemView for ActiveToolchain {
250 fn set_active_pane_item(
251 &mut self,
252 active_pane_item: Option<&dyn ItemHandle>,
253 window: &mut Window,
254 cx: &mut Context<Self>,
255 ) {
256 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
257 self.update_lister(editor, window, cx);
258 }
259 cx.notify();
260 }
261}