active_toolchain.rs

  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}