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:
parent
f6d4a73c34
commit
36fe364c05
13 changed files with 492 additions and 63 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -9354,6 +9354,7 @@ dependencies = [
|
||||||
"editor",
|
"editor",
|
||||||
"gpui",
|
"gpui",
|
||||||
"markdown_preview",
|
"markdown_preview",
|
||||||
|
"picker",
|
||||||
"repl",
|
"repl",
|
||||||
"search",
|
"search",
|
||||||
"settings",
|
"settings",
|
||||||
|
@ -9857,6 +9858,7 @@ dependencies = [
|
||||||
"menu",
|
"menu",
|
||||||
"multi_buffer",
|
"multi_buffer",
|
||||||
"nbformat",
|
"nbformat",
|
||||||
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
"runtimelib",
|
"runtimelib",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
|
|
@ -24,6 +24,7 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
|
picker.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View};
|
use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View};
|
||||||
|
use picker::Picker;
|
||||||
use repl::{
|
use repl::{
|
||||||
|
components::{KernelPickerDelegate, KernelSelector},
|
||||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, Session,
|
ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, Session,
|
||||||
SessionSupport,
|
SessionSupport,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
|
prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
|
||||||
Tooltip,
|
PopoverMenuHandle, Tooltip,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui::ElementId;
|
use gpui::ElementId;
|
||||||
|
@ -58,7 +60,6 @@ impl QuickActionBar {
|
||||||
let session = match session {
|
let session = match session {
|
||||||
SessionSupport::ActiveSession(session) => session,
|
SessionSupport::ActiveSession(session) => session,
|
||||||
SessionSupport::Inactive(spec) => {
|
SessionSupport::Inactive(spec) => {
|
||||||
let spec = *spec;
|
|
||||||
return self.render_repl_launch_menu(spec, cx);
|
return self.render_repl_launch_menu(spec, cx);
|
||||||
}
|
}
|
||||||
SessionSupport::RequiresSetup(language) => {
|
SessionSupport::RequiresSetup(language) => {
|
||||||
|
@ -246,44 +247,120 @@ impl QuickActionBar {
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.child(self.render_kernel_selector(cx))
|
||||||
.child(button)
|
.child(button)
|
||||||
.child(dropdown_menu)
|
.child(dropdown_menu)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_repl_launch_menu(
|
pub fn render_repl_launch_menu(
|
||||||
&self,
|
&self,
|
||||||
kernel_specification: KernelSpecification,
|
kernel_specification: KernelSpecification,
|
||||||
_cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let tooltip: SharedString =
|
let tooltip: SharedString =
|
||||||
SharedString::from(format!("Start REPL for {}", kernel_specification.name));
|
SharedString::from(format!("Start REPL for {}", kernel_specification.name()));
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
|
h_flex()
|
||||||
.size(ButtonSize::Compact)
|
.child(self.render_kernel_selector(cx))
|
||||||
.icon_color(Color::Muted)
|
.child(
|
||||||
.style(ButtonStyle::Subtle)
|
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
|
||||||
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
.size(ButtonSize::Compact)
|
||||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
.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(),
|
.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(
|
pub fn render_repl_setup(
|
||||||
&self,
|
&self,
|
||||||
language: &str,
|
language: &str,
|
||||||
_cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
|
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
|
||||||
Some(
|
Some(
|
||||||
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
|
h_flex()
|
||||||
.size(ButtonSize::Compact)
|
.child(self.render_kernel_selector(cx))
|
||||||
.icon_color(Color::Muted)
|
.child(
|
||||||
.style(ButtonStyle::Subtle)
|
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
|
||||||
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
.size(ButtonSize::Compact)
|
||||||
.on_click(|_, cx| cx.open_url(&format!("{}#installation", ZED_REPL_DOCUMENTATION)))
|
.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(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -292,13 +369,8 @@ impl QuickActionBar {
|
||||||
fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
|
fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
|
||||||
let session = session.read(cx);
|
let session = session.read(cx);
|
||||||
|
|
||||||
let kernel_name: SharedString = session.kernel_specification.name.clone().into();
|
let kernel_name = session.kernel_specification.name();
|
||||||
let kernel_language: SharedString = session
|
let kernel_language: SharedString = session.kernel_specification.language();
|
||||||
.kernel_specification
|
|
||||||
.kernelspec
|
|
||||||
.language
|
|
||||||
.clone()
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let fill_fields = || {
|
let fill_fields = || {
|
||||||
ReplMenuState {
|
ReplMenuState {
|
||||||
|
|
|
@ -45,6 +45,7 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
picker.workspace = true
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows.workspace = true
|
windows.workspace = true
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
mod kernel_list_item;
|
mod kernel_list_item;
|
||||||
|
mod kernel_options;
|
||||||
|
|
||||||
pub use kernel_list_item::*;
|
pub use kernel_list_item::*;
|
||||||
|
pub use kernel_options::*;
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl ParentElement for KernelListItem {
|
||||||
|
|
||||||
impl RenderOnce for KernelListItem {
|
impl RenderOnce for KernelListItem {
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
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)
|
.selectable(false)
|
||||||
.start_slot(
|
.start_slot(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
201
crates/repl/src/components/kernel_options.rs
Normal file
201
crates/repl/src/components/kernel_options.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,16 +19,82 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use ui::SharedString;
|
||||||
use uuid::Uuid;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct KernelSpecification {
|
pub struct LocalKernelSpecification {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub kernelspec: JupyterKernelspec,
|
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]
|
#[must_use]
|
||||||
fn command(&self, connection_path: &PathBuf) -> Result<Command> {
|
fn command(&self, connection_path: &PathBuf) -> Result<Command> {
|
||||||
let argv = &self.kernelspec.argv;
|
let argv = &self.kernelspec.argv;
|
||||||
|
@ -198,6 +264,17 @@ impl RunningKernel {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<(Self, JupyterMessageChannel)>> {
|
) -> 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 {
|
cx.spawn(|cx| async move {
|
||||||
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
let ports = peek_ports(ip).await?;
|
let ports = peek_ports(ip).await?;
|
||||||
|
@ -344,7 +421,7 @@ async fn read_kernelspec_at(
|
||||||
// /usr/local/share/jupyter/kernels/python3
|
// /usr/local/share/jupyter/kernels/python3
|
||||||
kernel_dir: PathBuf,
|
kernel_dir: PathBuf,
|
||||||
fs: &dyn Fs,
|
fs: &dyn Fs,
|
||||||
) -> Result<KernelSpecification> {
|
) -> Result<LocalKernelSpecification> {
|
||||||
let path = kernel_dir;
|
let path = kernel_dir;
|
||||||
let kernel_name = if let Some(kernel_name) = path.file_name() {
|
let kernel_name = if let Some(kernel_name) = path.file_name() {
|
||||||
kernel_name.to_string_lossy().to_string()
|
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 = fs.load(expected_kernel_json.as_path()).await?;
|
||||||
let spec = serde_json::from_str::<JupyterKernelspec>(&spec)?;
|
let spec = serde_json::from_str::<JupyterKernelspec>(&spec)?;
|
||||||
|
|
||||||
Ok(KernelSpecification {
|
Ok(LocalKernelSpecification {
|
||||||
name: kernel_name,
|
name: kernel_name,
|
||||||
path,
|
path,
|
||||||
kernelspec: spec,
|
kernelspec: spec,
|
||||||
|
@ -368,7 +445,7 @@ async fn read_kernelspec_at(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a directory of kernelspec directories
|
/// 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 kernelspec_dirs = fs.read_dir(&path).await?;
|
||||||
|
|
||||||
let mut valid_kernelspecs = Vec::new();
|
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)
|
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();
|
let mut data_dirs = dirs::data_dirs();
|
||||||
|
|
||||||
// Pick up any kernels from conda or conda environment
|
// Pick up any kernels from conda or conda environment
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod components;
|
pub mod components;
|
||||||
mod jupyter_settings;
|
mod jupyter_settings;
|
||||||
mod kernels;
|
mod kernels;
|
||||||
pub mod notebook;
|
pub mod notebook;
|
||||||
|
@ -26,6 +26,8 @@ use crate::repl_store::ReplStore;
|
||||||
pub use crate::session::Session;
|
pub use crate::session::Session;
|
||||||
use client::telemetry::Telemetry;
|
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) {
|
pub fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) {
|
||||||
set_dispatcher(zed_dispatcher(cx));
|
set_dispatcher(zed_dispatcher(cx));
|
||||||
JupyterSettings::register(cx);
|
JupyterSettings::register(cx);
|
||||||
|
|
|
@ -12,6 +12,56 @@ use crate::repl_store::ReplStore;
|
||||||
use crate::session::SessionEvent;
|
use crate::session::SessionEvent;
|
||||||
use crate::{KernelSpecification, Session};
|
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<()> {
|
pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) -> Result<()> {
|
||||||
let store = ReplStore::global(cx);
|
let store = ReplStore::global(cx);
|
||||||
if !store.read(cx).is_enabled() {
|
if !store.read(cx).is_enabled() {
|
||||||
|
@ -96,9 +146,10 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum SessionSupport {
|
pub enum SessionSupport {
|
||||||
ActiveSession(View<Session>),
|
ActiveSession(View<Session>),
|
||||||
Inactive(Box<KernelSpecification>),
|
Inactive(KernelSpecification),
|
||||||
RequiresSetup(LanguageName),
|
RequiresSetup(LanguageName),
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
@ -119,7 +170,7 @@ pub fn session(editor: WeakView<Editor>, cx: &mut WindowContext) -> SessionSuppo
|
||||||
});
|
});
|
||||||
|
|
||||||
match kernelspec {
|
match kernelspec {
|
||||||
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
|
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
|
||||||
None => {
|
None => {
|
||||||
if language_supported(&language) {
|
if language_supported(&language) {
|
||||||
SessionSupport::RequiresSetup(language.name())
|
SessionSupport::RequiresSetup(language.name())
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use collections::HashMap;
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, prelude::*, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
|
actions, prelude::*, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
|
||||||
FontWeight, Subscription, View,
|
FontWeight, Subscription, View,
|
||||||
};
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding, ListItem, Tooltip};
|
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding, ListItem, Tooltip};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::item::ItemEvent;
|
use workspace::item::ItemEvent;
|
||||||
|
@ -12,7 +12,7 @@ use workspace::{item::Item, Workspace};
|
||||||
|
|
||||||
use crate::jupyter_settings::JupyterSettings;
|
use crate::jupyter_settings::JupyterSettings;
|
||||||
use crate::repl_store::ReplStore;
|
use crate::repl_store::ReplStore;
|
||||||
use crate::KernelSpecification;
|
use crate::{KernelSpecification, KERNEL_DOCS_URL};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
repl,
|
repl,
|
||||||
|
@ -238,14 +238,24 @@ impl Render for ReplSessionsPage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut kernels_by_language: HashMap<String, Vec<KernelSpecification>> = HashMap::default();
|
let mut kernels_by_language: HashMap<SharedString, Vec<&KernelSpecification>> =
|
||||||
for spec in kernel_specifications {
|
kernel_specifications
|
||||||
kernels_by_language
|
.iter()
|
||||||
.entry(spec.kernelspec.language.clone())
|
.map(|spec| (spec.language(), spec))
|
||||||
.or_default()
|
.fold(HashMap::new(), |mut acc, (language, spec)| {
|
||||||
.push(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()
|
let kernels_available = v_flex()
|
||||||
.child(Label::new("Kernels available").size(LabelSize::Large))
|
.child(Label::new("Kernels available").size(LabelSize::Large))
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
@ -262,11 +272,11 @@ impl Render for ReplSessionsPage {
|
||||||
.child(Label::new("REPL documentation"))
|
.child(Label::new("REPL documentation"))
|
||||||
.child(Icon::new(IconName::Link))
|
.child(Icon::new(IconName::Link))
|
||||||
.on_click(move |_, cx| {
|
.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);
|
let chosen_kernel = store.read(cx).kernelspec(&language, cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
@ -274,13 +284,12 @@ impl Render for ReplSessionsPage {
|
||||||
.child(Label::new(language.clone()).weight(FontWeight::BOLD))
|
.child(Label::new(language.clone()).weight(FontWeight::BOLD))
|
||||||
.children(specs.into_iter().map(|spec| {
|
.children(specs.into_iter().map(|spec| {
|
||||||
let is_choice = if let Some(chosen_kernel) = &chosen_kernel {
|
let is_choice = if let Some(chosen_kernel) = &chosen_kernel {
|
||||||
chosen_kernel.name.to_lowercase() == spec.name.to_lowercase()
|
chosen_kernel == spec
|
||||||
&& chosen_kernel.path == spec.path
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = SharedString::from(spec.path.to_string_lossy().to_string());
|
let path = spec.path();
|
||||||
|
|
||||||
ListItem::new(path.clone())
|
ListItem::new(path.clone())
|
||||||
.selectable(false)
|
.selectable(false)
|
||||||
|
@ -290,7 +299,7 @@ impl Render for ReplSessionsPage {
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.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| {
|
.when(is_choice, |el| {
|
||||||
|
|
||||||
let language = language.clone();
|
let language = language.clone();
|
||||||
|
|
|
@ -10,7 +10,7 @@ use gpui::{
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
|
|
||||||
use crate::kernels::kernel_specifications;
|
use crate::kernels::local_kernel_specifications;
|
||||||
use crate::{JupyterSettings, KernelSpecification, Session};
|
use crate::{JupyterSettings, KernelSpecification, Session};
|
||||||
|
|
||||||
struct GlobalReplStore(Model<ReplStore>);
|
struct GlobalReplStore(Model<ReplStore>);
|
||||||
|
@ -106,12 +106,17 @@ impl ReplStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
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 {
|
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.update(&mut cx, |this, cx| {
|
||||||
this.kernel_specifications = kernel_specifications;
|
this.kernel_specifications = kernel_options;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -125,7 +130,9 @@ impl ReplStore {
|
||||||
.kernel_specifications
|
.kernel_specifications
|
||||||
.iter()
|
.iter()
|
||||||
.find(|runtime_specification| {
|
.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
|
// Top priority is the selected kernel
|
||||||
return runtime_specification.name.to_lowercase() == selected.to_lowercase();
|
return runtime_specification.name.to_lowercase() == selected.to_lowercase();
|
||||||
}
|
}
|
||||||
|
@ -139,9 +146,13 @@ impl ReplStore {
|
||||||
|
|
||||||
self.kernel_specifications
|
self.kernel_specifications
|
||||||
.iter()
|
.iter()
|
||||||
.find(|runtime_specification| {
|
.find(|kernel_option| match kernel_option {
|
||||||
runtime_specification.kernelspec.language.to_lowercase()
|
KernelSpecification::Jupyter(runtime_specification) => {
|
||||||
== language_name.to_lowercase()
|
runtime_specification.kernelspec.language.to_lowercase()
|
||||||
|
== language_name.to_lowercase()
|
||||||
|
}
|
||||||
|
// todo!()
|
||||||
|
_ => false,
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::components::KernelListItem;
|
use crate::components::KernelListItem;
|
||||||
use crate::KernelStatus;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
kernels::{Kernel, KernelSpecification, RunningKernel},
|
kernels::{Kernel, KernelSpecification, RunningKernel},
|
||||||
outputs::{ExecutionStatus, ExecutionView},
|
outputs::{ExecutionStatus, ExecutionView},
|
||||||
|
KernelStatus,
|
||||||
};
|
};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
|
@ -224,7 +224,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_kernel(&mut self, cx: &mut ViewContext<Self>) {
|
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 entity_id = self.editor.entity_id();
|
||||||
let working_directory = self
|
let working_directory = self
|
||||||
.editor
|
.editor
|
||||||
|
@ -233,7 +233,7 @@ impl Session {
|
||||||
.unwrap_or_else(temp_dir);
|
.unwrap_or_else(temp_dir);
|
||||||
|
|
||||||
self.telemetry.report_repl_event(
|
self.telemetry.report_repl_event(
|
||||||
kernel_language.clone(),
|
kernel_language.into(),
|
||||||
KernelStatus::Starting.to_string(),
|
KernelStatus::Starting.to_string(),
|
||||||
cx.entity_id().to_string(),
|
cx.entity_id().to_string(),
|
||||||
);
|
);
|
||||||
|
@ -556,7 +556,7 @@ impl Session {
|
||||||
self.kernel.set_execution_state(&status.execution_state);
|
self.kernel.set_execution_state(&status.execution_state);
|
||||||
|
|
||||||
self.telemetry.report_repl_event(
|
self.telemetry.report_repl_event(
|
||||||
self.kernel_specification.kernelspec.language.clone(),
|
self.kernel_specification.language().into(),
|
||||||
KernelStatus::from(&self.kernel).to_string(),
|
KernelStatus::from(&self.kernel).to_string(),
|
||||||
cx.entity_id().to_string(),
|
cx.entity_id().to_string(),
|
||||||
);
|
);
|
||||||
|
@ -607,7 +607,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
let kernel_status = KernelStatus::from(&kernel).to_string();
|
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(
|
self.telemetry.report_repl_event(
|
||||||
kernel_language,
|
kernel_language,
|
||||||
|
@ -749,7 +749,7 @@ impl Render for Session {
|
||||||
Kernel::Shutdown => Color::Disabled,
|
Kernel::Shutdown => Color::Disabled,
|
||||||
Kernel::Restarting => Color::Modified,
|
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})"))))
|
.children(status_text.map(|status_text| Label::new(format!("({status_text})"))))
|
||||||
.button(
|
.button(
|
||||||
Button::new("shutdown", "Shutdown")
|
Button::new("shutdown", "Shutdown")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue