Create RunningKernel
trait to allow for native and remote jupyter kernels (#20842)
Starts setting up a `RunningKernel` trait to make the remote kernel implementation easy to get started with. No release notes until this is all hooked up. Release Notes: - N/A
This commit is contained in:
parent
343c88574a
commit
bd0f197415
9 changed files with 1600 additions and 1088 deletions
1985
Cargo.lock
generated
1985
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -368,12 +368,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
|
||||||
indoc = "2"
|
indoc = "2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
|
jupyter-protocol = { version = "0.2.0" }
|
||||||
|
jupyter-websocket-client = { version = "0.4.1" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||||
markup5ever_rcdom = "0.3.0"
|
markup5ever_rcdom = "0.3.0"
|
||||||
nanoid = "0.4"
|
nanoid = "0.4"
|
||||||
nbformat = "0.5.0"
|
nbformat = "0.6.0"
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
@ -407,7 +409,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||||
"stream",
|
"stream",
|
||||||
] }
|
] }
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.6"
|
||||||
runtimelib = { version = "0.19.0", default-features = false, features = [
|
runtimelib = { version = "0.21.0", default-features = false, features = [
|
||||||
"async-dispatcher-runtime",
|
"async-dispatcher-runtime",
|
||||||
] }
|
] }
|
||||||
rustc-demangle = "0.1.23"
|
rustc-demangle = "0.1.23"
|
||||||
|
|
|
@ -402,7 +402,7 @@ fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
|
||||||
status: session.kernel.status(),
|
status: session.kernel.status(),
|
||||||
..fill_fields()
|
..fill_fields()
|
||||||
},
|
},
|
||||||
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
|
Kernel::RunningKernel(kernel) => match &kernel.execution_state() {
|
||||||
ExecutionState::Idle => ReplMenuState {
|
ExecutionState::Idle => ReplMenuState {
|
||||||
tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(),
|
tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(),
|
||||||
indicator: Some(Indicator::dot().color(Color::Success)),
|
indicator: Some(Indicator::dot().color(Color::Success)),
|
||||||
|
|
|
@ -25,6 +25,8 @@ feature_flags.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
image.workspace = true
|
image.workspace = true
|
||||||
|
jupyter-websocket-client.workspace = true
|
||||||
|
jupyter-protocol.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
markdown_preview.workspace = true
|
markdown_preview.workspace = true
|
||||||
|
|
227
crates/repl/src/kernels/mod.rs
Normal file
227
crates/repl/src/kernels/mod.rs
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
mod native_kernel;
|
||||||
|
use std::{fmt::Debug, future::Future, path::PathBuf};
|
||||||
|
|
||||||
|
use futures::{
|
||||||
|
channel::mpsc::{self, Receiver},
|
||||||
|
future::Shared,
|
||||||
|
stream,
|
||||||
|
};
|
||||||
|
use gpui::{AppContext, Model, Task};
|
||||||
|
use language::LanguageName;
|
||||||
|
pub use native_kernel::*;
|
||||||
|
|
||||||
|
mod remote_kernels;
|
||||||
|
use project::{Project, WorktreeId};
|
||||||
|
pub use remote_kernels::*;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply};
|
||||||
|
use smol::process::Command;
|
||||||
|
use ui::SharedString;
|
||||||
|
|
||||||
|
pub type JupyterMessageChannel = stream::SelectAll<Receiver<JupyterMessage>>;
|
||||||
|
|
||||||
|
#[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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 trait RunningKernel: Send + Debug {
|
||||||
|
fn request_tx(&self) -> mpsc::Sender<JupyterMessage>;
|
||||||
|
fn working_directory(&self) -> &PathBuf;
|
||||||
|
fn execution_state(&self) -> &ExecutionState;
|
||||||
|
fn set_execution_state(&mut self, state: ExecutionState);
|
||||||
|
fn kernel_info(&self) -> Option<&KernelInfoReply>;
|
||||||
|
fn set_kernel_info(&mut self, info: KernelInfoReply);
|
||||||
|
fn force_shutdown(&mut self) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum KernelStatus {
|
||||||
|
Idle,
|
||||||
|
Busy,
|
||||||
|
Starting,
|
||||||
|
Error,
|
||||||
|
ShuttingDown,
|
||||||
|
Shutdown,
|
||||||
|
Restarting,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KernelStatus {
|
||||||
|
pub fn is_connected(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
KernelStatus::Idle | KernelStatus::Busy => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for KernelStatus {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
KernelStatus::Idle => "Idle".to_string(),
|
||||||
|
KernelStatus::Busy => "Busy".to_string(),
|
||||||
|
KernelStatus::Starting => "Starting".to_string(),
|
||||||
|
KernelStatus::Error => "Error".to_string(),
|
||||||
|
KernelStatus::ShuttingDown => "Shutting Down".to_string(),
|
||||||
|
KernelStatus::Shutdown => "Shutdown".to_string(),
|
||||||
|
KernelStatus::Restarting => "Restarting".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Kernel {
|
||||||
|
RunningKernel(Box<dyn RunningKernel>),
|
||||||
|
StartingKernel(Shared<Task<()>>),
|
||||||
|
ErroredLaunch(String),
|
||||||
|
ShuttingDown,
|
||||||
|
Shutdown,
|
||||||
|
Restarting,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Kernel> for KernelStatus {
|
||||||
|
fn from(kernel: &Kernel) -> Self {
|
||||||
|
match kernel {
|
||||||
|
Kernel::RunningKernel(kernel) => match kernel.execution_state() {
|
||||||
|
ExecutionState::Idle => KernelStatus::Idle,
|
||||||
|
ExecutionState::Busy => KernelStatus::Busy,
|
||||||
|
},
|
||||||
|
Kernel::StartingKernel(_) => KernelStatus::Starting,
|
||||||
|
Kernel::ErroredLaunch(_) => KernelStatus::Error,
|
||||||
|
Kernel::ShuttingDown => KernelStatus::ShuttingDown,
|
||||||
|
Kernel::Shutdown => KernelStatus::Shutdown,
|
||||||
|
Kernel::Restarting => KernelStatus::Restarting,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kernel {
|
||||||
|
pub fn status(&self) -> KernelStatus {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_execution_state(&mut self, status: &ExecutionState) {
|
||||||
|
if let Kernel::RunningKernel(running_kernel) = self {
|
||||||
|
running_kernel.set_execution_state(status.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
|
||||||
|
if let Kernel::RunningKernel(running_kernel) = self {
|
||||||
|
running_kernel.set_kernel_info(kernel_info.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_shutting_down(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Kernel::Restarting | Kernel::ShuttingDown => true,
|
||||||
|
Kernel::RunningKernel(_)
|
||||||
|
| Kernel::StartingKernel(_)
|
||||||
|
| Kernel::ErroredLaunch(_)
|
||||||
|
| Kernel::Shutdown => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +1,24 @@
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc::{self, Receiver},
|
channel::mpsc::{self},
|
||||||
future::Shared,
|
stream::{SelectAll, StreamExt},
|
||||||
stream::{self, SelectAll, StreamExt},
|
|
||||||
SinkExt as _,
|
SinkExt as _,
|
||||||
};
|
};
|
||||||
use gpui::{AppContext, EntityId, Model, Task};
|
use gpui::{AppContext, EntityId, Task};
|
||||||
use language::LanguageName;
|
use jupyter_protocol::{JupyterMessage, JupyterMessageContent, KernelInfoReply};
|
||||||
use project::{Fs, Project, WorktreeId};
|
use project::Fs;
|
||||||
use runtimelib::{
|
use runtimelib::{dirs, ConnectionInfo, ExecutionState, JupyterKernelspec};
|
||||||
dirs, ConnectionInfo, ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent,
|
|
||||||
KernelInfoReply,
|
|
||||||
};
|
|
||||||
use smol::{net::TcpListener, process::Command};
|
use smol::{net::TcpListener, process::Command};
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
future::Future,
|
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use ui::SharedString;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
use super::{JupyterMessageChannel, RunningKernel};
|
||||||
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 LocalKernelSpecification {
|
pub struct LocalKernelSpecification {
|
||||||
|
@ -80,22 +35,6 @@ impl PartialEq for LocalKernelSpecification {
|
||||||
|
|
||||||
impl Eq for LocalKernelSpecification {}
|
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 {
|
impl LocalKernelSpecification {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn command(&self, connection_path: &PathBuf) -> Result<Command> {
|
fn command(&self, connection_path: &PathBuf) -> Result<Command> {
|
||||||
|
@ -147,95 +86,7 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
|
||||||
Ok(ports)
|
Ok(ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub struct NativeRunningKernel {
|
||||||
pub enum KernelStatus {
|
|
||||||
Idle,
|
|
||||||
Busy,
|
|
||||||
Starting,
|
|
||||||
Error,
|
|
||||||
ShuttingDown,
|
|
||||||
Shutdown,
|
|
||||||
Restarting,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KernelStatus {
|
|
||||||
pub fn is_connected(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
KernelStatus::Idle | KernelStatus::Busy => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for KernelStatus {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
match self {
|
|
||||||
KernelStatus::Idle => "Idle".to_string(),
|
|
||||||
KernelStatus::Busy => "Busy".to_string(),
|
|
||||||
KernelStatus::Starting => "Starting".to_string(),
|
|
||||||
KernelStatus::Error => "Error".to_string(),
|
|
||||||
KernelStatus::ShuttingDown => "Shutting Down".to_string(),
|
|
||||||
KernelStatus::Shutdown => "Shutdown".to_string(),
|
|
||||||
KernelStatus::Restarting => "Restarting".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Kernel> for KernelStatus {
|
|
||||||
fn from(kernel: &Kernel) -> Self {
|
|
||||||
match kernel {
|
|
||||||
Kernel::RunningKernel(kernel) => match kernel.execution_state {
|
|
||||||
ExecutionState::Idle => KernelStatus::Idle,
|
|
||||||
ExecutionState::Busy => KernelStatus::Busy,
|
|
||||||
},
|
|
||||||
Kernel::StartingKernel(_) => KernelStatus::Starting,
|
|
||||||
Kernel::ErroredLaunch(_) => KernelStatus::Error,
|
|
||||||
Kernel::ShuttingDown => KernelStatus::ShuttingDown,
|
|
||||||
Kernel::Shutdown => KernelStatus::Shutdown,
|
|
||||||
Kernel::Restarting => KernelStatus::Restarting,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Kernel {
|
|
||||||
RunningKernel(RunningKernel),
|
|
||||||
StartingKernel(Shared<Task<()>>),
|
|
||||||
ErroredLaunch(String),
|
|
||||||
ShuttingDown,
|
|
||||||
Shutdown,
|
|
||||||
Restarting,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Kernel {
|
|
||||||
pub fn status(&self) -> KernelStatus {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_execution_state(&mut self, status: &ExecutionState) {
|
|
||||||
if let Kernel::RunningKernel(running_kernel) = self {
|
|
||||||
running_kernel.execution_state = status.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
|
|
||||||
if let Kernel::RunningKernel(running_kernel) = self {
|
|
||||||
running_kernel.kernel_info = Some(kernel_info.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_shutting_down(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Kernel::Restarting | Kernel::ShuttingDown => true,
|
|
||||||
Kernel::RunningKernel(_)
|
|
||||||
| Kernel::StartingKernel(_)
|
|
||||||
| Kernel::ErroredLaunch(_)
|
|
||||||
| Kernel::Shutdown => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RunningKernel {
|
|
||||||
pub process: smol::process::Child,
|
pub process: smol::process::Child,
|
||||||
_shell_task: Task<Result<()>>,
|
_shell_task: Task<Result<()>>,
|
||||||
_iopub_task: Task<Result<()>>,
|
_iopub_task: Task<Result<()>>,
|
||||||
|
@ -248,9 +99,7 @@ pub struct RunningKernel {
|
||||||
pub kernel_info: Option<KernelInfoReply>,
|
pub kernel_info: Option<KernelInfoReply>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type JupyterMessageChannel = stream::SelectAll<Receiver<JupyterMessage>>;
|
impl Debug for NativeRunningKernel {
|
||||||
|
|
||||||
impl Debug for RunningKernel {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("RunningKernel")
|
f.debug_struct("RunningKernel")
|
||||||
.field("process", &self.process)
|
.field("process", &self.process)
|
||||||
|
@ -258,25 +107,14 @@ impl Debug for RunningKernel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunningKernel {
|
impl NativeRunningKernel {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
kernel_specification: KernelSpecification,
|
kernel_specification: LocalKernelSpecification,
|
||||||
entity_id: EntityId,
|
entity_id: EntityId,
|
||||||
working_directory: PathBuf,
|
working_directory: PathBuf,
|
||||||
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?;
|
||||||
|
@ -315,15 +153,13 @@ impl RunningKernel {
|
||||||
|
|
||||||
let session_id = Uuid::new_v4().to_string();
|
let session_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let mut iopub_socket = connection_info
|
let mut iopub_socket =
|
||||||
.create_client_iopub_connection("", &session_id)
|
runtimelib::create_client_iopub_connection(&connection_info, "", &session_id)
|
||||||
.await?;
|
.await?;
|
||||||
let mut shell_socket = connection_info
|
let mut shell_socket =
|
||||||
.create_client_shell_connection(&session_id)
|
runtimelib::create_client_shell_connection(&connection_info, &session_id).await?;
|
||||||
.await?;
|
let mut control_socket =
|
||||||
let mut control_socket = connection_info
|
runtimelib::create_client_control_connection(&connection_info, &session_id).await?;
|
||||||
.create_client_control_connection(&session_id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let (mut iopub, iosub) = futures::channel::mpsc::channel(100);
|
let (mut iopub, iosub) = futures::channel::mpsc::channel(100);
|
||||||
|
|
||||||
|
@ -410,7 +246,43 @@ impl RunningKernel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RunningKernel {
|
impl RunningKernel for NativeRunningKernel {
|
||||||
|
fn request_tx(&self) -> mpsc::Sender<JupyterMessage> {
|
||||||
|
self.request_tx.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(&self) -> &PathBuf {
|
||||||
|
&self.working_directory
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execution_state(&self) -> &ExecutionState {
|
||||||
|
&self.execution_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_execution_state(&mut self, state: ExecutionState) {
|
||||||
|
self.execution_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kernel_info(&self) -> Option<&KernelInfoReply> {
|
||||||
|
self.kernel_info.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_kernel_info(&mut self, info: KernelInfoReply) {
|
||||||
|
self.kernel_info = Some(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
|
match self.process.kill() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(error) => Err(anyhow::anyhow!(
|
||||||
|
"Failed to kill the kernel process: {}",
|
||||||
|
error
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for NativeRunningKernel {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
std::fs::remove_file(&self.connection_path).ok();
|
std::fs::remove_file(&self.connection_path).ok();
|
||||||
self.request_tx.close_channel();
|
self.request_tx.close_channel();
|
||||||
|
@ -467,72 +339,6 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<LocalKernelS
|
||||||
Ok(valid_kernelspecs)
|
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>> {
|
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();
|
||||||
|
|
122
crates/repl/src/kernels/remote_kernels.rs
Normal file
122
crates/repl/src/kernels/remote_kernels.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use futures::{channel::mpsc, StreamExt as _};
|
||||||
|
use gpui::AppContext;
|
||||||
|
use jupyter_protocol::{ExecutionState, JupyterMessage, KernelInfoReply};
|
||||||
|
// todo(kyle): figure out if this needs to be different
|
||||||
|
use runtimelib::JupyterKernelspec;
|
||||||
|
|
||||||
|
use super::RunningKernel;
|
||||||
|
use jupyter_websocket_client::RemoteServer;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
#[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 {}
|
||||||
|
|
||||||
|
pub struct RemoteRunningKernel {
|
||||||
|
remote_server: RemoteServer,
|
||||||
|
pub working_directory: std::path::PathBuf,
|
||||||
|
pub request_tx: mpsc::Sender<JupyterMessage>,
|
||||||
|
pub execution_state: ExecutionState,
|
||||||
|
pub kernel_info: Option<KernelInfoReply>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteRunningKernel {
|
||||||
|
pub async fn new(
|
||||||
|
kernelspec: RemoteKernelSpecification,
|
||||||
|
working_directory: std::path::PathBuf,
|
||||||
|
request_tx: mpsc::Sender<JupyterMessage>,
|
||||||
|
_cx: &mut AppContext,
|
||||||
|
) -> anyhow::Result<(
|
||||||
|
Self,
|
||||||
|
(), // Stream<Item=JupyterMessage>
|
||||||
|
)> {
|
||||||
|
let remote_server = RemoteServer {
|
||||||
|
base_url: kernelspec.url,
|
||||||
|
token: kernelspec.token,
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo: launch a kernel to get a kernel ID
|
||||||
|
let kernel_id = "not-implemented";
|
||||||
|
|
||||||
|
let kernel_socket = remote_server.connect_to_kernel(kernel_id).await?;
|
||||||
|
|
||||||
|
let (mut _w, mut _r) = kernel_socket.split();
|
||||||
|
|
||||||
|
let (_messages_tx, _messages_rx) = mpsc::channel::<JupyterMessage>(100);
|
||||||
|
|
||||||
|
// let routing_task = cx.background_executor().spawn({
|
||||||
|
// async move {
|
||||||
|
// while let Some(message) = request_rx.next().await {
|
||||||
|
// w.send(message).await;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// let messages_rx = r.into();
|
||||||
|
|
||||||
|
anyhow::Ok((
|
||||||
|
Self {
|
||||||
|
remote_server,
|
||||||
|
working_directory,
|
||||||
|
request_tx,
|
||||||
|
execution_state: ExecutionState::Idle,
|
||||||
|
kernel_info: None,
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RemoteRunningKernel {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("RemoteRunningKernel")
|
||||||
|
// custom debug that keeps tokens out of logs
|
||||||
|
.field("remote_server url", &self.remote_server.base_url)
|
||||||
|
.field("working_directory", &self.working_directory)
|
||||||
|
.field("request_tx", &self.request_tx)
|
||||||
|
.field("execution_state", &self.execution_state)
|
||||||
|
.field("kernel_info", &self.kernel_info)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunningKernel for RemoteRunningKernel {
|
||||||
|
fn request_tx(&self) -> futures::channel::mpsc::Sender<runtimelib::JupyterMessage> {
|
||||||
|
self.request_tx.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(&self) -> &std::path::PathBuf {
|
||||||
|
&self.working_directory
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execution_state(&self) -> &runtimelib::ExecutionState {
|
||||||
|
&self.execution_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_execution_state(&mut self, state: runtimelib::ExecutionState) {
|
||||||
|
self.execution_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kernel_info(&self) -> Option<&runtimelib::KernelInfoReply> {
|
||||||
|
self.kernel_info.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_kernel_info(&mut self, info: runtimelib::KernelInfoReply) {
|
||||||
|
self.kernel_info = Some(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_shutdown(&mut self) -> anyhow::Result<()> {
|
||||||
|
unimplemented!("force_shutdown")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod components;
|
pub mod components;
|
||||||
mod jupyter_settings;
|
mod jupyter_settings;
|
||||||
mod kernels;
|
pub mod kernels;
|
||||||
pub mod notebook;
|
pub mod notebook;
|
||||||
mod outputs;
|
mod outputs;
|
||||||
mod repl_editor;
|
mod repl_editor;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::components::KernelListItem;
|
use crate::components::KernelListItem;
|
||||||
use crate::setup_editor_session_actions;
|
use crate::setup_editor_session_actions;
|
||||||
use crate::{
|
use crate::{
|
||||||
kernels::{Kernel, KernelSpecification, RunningKernel},
|
kernels::{Kernel, KernelSpecification, NativeRunningKernel},
|
||||||
outputs::{ExecutionStatus, ExecutionView},
|
outputs::{ExecutionStatus, ExecutionView},
|
||||||
KernelStatus,
|
KernelStatus,
|
||||||
};
|
};
|
||||||
|
@ -246,13 +246,19 @@ impl Session {
|
||||||
cx.entity_id().to_string(),
|
cx.entity_id().to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let kernel = RunningKernel::new(
|
let kernel = match self.kernel_specification.clone() {
|
||||||
self.kernel_specification.clone(),
|
KernelSpecification::Jupyter(kernel_specification)
|
||||||
entity_id,
|
| KernelSpecification::PythonEnv(kernel_specification) => NativeRunningKernel::new(
|
||||||
working_directory,
|
kernel_specification,
|
||||||
self.fs.clone(),
|
entity_id,
|
||||||
cx,
|
working_directory,
|
||||||
);
|
self.fs.clone(),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
KernelSpecification::Remote(_remote_kernel_specification) => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let pending_kernel = cx
|
let pending_kernel = cx
|
||||||
.spawn(|this, mut cx| async move {
|
.spawn(|this, mut cx| async move {
|
||||||
|
@ -291,7 +297,7 @@ impl Session {
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let status = kernel.process.status();
|
let status = kernel.process.status();
|
||||||
session.kernel(Kernel::RunningKernel(kernel), cx);
|
session.kernel(Kernel::RunningKernel(Box::new(kernel)), cx);
|
||||||
|
|
||||||
let process_status_task = cx.spawn(|session, mut cx| async move {
|
let process_status_task = cx.spawn(|session, mut cx| async move {
|
||||||
let error_message = match status.await {
|
let error_message = match status.await {
|
||||||
|
@ -416,7 +422,7 @@ impl Session {
|
||||||
|
|
||||||
fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
|
fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
|
||||||
if let Kernel::RunningKernel(kernel) = &mut self.kernel {
|
if let Kernel::RunningKernel(kernel) = &mut self.kernel {
|
||||||
kernel.request_tx.try_send(message).ok();
|
kernel.request_tx().try_send(message).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -631,7 +637,7 @@ impl Session {
|
||||||
|
|
||||||
match kernel {
|
match kernel {
|
||||||
Kernel::RunningKernel(mut kernel) => {
|
Kernel::RunningKernel(mut kernel) => {
|
||||||
let mut request_tx = kernel.request_tx.clone();
|
let mut request_tx = kernel.request_tx().clone();
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let message: JupyterMessage = ShutdownRequest { restart: false }.into();
|
let message: JupyterMessage = ShutdownRequest { restart: false }.into();
|
||||||
|
@ -646,7 +652,7 @@ impl Session {
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
kernel.process.kill().ok();
|
kernel.force_shutdown().ok();
|
||||||
|
|
||||||
this.update(&mut cx, |session, cx| {
|
this.update(&mut cx, |session, cx| {
|
||||||
session.clear_outputs(cx);
|
session.clear_outputs(cx);
|
||||||
|
@ -674,7 +680,7 @@ impl Session {
|
||||||
// Do nothing if already restarting
|
// Do nothing if already restarting
|
||||||
}
|
}
|
||||||
Kernel::RunningKernel(mut kernel) => {
|
Kernel::RunningKernel(mut kernel) => {
|
||||||
let mut request_tx = kernel.request_tx.clone();
|
let mut request_tx = kernel.request_tx().clone();
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
// Send shutdown request with restart flag
|
// Send shutdown request with restart flag
|
||||||
|
@ -692,7 +698,7 @@ impl Session {
|
||||||
cx.background_executor().timer(Duration::from_secs(1)).await;
|
cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||||
|
|
||||||
// Force kill the kernel if it hasn't shut down
|
// Force kill the kernel if it hasn't shut down
|
||||||
kernel.process.kill().ok();
|
kernel.force_shutdown().ok();
|
||||||
|
|
||||||
// Start a new kernel
|
// Start a new kernel
|
||||||
this.update(&mut cx, |session, cx| {
|
this.update(&mut cx, |session, cx| {
|
||||||
|
@ -727,7 +733,7 @@ impl Render for Session {
|
||||||
let (status_text, interrupt_button) = match &self.kernel {
|
let (status_text, interrupt_button) = match &self.kernel {
|
||||||
Kernel::RunningKernel(kernel) => (
|
Kernel::RunningKernel(kernel) => (
|
||||||
kernel
|
kernel
|
||||||
.kernel_info
|
.kernel_info()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|info| info.language_info.name.clone()),
|
.map(|info| info.language_info.name.clone()),
|
||||||
Some(
|
Some(
|
||||||
|
@ -747,7 +753,7 @@ impl Render for Session {
|
||||||
|
|
||||||
KernelListItem::new(self.kernel_specification.clone())
|
KernelListItem::new(self.kernel_specification.clone())
|
||||||
.status_color(match &self.kernel {
|
.status_color(match &self.kernel {
|
||||||
Kernel::RunningKernel(kernel) => match kernel.execution_state {
|
Kernel::RunningKernel(kernel) => match kernel.execution_state() {
|
||||||
ExecutionState::Idle => Color::Success,
|
ExecutionState::Idle => Color::Success,
|
||||||
ExecutionState::Busy => Color::Modified,
|
ExecutionState::Busy => Color::Modified,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue