Skip to content

Commit

Permalink
Discover available python environments with Jupyter kernel support (#…
Browse files Browse the repository at this point in the history
…20467)

![image](https://github.com/user-attachments/assets/7c042bc9-88be-4d7b-b63d-e5b555d54b18)

Closes #18291
Closes #16757
Closes #15563

Release Notes:

- Added support for kernelspecs based on python environments
  • Loading branch information
rgbkrk authored Nov 11, 2024
1 parent 6152230 commit 97b542b
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 148 deletions.
20 changes: 7 additions & 13 deletions crates/quick_action_bar/src/repl_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View
use picker::Picker;
use repl::{
components::{KernelPickerDelegate, KernelSelector},
ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, Session,
SessionSupport,
worktree_id_for_editor, ExecutionState, JupyterSettings, Kernel, KernelSpecification,
KernelStatus, Session, SessionSupport,
};
use ui::{
prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
Expand All @@ -30,9 +30,6 @@ struct ReplMenuState {
status: KernelStatus,
kernel_name: SharedString,
kernel_language: SharedString,
// TODO: Persist rotation state so the
// icon doesn't reset on every state change
// current_delta: Duration,
}

impl QuickActionBar {
Expand Down Expand Up @@ -178,12 +175,6 @@ impl QuickActionBar {
},
)
.separator()
.link(
"Change Kernel",
Box::new(zed_actions::OpenBrowser {
url: format!("{}#change-kernel", ZED_REPL_DOCUMENTATION),
}),
)
.custom_entry(
move |_cx| {
Label::new("Shut Down Kernel")
Expand Down Expand Up @@ -290,7 +281,10 @@ impl QuickActionBar {
let editor = if let Some(editor) = self.active_editor() {
editor
} else {
// todo!()
return div().into_any_element();
};

let Some(worktree_id) = worktree_id_for_editor(editor.downgrade(), cx) else {
return div().into_any_element();
};

Expand All @@ -313,7 +307,7 @@ impl QuickActionBar {
repl::assign_kernelspec(kernelspec, editor.downgrade(), cx).ok();
})
},
current_kernelspec.clone(),
worktree_id,
ButtonLike::new("kernel-selector")
.style(ButtonStyle::Subtle)
.child(
Expand Down
57 changes: 34 additions & 23 deletions crates/repl/src/components/kernel_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use crate::KERNEL_DOCS_URL;

use gpui::DismissEvent;

use gpui::FontWeight;
use picker::Picker;
use picker::PickerDelegate;
use project::WorktreeId;

use std::sync::Arc;
use ui::ListItemSpacing;
Expand All @@ -22,7 +24,7 @@ pub struct KernelSelector<T: PopoverTrigger> {
on_select: OnSelect,
trigger: T,
info_text: Option<SharedString>,
current_kernelspec: Option<KernelSpecification>,
worktree_id: WorktreeId,
}

pub struct KernelPickerDelegate {
Expand All @@ -33,17 +35,13 @@ pub struct KernelPickerDelegate {
}

impl<T: PopoverTrigger> KernelSelector<T> {
pub fn new(
on_select: OnSelect,
current_kernelspec: Option<KernelSpecification>,
trigger: T,
) -> Self {
pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T) -> Self {
KernelSelector {
on_select,
handle: None,
trigger,
info_text: None,
current_kernelspec,
worktree_id,
}
}

Expand Down Expand Up @@ -130,24 +128,34 @@ impl PickerDelegate for KernelPickerDelegate {
.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),
),
),
v_flex()
.min_w(px(600.))
.w_full()
.gap_0p5()
.child(
h_flex()
.w_full()
.gap_1()
.child(Label::new(kernelspec.name()).weight(FontWeight::MEDIUM))
.child(
Label::new(kernelspec.language())
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.child(
Label::new(kernelspec.path())
.size(LabelSize::XSmall)
.color(Color::Muted),
),
)
.end_slot(div().when(is_selected, |this| {
this.child(
.when(is_selected, |item| {
item.end_slot(
Icon::new(IconName::Check)
.color(Color::Accent)
.size(IconSize::Small),
)
})),
}),
)
}

Expand Down Expand Up @@ -175,10 +183,13 @@ impl PickerDelegate for KernelPickerDelegate {
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 all_kernels: Vec<KernelSpecification> = store
.kernel_specifications_for_worktree(self.worktree_id)
.cloned()
.collect();

let selected_kernelspec = store.active_kernelspec(self.worktree_id, None, cx);

let delegate = KernelPickerDelegate {
on_select: self.on_select,
Expand Down
72 changes: 70 additions & 2 deletions crates/repl/src/kernels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use futures::{
stream::{self, SelectAll, StreamExt},
SinkExt as _,
};
use gpui::{AppContext, EntityId, Task};
use project::Fs;
use gpui::{AppContext, EntityId, Model, Task};
use language::LanguageName;
use project::{Fs, Project, WorktreeId};
use runtimelib::{
dirs, ConnectionInfo, ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent,
KernelInfoReply,
Expand All @@ -15,6 +16,7 @@ use smol::{net::TcpListener, process::Command};
use std::{
env,
fmt::Debug,
future::Future,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
sync::Arc,
Expand Down Expand Up @@ -465,6 +467,72 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<LocalKernelS
Ok(valid_kernelspecs)
}

pub fn python_env_kernel_specifications(
project: &Model<Project>,
worktree_id: WorktreeId,
cx: &mut AppContext,
) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
let python_language = LanguageName::new("Python");
let toolchains = project
.read(cx)
.available_toolchains(worktree_id, python_language, cx);
let background_executor = cx.background_executor().clone();

async move {
let toolchains = if let Some(toolchains) = toolchains.await {
toolchains
} else {
return Ok(Vec::new());
};

let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| {
background_executor.spawn(async move {
let python_path = toolchain.path.to_string();

// Check if ipykernel is installed
let ipykernel_check = Command::new(&python_path)
.args(&["-c", "import ipykernel"])
.output()
.await;

if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() {
// Create a default kernelspec for this environment
let default_kernelspec = JupyterKernelspec {
argv: vec![
python_path.clone(),
"-m".to_string(),
"ipykernel_launcher".to_string(),
"-f".to_string(),
"{connection_file}".to_string(),
],
display_name: toolchain.name.to_string(),
language: "python".to_string(),
interrupt_mode: None,
metadata: None,
env: None,
};

Some(KernelSpecification::PythonEnv(LocalKernelSpecification {
name: toolchain.name.to_string(),
path: PathBuf::from(&python_path),
kernelspec: default_kernelspec,
}))
} else {
None
}
})
});

let kernel_specs = futures::future::join_all(kernelspecs)
.await
.into_iter()
.flatten()
.collect();

anyhow::Ok(kernel_specs)
}
}

pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKernelSpecification>> {
let mut data_dirs = dirs::data_dirs();

Expand Down
54 changes: 44 additions & 10 deletions crates/repl/src/repl_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use anyhow::{Context, Result};
use editor::Editor;
use gpui::{prelude::*, Entity, View, WeakView, WindowContext};
use language::{BufferSnapshot, Language, LanguageName, Point};
use project::{Item as _, WorktreeId};

use crate::repl_store::ReplStore;
use crate::session::SessionEvent;
Expand All @@ -24,6 +25,13 @@ pub fn assign_kernelspec(
return Ok(());
}

let worktree_id = crate::repl_editor::worktree_id_for_editor(weak_editor.clone(), cx)
.context("editor is not in a worktree")?;

store.update(cx, |store, cx| {
store.set_active_kernelspec(worktree_id, kernel_specification.clone(), cx);
});

let fs = store.read(cx).fs().clone();
let telemetry = store.read(cx).telemetry().clone();

Expand Down Expand Up @@ -79,6 +87,10 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
return Ok(());
};

let Some(project_path) = buffer.read(cx).project_path(cx) else {
return Ok(());
};

let (runnable_ranges, next_cell_point) =
runnable_ranges(&buffer.read(cx).snapshot(), selected_range);

Expand All @@ -87,11 +99,10 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
continue;
};

let kernel_specification = store.update(cx, |store, cx| {
store
.kernelspec(language.code_fence_block_name().as_ref(), cx)
.with_context(|| format!("No kernel found for language: {}", language.name()))
})?;
let kernel_specification = store
.read(cx)
.active_kernelspec(project_path.worktree_id, Some(language.clone()), cx)
.ok_or_else(|| anyhow::anyhow!("No kernel found for language: {}", language.name()))?;

let fs = store.read(cx).fs().clone();
let telemetry = store.read(cx).telemetry().clone();
Expand Down Expand Up @@ -156,6 +167,22 @@ pub enum SessionSupport {
Unsupported,
}

pub fn worktree_id_for_editor(
editor: WeakView<Editor>,
cx: &mut WindowContext,
) -> Option<WorktreeId> {
editor.upgrade().and_then(|editor| {
editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()?
.read(cx)
.project_path(cx)
.map(|path| path.worktree_id)
})
}

pub fn session(editor: WeakView<Editor>, cx: &mut WindowContext) -> SessionSupport {
let store = ReplStore::global(cx);
let entity_id = editor.entity_id();
Expand All @@ -164,17 +191,24 @@ pub fn session(editor: WeakView<Editor>, cx: &mut WindowContext) -> SessionSuppo
return SessionSupport::ActiveSession(session);
};

let Some(language) = get_language(editor, cx) else {
let Some(language) = get_language(editor.clone(), cx) else {
return SessionSupport::Unsupported;
};
let kernelspec = store.update(cx, |store, cx| {
store.kernelspec(language.code_fence_block_name().as_ref(), cx)
});

let worktree_id = worktree_id_for_editor(editor.clone(), cx);

let Some(worktree_id) = worktree_id else {
return SessionSupport::Unsupported;
};

let kernelspec = store
.read(cx)
.active_kernelspec(worktree_id, Some(language.clone()), cx);

match kernelspec {
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
None => {
if language_supported(&language) {
if language_supported(&language.clone()) {
SessionSupport::RequiresSetup(language.name())
} else {
SessionSupport::Unsupported
Expand Down
Loading

0 comments on commit 97b542b

Please sign in to comment.