diff --git a/Cargo.lock b/Cargo.lock index 604c5da77a..3e77ccc35c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9354,6 +9354,7 @@ dependencies = [ "editor", "gpui", "markdown_preview", + "picker", "repl", "search", "settings", @@ -9857,6 +9858,7 @@ dependencies = [ "menu", "multi_buffer", "nbformat", + "picker", "project", "runtimelib", "schemars", diff --git a/crates/quick_action_bar/Cargo.toml b/crates/quick_action_bar/Cargo.toml index 6f0d01f2a3..b3228820f6 100644 --- a/crates/quick_action_bar/Cargo.toml +++ b/crates/quick_action_bar/Cargo.toml @@ -24,6 +24,7 @@ ui.workspace = true util.workspace = true workspace.workspace = true zed_actions.workspace = true +picker.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/quick_action_bar/src/repl_menu.rs b/crates/quick_action_bar/src/repl_menu.rs index f4e4cd2d1a..372c46b212 100644 --- a/crates/quick_action_bar/src/repl_menu.rs +++ b/crates/quick_action_bar/src/repl_menu.rs @@ -1,13 +1,15 @@ use std::time::Duration; use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View}; +use picker::Picker; use repl::{ + components::{KernelPickerDelegate, KernelSelector}, ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, Session, SessionSupport, }; use ui::{ prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu, - Tooltip, + PopoverMenuHandle, Tooltip, }; use gpui::ElementId; @@ -58,7 +60,6 @@ impl QuickActionBar { let session = match session { SessionSupport::ActiveSession(session) => session, SessionSupport::Inactive(spec) => { - let spec = *spec; return self.render_repl_launch_menu(spec, cx); } SessionSupport::RequiresSetup(language) => { @@ -246,44 +247,120 @@ impl QuickActionBar { Some( h_flex() + .child(self.render_kernel_selector(cx)) .child(button) .child(dropdown_menu) .into_any_element(), ) } - pub fn render_repl_launch_menu( &self, kernel_specification: KernelSpecification, - _cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option { let tooltip: SharedString = - SharedString::from(format!("Start REPL for {}", kernel_specification.name)); + SharedString::from(format!("Start REPL for {}", kernel_specification.name())); Some( - IconButton::new("toggle_repl_icon", IconName::ReplNeutral) - .size(ButtonSize::Compact) - .icon_color(Color::Muted) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) - .on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {}))) + h_flex() + .child(self.render_kernel_selector(cx)) + .child( + IconButton::new("toggle_repl_icon", IconName::ReplNeutral) + .size(ButtonSize::Compact) + .icon_color(Color::Muted) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) + .on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {}))), + ) .into_any_element(), ) } + pub fn render_kernel_selector(&self, cx: &mut ViewContext) -> impl IntoElement { + let editor = if let Some(editor) = self.active_editor() { + editor + } else { + // todo!() + return div().into_any_element(); + }; + + let session = repl::session(editor.downgrade(), cx); + + let current_kernelspec = match session { + SessionSupport::ActiveSession(view) => Some(view.read(cx).kernel_specification.clone()), + SessionSupport::Inactive(kernel_specification) => Some(kernel_specification), + SessionSupport::RequiresSetup(_language_name) => None, + SessionSupport::Unsupported => None, + }; + + let current_kernel_name = current_kernelspec.as_ref().map(|spec| spec.name()); + + let menu_handle: PopoverMenuHandle> = + PopoverMenuHandle::default(); + KernelSelector::new( + { + Box::new(move |kernelspec, cx| { + repl::assign_kernelspec(kernelspec, editor.downgrade(), cx).ok(); + }) + }, + current_kernelspec.clone(), + ButtonLike::new("kernel-selector") + .style(ButtonStyle::Subtle) + .child( + h_flex() + .w_full() + .gap_0p5() + .child( + div() + .overflow_x_hidden() + .flex_grow() + .whitespace_nowrap() + .child( + Label::new(if let Some(name) = current_kernel_name { + name + } else { + SharedString::from("Select Kernel") + }) + .size(LabelSize::Small) + .color(if current_kernelspec.is_some() { + Color::Default + } else { + Color::Placeholder + }) + .into_any_element(), + ), + ) + .child( + Icon::new(IconName::ChevronDown) + .color(Color::Muted) + .size(IconSize::XSmall), + ), + ) + .tooltip(move |cx| Tooltip::text("Select Kernel", cx)), + ) + .with_handle(menu_handle.clone()) + .into_any_element() + } + pub fn render_repl_setup( &self, language: &str, - _cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option { let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language)); Some( - IconButton::new("toggle_repl_icon", IconName::ReplNeutral) - .size(ButtonSize::Compact) - .icon_color(Color::Muted) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) - .on_click(|_, cx| cx.open_url(&format!("{}#installation", ZED_REPL_DOCUMENTATION))) + h_flex() + .child(self.render_kernel_selector(cx)) + .child( + IconButton::new("toggle_repl_icon", IconName::ReplNeutral) + .size(ButtonSize::Compact) + .icon_color(Color::Muted) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) + .on_click(|_, cx| { + cx.open_url(&format!("{}#installation", ZED_REPL_DOCUMENTATION)) + }), + ) .into_any_element(), ) } @@ -292,13 +369,8 @@ impl QuickActionBar { fn session_state(session: View, cx: &WindowContext) -> ReplMenuState { let session = session.read(cx); - let kernel_name: SharedString = session.kernel_specification.name.clone().into(); - let kernel_language: SharedString = session - .kernel_specification - .kernelspec - .language - .clone() - .into(); + let kernel_name = session.kernel_specification.name(); + let kernel_language: SharedString = session.kernel_specification.language(); let fill_fields = || { ReplMenuState { diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index f035878d33..b170def71f 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -45,6 +45,7 @@ ui.workspace = true util.workspace = true uuid.workspace = true workspace.workspace = true +picker.workspace = true [target.'cfg(target_os = "windows")'.dependencies] windows.workspace = true diff --git a/crates/repl/src/components.rs b/crates/repl/src/components.rs index 5002ae6eb1..53236bc6a8 100644 --- a/crates/repl/src/components.rs +++ b/crates/repl/src/components.rs @@ -1,3 +1,5 @@ mod kernel_list_item; +mod kernel_options; pub use kernel_list_item::*; +pub use kernel_options::*; diff --git a/crates/repl/src/components/kernel_list_item.rs b/crates/repl/src/components/kernel_list_item.rs index 9de6ae2d43..f084275a41 100644 --- a/crates/repl/src/components/kernel_list_item.rs +++ b/crates/repl/src/components/kernel_list_item.rs @@ -46,7 +46,7 @@ impl ParentElement for KernelListItem { impl RenderOnce for KernelListItem { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - ListItem::new(SharedString::from(self.kernel_specification.name.clone())) + ListItem::new(self.kernel_specification.name()) .selectable(false) .start_slot( h_flex() diff --git a/crates/repl/src/components/kernel_options.rs b/crates/repl/src/components/kernel_options.rs new file mode 100644 index 0000000000..361275c907 --- /dev/null +++ b/crates/repl/src/components/kernel_options.rs @@ -0,0 +1,201 @@ +use crate::kernels::KernelSpecification; +use crate::repl_store::ReplStore; +use crate::KERNEL_DOCS_URL; + +use gpui::DismissEvent; + +use picker::Picker; +use picker::PickerDelegate; + +use std::sync::Arc; +use ui::ListItemSpacing; + +use gpui::SharedString; +use gpui::Task; +use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; + +type OnSelect = Box; + +#[derive(IntoElement)] +pub struct KernelSelector { + handle: Option>>, + on_select: OnSelect, + trigger: T, + info_text: Option, + current_kernelspec: Option, +} + +pub struct KernelPickerDelegate { + all_kernels: Vec, + filtered_kernels: Vec, + selected_kernelspec: Option, + on_select: OnSelect, +} + +impl KernelSelector { + pub fn new( + on_select: OnSelect, + current_kernelspec: Option, + trigger: T, + ) -> Self { + KernelSelector { + on_select, + handle: None, + trigger, + info_text: None, + current_kernelspec, + } + } + + pub fn with_handle(mut self, handle: PopoverMenuHandle>) -> Self { + self.handle = Some(handle); + self + } + + pub fn with_info_text(mut self, text: impl Into) -> Self { + self.info_text = Some(text.into()); + self + } +} + +impl PickerDelegate for KernelPickerDelegate { + type ListItem = ListItem; + + fn match_count(&self) -> usize { + self.filtered_kernels.len() + } + + fn selected_index(&self) -> usize { + if let Some(kernelspec) = self.selected_kernelspec.as_ref() { + self.filtered_kernels + .iter() + .position(|k| k == kernelspec) + .unwrap_or(0) + } else { + 0 + } + } + + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>) { + self.selected_kernelspec = self.filtered_kernels.get(ix).cloned(); + cx.notify(); + } + + fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc { + "Select a kernel...".into() + } + + fn update_matches(&mut self, query: String, _cx: &mut ViewContext>) -> Task<()> { + let all_kernels = self.all_kernels.clone(); + + if query.is_empty() { + self.filtered_kernels = all_kernels; + return Task::Ready(Some(())); + } + + self.filtered_kernels = if query.is_empty() { + all_kernels + } else { + all_kernels + .into_iter() + .filter(|kernel| kernel.name().to_lowercase().contains(&query.to_lowercase())) + .collect() + }; + + return Task::Ready(Some(())); + } + + fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext>) { + if let Some(kernelspec) = &self.selected_kernelspec { + (self.on_select)(kernelspec.clone(), cx.window_context()); + cx.emit(DismissEvent); + } + } + + fn dismissed(&mut self, _cx: &mut ViewContext>) {} + + fn render_match( + &self, + ix: usize, + selected: bool, + _cx: &mut ViewContext>, + ) -> Option { + let kernelspec = self.filtered_kernels.get(ix)?; + + let is_selected = self.selected_kernelspec.as_ref() == Some(kernelspec); + + Some( + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .selected(selected) + .child( + h_flex().w_full().justify_between().min_w(px(200.)).child( + h_flex() + .gap_1p5() + .child(Label::new(kernelspec.name())) + .child( + Label::new(kernelspec.type_name()) + .size(LabelSize::XSmall) + .color(Color::Muted), + ), + ), + ) + .end_slot(div().when(is_selected, |this| { + this.child( + Icon::new(IconName::Check) + .color(Color::Accent) + .size(IconSize::Small), + ) + })), + ) + } + + fn render_footer(&self, cx: &mut ViewContext>) -> Option { + Some( + h_flex() + .w_full() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .p_1() + .gap_4() + .child( + Button::new("kernel-docs", "Kernel Docs") + .icon(IconName::ExternalLink) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .icon_position(IconPosition::End) + .on_click(move |_, cx| cx.open_url(KERNEL_DOCS_URL)), + ) + .into_any(), + ) + } +} + +impl RenderOnce for KernelSelector { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let store = ReplStore::global(cx).read(cx); + let all_kernels: Vec = + store.kernel_specifications().cloned().collect(); + + let selected_kernelspec = self.current_kernelspec; + + let delegate = KernelPickerDelegate { + on_select: self.on_select, + all_kernels: all_kernels.clone(), + filtered_kernels: all_kernels, + selected_kernelspec, + }; + + let picker_view = cx.new_view(|cx| { + let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())); + picker + }); + + PopoverMenu::new("kernel-switcher") + .menu(move |_cx| Some(picker_view.clone())) + .trigger(self.trigger) + .attach(gpui::AnchorCorner::BottomLeft) + .when_some(self.handle, |menu, handle| menu.with_handle(handle)) + } +} diff --git a/crates/repl/src/kernels.rs b/crates/repl/src/kernels.rs index 7f011d116e..eb0b886507 100644 --- a/crates/repl/src/kernels.rs +++ b/crates/repl/src/kernels.rs @@ -19,16 +19,82 @@ use std::{ path::PathBuf, sync::Arc, }; +use ui::SharedString; use uuid::Uuid; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum KernelSpecification { + Remote(RemoteKernelSpecification), + Jupyter(LocalKernelSpecification), + PythonEnv(LocalKernelSpecification), +} + +impl KernelSpecification { + pub fn name(&self) -> SharedString { + match self { + Self::Jupyter(spec) => spec.name.clone().into(), + Self::PythonEnv(spec) => spec.name.clone().into(), + Self::Remote(spec) => spec.name.clone().into(), + } + } + + pub fn type_name(&self) -> SharedString { + match self { + Self::Jupyter(_) => "Jupyter".into(), + Self::PythonEnv(_) => "Python Environment".into(), + Self::Remote(_) => "Remote".into(), + } + } + + pub fn path(&self) -> SharedString { + SharedString::from(match self { + Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(), + Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(), + Self::Remote(spec) => spec.url.to_string(), + }) + } + + pub fn language(&self) -> SharedString { + SharedString::from(match self { + Self::Jupyter(spec) => spec.kernelspec.language.clone(), + Self::PythonEnv(spec) => spec.kernelspec.language.clone(), + Self::Remote(spec) => spec.kernelspec.language.clone(), + }) + } +} + #[derive(Debug, Clone)] -pub struct KernelSpecification { +pub struct LocalKernelSpecification { pub name: String, pub path: PathBuf, pub kernelspec: JupyterKernelspec, } -impl KernelSpecification { +impl PartialEq for LocalKernelSpecification { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.path == other.path + } +} + +impl Eq for LocalKernelSpecification {} + +#[derive(Debug, Clone)] +pub struct RemoteKernelSpecification { + pub name: String, + pub url: String, + pub token: String, + pub kernelspec: JupyterKernelspec, +} + +impl PartialEq for RemoteKernelSpecification { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.url == other.url + } +} + +impl Eq for RemoteKernelSpecification {} + +impl LocalKernelSpecification { #[must_use] fn command(&self, connection_path: &PathBuf) -> Result { let argv = &self.kernelspec.argv; @@ -198,6 +264,17 @@ impl RunningKernel { fs: Arc, cx: &mut AppContext, ) -> Task> { + let kernel_specification = match kernel_specification { + KernelSpecification::Jupyter(spec) => spec, + KernelSpecification::PythonEnv(spec) => spec, + KernelSpecification::Remote(_spec) => { + // todo!(): Implement remote kernel specification + return Task::ready(Err(anyhow::anyhow!( + "Running remote kernels is not supported" + ))); + } + }; + cx.spawn(|cx| async move { let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let ports = peek_ports(ip).await?; @@ -344,7 +421,7 @@ async fn read_kernelspec_at( // /usr/local/share/jupyter/kernels/python3 kernel_dir: PathBuf, fs: &dyn Fs, -) -> Result { +) -> Result { let path = kernel_dir; let kernel_name = if let Some(kernel_name) = path.file_name() { kernel_name.to_string_lossy().to_string() @@ -360,7 +437,7 @@ async fn read_kernelspec_at( let spec = fs.load(expected_kernel_json.as_path()).await?; let spec = serde_json::from_str::(&spec)?; - Ok(KernelSpecification { + Ok(LocalKernelSpecification { name: kernel_name, path, kernelspec: spec, @@ -368,7 +445,7 @@ async fn read_kernelspec_at( } /// Read a directory of kernelspec directories -async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result> { +async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result> { let mut kernelspec_dirs = fs.read_dir(&path).await?; let mut valid_kernelspecs = Vec::new(); @@ -388,7 +465,7 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result) -> Result> { +pub async fn local_kernel_specifications(fs: Arc) -> Result> { let mut data_dirs = dirs::data_dirs(); // Pick up any kernels from conda or conda environment diff --git a/crates/repl/src/repl.rs b/crates/repl/src/repl.rs index 75a3da6456..be187ff16f 100644 --- a/crates/repl/src/repl.rs +++ b/crates/repl/src/repl.rs @@ -1,4 +1,4 @@ -mod components; +pub mod components; mod jupyter_settings; mod kernels; pub mod notebook; @@ -26,6 +26,8 @@ use crate::repl_store::ReplStore; pub use crate::session::Session; use client::telemetry::Telemetry; +pub const KERNEL_DOCS_URL: &str = "https://zed.dev/docs/repl#changing-kernels"; + pub fn init(fs: Arc, telemetry: Arc, cx: &mut AppContext) { set_dispatcher(zed_dispatcher(cx)); JupyterSettings::register(cx); diff --git a/crates/repl/src/repl_editor.rs b/crates/repl/src/repl_editor.rs index e07958d0e4..33169583b3 100644 --- a/crates/repl/src/repl_editor.rs +++ b/crates/repl/src/repl_editor.rs @@ -12,6 +12,56 @@ use crate::repl_store::ReplStore; use crate::session::SessionEvent; use crate::{KernelSpecification, Session}; +pub fn assign_kernelspec( + kernel_specification: KernelSpecification, + weak_editor: WeakView, + cx: &mut WindowContext, +) -> Result<()> { + let store = ReplStore::global(cx); + if !store.read(cx).is_enabled() { + return Ok(()); + } + + let fs = store.read(cx).fs().clone(); + let telemetry = store.read(cx).telemetry().clone(); + + if let Some(session) = store.read(cx).get_session(weak_editor.entity_id()).cloned() { + // Drop previous session, start new one + session.update(cx, |session, cx| { + session.clear_outputs(cx); + session.shutdown(cx); + cx.notify(); + }); + } + + let session = cx + .new_view(|cx| Session::new(weak_editor.clone(), fs, telemetry, kernel_specification, cx)); + + weak_editor + .update(cx, |_editor, cx| { + cx.notify(); + + cx.subscribe(&session, { + let store = store.clone(); + move |_this, _session, event, cx| match event { + SessionEvent::Shutdown(shutdown_event) => { + store.update(cx, |store, _cx| { + store.remove_session(shutdown_event.entity_id()); + }); + } + } + }) + .detach(); + }) + .ok(); + + store.update(cx, |store, _cx| { + store.insert_session(weak_editor.entity_id(), session.clone()); + }); + + Ok(()) +} + pub fn run(editor: WeakView, move_down: bool, cx: &mut WindowContext) -> Result<()> { let store = ReplStore::global(cx); if !store.read(cx).is_enabled() { @@ -96,9 +146,10 @@ pub fn run(editor: WeakView, move_down: bool, cx: &mut WindowContext) -> anyhow::Ok(()) } +#[allow(clippy::large_enum_variant)] pub enum SessionSupport { ActiveSession(View), - Inactive(Box), + Inactive(KernelSpecification), RequiresSetup(LanguageName), Unsupported, } @@ -119,7 +170,7 @@ pub fn session(editor: WeakView, cx: &mut WindowContext) -> SessionSuppo }); match kernelspec { - Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)), + Some(kernelspec) => SessionSupport::Inactive(kernelspec), None => { if language_supported(&language) { SessionSupport::RequiresSetup(language.name()) diff --git a/crates/repl/src/repl_sessions_ui.rs b/crates/repl/src/repl_sessions_ui.rs index efc5f14d07..3a835f74a8 100644 --- a/crates/repl/src/repl_sessions_ui.rs +++ b/crates/repl/src/repl_sessions_ui.rs @@ -1,9 +1,9 @@ -use collections::HashMap; use editor::Editor; use gpui::{ actions, prelude::*, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, FontWeight, Subscription, View, }; +use std::collections::HashMap; use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding, ListItem, Tooltip}; use util::ResultExt as _; use workspace::item::ItemEvent; @@ -12,7 +12,7 @@ use workspace::{item::Item, Workspace}; use crate::jupyter_settings::JupyterSettings; use crate::repl_store::ReplStore; -use crate::KernelSpecification; +use crate::{KernelSpecification, KERNEL_DOCS_URL}; actions!( repl, @@ -238,14 +238,24 @@ impl Render for ReplSessionsPage { ); } - let mut kernels_by_language: HashMap> = HashMap::default(); - for spec in kernel_specifications { - kernels_by_language - .entry(spec.kernelspec.language.clone()) - .or_default() - .push(spec); + let mut kernels_by_language: HashMap> = + kernel_specifications + .iter() + .map(|spec| (spec.language(), spec)) + .fold(HashMap::new(), |mut acc, (language, spec)| { + acc.entry(language).or_default().push(spec); + acc + }); + + for kernels in kernels_by_language.values_mut() { + kernels.sort_by_key(|a| a.name()) } + // Convert to a sorted Vec of tuples + let mut sorted_kernels: Vec<(SharedString, Vec<&KernelSpecification>)> = + kernels_by_language.into_iter().collect(); + sorted_kernels.sort_by(|a, b| a.0.cmp(&b.0)); + let kernels_available = v_flex() .child(Label::new("Kernels available").size(LabelSize::Large)) .gap_2() @@ -262,11 +272,11 @@ impl Render for ReplSessionsPage { .child(Label::new("REPL documentation")) .child(Icon::new(IconName::Link)) .on_click(move |_, cx| { - cx.open_url("https://zed.dev/docs/repl#changing-kernels") + cx.open_url(KERNEL_DOCS_URL) }), ), ) - .children(kernels_by_language.into_iter().map(|(language, specs)| { + .children(sorted_kernels.into_iter().map(|(language, specs)| { let chosen_kernel = store.read(cx).kernelspec(&language, cx); v_flex() @@ -274,13 +284,12 @@ impl Render for ReplSessionsPage { .child(Label::new(language.clone()).weight(FontWeight::BOLD)) .children(specs.into_iter().map(|spec| { let is_choice = if let Some(chosen_kernel) = &chosen_kernel { - chosen_kernel.name.to_lowercase() == spec.name.to_lowercase() - && chosen_kernel.path == spec.path + chosen_kernel == spec } else { false }; - let path = SharedString::from(spec.path.to_string_lossy().to_string()); + let path = spec.path(); ListItem::new(path.clone()) .selectable(false) @@ -290,7 +299,7 @@ impl Render for ReplSessionsPage { .child( h_flex() .gap_1() - .child(div().id(path.clone()).child(Label::new(spec.name.clone()))) + .child(div().id(path.clone()).child(Label::new(spec.name()))) .when(is_choice, |el| { let language = language.clone(); diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index 46fde4648a..b4ff41cee8 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -10,7 +10,7 @@ use gpui::{ use project::Fs; use settings::{Settings, SettingsStore}; -use crate::kernels::kernel_specifications; +use crate::kernels::local_kernel_specifications; use crate::{JupyterSettings, KernelSpecification, Session}; struct GlobalReplStore(Model); @@ -106,12 +106,17 @@ impl ReplStore { } pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext) -> Task> { - let kernel_specifications = kernel_specifications(self.fs.clone()); + let local_kernel_specifications = local_kernel_specifications(self.fs.clone()); cx.spawn(|this, mut cx| async move { - let kernel_specifications = kernel_specifications.await?; + let local_kernel_specifications = local_kernel_specifications.await?; + + let mut kernel_options = Vec::new(); + for kernel_specification in local_kernel_specifications { + kernel_options.push(KernelSpecification::Jupyter(kernel_specification)); + } this.update(&mut cx, |this, cx| { - this.kernel_specifications = kernel_specifications; + this.kernel_specifications = kernel_options; cx.notify(); }) }) @@ -125,7 +130,9 @@ impl ReplStore { .kernel_specifications .iter() .find(|runtime_specification| { - if let Some(selected) = selected_kernel { + if let (Some(selected), KernelSpecification::Jupyter(runtime_specification)) = + (selected_kernel, runtime_specification) + { // Top priority is the selected kernel return runtime_specification.name.to_lowercase() == selected.to_lowercase(); } @@ -139,9 +146,13 @@ impl ReplStore { self.kernel_specifications .iter() - .find(|runtime_specification| { - runtime_specification.kernelspec.language.to_lowercase() - == language_name.to_lowercase() + .find(|kernel_option| match kernel_option { + KernelSpecification::Jupyter(runtime_specification) => { + runtime_specification.kernelspec.language.to_lowercase() + == language_name.to_lowercase() + } + // todo!() + _ => false, }) .cloned() } diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 2eba678fde..0ac6aabbdb 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -1,8 +1,8 @@ use crate::components::KernelListItem; -use crate::KernelStatus; use crate::{ kernels::{Kernel, KernelSpecification, RunningKernel}, outputs::{ExecutionStatus, ExecutionView}, + KernelStatus, }; use client::telemetry::Telemetry; use collections::{HashMap, HashSet}; @@ -224,7 +224,7 @@ impl Session { } fn start_kernel(&mut self, cx: &mut ViewContext) { - let kernel_language = self.kernel_specification.kernelspec.language.clone(); + let kernel_language = self.kernel_specification.language(); let entity_id = self.editor.entity_id(); let working_directory = self .editor @@ -233,7 +233,7 @@ impl Session { .unwrap_or_else(temp_dir); self.telemetry.report_repl_event( - kernel_language.clone(), + kernel_language.into(), KernelStatus::Starting.to_string(), cx.entity_id().to_string(), ); @@ -556,7 +556,7 @@ impl Session { self.kernel.set_execution_state(&status.execution_state); self.telemetry.report_repl_event( - self.kernel_specification.kernelspec.language.clone(), + self.kernel_specification.language().into(), KernelStatus::from(&self.kernel).to_string(), cx.entity_id().to_string(), ); @@ -607,7 +607,7 @@ impl Session { } let kernel_status = KernelStatus::from(&kernel).to_string(); - let kernel_language = self.kernel_specification.kernelspec.language.clone(); + let kernel_language = self.kernel_specification.language().into(); self.telemetry.report_repl_event( kernel_language, @@ -749,7 +749,7 @@ impl Render for Session { Kernel::Shutdown => Color::Disabled, Kernel::Restarting => Color::Modified, }) - .child(Label::new(self.kernel_specification.name.clone())) + .child(Label::new(self.kernel_specification.name())) .children(status_text.map(|status_text| Label::new(format!("({status_text})")))) .button( Button::new("shutdown", "Shutdown")