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.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 term = workspace
73 .update(cx, |workspace, cx| {
74 let languages = workspace.project().read(cx).languages();
75 Project::toolchain_term(languages.clone(), language_name.clone())
76 })
77 .ok()?
78 .await?;
79 let _ = this.update(cx, |this, cx| {
80 this.term = 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, relative_path) = cx
174 .update(|_, cx| {
175 project.read(cx).available_toolchains(
176 ProjectPath {
177 worktree_id,
178 path: relative_path.clone(),
179 },
180 language_name,
181 cx,
182 )
183 })
184 .ok()?
185 .await?;
186 if let Some(toolchain) = toolchains.toolchains.first() {
187 // Since we don't have a selected toolchain, pick one for user here.
188 workspace::WORKSPACE_DB
189 .set_toolchain(
190 workspace_id,
191 worktree_id,
192 relative_path.to_string_lossy().into_owned(),
193 toolchain.clone(),
194 )
195 .await
196 .ok()?;
197 project
198 .update(cx, |this, cx| {
199 this.activate_toolchain(
200 ProjectPath {
201 worktree_id,
202 path: relative_path,
203 },
204 toolchain.clone(),
205 cx,
206 )
207 })
208 .ok()?
209 .await;
210 }
211
212 toolchains.toolchains.first().cloned()
213 }
214 })
215 }
216}
217
218impl Render for ActiveToolchain {
219 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
220 div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
221 let term = self.term.clone();
222 el.child(
223 Button::new("change-toolchain", active_toolchain.name.clone())
224 .label_size(LabelSize::Small)
225 .on_click(cx.listener(|this, _, window, cx| {
226 if let Some(workspace) = this.workspace.upgrade() {
227 workspace.update(cx, |workspace, cx| {
228 ToolchainSelector::toggle(workspace, window, cx)
229 });
230 }
231 }))
232 .tooltip(Tooltip::text(format!("Select {}", &term))),
233 )
234 })
235 }
236}
237
238impl StatusItemView for ActiveToolchain {
239 fn set_active_pane_item(
240 &mut self,
241 active_pane_item: Option<&dyn ItemHandle>,
242 window: &mut Window,
243 cx: &mut Context<Self>,
244 ) {
245 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
246 self.update_lister(editor, window, cx);
247 }
248 cx.notify();
249 }
250}