active_toolchain.rs

  1use std::{path::Path, 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, toolchain_store::ToolchainStoreEvent};
 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        if let Some(store) = workspace.project().read(cx).toolchain_store() {
 26            cx.subscribe_in(
 27                &store,
 28                window,
 29                |this, _, _: &ToolchainStoreEvent, window, cx| {
 30                    let editor = this
 31                        .workspace
 32                        .update(cx, |workspace, cx| {
 33                            workspace
 34                                .active_item(cx)
 35                                .and_then(|item| item.downcast::<Editor>())
 36                        })
 37                        .ok()
 38                        .flatten();
 39                    if let Some(editor) = editor {
 40                        this.active_toolchain.take();
 41                        this.update_lister(editor, window, cx);
 42                    }
 43                },
 44            )
 45            .detach();
 46        }
 47        Self {
 48            active_toolchain: None,
 49            active_buffer: None,
 50            term: SharedString::new_static("Toolchain"),
 51            workspace: workspace.weak_handle(),
 52
 53            _update_toolchain_task: Self::spawn_tracker_task(window, cx),
 54        }
 55    }
 56    fn spawn_tracker_task(window: &mut Window, cx: &mut Context<Self>) -> Task<Option<()>> {
 57        cx.spawn_in(window, async move |this, cx| {
 58            let active_file = this
 59                .read_with(cx, |this, _| {
 60                    this.active_buffer
 61                        .as_ref()
 62                        .map(|(_, buffer, _)| buffer.clone())
 63                })
 64                .ok()
 65                .flatten()?;
 66            let workspace = this.read_with(cx, |this, _| this.workspace.clone()).ok()?;
 67            let language_name = active_file
 68                .read_with(cx, |this, _| Some(this.language()?.name()))
 69                .ok()
 70                .flatten()?;
 71            let term = workspace
 72                .update(cx, |workspace, cx| {
 73                    let languages = workspace.project().read(cx).languages();
 74                    Project::toolchain_term(languages.clone(), language_name.clone())
 75                })
 76                .ok()?
 77                .await?;
 78            let _ = this.update(cx, |this, cx| {
 79                this.term = term;
 80                cx.notify();
 81            });
 82            let (worktree_id, path) = active_file
 83                .update(cx, |this, cx| {
 84                    this.file().and_then(|file| {
 85                        Some((
 86                            file.worktree_id(cx),
 87                            Arc::<Path>::from(file.path().parent()?),
 88                        ))
 89                    })
 90                })
 91                .ok()
 92                .flatten()?;
 93            let toolchain =
 94                Self::active_toolchain(workspace, worktree_id, path, language_name, cx).await?;
 95            let _ = this.update(cx, |this, cx| {
 96                this.active_toolchain = Some(toolchain);
 97
 98                cx.notify();
 99            });
100            Some(())
101        })
102    }
103
104    fn update_lister(
105        &mut self,
106        editor: Entity<Editor>,
107        window: &mut Window,
108        cx: &mut Context<Self>,
109    ) {
110        let editor = editor.read(cx);
111        if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
112            if let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx)) {
113                let subscription = cx.subscribe_in(
114                    &buffer,
115                    window,
116                    |this, _, event: &BufferEvent, window, cx| {
117                        if matches!(event, BufferEvent::LanguageChanged) {
118                            this._update_toolchain_task = Self::spawn_tracker_task(window, cx);
119                        }
120                    },
121                );
122                self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription));
123                self._update_toolchain_task = Self::spawn_tracker_task(window, cx);
124            }
125        }
126
127        cx.notify();
128    }
129
130    fn active_toolchain(
131        workspace: WeakEntity<Workspace>,
132        worktree_id: WorktreeId,
133        relative_path: Arc<Path>,
134        language_name: LanguageName,
135        cx: &mut AsyncWindowContext,
136    ) -> Task<Option<Toolchain>> {
137        cx.spawn(async move |cx| {
138            let workspace_id = workspace
139                .read_with(cx, |this, _| this.database_id())
140                .ok()
141                .flatten()?;
142            let selected_toolchain = workspace
143                .update(cx, |this, cx| {
144                    this.project().read(cx).active_toolchain(
145                        ProjectPath {
146                            worktree_id,
147                            path: relative_path.clone(),
148                        },
149                        language_name.clone(),
150                        cx,
151                    )
152                })
153                .ok()?
154                .await;
155            if let Some(toolchain) = selected_toolchain {
156                Some(toolchain)
157            } else {
158                let project = workspace
159                    .read_with(cx, |this, _| this.project().clone())
160                    .ok()?;
161                let (toolchains, relative_path) = cx
162                    .update(|_, cx| {
163                        project.read(cx).available_toolchains(
164                            ProjectPath {
165                                worktree_id,
166                                path: relative_path.clone(),
167                            },
168                            language_name,
169                            cx,
170                        )
171                    })
172                    .ok()?
173                    .await?;
174                if let Some(toolchain) = toolchains.toolchains.first() {
175                    // Since we don't have a selected toolchain, pick one for user here.
176                    workspace::WORKSPACE_DB
177                        .set_toolchain(
178                            workspace_id,
179                            worktree_id,
180                            relative_path.to_string_lossy().into_owned(),
181                            toolchain.clone(),
182                        )
183                        .await
184                        .ok()?;
185                    project
186                        .update(cx, |this, cx| {
187                            this.activate_toolchain(
188                                ProjectPath {
189                                    worktree_id,
190                                    path: relative_path,
191                                },
192                                toolchain.clone(),
193                                cx,
194                            )
195                        })
196                        .ok()?
197                        .await;
198                }
199
200                toolchains.toolchains.first().cloned()
201            }
202        })
203    }
204}
205
206impl Render for ActiveToolchain {
207    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
208        div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
209            let term = self.term.clone();
210            el.child(
211                Button::new("change-toolchain", active_toolchain.name.clone())
212                    .label_size(LabelSize::Small)
213                    .on_click(cx.listener(|this, _, window, cx| {
214                        if let Some(workspace) = this.workspace.upgrade() {
215                            workspace.update(cx, |workspace, cx| {
216                                ToolchainSelector::toggle(workspace, window, cx)
217                            });
218                        }
219                    }))
220                    .tooltip(Tooltip::text(format!("Select {}", &term))),
221            )
222        })
223    }
224}
225
226impl StatusItemView for ActiveToolchain {
227    fn set_active_pane_item(
228        &mut self,
229        active_pane_item: Option<&dyn ItemHandle>,
230        window: &mut Window,
231        cx: &mut Context<Self>,
232    ) {
233        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
234            self.active_toolchain.take();
235            self.update_lister(editor, window, cx);
236        }
237        cx.notify();
238    }
239}