repl: Factor out ReplStore
(#14970)
This PR factors a `ReplStore` out of the `RuntimePanel`. Since we're planning to remove the `RuntimePanel` and replace it with an ephemeral tab that can be opened, we need the kernel specifications and sessions to have somewhere long-lived that they can reside in. Release Notes: - N/A
This commit is contained in:
parent
2e23527e09
commit
28baa56e3d
6 changed files with 264 additions and 185 deletions
|
@ -29,7 +29,7 @@ pub struct KernelSpecification {
|
||||||
|
|
||||||
impl KernelSpecification {
|
impl KernelSpecification {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn command(&self, connection_path: &PathBuf) -> anyhow::Result<Command> {
|
fn command(&self, connection_path: &PathBuf) -> Result<Command> {
|
||||||
let argv = &self.kernelspec.argv;
|
let argv = &self.kernelspec.argv;
|
||||||
|
|
||||||
anyhow::ensure!(!argv.is_empty(), "Empty argv in kernelspec {}", self.name);
|
anyhow::ensure!(!argv.is_empty(), "Empty argv in kernelspec {}", self.name);
|
||||||
|
@ -60,7 +60,7 @@ impl KernelSpecification {
|
||||||
|
|
||||||
// Find a set of open ports. This creates a listener with port set to 0. The listener will be closed at the end when it goes out of scope.
|
// Find a set of open ports. This creates a listener with port set to 0. The listener will be closed at the end when it goes out of scope.
|
||||||
// There's a race condition between closing the ports and usage by a kernel, but it's inherent to the Jupyter protocol.
|
// There's a race condition between closing the ports and usage by a kernel, but it's inherent to the Jupyter protocol.
|
||||||
async fn peek_ports(ip: IpAddr) -> anyhow::Result<[u16; 5]> {
|
async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
|
||||||
let mut addr_zeroport: SocketAddr = SocketAddr::new(ip, 0);
|
let mut addr_zeroport: SocketAddr = SocketAddr::new(ip, 0);
|
||||||
addr_zeroport.set_port(0);
|
addr_zeroport.set_port(0);
|
||||||
let mut ports: [u16; 5] = [0; 5];
|
let mut ports: [u16; 5] = [0; 5];
|
||||||
|
@ -166,10 +166,10 @@ impl Kernel {
|
||||||
|
|
||||||
pub struct RunningKernel {
|
pub struct RunningKernel {
|
||||||
pub process: smol::process::Child,
|
pub process: smol::process::Child,
|
||||||
_shell_task: Task<anyhow::Result<()>>,
|
_shell_task: Task<Result<()>>,
|
||||||
_iopub_task: Task<anyhow::Result<()>>,
|
_iopub_task: Task<Result<()>>,
|
||||||
_control_task: Task<anyhow::Result<()>>,
|
_control_task: Task<Result<()>>,
|
||||||
_routing_task: Task<anyhow::Result<()>>,
|
_routing_task: Task<Result<()>>,
|
||||||
connection_path: PathBuf,
|
connection_path: PathBuf,
|
||||||
pub working_directory: PathBuf,
|
pub working_directory: PathBuf,
|
||||||
pub request_tx: mpsc::Sender<JupyterMessage>,
|
pub request_tx: mpsc::Sender<JupyterMessage>,
|
||||||
|
@ -194,7 +194,7 @@ impl RunningKernel {
|
||||||
working_directory: PathBuf,
|
working_directory: PathBuf,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<anyhow::Result<(Self, JupyterMessageChannel)>> {
|
) -> Task<Result<(Self, JupyterMessageChannel)>> {
|
||||||
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?;
|
||||||
|
@ -332,7 +332,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,
|
||||||
) -> anyhow::Result<KernelSpecification> {
|
) -> Result<KernelSpecification> {
|
||||||
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()
|
||||||
|
@ -356,7 +356,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) -> anyhow::Result<Vec<KernelSpecification>> {
|
async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<KernelSpecification>> {
|
||||||
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();
|
||||||
|
@ -376,7 +376,7 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> anyhow::Result<Vec<Kern
|
||||||
Ok(valid_kernelspecs)
|
Ok(valid_kernelspecs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn kernel_specifications(fs: Arc<dyn Fs>) -> anyhow::Result<Vec<KernelSpecification>> {
|
pub async fn kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<KernelSpecification>> {
|
||||||
let data_dirs = dirs::data_dirs();
|
let data_dirs = dirs::data_dirs();
|
||||||
let kernel_dirs = data_dirs
|
let kernel_dirs = data_dirs
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
|
use async_dispatcher::{set_dispatcher, Dispatcher, Runnable};
|
||||||
use gpui::{AppContext, PlatformDispatcher};
|
use gpui::{AppContext, PlatformDispatcher};
|
||||||
|
use project::Fs;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
mod jupyter_settings;
|
mod jupyter_settings;
|
||||||
mod kernels;
|
mod kernels;
|
||||||
mod outputs;
|
mod outputs;
|
||||||
|
mod repl_store;
|
||||||
mod runtime_panel;
|
mod runtime_panel;
|
||||||
mod session;
|
mod session;
|
||||||
mod stdio;
|
mod stdio;
|
||||||
|
@ -17,6 +19,8 @@ pub use runtime_panel::{RuntimePanel, SessionSupport};
|
||||||
pub use runtimelib::ExecutionState;
|
pub use runtimelib::ExecutionState;
|
||||||
pub use session::Session;
|
pub use session::Session;
|
||||||
|
|
||||||
|
use crate::repl_store::ReplStore;
|
||||||
|
|
||||||
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
||||||
struct ZedDispatcher {
|
struct ZedDispatcher {
|
||||||
dispatcher: Arc<dyn PlatformDispatcher>,
|
dispatcher: Arc<dyn PlatformDispatcher>,
|
||||||
|
@ -41,8 +45,10 @@ fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||||
set_dispatcher(zed_dispatcher(cx));
|
set_dispatcher(zed_dispatcher(cx));
|
||||||
JupyterSettings::register(cx);
|
JupyterSettings::register(cx);
|
||||||
runtime_panel::init(cx)
|
editor::init_settings(cx);
|
||||||
|
runtime_panel::init(cx);
|
||||||
|
ReplStore::init(fs, cx);
|
||||||
}
|
}
|
||||||
|
|
118
crates/repl/src/repl_store.rs
Normal file
118
crates/repl/src/repl_store.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{
|
||||||
|
prelude::*, AppContext, EntityId, Global, Model, ModelContext, Subscription, Task, View,
|
||||||
|
};
|
||||||
|
use language::Language;
|
||||||
|
use project::Fs;
|
||||||
|
use settings::{Settings, SettingsStore};
|
||||||
|
|
||||||
|
use crate::kernels::kernel_specifications;
|
||||||
|
use crate::{JupyterSettings, KernelSpecification, Session};
|
||||||
|
|
||||||
|
struct GlobalReplStore(Model<ReplStore>);
|
||||||
|
|
||||||
|
impl Global for GlobalReplStore {}
|
||||||
|
|
||||||
|
pub struct ReplStore {
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
enabled: bool,
|
||||||
|
sessions: HashMap<EntityId, View<Session>>,
|
||||||
|
kernel_specifications: Vec<KernelSpecification>,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReplStore {
|
||||||
|
pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||||
|
let store = cx.new_model(move |cx| Self::new(fs, cx));
|
||||||
|
|
||||||
|
cx.set_global(GlobalReplStore(store))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||||
|
cx.global::<GlobalReplStore>().0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
|
let subscriptions = vec![cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||||
|
this.set_enabled(JupyterSettings::enabled(cx), cx);
|
||||||
|
})];
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fs,
|
||||||
|
enabled: JupyterSettings::enabled(cx),
|
||||||
|
sessions: HashMap::default(),
|
||||||
|
kernel_specifications: Vec::new(),
|
||||||
|
_subscriptions: subscriptions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_enabled(&self) -> bool {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kernel_specifications(&self) -> impl Iterator<Item = &KernelSpecification> {
|
||||||
|
self.kernel_specifications.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sessions(&self) -> impl Iterator<Item = &View<Session>> {
|
||||||
|
self.sessions.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_enabled(&mut self, enabled: bool, cx: &mut ModelContext<Self>) {
|
||||||
|
if self.enabled != enabled {
|
||||||
|
self.enabled = enabled;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
|
let kernel_specifications = kernel_specifications(self.fs.clone());
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let kernel_specifications = kernel_specifications.await?;
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.kernel_specifications = kernel_specifications;
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kernelspec(
|
||||||
|
&self,
|
||||||
|
language: &Language,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<KernelSpecification> {
|
||||||
|
let settings = JupyterSettings::get_global(cx);
|
||||||
|
let language_name = language.code_fence_block_name();
|
||||||
|
let selected_kernel = settings.kernel_selections.get(language_name.as_ref());
|
||||||
|
|
||||||
|
self.kernel_specifications
|
||||||
|
.iter()
|
||||||
|
.find(|runtime_specification| {
|
||||||
|
if let Some(selected) = selected_kernel {
|
||||||
|
// Top priority is the selected kernel
|
||||||
|
runtime_specification.name.to_lowercase() == selected.to_lowercase()
|
||||||
|
} else {
|
||||||
|
// Otherwise, we'll try to find a kernel that matches the language
|
||||||
|
runtime_specification.kernelspec.language.to_lowercase()
|
||||||
|
== language_name.to_lowercase()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_session(&self, entity_id: EntityId) -> Option<&View<Session>> {
|
||||||
|
self.sessions.get(&entity_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_session(&mut self, entity_id: EntityId, session: View<Session>) {
|
||||||
|
self.sessions.insert(entity_id, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_session(&mut self, entity_id: EntityId) {
|
||||||
|
self.sessions.remove(&entity_id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
|
use crate::repl_store::ReplStore;
|
||||||
use crate::{
|
use crate::{
|
||||||
jupyter_settings::{JupyterDockPosition, JupyterSettings},
|
jupyter_settings::{JupyterDockPosition, JupyterSettings},
|
||||||
kernels::{kernel_specifications, KernelSpecification},
|
kernels::KernelSpecification,
|
||||||
session::{Session, SessionEvent},
|
session::{Session, SessionEvent},
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use collections::HashMap;
|
|
||||||
use editor::{Anchor, Editor, RangeToAnchorExt};
|
use editor::{Anchor, Editor, RangeToAnchorExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, prelude::*, AppContext, AsyncWindowContext, EntityId, EventEmitter, FocusHandle,
|
actions, prelude::*, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, FocusOutEvent,
|
||||||
FocusOutEvent, FocusableView, Subscription, Task, View, WeakView,
|
FocusableView, Subscription, Task, View, WeakView,
|
||||||
};
|
};
|
||||||
use language::{Language, Point};
|
use language::{Language, Point};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use settings::{Settings as _, SettingsStore};
|
use settings::Settings as _;
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
|
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
@ -28,6 +28,13 @@ actions!(
|
||||||
);
|
);
|
||||||
actions!(repl_panel, [ToggleFocus]);
|
actions!(repl_panel, [ToggleFocus]);
|
||||||
|
|
||||||
|
pub enum SessionSupport {
|
||||||
|
ActiveSession(View<Session>),
|
||||||
|
Inactive(Box<KernelSpecification>),
|
||||||
|
RequiresSetup(Arc<str>),
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(
|
cx.observe_new_views(
|
||||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||||
|
@ -35,12 +42,11 @@ pub fn init(cx: &mut AppContext) {
|
||||||
workspace.toggle_panel_focus::<RuntimePanel>(cx);
|
workspace.toggle_panel_focus::<RuntimePanel>(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace.register_action(|workspace, _: &RefreshKernelspecs, cx| {
|
workspace.register_action(|_workspace, _: &RefreshKernelspecs, cx| {
|
||||||
if let Some(panel) = workspace.panel::<RuntimePanel>(cx) {
|
let store = ReplStore::global(cx);
|
||||||
panel.update(cx, |panel, cx| {
|
store.update(cx, |store, cx| {
|
||||||
panel.refresh_kernelspecs(cx).detach();
|
store.refresh_kernelspecs(cx).detach();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -145,11 +151,8 @@ pub fn init(cx: &mut AppContext) {
|
||||||
|
|
||||||
pub struct RuntimePanel {
|
pub struct RuntimePanel {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
enabled: bool,
|
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
sessions: HashMap<EntityId, View<Session>>,
|
|
||||||
kernel_specifications: Vec<KernelSpecification>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,39 +171,29 @@ impl RuntimePanel {
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.on_focus_in(&focus_handle, Self::focus_in),
|
cx.on_focus_in(&focus_handle, Self::focus_in),
|
||||||
cx.on_focus_out(&focus_handle, Self::focus_out),
|
cx.on_focus_out(&focus_handle, Self::focus_out),
|
||||||
cx.observe_global::<SettingsStore>(move |this, cx| {
|
|
||||||
this.set_enabled(JupyterSettings::enabled(cx), cx);
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let runtime_panel = Self {
|
let runtime_panel = Self {
|
||||||
fs: fs.clone(),
|
fs,
|
||||||
width: None,
|
width: None,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
kernel_specifications: Vec::new(),
|
|
||||||
sessions: Default::default(),
|
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
enabled: JupyterSettings::enabled(cx),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
runtime_panel
|
runtime_panel
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
view.update(&mut cx, |this, cx| this.refresh_kernelspecs(cx))?
|
view.update(&mut cx, |_panel, cx| {
|
||||||
|
let store = ReplStore::global(cx);
|
||||||
|
store.update(cx, |store, cx| store.refresh_kernelspecs(cx))
|
||||||
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(view)
|
Ok(view)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_enabled(&mut self, enabled: bool, cx: &mut ViewContext<Self>) {
|
|
||||||
if self.enabled != enabled {
|
|
||||||
self.enabled = enabled;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
|
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -209,8 +202,7 @@ impl RuntimePanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snippet(
|
fn snippet(
|
||||||
&self,
|
|
||||||
editor: WeakView<Editor>,
|
editor: WeakView<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
|
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
|
||||||
|
@ -255,94 +247,60 @@ impl RuntimePanel {
|
||||||
Some((selected_text, start_language.clone(), anchor_range))
|
Some((selected_text, start_language.clone(), anchor_range))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language(
|
fn language(editor: WeakView<Editor>, cx: &mut ViewContext<Self>) -> Option<Arc<Language>> {
|
||||||
&self,
|
|
||||||
editor: WeakView<Editor>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Option<Arc<Language>> {
|
|
||||||
let editor = editor.upgrade()?;
|
let editor = editor.upgrade()?;
|
||||||
let selection = editor.read(cx).selections.newest::<usize>(cx);
|
let selection = editor.read(cx).selections.newest::<usize>(cx);
|
||||||
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
|
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||||
buffer.language_at(selection.head()).cloned()
|
buffer.language_at(selection.head()).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
|
pub fn run(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) -> Result<()> {
|
||||||
let kernel_specifications = kernel_specifications(self.fs.clone());
|
let store = ReplStore::global(cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let kernel_specifications = kernel_specifications.await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
if !store.read(cx).is_enabled() {
|
||||||
this.kernel_specifications = kernel_specifications;
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kernelspec(
|
|
||||||
&self,
|
|
||||||
language: &Language,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Option<KernelSpecification> {
|
|
||||||
let settings = JupyterSettings::get_global(cx);
|
|
||||||
let language_name = language.code_fence_block_name();
|
|
||||||
let selected_kernel = settings.kernel_selections.get(language_name.as_ref());
|
|
||||||
|
|
||||||
self.kernel_specifications
|
|
||||||
.iter()
|
|
||||||
.find(|runtime_specification| {
|
|
||||||
if let Some(selected) = selected_kernel {
|
|
||||||
// Top priority is the selected kernel
|
|
||||||
runtime_specification.name.to_lowercase() == selected.to_lowercase()
|
|
||||||
} else {
|
|
||||||
// Otherwise, we'll try to find a kernel that matches the language
|
|
||||||
runtime_specification.kernelspec.language.to_lowercase()
|
|
||||||
== language_name.to_lowercase()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(
|
|
||||||
&mut self,
|
|
||||||
editor: WeakView<Editor>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if !self.enabled {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (selected_text, language, anchor_range) = match self.snippet(editor.clone(), cx) {
|
let (selected_text, language, anchor_range) = match Self::snippet(editor.clone(), cx) {
|
||||||
Some(snippet) => snippet,
|
Some(snippet) => snippet,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let entity_id = editor.entity_id();
|
let entity_id = editor.entity_id();
|
||||||
|
|
||||||
let kernel_specification = self
|
let kernel_specification = store.update(cx, |store, cx| {
|
||||||
|
store
|
||||||
.kernelspec(&language, cx)
|
.kernelspec(&language, cx)
|
||||||
.with_context(|| format!("No kernel found for language: {}", language.name()))?;
|
.with_context(|| format!("No kernel found for language: {}", language.name()))
|
||||||
|
})?;
|
||||||
|
|
||||||
let session = self.sessions.entry(entity_id).or_insert_with(|| {
|
let session = if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||||
let view =
|
session
|
||||||
|
} else {
|
||||||
|
let session =
|
||||||
cx.new_view(|cx| Session::new(editor, self.fs.clone(), kernel_specification, cx));
|
cx.new_view(|cx| Session::new(editor, self.fs.clone(), kernel_specification, cx));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
let subscription = cx.subscribe(
|
let subscription = cx.subscribe(&session, {
|
||||||
&view,
|
let store = store.clone();
|
||||||
|panel: &mut RuntimePanel, _session: View<Session>, event: &SessionEvent, _cx| {
|
move |_this, _session, event, cx| match event {
|
||||||
match event {
|
|
||||||
SessionEvent::Shutdown(shutdown_event) => {
|
SessionEvent::Shutdown(shutdown_event) => {
|
||||||
panel.sessions.remove(&shutdown_event.entity_id());
|
store.update(cx, |store, _cx| {
|
||||||
|
store.remove_session(shutdown_event.entity_id());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
subscription.detach();
|
subscription.detach();
|
||||||
|
|
||||||
view
|
store.update(cx, |store, _cx| {
|
||||||
|
store.insert_session(entity_id, session.clone());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
session
|
||||||
|
};
|
||||||
|
|
||||||
session.update(cx, |session, cx| {
|
session.update(cx, |session, cx| {
|
||||||
session.execute(&selected_text, anchor_range, cx);
|
session.execute(&selected_text, anchor_range, cx);
|
||||||
});
|
});
|
||||||
|
@ -350,9 +308,38 @@ impl RuntimePanel {
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_outputs(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
|
pub fn session(
|
||||||
|
&mut self,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> SessionSupport {
|
||||||
|
let store = ReplStore::global(cx);
|
||||||
let entity_id = editor.entity_id();
|
let entity_id = editor.entity_id();
|
||||||
if let Some(session) = self.sessions.get_mut(&entity_id) {
|
|
||||||
|
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||||
|
return SessionSupport::ActiveSession(session);
|
||||||
|
};
|
||||||
|
|
||||||
|
let language = Self::language(editor, cx);
|
||||||
|
let language = match language {
|
||||||
|
Some(language) => language,
|
||||||
|
None => return SessionSupport::Unsupported,
|
||||||
|
};
|
||||||
|
let kernelspec = store.update(cx, |store, cx| store.kernelspec(&language, cx));
|
||||||
|
|
||||||
|
match kernelspec {
|
||||||
|
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
|
||||||
|
None => match language.name().as_ref() {
|
||||||
|
"TypeScript" | "Python" => SessionSupport::RequiresSetup(language.name()),
|
||||||
|
_ => SessionSupport::Unsupported,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_outputs(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
|
let store = ReplStore::global(cx);
|
||||||
|
let entity_id = editor.entity_id();
|
||||||
|
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||||
session.update(cx, |session, cx| {
|
session.update(cx, |session, cx| {
|
||||||
session.clear_outputs(cx);
|
session.clear_outputs(cx);
|
||||||
});
|
});
|
||||||
|
@ -361,8 +348,9 @@ impl RuntimePanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interrupt(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
|
pub fn interrupt(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
|
let store = ReplStore::global(cx);
|
||||||
let entity_id = editor.entity_id();
|
let entity_id = editor.entity_id();
|
||||||
if let Some(session) = self.sessions.get_mut(&entity_id) {
|
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||||
session.update(cx, |session, cx| {
|
session.update(cx, |session, cx| {
|
||||||
session.interrupt(cx);
|
session.interrupt(cx);
|
||||||
});
|
});
|
||||||
|
@ -370,9 +358,10 @@ impl RuntimePanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown(&self, editor: WeakView<Editor>, cx: &mut ViewContext<RuntimePanel>) {
|
pub fn shutdown(&self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
|
||||||
|
let store = ReplStore::global(cx);
|
||||||
let entity_id = editor.entity_id();
|
let entity_id = editor.entity_id();
|
||||||
if let Some(session) = self.sessions.get(&entity_id) {
|
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||||
session.update(cx, |session, cx| {
|
session.update(cx, |session, cx| {
|
||||||
session.shutdown(cx);
|
session.shutdown(cx);
|
||||||
});
|
});
|
||||||
|
@ -381,51 +370,6 @@ impl RuntimePanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SessionSupport {
|
|
||||||
ActiveSession(View<Session>),
|
|
||||||
Inactive(Box<KernelSpecification>),
|
|
||||||
RequiresSetup(Arc<str>),
|
|
||||||
Unsupported,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RuntimePanel {
|
|
||||||
pub fn session(
|
|
||||||
&mut self,
|
|
||||||
editor: WeakView<Editor>,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> SessionSupport {
|
|
||||||
let entity_id = editor.entity_id();
|
|
||||||
let session = self.sessions.get(&entity_id).cloned();
|
|
||||||
|
|
||||||
match session {
|
|
||||||
Some(session) => SessionSupport::ActiveSession(session),
|
|
||||||
None => {
|
|
||||||
let language = self.language(editor, cx);
|
|
||||||
let language = match language {
|
|
||||||
Some(language) => language,
|
|
||||||
None => return SessionSupport::Unsupported,
|
|
||||||
};
|
|
||||||
// Check for kernelspec
|
|
||||||
let kernelspec = self.kernelspec(&language, cx);
|
|
||||||
|
|
||||||
match kernelspec {
|
|
||||||
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
|
|
||||||
None => {
|
|
||||||
// If no kernelspec but language is one of typescript or python
|
|
||||||
// then we return RequiresSetup
|
|
||||||
match language.name().as_ref() {
|
|
||||||
"TypeScript" | "Python" => {
|
|
||||||
SessionSupport::RequiresSetup(language.name())
|
|
||||||
}
|
|
||||||
_ => SessionSupport::Unsupported,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Panel for RuntimePanel {
|
impl Panel for RuntimePanel {
|
||||||
fn persistent_name() -> &'static str {
|
fn persistent_name() -> &'static str {
|
||||||
"RuntimePanel"
|
"RuntimePanel"
|
||||||
|
@ -468,8 +412,10 @@ impl Panel for RuntimePanel {
|
||||||
self.width = size;
|
self.width = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn icon(&self, _cx: &ui::WindowContext) -> Option<ui::IconName> {
|
fn icon(&self, cx: &ui::WindowContext) -> Option<ui::IconName> {
|
||||||
if !self.enabled {
|
let store = ReplStore::global(cx);
|
||||||
|
|
||||||
|
if !store.read(cx).is_enabled() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,10 +441,19 @@ impl FocusableView for RuntimePanel {
|
||||||
|
|
||||||
impl Render for RuntimePanel {
|
impl Render for RuntimePanel {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let store = ReplStore::global(cx);
|
||||||
|
|
||||||
|
let (kernel_specifications, sessions) = store.update(cx, |store, _cx| {
|
||||||
|
(
|
||||||
|
store.kernel_specifications().cloned().collect::<Vec<_>>(),
|
||||||
|
store.sessions().cloned().collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// When there are no kernel specifications, show a link to the Zed docs explaining how to
|
// When there are no kernel specifications, show a link to the Zed docs explaining how to
|
||||||
// install kernels. It can be assumed they don't have a running kernel if we have no
|
// install kernels. It can be assumed they don't have a running kernel if we have no
|
||||||
// specifications.
|
// specifications.
|
||||||
if self.kernel_specifications.is_empty() {
|
if kernel_specifications.is_empty() {
|
||||||
return v_flex()
|
return v_flex()
|
||||||
.p_4()
|
.p_4()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
@ -526,7 +481,7 @@ impl Render for RuntimePanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// When there are no sessions, show the command to run code in an editor
|
// When there are no sessions, show the command to run code in an editor
|
||||||
if self.sessions.is_empty() {
|
if sessions.is_empty() {
|
||||||
return v_flex()
|
return v_flex()
|
||||||
.p_4()
|
.p_4()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
@ -546,7 +501,7 @@ impl Render for RuntimePanel {
|
||||||
)
|
)
|
||||||
.child(Label::new("Kernels available").size(LabelSize::Large))
|
.child(Label::new("Kernels available").size(LabelSize::Large))
|
||||||
.children(
|
.children(
|
||||||
self.kernel_specifications.iter().map(|spec| {
|
kernel_specifications.into_iter().map(|spec| {
|
||||||
h_flex().gap_2().child(Label::new(spec.name.clone()))
|
h_flex().gap_2().child(Label::new(spec.name.clone()))
|
||||||
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
|
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
|
||||||
})
|
})
|
||||||
|
@ -559,8 +514,8 @@ impl Render for RuntimePanel {
|
||||||
.p_4()
|
.p_4()
|
||||||
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
|
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
|
||||||
.children(
|
.children(
|
||||||
self.sessions
|
sessions
|
||||||
.values()
|
.into_iter()
|
||||||
.map(|session| session.clone().into_any_element()),
|
.map(|session| session.clone().into_any_element()),
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
|
|
@ -167,7 +167,7 @@ fn init_common(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
supermaven::init(app_state.client.clone(), cx);
|
supermaven::init(app_state.client.clone(), cx);
|
||||||
inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
|
inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
|
||||||
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
|
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
|
||||||
repl::init(cx);
|
repl::init(app_state.fs.clone(), cx);
|
||||||
extension::init(
|
extension::init(
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
|
|
|
@ -3417,7 +3417,7 @@ mod tests {
|
||||||
outline_panel::init((), cx);
|
outline_panel::init((), cx);
|
||||||
terminal_view::init(cx);
|
terminal_view::init(cx);
|
||||||
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
|
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
|
||||||
repl::init(cx);
|
repl::init(app_state.fs.clone(), cx);
|
||||||
tasks_ui::init(cx);
|
tasks_ui::init(cx);
|
||||||
initialize_workspace(app_state.clone(), cx);
|
initialize_workspace(app_state.clone(), cx);
|
||||||
app_state
|
app_state
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue