Show kernel options in a picker (#20274)

Closes #18341

* [x] Remove "Change Kernel" Doc link from REPL menu
* [x] Remove chevron
* [x] Set a higher min width
* [x] Include the language along with the kernel name

Future PRs will address

* Add support for Python envs (#18291, #16757, #15563)
* Add support for Remote kernels
* Project settings support (#16898)

Release Notes:

- Added kernel picker for repl

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Kyle Kelley 2024-11-07 17:59:53 -08:00 committed by GitHub
parent f6d4a73c34
commit 36fe364c05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 492 additions and 63 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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<Self>,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
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<Self>) -> 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<Picker<KernelPickerDelegate>> =
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<Self>,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
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<Session>, 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 {

View file

@ -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

View file

@ -1,3 +1,5 @@
mod kernel_list_item;
mod kernel_options;
pub use kernel_list_item::*;
pub use kernel_options::*;

View file

@ -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()

View file

@ -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<dyn Fn(KernelSpecification, &mut WindowContext)>;
#[derive(IntoElement)]
pub struct KernelSelector<T: PopoverTrigger> {
handle: Option<PopoverMenuHandle<Picker<KernelPickerDelegate>>>,
on_select: OnSelect,
trigger: T,
info_text: Option<SharedString>,
current_kernelspec: Option<KernelSpecification>,
}
pub struct KernelPickerDelegate {
all_kernels: Vec<KernelSpecification>,
filtered_kernels: Vec<KernelSpecification>,
selected_kernelspec: Option<KernelSpecification>,
on_select: OnSelect,
}
impl<T: PopoverTrigger> KernelSelector<T> {
pub fn new(
on_select: OnSelect,
current_kernelspec: Option<KernelSpecification>,
trigger: T,
) -> Self {
KernelSelector {
on_select,
handle: None,
trigger,
info_text: None,
current_kernelspec,
}
}
pub fn with_handle(mut self, handle: PopoverMenuHandle<Picker<KernelPickerDelegate>>) -> Self {
self.handle = Some(handle);
self
}
pub fn with_info_text(mut self, text: impl Into<SharedString>) -> 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<Picker<Self>>) {
self.selected_kernelspec = self.filtered_kernels.get(ix).cloned();
cx.notify();
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Select a kernel...".into()
}
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> 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<Picker<Self>>) {
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<Picker<Self>>) {}
fn render_match(
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
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<Picker<Self>>) -> Option<gpui::AnyElement> {
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<T: PopoverTrigger> RenderOnce for KernelSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let store = ReplStore::global(cx).read(cx);
let all_kernels: Vec<KernelSpecification> =
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))
}
}

View file

@ -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<Command> {
let argv = &self.kernelspec.argv;
@ -198,6 +264,17 @@ impl RunningKernel {
fs: Arc<dyn Fs>,
cx: &mut AppContext,
) -> Task<Result<(Self, JupyterMessageChannel)>> {
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<KernelSpecification> {
) -> Result<LocalKernelSpecification> {
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::<JupyterKernelspec>(&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<Vec<KernelSpecification>> {
async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<LocalKernelSpecification>> {
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<Vec<KernelSpecif
Ok(valid_kernelspecs)
}
pub async fn kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<KernelSpecification>> {
pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKernelSpecification>> {
let mut data_dirs = dirs::data_dirs();
// Pick up any kernels from conda or conda environment

View file

@ -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<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) {
set_dispatcher(zed_dispatcher(cx));
JupyterSettings::register(cx);

View file

@ -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<Editor>,
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<Editor>, 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<Editor>, move_down: bool, cx: &mut WindowContext) ->
anyhow::Ok(())
}
#[allow(clippy::large_enum_variant)]
pub enum SessionSupport {
ActiveSession(View<Session>),
Inactive(Box<KernelSpecification>),
Inactive(KernelSpecification),
RequiresSetup(LanguageName),
Unsupported,
}
@ -119,7 +170,7 @@ pub fn session(editor: WeakView<Editor>, 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())

View file

@ -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<String, Vec<KernelSpecification>> = 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<SharedString, Vec<&KernelSpecification>> =
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();

View file

@ -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<ReplStore>);
@ -106,12 +106,17 @@ impl ReplStore {
}
pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
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()
}

View file

@ -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<Self>) {
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")