active_toolchain.rs

  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}