kernel_options.rs

  1use crate::kernels::KernelSpecification;
  2use crate::repl_store::ReplStore;
  3use crate::KERNEL_DOCS_URL;
  4
  5use gpui::DismissEvent;
  6
  7use gpui::FontWeight;
  8use picker::Picker;
  9use picker::PickerDelegate;
 10use project::WorktreeId;
 11
 12use std::sync::Arc;
 13use ui::ListItemSpacing;
 14
 15use gpui::SharedString;
 16use gpui::Task;
 17use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
 18
 19type OnSelect = Box<dyn Fn(KernelSpecification, &mut WindowContext)>;
 20
 21#[derive(IntoElement)]
 22pub struct KernelSelector<T: PopoverTrigger> {
 23    handle: Option<PopoverMenuHandle<Picker<KernelPickerDelegate>>>,
 24    on_select: OnSelect,
 25    trigger: T,
 26    info_text: Option<SharedString>,
 27    worktree_id: WorktreeId,
 28}
 29
 30pub struct KernelPickerDelegate {
 31    all_kernels: Vec<KernelSpecification>,
 32    filtered_kernels: Vec<KernelSpecification>,
 33    selected_kernelspec: Option<KernelSpecification>,
 34    on_select: OnSelect,
 35}
 36
 37impl<T: PopoverTrigger> KernelSelector<T> {
 38    pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T) -> Self {
 39        KernelSelector {
 40            on_select,
 41            handle: None,
 42            trigger,
 43            info_text: None,
 44            worktree_id,
 45        }
 46    }
 47
 48    pub fn with_handle(mut self, handle: PopoverMenuHandle<Picker<KernelPickerDelegate>>) -> Self {
 49        self.handle = Some(handle);
 50        self
 51    }
 52
 53    pub fn with_info_text(mut self, text: impl Into<SharedString>) -> Self {
 54        self.info_text = Some(text.into());
 55        self
 56    }
 57}
 58
 59impl PickerDelegate for KernelPickerDelegate {
 60    type ListItem = ListItem;
 61
 62    fn match_count(&self) -> usize {
 63        self.filtered_kernels.len()
 64    }
 65
 66    fn selected_index(&self) -> usize {
 67        if let Some(kernelspec) = self.selected_kernelspec.as_ref() {
 68            self.filtered_kernels
 69                .iter()
 70                .position(|k| k == kernelspec)
 71                .unwrap_or(0)
 72        } else {
 73            0
 74        }
 75    }
 76
 77    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
 78        self.selected_kernelspec = self.filtered_kernels.get(ix).cloned();
 79        cx.notify();
 80    }
 81
 82    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 83        "Select a kernel...".into()
 84    }
 85
 86    fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 87        let all_kernels = self.all_kernels.clone();
 88
 89        if query.is_empty() {
 90            self.filtered_kernels = all_kernels;
 91            return Task::Ready(Some(()));
 92        }
 93
 94        self.filtered_kernels = if query.is_empty() {
 95            all_kernels
 96        } else {
 97            all_kernels
 98                .into_iter()
 99                .filter(|kernel| kernel.name().to_lowercase().contains(&query.to_lowercase()))
100                .collect()
101        };
102
103        return Task::Ready(Some(()));
104    }
105
106    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
107        if let Some(kernelspec) = &self.selected_kernelspec {
108            (self.on_select)(kernelspec.clone(), cx.window_context());
109            cx.emit(DismissEvent);
110        }
111    }
112
113    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
114
115    fn render_match(
116        &self,
117        ix: usize,
118        selected: bool,
119        _cx: &mut ViewContext<Picker<Self>>,
120    ) -> Option<Self::ListItem> {
121        let kernelspec = self.filtered_kernels.get(ix)?;
122
123        let is_selected = self.selected_kernelspec.as_ref() == Some(kernelspec);
124
125        Some(
126            ListItem::new(ix)
127                .inset(true)
128                .spacing(ListItemSpacing::Sparse)
129                .selected(selected)
130                .child(
131                    v_flex()
132                        .min_w(px(600.))
133                        .w_full()
134                        .gap_0p5()
135                        .child(
136                            h_flex()
137                                .w_full()
138                                .gap_1()
139                                .child(Label::new(kernelspec.name()).weight(FontWeight::MEDIUM))
140                                .child(
141                                    Label::new(kernelspec.language())
142                                        .size(LabelSize::Small)
143                                        .color(Color::Muted),
144                                ),
145                        )
146                        .child(
147                            Label::new(kernelspec.path())
148                                .size(LabelSize::XSmall)
149                                .color(Color::Muted),
150                        ),
151                )
152                .when(is_selected, |item| {
153                    item.end_slot(
154                        Icon::new(IconName::Check)
155                            .color(Color::Accent)
156                            .size(IconSize::Small),
157                    )
158                }),
159        )
160    }
161
162    fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<gpui::AnyElement> {
163        Some(
164            h_flex()
165                .w_full()
166                .border_t_1()
167                .border_color(cx.theme().colors().border_variant)
168                .p_1()
169                .gap_4()
170                .child(
171                    Button::new("kernel-docs", "Kernel Docs")
172                        .icon(IconName::ExternalLink)
173                        .icon_size(IconSize::XSmall)
174                        .icon_color(Color::Muted)
175                        .icon_position(IconPosition::End)
176                        .on_click(move |_, cx| cx.open_url(KERNEL_DOCS_URL)),
177                )
178                .into_any(),
179        )
180    }
181}
182
183impl<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
184    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
185        let store = ReplStore::global(cx).read(cx);
186
187        let all_kernels: Vec<KernelSpecification> = store
188            .kernel_specifications_for_worktree(self.worktree_id)
189            .cloned()
190            .collect();
191
192        let selected_kernelspec = store.active_kernelspec(self.worktree_id, None, cx);
193
194        let delegate = KernelPickerDelegate {
195            on_select: self.on_select,
196            all_kernels: all_kernels.clone(),
197            filtered_kernels: all_kernels,
198            selected_kernelspec,
199        };
200
201        let picker_view = cx.new_view(|cx| {
202            let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
203            picker
204        });
205
206        PopoverMenu::new("kernel-switcher")
207            .menu(move |_cx| Some(picker_view.clone()))
208            .trigger(self.trigger)
209            .attach(gpui::AnchorCorner::BottomLeft)
210            .when_some(self.handle, |menu, handle| menu.with_handle(handle))
211    }
212}