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, ToolchainScope};
9use project::{Project, ProjectPath, Toolchains, 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.update_lister(editor, window, cx);
42 }
43 },
44 )
45 .detach();
46 }
47 Self {
48 active_toolchain: None,
49 active_buffer: None,
50 term: SharedString::new_static("Toolchain"),
51 workspace: workspace.weak_handle(),
52
53 _update_toolchain_task: Self::spawn_tracker_task(window, cx),
54 }
55 }
56 fn spawn_tracker_task(window: &mut Window, cx: &mut Context<Self>) -> Task<Option<()>> {
57 cx.spawn_in(window, async move |this, cx| {
58 let did_set_toolchain = maybe!(async {
59 let active_file = this
60 .read_with(cx, |this, _| {
61 this.active_buffer
62 .as_ref()
63 .map(|(_, buffer, _)| buffer.clone())
64 })
65 .ok()
66 .flatten()?;
67 let workspace = this.read_with(cx, |this, _| this.workspace.clone()).ok()?;
68 let language_name = active_file
69 .read_with(cx, |this, _| Some(this.language()?.name()))
70 .ok()
71 .flatten()?;
72 let meta = workspace
73 .update(cx, |workspace, cx| {
74 let languages = workspace.project().read(cx).languages();
75 Project::toolchain_metadata(languages.clone(), language_name.clone())
76 })
77 .ok()?
78 .await?;
79 let _ = this.update(cx, |this, cx| {
80 this.term = meta.term;
81 cx.notify();
82 });
83 let (worktree_id, path) = active_file
84 .update(cx, |this, cx| {
85 this.file().and_then(|file| {
86 Some((
87 file.worktree_id(cx),
88 Arc::<Path>::from(file.path().parent()?),
89 ))
90 })
91 })
92 .ok()
93 .flatten()?;
94 let toolchain =
95 Self::active_toolchain(workspace, worktree_id, path, language_name, cx).await?;
96 this.update(cx, |this, cx| {
97 this.active_toolchain = Some(toolchain);
98
99 cx.notify();
100 })
101 .ok()
102 })
103 .await
104 .is_some();
105 if !did_set_toolchain {
106 this.update(cx, |this, cx| {
107 this.active_toolchain = None;
108 cx.notify();
109 })
110 .ok();
111 }
112 did_set_toolchain.then_some(())
113 })
114 }
115
116 fn update_lister(
117 &mut self,
118 editor: Entity<Editor>,
119 window: &mut Window,
120 cx: &mut Context<Self>,
121 ) {
122 let editor = editor.read(cx);
123 if let Some((_, buffer, _)) = editor.active_excerpt(cx)
124 && let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx))
125 {
126 let subscription = cx.subscribe_in(
127 &buffer,
128 window,
129 |this, _, event: &BufferEvent, window, cx| {
130 if matches!(event, BufferEvent::LanguageChanged) {
131 this._update_toolchain_task = Self::spawn_tracker_task(window, cx);
132 }
133 },
134 );
135 self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription));
136 self._update_toolchain_task = Self::spawn_tracker_task(window, cx);
137 }
138
139 cx.notify();
140 }
141
142 fn active_toolchain(
143 workspace: WeakEntity<Workspace>,
144 worktree_id: WorktreeId,
145 relative_path: Arc<Path>,
146 language_name: LanguageName,
147 cx: &mut AsyncWindowContext,
148 ) -> Task<Option<Toolchain>> {
149 cx.spawn(async move |cx| {
150 let workspace_id = workspace
151 .read_with(cx, |this, _| this.database_id())
152 .ok()
153 .flatten()?;
154 let selected_toolchain = workspace
155 .update(cx, |this, cx| {
156 this.project().read(cx).active_toolchain(
157 ProjectPath {
158 worktree_id,
159 path: relative_path.clone(),
160 },
161 language_name.clone(),
162 cx,
163 )
164 })
165 .ok()?
166 .await;
167 if let Some(toolchain) = selected_toolchain {
168 Some(toolchain)
169 } else {
170 let project = workspace
171 .read_with(cx, |this, _| this.project().clone())
172 .ok()?;
173 let Toolchains {
174 toolchains,
175 root_path: relative_path,
176 user_toolchains,
177 } = cx
178 .update(|_, cx| {
179 project.read(cx).available_toolchains(
180 ProjectPath {
181 worktree_id,
182 path: relative_path.clone(),
183 },
184 language_name,
185 cx,
186 )
187 })
188 .ok()?
189 .await?;
190 // Since we don't have a selected toolchain, pick one for user here.
191 let default_choice = user_toolchains
192 .iter()
193 .find_map(|(scope, toolchains)| {
194 if scope == &ToolchainScope::Global {
195 // Ignore global toolchains when making a default choice. They're unlikely to be the right choice.
196 None
197 } else {
198 toolchains.first()
199 }
200 })
201 .or_else(|| toolchains.toolchains.first())
202 .cloned();
203 if let Some(toolchain) = &default_choice {
204 workspace::WORKSPACE_DB
205 .set_toolchain(
206 workspace_id,
207 worktree_id,
208 relative_path.to_string_lossy().into_owned(),
209 toolchain.clone(),
210 )
211 .await
212 .ok()?;
213 project
214 .update(cx, |this, cx| {
215 this.activate_toolchain(
216 ProjectPath {
217 worktree_id,
218 path: relative_path,
219 },
220 toolchain.clone(),
221 cx,
222 )
223 })
224 .ok()?
225 .await;
226 }
227
228 default_choice
229 }
230 })
231 }
232}
233
234impl Render for ActiveToolchain {
235 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
236 div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
237 let term = self.term.clone();
238 el.child(
239 Button::new("change-toolchain", active_toolchain.name.clone())
240 .label_size(LabelSize::Small)
241 .on_click(cx.listener(|this, _, window, cx| {
242 if let Some(workspace) = this.workspace.upgrade() {
243 workspace.update(cx, |workspace, cx| {
244 ToolchainSelector::toggle(workspace, window, cx)
245 });
246 }
247 }))
248 .tooltip(Tooltip::text(format!("Select {}", &term))),
249 )
250 })
251 }
252}
253
254impl StatusItemView for ActiveToolchain {
255 fn set_active_pane_item(
256 &mut self,
257 active_pane_item: Option<&dyn ItemHandle>,
258 window: &mut Window,
259 cx: &mut Context<Self>,
260 ) {
261 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
262 self.update_lister(editor, window, cx);
263 }
264 cx.notify();
265 }
266}