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