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, ToolchainScope};
  9use project::{Project, ProjectPath, Toolchains, WorktreeId, toolchain_store::ToolchainStoreEvent};
 10use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, SharedString, Tooltip};
 11use util::{maybe, rel_path::RelPath};
 12use workspace::{StatusItemView, Workspace, item::ItemHandle};
 13
 14use crate::ToolchainSelector;
 15
 16pub struct ActiveToolchain {
 17    active_toolchain: Option<Toolchain>,
 18    term: SharedString,
 19    workspace: WeakEntity<Workspace>,
 20    active_buffer: Option<(WorktreeId, WeakEntity<Buffer>, Subscription)>,
 21    _update_toolchain_task: Task<Option<()>>,
 22}
 23
 24impl ActiveToolchain {
 25    pub fn new(workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>) -> Self {
 26        if let Some(store) = workspace.project().read(cx).toolchain_store() {
 27            cx.subscribe_in(
 28                &store,
 29                window,
 30                |this, _, _: &ToolchainStoreEvent, window, cx| {
 31                    let editor = this
 32                        .workspace
 33                        .update(cx, |workspace, cx| {
 34                            workspace
 35                                .active_item(cx)
 36                                .and_then(|item| item.downcast::<Editor>())
 37                        })
 38                        .ok()
 39                        .flatten();
 40                    if let Some(editor) = editor {
 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 did_set_toolchain = maybe!(async {
 59                let active_file = this
 60                    .read_with(cx, |this, _| {
 61                        this.active_buffer
 62                            .as_ref()
 63                            .map(|(_, buffer, _)| buffer.clone())
 64                    })
 65                    .ok()
 66                    .flatten()?;
 67                let workspace = this.read_with(cx, |this, _| this.workspace.clone()).ok()?;
 68                let language_name = active_file
 69                    .read_with(cx, |this, _| Some(this.language()?.name()))
 70                    .ok()
 71                    .flatten()?;
 72                let meta = workspace
 73                    .update(cx, |workspace, cx| {
 74                        let languages = workspace.project().read(cx).languages();
 75                        Project::toolchain_metadata(languages.clone(), language_name.clone())
 76                    })
 77                    .ok()?
 78                    .await?;
 79                let _ = this.update(cx, |this, cx| {
 80                    this.term = meta.term;
 81                    cx.notify();
 82                });
 83                let (worktree_id, path) = active_file
 84                    .update(cx, |this, cx| {
 85                        this.file().and_then(|file| {
 86                            Some((file.worktree_id(cx), file.path().parent()?.into()))
 87                        })
 88                    })
 89                    .ok()
 90                    .flatten()?;
 91                let toolchain =
 92                    Self::active_toolchain(workspace, worktree_id, path, language_name, cx).await?;
 93                this.update(cx, |this, cx| {
 94                    this.active_toolchain = Some(toolchain);
 95
 96                    cx.notify();
 97                })
 98                .ok()
 99            })
100            .await
101            .is_some();
102            if !did_set_toolchain {
103                this.update(cx, |this, cx| {
104                    this.active_toolchain = None;
105                    cx.notify();
106                })
107                .ok();
108            }
109            did_set_toolchain.then_some(())
110        })
111    }
112
113    fn update_lister(
114        &mut self,
115        editor: Entity<Editor>,
116        window: &mut Window,
117        cx: &mut Context<Self>,
118    ) {
119        let editor = editor.read(cx);
120        if let Some((_, buffer, _)) = editor.active_excerpt(cx)
121            && let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx))
122        {
123            let subscription = cx.subscribe_in(
124                &buffer,
125                window,
126                |this, _, event: &BufferEvent, window, cx| {
127                    if matches!(event, BufferEvent::LanguageChanged) {
128                        this._update_toolchain_task = Self::spawn_tracker_task(window, cx);
129                    }
130                },
131            );
132            self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription));
133            self._update_toolchain_task = Self::spawn_tracker_task(window, cx);
134        }
135
136        cx.notify();
137    }
138
139    fn active_toolchain(
140        workspace: WeakEntity<Workspace>,
141        worktree_id: WorktreeId,
142        relative_path: Arc<RelPath>,
143        language_name: LanguageName,
144        cx: &mut AsyncWindowContext,
145    ) -> Task<Option<Toolchain>> {
146        cx.spawn(async move |cx| {
147            let workspace_id = workspace
148                .read_with(cx, |this, _| this.database_id())
149                .ok()
150                .flatten()?;
151            let selected_toolchain = workspace
152                .update(cx, |this, cx| {
153                    this.project().read(cx).active_toolchain(
154                        ProjectPath {
155                            worktree_id,
156                            path: relative_path.clone(),
157                        },
158                        language_name.clone(),
159                        cx,
160                    )
161                })
162                .ok()?
163                .await;
164            if let Some(toolchain) = selected_toolchain {
165                Some(toolchain)
166            } else {
167                let project = workspace
168                    .read_with(cx, |this, _| this.project().clone())
169                    .ok()?;
170                let Toolchains {
171                    toolchains,
172                    root_path: relative_path,
173                    user_toolchains,
174                } = cx
175                    .update(|_, cx| {
176                        project.read(cx).available_toolchains(
177                            ProjectPath {
178                                worktree_id,
179                                path: relative_path.clone(),
180                            },
181                            language_name,
182                            cx,
183                        )
184                    })
185                    .ok()?
186                    .await?;
187                // Since we don't have a selected toolchain, pick one for user here.
188                let default_choice = user_toolchains
189                    .iter()
190                    .find_map(|(scope, toolchains)| {
191                        if scope == &ToolchainScope::Global {
192                            // Ignore global toolchains when making a default choice. They're unlikely to be the right choice.
193                            None
194                        } else {
195                            toolchains.first()
196                        }
197                    })
198                    .or_else(|| toolchains.toolchains.first())
199                    .cloned();
200                if let Some(toolchain) = &default_choice {
201                    workspace::WORKSPACE_DB
202                        .set_toolchain(
203                            workspace_id,
204                            worktree_id,
205                            relative_path.clone(),
206                            toolchain.clone(),
207                        )
208                        .await
209                        .ok()?;
210                    project
211                        .update(cx, |this, cx| {
212                            this.activate_toolchain(
213                                ProjectPath {
214                                    worktree_id,
215                                    path: relative_path,
216                                },
217                                toolchain.clone(),
218                                cx,
219                            )
220                        })
221                        .ok()?
222                        .await;
223                }
224
225                default_choice
226            }
227        })
228    }
229}
230
231impl Render for ActiveToolchain {
232    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
233        div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
234            let term = self.term.clone();
235            el.child(
236                Button::new("change-toolchain", active_toolchain.name.clone())
237                    .label_size(LabelSize::Small)
238                    .on_click(cx.listener(|this, _, window, cx| {
239                        if let Some(workspace) = this.workspace.upgrade() {
240                            workspace.update(cx, |workspace, cx| {
241                                ToolchainSelector::toggle(workspace, window, cx)
242                            });
243                        }
244                    }))
245                    .tooltip(Tooltip::text(format!("Select {}", &term))),
246            )
247        })
248    }
249}
250
251impl StatusItemView for ActiveToolchain {
252    fn set_active_pane_item(
253        &mut self,
254        active_pane_item: Option<&dyn ItemHandle>,
255        window: &mut Window,
256        cx: &mut Context<Self>,
257    ) {
258        if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
259            self.update_lister(editor, window, cx);
260        }
261        cx.notify();
262    }
263}