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 if let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx)) {
126 if self
127 .active_buffer
128 .as_ref()
129 .is_some_and(|(old_worktree_id, old_buffer, _)| {
130 (old_worktree_id, old_buffer.entity_id())
131 == (&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
151 cx.notify();
152 }
153
154 fn active_toolchain(
155 workspace: WeakEntity<Workspace>,
156 worktree_id: WorktreeId,
157 relative_path: Arc<Path>,
158 language_name: LanguageName,
159 cx: &mut AsyncWindowContext,
160 ) -> Task<Option<Toolchain>> {
161 cx.spawn(async move |cx| {
162 let workspace_id = workspace
163 .read_with(cx, |this, _| this.database_id())
164 .ok()
165 .flatten()?;
166 let selected_toolchain = workspace
167 .update(cx, |this, cx| {
168 this.project().read(cx).active_toolchain(
169 ProjectPath {
170 worktree_id,
171 path: relative_path.clone(),
172 },
173 language_name.clone(),
174 cx,
175 )
176 })
177 .ok()?
178 .await;
179 if let Some(toolchain) = selected_toolchain {
180 Some(toolchain)
181 } else {
182 let project = workspace
183 .read_with(cx, |this, _| this.project().clone())
184 .ok()?;
185 let (toolchains, relative_path) = cx
186 .update(|_, cx| {
187 project.read(cx).available_toolchains(
188 ProjectPath {
189 worktree_id,
190 path: relative_path.clone(),
191 },
192 language_name,
193 cx,
194 )
195 })
196 .ok()?
197 .await?;
198 if let Some(toolchain) = toolchains.toolchains.first() {
199 // Since we don't have a selected toolchain, pick one for user here.
200 workspace::WORKSPACE_DB
201 .set_toolchain(
202 workspace_id,
203 worktree_id,
204 relative_path.to_string_lossy().into_owned(),
205 toolchain.clone(),
206 )
207 .await
208 .ok()?;
209 project
210 .update(cx, |this, cx| {
211 this.activate_toolchain(
212 ProjectPath {
213 worktree_id,
214 path: relative_path,
215 },
216 toolchain.clone(),
217 cx,
218 )
219 })
220 .ok()?
221 .await;
222 }
223
224 toolchains.toolchains.first().cloned()
225 }
226 })
227 }
228}
229
230impl Render for ActiveToolchain {
231 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
232 div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
233 let term = self.term.clone();
234 el.child(
235 Button::new("change-toolchain", active_toolchain.name.clone())
236 .label_size(LabelSize::Small)
237 .on_click(cx.listener(|this, _, window, cx| {
238 if let Some(workspace) = this.workspace.upgrade() {
239 workspace.update(cx, |workspace, cx| {
240 ToolchainSelector::toggle(workspace, window, cx)
241 });
242 }
243 }))
244 .tooltip(Tooltip::text(format!("Select {}", &term))),
245 )
246 })
247 }
248}
249
250impl StatusItemView for ActiveToolchain {
251 fn set_active_pane_item(
252 &mut self,
253 active_pane_item: Option<&dyn ItemHandle>,
254 window: &mut Window,
255 cx: &mut Context<Self>,
256 ) {
257 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
258 self.update_lister(editor, window, cx);
259 }
260 cx.notify();
261 }
262}