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