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, |this, mut cx| async move {
34 let active_file = this
35 .update(&mut cx, |this, _| {
36 this.active_buffer
37 .as_ref()
38 .map(|(_, buffer, _)| buffer.clone())
39 })
40 .ok()
41 .flatten()?;
42 let workspace = this
43 .update(&mut cx, |this, _| this.workspace.clone())
44 .ok()?;
45 let language_name = active_file
46 .update(&mut cx, |this, _| Some(this.language()?.name()))
47 .ok()
48 .flatten()?;
49 let term = workspace
50 .update(&mut 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(&mut cx, |this, cx| {
57 this.term = term;
58 cx.notify();
59 });
60 let worktree_id = active_file
61 .update(&mut 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, &mut cx).await?;
66 let _ = this.update(&mut 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(move |mut cx| async move {
108 let workspace_id = workspace
109 .update(&mut cx, |this, _| this.database_id())
110 .ok()
111 .flatten()?;
112 let selected_toolchain = workspace
113 .update(&mut cx, |this, cx| {
114 this.project()
115 .read(cx)
116 .active_toolchain(worktree_id, language_name.clone(), cx)
117 })
118 .ok()?
119 .await;
120 if let Some(toolchain) = selected_toolchain {
121 Some(toolchain)
122 } else {
123 let project = workspace
124 .update(&mut cx, |this, _| this.project().clone())
125 .ok()?;
126 let toolchains = cx
127 .update(|_, cx| {
128 project
129 .read(cx)
130 .available_toolchains(worktree_id, language_name, cx)
131 })
132 .ok()?
133 .await?;
134 if let Some(toolchain) = toolchains.toolchains.first() {
135 // Since we don't have a selected toolchain, pick one for user here.
136 workspace::WORKSPACE_DB
137 .set_toolchain(workspace_id, worktree_id, toolchain.clone())
138 .await
139 .ok()?;
140 project
141 .update(&mut cx, |this, cx| {
142 this.activate_toolchain(worktree_id, toolchain.clone(), cx)
143 })
144 .ok()?
145 .await;
146 }
147
148 toolchains.toolchains.first().cloned()
149 }
150 })
151 }
152}
153
154impl Render for ActiveToolchain {
155 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
156 div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
157 let term = self.term.clone();
158 el.child(
159 Button::new("change-toolchain", active_toolchain.name.clone())
160 .label_size(LabelSize::Small)
161 .on_click(cx.listener(|this, _, window, cx| {
162 if let Some(workspace) = this.workspace.upgrade() {
163 workspace.update(cx, |workspace, cx| {
164 ToolchainSelector::toggle(workspace, window, cx)
165 });
166 }
167 }))
168 .tooltip(Tooltip::text(format!("Select {}", &term))),
169 )
170 })
171 }
172}
173
174impl StatusItemView for ActiveToolchain {
175 fn set_active_pane_item(
176 &mut self,
177 active_pane_item: Option<&dyn ItemHandle>,
178 window: &mut Window,
179 cx: &mut Context<Self>,
180 ) {
181 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
182 self.active_toolchain.take();
183 self.update_lister(editor, window, cx);
184 }
185 cx.notify();
186 }
187}