active_toolchain.rs

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