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};
9use project::{Project, ProjectPath, WorktreeId};
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 Self {
26 active_toolchain: None,
27 active_buffer: None,
28 term: SharedString::new_static("Toolchain"),
29 workspace: workspace.weak_handle(),
30
31 _update_toolchain_task: Self::spawn_tracker_task(window, cx),
32 }
33 }
34 fn spawn_tracker_task(window: &mut Window, cx: &mut Context<Self>) -> Task<Option<()>> {
35 cx.spawn_in(window, async move |this, cx| {
36 let active_file = this
37 .update(cx, |this, _| {
38 this.active_buffer
39 .as_ref()
40 .map(|(_, buffer, _)| buffer.clone())
41 })
42 .ok()
43 .flatten()?;
44 let workspace = this.update(cx, |this, _| this.workspace.clone()).ok()?;
45 let language_name = active_file
46 .update(cx, |this, _| Some(this.language()?.name()))
47 .ok()
48 .flatten()?;
49 let term = workspace
50 .update(cx, |workspace, cx| {
51 let languages = workspace.project().read(cx).languages();
52 Project::toolchain_term(languages.clone(), language_name.clone())
53 })
54 .ok()?
55 .await?;
56 let _ = this.update(cx, |this, cx| {
57 this.term = term;
58 cx.notify();
59 });
60 let worktree_id = active_file
61 .update(cx, |this, cx| Some(this.file()?.worktree_id(cx)))
62 .ok()
63 .flatten()?;
64 let toolchain =
65 Self::active_toolchain(workspace, worktree_id, language_name, cx).await?;
66 let _ = this.update(cx, |this, cx| {
67 this.active_toolchain = Some(toolchain);
68
69 cx.notify();
70 });
71 Some(())
72 })
73 }
74
75 fn update_lister(
76 &mut self,
77 editor: Entity<Editor>,
78 window: &mut Window,
79 cx: &mut Context<Self>,
80 ) {
81 let editor = editor.read(cx);
82 if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
83 if let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx)) {
84 let subscription = cx.subscribe_in(
85 &buffer,
86 window,
87 |this, _, event: &BufferEvent, window, cx| {
88 if matches!(event, BufferEvent::LanguageChanged) {
89 this._update_toolchain_task = Self::spawn_tracker_task(window, cx);
90 }
91 },
92 );
93 self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription));
94 self._update_toolchain_task = Self::spawn_tracker_task(window, cx);
95 }
96 }
97
98 cx.notify();
99 }
100
101 fn active_toolchain(
102 workspace: WeakEntity<Workspace>,
103 worktree_id: WorktreeId,
104 language_name: LanguageName,
105 cx: &mut AsyncWindowContext,
106 ) -> Task<Option<Toolchain>> {
107 cx.spawn(async move |cx| {
108 let workspace_id = workspace
109 .update(cx, |this, _| this.database_id())
110 .ok()
111 .flatten()?;
112 let selected_toolchain = workspace
113 .update(cx, |this, cx| {
114 this.project().read(cx).active_toolchain(
115 ProjectPath {
116 worktree_id,
117 path: Arc::from("".as_ref()),
118 },
119 language_name.clone(),
120 cx,
121 )
122 })
123 .ok()?
124 .await;
125 if let Some(toolchain) = selected_toolchain {
126 Some(toolchain)
127 } else {
128 let project = workspace
129 .update(cx, |this, _| this.project().clone())
130 .ok()?;
131 let toolchains = cx
132 .update(|_, cx| {
133 project.read(cx).available_toolchains(
134 ProjectPath {
135 worktree_id,
136 path: Arc::from("".as_ref()),
137 },
138 language_name,
139 cx,
140 )
141 })
142 .ok()?
143 .await?;
144 if let Some(toolchain) = toolchains.toolchains.first() {
145 // Since we don't have a selected toolchain, pick one for user here.
146 workspace::WORKSPACE_DB
147 .set_toolchain(workspace_id, worktree_id, "".to_owned(), toolchain.clone())
148 .await
149 .ok()?;
150 project
151 .update(cx, |this, cx| {
152 this.activate_toolchain(
153 ProjectPath {
154 worktree_id,
155 path: Arc::from("".as_ref()),
156 },
157 toolchain.clone(),
158 cx,
159 )
160 })
161 .ok()?
162 .await;
163 }
164
165 toolchains.toolchains.first().cloned()
166 }
167 })
168 }
169}
170
171impl Render for ActiveToolchain {
172 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
173 div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
174 let term = self.term.clone();
175 el.child(
176 Button::new("change-toolchain", active_toolchain.name.clone())
177 .label_size(LabelSize::Small)
178 .on_click(cx.listener(|this, _, window, cx| {
179 if let Some(workspace) = this.workspace.upgrade() {
180 workspace.update(cx, |workspace, cx| {
181 ToolchainSelector::toggle(workspace, window, cx)
182 });
183 }
184 }))
185 .tooltip(Tooltip::text(format!("Select {}", &term))),
186 )
187 })
188 }
189}
190
191impl StatusItemView for ActiveToolchain {
192 fn set_active_pane_item(
193 &mut self,
194 active_pane_item: Option<&dyn ItemHandle>,
195 window: &mut Window,
196 cx: &mut Context<Self>,
197 ) {
198 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
199 self.active_toolchain.take();
200 self.update_lister(editor, window, cx);
201 }
202 cx.notify();
203 }
204}