repl: Replace REPL panel with sessions view (#14981)
This PR removes the REPL panel and replaces it with a new sessions view that gets displayed in its own pane. The sessions view can be opened with the `repl: sessions` command (we can adjust the name, as needed). There was a rather in-depth refactoring needed to extricate the various REPL functionality on the editor from the `RuntimePanel`. <img width="1136" alt="Screenshot 2024-07-22 at 4 12 12 PM" src="https://github.com/user-attachments/assets/ac0da351-778e-4200-b08c-39f9e77d78bf"> <img width="1136" alt="Screenshot 2024-07-22 at 4 12 17 PM" src="https://github.com/user-attachments/assets/6ca53476-6ac4-4f8b-afc8-f7863f7065c7"> Release Notes: - N/A
This commit is contained in:
parent
8f20ea1093
commit
d8a42bbf63
9 changed files with 474 additions and 637 deletions
|
@ -2,8 +2,8 @@ use std::time::Duration;
|
|||
|
||||
use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View};
|
||||
use repl::{
|
||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, RuntimePanel,
|
||||
Session, SessionSupport,
|
||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, Session,
|
||||
SessionSupport,
|
||||
};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
|
||||
|
@ -39,15 +39,7 @@ impl QuickActionBar {
|
|||
return None;
|
||||
}
|
||||
|
||||
let workspace = self.workspace.upgrade()?.read(cx);
|
||||
|
||||
let (editor, repl_panel) = if let (Some(editor), Some(repl_panel)) =
|
||||
(self.active_editor(), workspace.panel::<RuntimePanel>(cx))
|
||||
{
|
||||
(editor, repl_panel)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let editor = self.active_editor()?;
|
||||
|
||||
let has_nonempty_selection = {
|
||||
editor.update(cx, |this, cx| {
|
||||
|
@ -62,10 +54,7 @@ impl QuickActionBar {
|
|||
})
|
||||
};
|
||||
|
||||
let session = repl_panel.update(cx, |repl_panel, cx| {
|
||||
repl_panel.session(editor.downgrade(), cx)
|
||||
});
|
||||
|
||||
let session = repl::session(editor.downgrade(), cx);
|
||||
let session = match session {
|
||||
SessionSupport::ActiveSession(session) => session,
|
||||
SessionSupport::Inactive(spec) => {
|
||||
|
@ -84,18 +73,15 @@ impl QuickActionBar {
|
|||
|
||||
let element_id = |suffix| ElementId::Name(format!("{}-{}", id, suffix).into());
|
||||
|
||||
let panel_clone = repl_panel.clone();
|
||||
let editor_clone = editor.downgrade();
|
||||
let editor = editor.downgrade();
|
||||
let dropdown_menu = PopoverMenu::new(element_id("menu"))
|
||||
.menu(move |cx| {
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
let editor = editor.clone();
|
||||
let session = session.clone();
|
||||
ContextMenu::build(cx, move |menu, cx| {
|
||||
let menu_state = session_state(session, cx);
|
||||
let status = menu_state.status;
|
||||
let editor_clone = editor_clone.clone();
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor = editor.clone();
|
||||
|
||||
menu.when_else(
|
||||
status.is_connected(),
|
||||
|
@ -139,7 +125,6 @@ impl QuickActionBar {
|
|||
},
|
||||
)
|
||||
.separator()
|
||||
// Run
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new(if has_nonempty_selection {
|
||||
|
@ -150,17 +135,12 @@ impl QuickActionBar {
|
|||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
let editor = editor.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.run(editor_clone.clone(), cx).log_err();
|
||||
});
|
||||
repl::run(editor.clone(), cx).log_err();
|
||||
}
|
||||
},
|
||||
)
|
||||
// Interrupt
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new("Interrupt")
|
||||
|
@ -169,17 +149,12 @@ impl QuickActionBar {
|
|||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
let editor = editor.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.interrupt(editor_clone, cx);
|
||||
});
|
||||
repl::interrupt(editor.clone(), cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
// Clear Outputs
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new("Clear Outputs")
|
||||
|
@ -188,13 +163,9 @@ impl QuickActionBar {
|
|||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
let editor = editor.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.clear_outputs(editor_clone, cx);
|
||||
});
|
||||
repl::clear_outputs(editor.clone(), cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -207,7 +178,6 @@ impl QuickActionBar {
|
|||
)
|
||||
// TODO: Add Restart action
|
||||
// .action("Restart", Box::new(gpui::NoAction))
|
||||
// Shut down kernel
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new("Shut Down Kernel")
|
||||
|
@ -216,13 +186,9 @@ impl QuickActionBar {
|
|||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
let editor = editor.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.shutdown(editor_clone, cx);
|
||||
});
|
||||
repl::shutdown(editor.clone(), cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
@ -5,21 +5,9 @@ use gpui::AppContext;
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use ui::Pixels;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum JupyterDockPosition {
|
||||
Left,
|
||||
#[default]
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct JupyterSettings {
|
||||
pub dock: JupyterDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub kernel_selections: HashMap<String, String>,
|
||||
}
|
||||
|
||||
|
@ -34,31 +22,15 @@ impl JupyterSettings {
|
|||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct JupyterSettingsContent {
|
||||
/// Where to dock the Jupyter panel.
|
||||
///
|
||||
/// Default: `right`
|
||||
dock: Option<JupyterDockPosition>,
|
||||
/// Default width in pixels when the jupyter panel is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
pub default_width: Option<f32>,
|
||||
/// Default kernels to select for each language.
|
||||
///
|
||||
/// Default: `{}`
|
||||
pub kernel_selections: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl JupyterSettingsContent {
|
||||
pub fn set_dock(&mut self, dock: JupyterDockPosition) {
|
||||
self.dock = Some(dock);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JupyterSettingsContent {
|
||||
fn default() -> Self {
|
||||
JupyterSettingsContent {
|
||||
dock: Some(JupyterDockPosition::Right),
|
||||
default_width: Some(640.0),
|
||||
kernel_selections: Some(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
@ -79,14 +51,6 @@ impl Settings for JupyterSettings {
|
|||
let mut settings = JupyterSettings::default();
|
||||
|
||||
for value in sources.defaults_and_customizations() {
|
||||
if let Some(dock) = value.dock {
|
||||
settings.dock = dock;
|
||||
}
|
||||
|
||||
if let Some(default_width) = value.default_width {
|
||||
settings.default_width = Pixels::from(default_width);
|
||||
}
|
||||
|
||||
if let Some(source) = &value.kernel_selections {
|
||||
for (k, v) in source {
|
||||
settings.kernel_selections.insert(k.clone(), v.clone());
|
||||
|
@ -114,14 +78,6 @@ mod tests {
|
|||
JupyterSettings::register(cx);
|
||||
|
||||
assert_eq!(JupyterSettings::enabled(cx), false);
|
||||
assert_eq!(
|
||||
JupyterSettings::get_global(cx).dock,
|
||||
JupyterDockPosition::Right
|
||||
);
|
||||
assert_eq!(
|
||||
JupyterSettings::get_global(cx).default_width,
|
||||
Pixels::from(640.0)
|
||||
);
|
||||
|
||||
// Setting a custom setting through user settings
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
|
@ -140,13 +96,5 @@ mod tests {
|
|||
});
|
||||
|
||||
assert_eq!(JupyterSettings::enabled(cx), true);
|
||||
assert_eq!(
|
||||
JupyterSettings::get_global(cx).dock,
|
||||
JupyterDockPosition::Left
|
||||
);
|
||||
assert_eq!(
|
||||
JupyterSettings::get_global(cx).default_width,
|
||||
Pixels::from(800.0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,16 @@ use std::{sync::Arc, time::Duration};
|
|||
mod jupyter_settings;
|
||||
mod kernels;
|
||||
mod outputs;
|
||||
mod repl_editor;
|
||||
mod repl_sessions_ui;
|
||||
mod repl_store;
|
||||
mod runtime_panel;
|
||||
mod session;
|
||||
mod stdio;
|
||||
|
||||
pub use jupyter_settings::JupyterSettings;
|
||||
pub use kernels::{Kernel, KernelSpecification, KernelStatus};
|
||||
pub use runtime_panel::{ClearOutputs, Interrupt, Run, Shutdown};
|
||||
pub use runtime_panel::{RuntimePanel, SessionSupport};
|
||||
pub use repl_editor::*;
|
||||
pub use repl_sessions_ui::{ClearOutputs, Interrupt, ReplSessionsPage, Run, Shutdown};
|
||||
pub use runtimelib::ExecutionState;
|
||||
pub use session::Session;
|
||||
|
||||
|
@ -48,7 +49,7 @@ fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
|
|||
pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||
set_dispatcher(zed_dispatcher(cx));
|
||||
JupyterSettings::register(cx);
|
||||
editor::init_settings(cx);
|
||||
runtime_panel::init(cx);
|
||||
::editor::init_settings(cx);
|
||||
repl_sessions_ui::init(cx);
|
||||
ReplStore::init(fs, cx);
|
||||
}
|
||||
|
|
185
crates/repl/src/repl_editor.rs
Normal file
185
crates/repl/src/repl_editor.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
//! REPL operations on an [`Editor`].
|
||||
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use editor::{Anchor, Editor, RangeToAnchorExt};
|
||||
use gpui::{prelude::*, AppContext, View, WeakView, WindowContext};
|
||||
use language::{Language, Point};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
|
||||
use crate::repl_store::ReplStore;
|
||||
use crate::session::SessionEvent;
|
||||
use crate::{KernelSpecification, Session};
|
||||
|
||||
pub fn run(editor: WeakView<Editor>, cx: &mut WindowContext) -> Result<()> {
|
||||
let store = ReplStore::global(cx);
|
||||
if !store.read(cx).is_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (selected_text, language, anchor_range) = match snippet(editor.clone(), cx) {
|
||||
Some(snippet) => snippet,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let entity_id = editor.entity_id();
|
||||
|
||||
let kernel_specification = store.update(cx, |store, cx| {
|
||||
store
|
||||
.kernelspec(&language, cx)
|
||||
.with_context(|| format!("No kernel found for language: {}", language.name()))
|
||||
})?;
|
||||
|
||||
let fs = store.read(cx).fs().clone();
|
||||
let session = if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
session
|
||||
} else {
|
||||
let session = cx.new_view(|cx| Session::new(editor.clone(), fs, kernel_specification, cx));
|
||||
|
||||
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();
|
||||
})?;
|
||||
|
||||
store.update(cx, |store, _cx| {
|
||||
store.insert_session(entity_id, session.clone());
|
||||
});
|
||||
|
||||
session
|
||||
};
|
||||
|
||||
session.update(cx, |session, cx| {
|
||||
session.execute(&selected_text, anchor_range, cx);
|
||||
});
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(View<Session>),
|
||||
Inactive(Box<KernelSpecification>),
|
||||
RequiresSetup(Arc<str>),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
pub fn session(editor: WeakView<Editor>, cx: &mut AppContext) -> SessionSupport {
|
||||
let store = ReplStore::global(cx);
|
||||
let entity_id = editor.entity_id();
|
||||
|
||||
if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
return SessionSupport::ActiveSession(session);
|
||||
};
|
||||
|
||||
let language = get_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(editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||
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.clear_outputs(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt(editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||
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.interrupt(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(editor: WeakView<Editor>, cx: &mut WindowContext) {
|
||||
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.shutdown(cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn snippet(
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
|
||||
let editor = editor.upgrade()?;
|
||||
let editor = editor.read(cx);
|
||||
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let range = if selection.is_empty() {
|
||||
let cursor = selection.head();
|
||||
|
||||
let cursor_row = multi_buffer_snapshot.offset_to_point(cursor).row;
|
||||
let start_offset = multi_buffer_snapshot.point_to_offset(Point::new(cursor_row, 0));
|
||||
|
||||
let end_point = Point::new(
|
||||
cursor_row,
|
||||
multi_buffer_snapshot.line_len(MultiBufferRow(cursor_row)),
|
||||
);
|
||||
let end_offset = start_offset.saturating_add(end_point.column as usize);
|
||||
|
||||
// Create a range from the start to the end of the line
|
||||
start_offset..end_offset
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
let anchor_range = range.to_anchors(&multi_buffer_snapshot);
|
||||
|
||||
let selected_text = buffer
|
||||
.text_for_range(anchor_range.clone())
|
||||
.collect::<String>();
|
||||
|
||||
let start_language = buffer.language_at(anchor_range.start)?;
|
||||
let end_language = buffer.language_at(anchor_range.end)?;
|
||||
if start_language != end_language {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((selected_text, start_language.clone(), anchor_range))
|
||||
}
|
||||
|
||||
fn get_language(editor: WeakView<Editor>, cx: &mut AppContext) -> Option<Arc<Language>> {
|
||||
let editor = editor.upgrade()?;
|
||||
let selection = editor.read(cx).selections.newest::<usize>(cx);
|
||||
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
buffer.language_at(selection.head()).cloned()
|
||||
}
|
257
crates/repl/src/repl_sessions_ui.rs
Normal file
257
crates/repl/src/repl_sessions_ui.rs
Normal file
|
@ -0,0 +1,257 @@
|
|||
use editor::Editor;
|
||||
use gpui::{
|
||||
actions, prelude::*, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription, View,
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
|
||||
use util::ResultExt as _;
|
||||
use workspace::item::ItemEvent;
|
||||
use workspace::WorkspaceId;
|
||||
use workspace::{item::Item, Workspace};
|
||||
|
||||
use crate::jupyter_settings::JupyterSettings;
|
||||
use crate::repl_store::ReplStore;
|
||||
|
||||
actions!(
|
||||
repl,
|
||||
[
|
||||
Run,
|
||||
ClearOutputs,
|
||||
Sessions,
|
||||
Interrupt,
|
||||
Shutdown,
|
||||
RefreshKernelspecs
|
||||
]
|
||||
);
|
||||
actions!(repl_panel, [ToggleFocus]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(|workspace, _: &Sessions, cx| {
|
||||
let existing = workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ReplSessionsPage>());
|
||||
|
||||
if let Some(existing) = existing {
|
||||
workspace.activate_item(&existing, true, true, cx);
|
||||
} else {
|
||||
let extensions_page = ReplSessionsPage::new(cx);
|
||||
workspace.add_item_to_active_pane(Box::new(extensions_page), None, true, cx)
|
||||
}
|
||||
});
|
||||
|
||||
workspace.register_action(|_workspace, _: &RefreshKernelspecs, cx| {
|
||||
let store = ReplStore::global(cx);
|
||||
store.update(cx, |store, cx| {
|
||||
store.refresh_kernelspecs(cx).detach();
|
||||
});
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
cx.observe_new_views(move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
|
||||
if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() {
|
||||
return;
|
||||
}
|
||||
|
||||
let editor_handle = cx.view().downgrade();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Run, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
crate::run(editor_handle.clone(), cx).log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &ClearOutputs, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
crate::clear_outputs(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Interrupt, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
crate::interrupt(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor_handle = editor_handle.clone();
|
||||
move |_: &Shutdown, cx| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
crate::shutdown(editor_handle.clone(), cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct ReplSessionsPage {
|
||||
focus_handle: FocusHandle,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl ReplSessionsPage {
|
||||
pub fn new(cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.on_focus_in(&focus_handle, |_this, cx| cx.notify()),
|
||||
cx.on_focus_out(&focus_handle, |_this, _event, cx| cx.notify()),
|
||||
];
|
||||
|
||||
Self {
|
||||
focus_handle,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ItemEvent> for ReplSessionsPage {}
|
||||
|
||||
impl FocusableView for ReplSessionsPage {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ReplSessionsPage {
|
||||
type Event = ItemEvent;
|
||||
|
||||
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||
Some("REPL Sessions".into())
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
Some("repl sessions")
|
||||
}
|
||||
|
||||
fn show_toolbar(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_: &mut ViewContext<Self>,
|
||||
) -> Option<View<Self>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
f(*event)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ReplSessionsPage {
|
||||
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
|
||||
// install kernels. It can be assumed they don't have a running kernel if we have no
|
||||
// specifications.
|
||||
if kernel_specifications.is_empty() {
|
||||
return v_flex()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Label::new("No Jupyter Kernels Available").size(LabelSize::Large))
|
||||
.child(
|
||||
Label::new("To start interactively running code in your editor, you need to install and configure Jupyter kernels.")
|
||||
.size(LabelSize::Default),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().p_4().justify_center().gap_2().child(
|
||||
ButtonLike::new("install-kernels")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Install Kernels"))
|
||||
.on_click(move |_, cx| {
|
||||
cx.open_url(
|
||||
"https://docs.jupyter.org/en/latest/install/kernels.html",
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
// When there are no sessions, show the command to run code in an editor
|
||||
if sessions.is_empty() {
|
||||
return v_flex()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Label::new("No Jupyter Kernel Sessions").size(LabelSize::Large))
|
||||
.child(
|
||||
v_flex().child(
|
||||
Label::new("To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.")
|
||||
.size(LabelSize::Default)
|
||||
)
|
||||
.children(
|
||||
KeyBinding::for_action(&Run, cx)
|
||||
.map(|binding|
|
||||
binding.into_any_element()
|
||||
)
|
||||
)
|
||||
)
|
||||
.child(Label::new("Kernels available").size(LabelSize::Large))
|
||||
.children(
|
||||
kernel_specifications.into_iter().map(|spec| {
|
||||
h_flex().gap_2().child(Label::new(spec.name.clone()))
|
||||
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
|
||||
})
|
||||
)
|
||||
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.p_4()
|
||||
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
|
||||
.children(
|
||||
sessions
|
||||
.into_iter()
|
||||
.map(|session| session.clone().into_any_element()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
|
@ -28,6 +28,10 @@ impl ReplStore {
|
|||
pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
|
||||
let store = cx.new_model(move |cx| Self::new(fs, cx));
|
||||
|
||||
store
|
||||
.update(cx, |store, cx| store.refresh_kernelspecs(cx))
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
cx.set_global(GlobalReplStore(store))
|
||||
}
|
||||
|
||||
|
@ -49,6 +53,10 @@ impl ReplStore {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn fs(&self) -> &Arc<dyn Fs> {
|
||||
&self.fs
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
|
|
@ -1,523 +0,0 @@
|
|||
use crate::repl_store::ReplStore;
|
||||
use crate::{
|
||||
jupyter_settings::{JupyterDockPosition, JupyterSettings},
|
||||
kernels::KernelSpecification,
|
||||
session::{Session, SessionEvent},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use editor::{Anchor, Editor, RangeToAnchorExt};
|
||||
use gpui::{
|
||||
actions, prelude::*, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
FocusableView, Subscription, Task, View, WeakView,
|
||||
};
|
||||
use language::{Language, Point};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use project::Fs;
|
||||
use settings::Settings as _;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
dock::{Panel, PanelEvent},
|
||||
Workspace,
|
||||
};
|
||||
|
||||
actions!(
|
||||
repl,
|
||||
[Run, ClearOutputs, Interrupt, Shutdown, RefreshKernelspecs]
|
||||
);
|
||||
actions!(repl_panel, [ToggleFocus]);
|
||||
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(View<Session>),
|
||||
Inactive(Box<KernelSpecification>),
|
||||
RequiresSetup(Arc<str>),
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<RuntimePanel>(cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|_workspace, _: &RefreshKernelspecs, cx| {
|
||||
let store = ReplStore::global(cx);
|
||||
store.update(cx, |store, cx| {
|
||||
store.refresh_kernelspecs(cx).detach();
|
||||
});
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
cx.observe_new_views(move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
|
||||
// Only allow editors that support vim mode and are singleton buffers
|
||||
if !editor.use_modal_editing() || !editor.buffer().read(cx).is_singleton() {
|
||||
return;
|
||||
}
|
||||
|
||||
editor
|
||||
.register_action(cx.listener(
|
||||
move |editor: &mut Editor, _: &Run, cx: &mut ViewContext<Editor>| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
let Some(panel) = workspace.read(cx).panel::<RuntimePanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let weak_editor = cx.view().downgrade();
|
||||
panel.update(cx, |_, cx| {
|
||||
cx.defer(|panel, cx| {
|
||||
panel.run(weak_editor, cx).log_err();
|
||||
});
|
||||
})
|
||||
},
|
||||
))
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action(cx.listener(
|
||||
move |editor: &mut Editor, _: &ClearOutputs, cx: &mut ViewContext<Editor>| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
let Some(panel) = workspace.read(cx).panel::<RuntimePanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let weak_editor = cx.view().downgrade();
|
||||
panel.update(cx, |_, cx| {
|
||||
cx.defer(|panel, cx| {
|
||||
panel.clear_outputs(weak_editor, cx);
|
||||
});
|
||||
})
|
||||
},
|
||||
))
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action(cx.listener(
|
||||
move |editor: &mut Editor, _: &Interrupt, cx: &mut ViewContext<Editor>| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
let Some(panel) = workspace.read(cx).panel::<RuntimePanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let weak_editor = cx.view().downgrade();
|
||||
panel.update(cx, |_, cx| {
|
||||
cx.defer(|panel, cx| {
|
||||
panel.interrupt(weak_editor, cx);
|
||||
});
|
||||
})
|
||||
},
|
||||
))
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action(cx.listener(
|
||||
move |editor: &mut Editor, _: &Shutdown, cx: &mut ViewContext<Editor>| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
let Some(panel) = workspace.read(cx).panel::<RuntimePanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let weak_editor = cx.view().downgrade();
|
||||
panel.update(cx, |_, cx| {
|
||||
cx.defer(|panel, cx| {
|
||||
panel.shutdown(weak_editor, cx);
|
||||
});
|
||||
})
|
||||
},
|
||||
))
|
||||
.detach();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct RuntimePanel {
|
||||
fs: Arc<dyn Fs>,
|
||||
focus_handle: FocusHandle,
|
||||
width: Option<Pixels>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl RuntimePanel {
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let view = workspace.update(&mut cx, |workspace, cx| {
|
||||
cx.new_view::<Self>(|cx| {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
|
||||
let subscriptions = vec![
|
||||
cx.on_focus_in(&focus_handle, Self::focus_in),
|
||||
cx.on_focus_out(&focus_handle, Self::focus_out),
|
||||
];
|
||||
|
||||
let runtime_panel = Self {
|
||||
fs,
|
||||
width: None,
|
||||
focus_handle,
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
|
||||
runtime_panel
|
||||
})
|
||||
})?;
|
||||
|
||||
view.update(&mut cx, |_panel, cx| {
|
||||
let store = ReplStore::global(cx);
|
||||
store.update(cx, |store, cx| store.refresh_kernelspecs(cx))
|
||||
})?
|
||||
.await?;
|
||||
|
||||
Ok(view)
|
||||
})
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn snippet(
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<(String, Arc<Language>, Range<Anchor>)> {
|
||||
let editor = editor.upgrade()?;
|
||||
let editor = editor.read(cx);
|
||||
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let range = if selection.is_empty() {
|
||||
let cursor = selection.head();
|
||||
|
||||
let cursor_row = multi_buffer_snapshot.offset_to_point(cursor).row;
|
||||
let start_offset = multi_buffer_snapshot.point_to_offset(Point::new(cursor_row, 0));
|
||||
|
||||
let end_point = Point::new(
|
||||
cursor_row,
|
||||
multi_buffer_snapshot.line_len(MultiBufferRow(cursor_row)),
|
||||
);
|
||||
let end_offset = start_offset.saturating_add(end_point.column as usize);
|
||||
|
||||
// Create a range from the start to the end of the line
|
||||
start_offset..end_offset
|
||||
} else {
|
||||
selection.range()
|
||||
};
|
||||
|
||||
let anchor_range = range.to_anchors(&multi_buffer_snapshot);
|
||||
|
||||
let selected_text = buffer
|
||||
.text_for_range(anchor_range.clone())
|
||||
.collect::<String>();
|
||||
|
||||
let start_language = buffer.language_at(anchor_range.start)?;
|
||||
let end_language = buffer.language_at(anchor_range.end)?;
|
||||
if start_language != end_language {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((selected_text, start_language.clone(), anchor_range))
|
||||
}
|
||||
|
||||
fn language(editor: WeakView<Editor>, cx: &mut ViewContext<Self>) -> Option<Arc<Language>> {
|
||||
let editor = editor.upgrade()?;
|
||||
let selection = editor.read(cx).selections.newest::<usize>(cx);
|
||||
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
buffer.language_at(selection.head()).cloned()
|
||||
}
|
||||
|
||||
pub fn run(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) -> Result<()> {
|
||||
let store = ReplStore::global(cx);
|
||||
|
||||
if !store.read(cx).is_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (selected_text, language, anchor_range) = match Self::snippet(editor.clone(), cx) {
|
||||
Some(snippet) => snippet,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let entity_id = editor.entity_id();
|
||||
|
||||
let kernel_specification = store.update(cx, |store, cx| {
|
||||
store
|
||||
.kernelspec(&language, cx)
|
||||
.with_context(|| format!("No kernel found for language: {}", language.name()))
|
||||
})?;
|
||||
|
||||
let session = if let Some(session) = store.read(cx).get_session(entity_id).cloned() {
|
||||
session
|
||||
} else {
|
||||
let session =
|
||||
cx.new_view(|cx| Session::new(editor, self.fs.clone(), kernel_specification, cx));
|
||||
cx.notify();
|
||||
|
||||
let subscription = 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
subscription.detach();
|
||||
|
||||
store.update(cx, |store, _cx| {
|
||||
store.insert_session(entity_id, session.clone());
|
||||
});
|
||||
|
||||
session
|
||||
};
|
||||
|
||||
session.update(cx, |session, cx| {
|
||||
session.execute(&selected_text, anchor_range, cx);
|
||||
});
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
pub fn session(
|
||||
&mut self,
|
||||
editor: WeakView<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> SessionSupport {
|
||||
let store = ReplStore::global(cx);
|
||||
let entity_id = editor.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.clear_outputs(cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt(&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.interrupt(cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&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.shutdown(cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for RuntimePanel {
|
||||
fn persistent_name() -> &'static str {
|
||||
"RuntimePanel"
|
||||
}
|
||||
|
||||
fn position(&self, cx: &ui::WindowContext) -> workspace::dock::DockPosition {
|
||||
match JupyterSettings::get_global(cx).dock {
|
||||
JupyterDockPosition::Left => workspace::dock::DockPosition::Left,
|
||||
JupyterDockPosition::Right => workspace::dock::DockPosition::Right,
|
||||
JupyterDockPosition::Bottom => workspace::dock::DockPosition::Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
fn position_is_valid(&self, _position: workspace::dock::DockPosition) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_position(
|
||||
&mut self,
|
||||
position: workspace::dock::DockPosition,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
settings::update_settings_file::<JupyterSettings>(self.fs.clone(), cx, move |settings| {
|
||||
let dock = match position {
|
||||
workspace::dock::DockPosition::Left => JupyterDockPosition::Left,
|
||||
workspace::dock::DockPosition::Right => JupyterDockPosition::Right,
|
||||
workspace::dock::DockPosition::Bottom => JupyterDockPosition::Bottom,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
})
|
||||
}
|
||||
|
||||
fn size(&self, cx: &ui::WindowContext) -> Pixels {
|
||||
let settings = JupyterSettings::get_global(cx);
|
||||
|
||||
self.width.unwrap_or(settings.default_width)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<ui::Pixels>, _cx: &mut ViewContext<Self>) {
|
||||
self.width = size;
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &ui::WindowContext) -> Option<ui::IconName> {
|
||||
let store = ReplStore::global(cx);
|
||||
|
||||
if !store.read(cx).is_enabled() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(IconName::Code)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &ui::WindowContext) -> Option<&'static str> {
|
||||
Some("Runtime Panel")
|
||||
}
|
||||
|
||||
fn toggle_action(&self) -> Box<dyn gpui::Action> {
|
||||
Box::new(ToggleFocus)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for RuntimePanel {}
|
||||
|
||||
impl FocusableView for RuntimePanel {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for RuntimePanel {
|
||||
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
|
||||
// install kernels. It can be assumed they don't have a running kernel if we have no
|
||||
// specifications.
|
||||
if kernel_specifications.is_empty() {
|
||||
return v_flex()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Label::new("No Jupyter Kernels Available").size(LabelSize::Large))
|
||||
.child(
|
||||
Label::new("To start interactively running code in your editor, you need to install and configure Jupyter kernels.")
|
||||
.size(LabelSize::Default),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().p_4().justify_center().gap_2().child(
|
||||
ButtonLike::new("install-kernels")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Install Kernels"))
|
||||
.on_click(move |_, cx| {
|
||||
cx.open_url(
|
||||
"https://docs.jupyter.org/en/latest/install/kernels.html",
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
// When there are no sessions, show the command to run code in an editor
|
||||
if sessions.is_empty() {
|
||||
return v_flex()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Label::new("No Jupyter Kernel Sessions").size(LabelSize::Large))
|
||||
.child(
|
||||
v_flex().child(
|
||||
Label::new("To run code in a Jupyter kernel, select some code and use the 'repl::Run' command.")
|
||||
.size(LabelSize::Default)
|
||||
)
|
||||
.children(
|
||||
KeyBinding::for_action(&Run, cx)
|
||||
.map(|binding|
|
||||
binding.into_any_element()
|
||||
)
|
||||
)
|
||||
)
|
||||
.child(Label::new("Kernels available").size(LabelSize::Large))
|
||||
.children(
|
||||
kernel_specifications.into_iter().map(|spec| {
|
||||
h_flex().gap_2().child(Label::new(spec.name.clone()))
|
||||
.child(Label::new(spec.kernelspec.language.clone()).color(Color::Muted))
|
||||
})
|
||||
)
|
||||
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.p_4()
|
||||
.child(Label::new("Jupyter Kernel Sessions").size(LabelSize::Large))
|
||||
.children(
|
||||
sessions
|
||||
.into_iter()
|
||||
.map(|session| session.clone().into_any_element()),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ impl EditorBlock {
|
|||
position: code_range.end,
|
||||
height: execution_view.num_lines(cx).saturating_add(1),
|
||||
style: BlockStyle::Sticky,
|
||||
render: Self::create_output_area_render(execution_view.clone(), on_close.clone()),
|
||||
render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()),
|
||||
disposition: BlockDisposition::Below,
|
||||
};
|
||||
|
||||
|
@ -111,7 +111,7 @@ impl EditorBlock {
|
|||
self.block_id,
|
||||
(
|
||||
Some(self.execution_view.num_lines(cx).saturating_add(1)),
|
||||
Self::create_output_area_render(
|
||||
Self::create_output_area_renderer(
|
||||
self.execution_view.clone(),
|
||||
self.on_close.clone(),
|
||||
),
|
||||
|
@ -122,7 +122,7 @@ impl EditorBlock {
|
|||
.ok();
|
||||
}
|
||||
|
||||
fn create_output_area_render(
|
||||
fn create_output_area_renderer(
|
||||
execution_view: View<ExecutionView>,
|
||||
on_close: CloseBlockFn,
|
||||
) -> RenderBlock {
|
||||
|
|
|
@ -199,8 +199,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
let assistant_panel =
|
||||
assistant::AssistantPanel::load(workspace_handle.clone(), cx.clone());
|
||||
|
||||
let runtime_panel = repl::RuntimePanel::load(workspace_handle.clone(), cx.clone());
|
||||
|
||||
let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
|
||||
let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone());
|
||||
let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
|
||||
|
@ -218,7 +216,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
outline_panel,
|
||||
terminal_panel,
|
||||
assistant_panel,
|
||||
runtime_panel,
|
||||
channels_panel,
|
||||
chat_panel,
|
||||
notification_panel,
|
||||
|
@ -227,7 +224,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
outline_panel,
|
||||
terminal_panel,
|
||||
assistant_panel,
|
||||
runtime_panel,
|
||||
channels_panel,
|
||||
chat_panel,
|
||||
notification_panel,
|
||||
|
@ -235,7 +231,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
|
||||
workspace_handle.update(&mut cx, |workspace, cx| {
|
||||
workspace.add_panel(assistant_panel, cx);
|
||||
workspace.add_panel(runtime_panel, cx);
|
||||
workspace.add_panel(project_panel, cx);
|
||||
workspace.add_panel(outline_panel, cx);
|
||||
workspace.add_panel(terminal_panel, cx);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue