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