kernel_options.rs

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