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, |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}