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",
|
||||
"gpui",
|
||||
"markdown_preview",
|
||||
"picker",
|
||||
"repl",
|
||||
"search",
|
||||
"settings",
|
||||
|
@ -9857,6 +9858,7 @@ dependencies = [
|
|||
"menu",
|
||||
"multi_buffer",
|
||||
"nbformat",
|
||||
"picker",
|
||||
"project",
|
||||
"runtimelib",
|
||||
"schemars",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
mod kernel_list_item;
|
||||
mod kernel_options;
|
||||
|
||||
pub use kernel_list_item::*;
|
||||
pub use kernel_options::*;
|
||||
|
|
|
@ -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()
|
||||
|
|
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,
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue