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