Add REPL dropdown menu to toolbar (#14493)
TODO: - [x] Actions run from menu not firing - [x] Menu differentiates idle and busy for running kernel Menu States: - [x] No session && no support known No session && no kernel installed for languages of known support - (TODO after) Intro to REPL - [x] Link to docs No session but can start one - [x] Start REPL - (TODO after) More info -> Docs? Yes Session - [x] Info: Kernel name, language example: chatlab-3.7-adsf87fsa (Python) example: condapy-3.7 (Python) - [x] Change Kernel -> https://zed.dev/docs/repl#change-kernel - --- - [x] Run - [x] Interrupt - [x] Clear Outputs - --- - [x] Shutdown (Release notes left empty as the change will be documented in the REPL release!) Reserved for a follow on PR: ``` - [ ] Status should update when the menu is open (missing `cx.notify`?) - [ ] Shutdown all kernels action - [ ] Restart action - [ ] [Default kernel changed - restart (this kernel) to apply] // todo!(kyle): need some kind of state thing that says if this has happened ``` Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <elliott.codes@gmail.com> Co-authored-by: Kyle Kelley <rgbkrk@gmail.com> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
1856320516
commit
fa3d29087d
12 changed files with 489 additions and 71 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -8310,7 +8310,9 @@ dependencies = [
|
|||
"search",
|
||||
"settings",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
3
assets/icons/chevron_down_small.svg
Normal file
3
assets/icons/chevron_down_small.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.49574 4.74787L5.99574 7.25214L8.49574 4.74787" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 246 B |
|
@ -1,14 +1,13 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_32_58)">
|
||||
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_62_95)">
|
||||
<path d="M4.5 6C3.67157 6 3 5.32843 3 4.5C3 3.67157 3.67157 3 4.5 3C5.32843 3 6 3.67157 6 4.5C6 5.32843 5.32843 6 4.5 6Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.54433 13.4334C6.87775 13.5227 7.22046 13.3249 7.3098 12.9914C7.39914 12.658 7.20127 12.3153 6.86786 12.226L6.54433 13.4334ZM3.77426 6.86772L3.93603 6.26401L2.72862 5.94049L2.56686 6.54419L3.77426 6.86772ZM6.86786 12.226C4.53394 11.6006 3.14889 9.20163 3.77426 6.86772L2.56686 6.54419C1.76281 9.54494 3.54359 12.6293 6.54433 13.4334L6.86786 12.226Z" fill="white"/>
|
||||
<path d="M11.5 13C10.6716 13 10 12.3284 10 11.5C10 10.6716 10.6716 10 11.5 10C12.3284 10 13 10.6716 13 11.5C13 12.3284 12.3284 13 11.5 13Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.1875 4.21113C9.88852 4.03854 9.7861 3.65629 9.95869 3.35736C10.1313 3.05843 10.5135 2.95601 10.8125 3.12859L10.1875 4.21113ZM11.7888 10.1875C12.9969 8.09496 12.28 5.41925 10.1875 4.21113L10.8125 3.12859C13.5028 4.6819 14.4246 8.12209 12.8713 10.8125L11.7888 10.1875Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_32_58">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
<clipPath id="clip0_62_95">
|
||||
<rect width="16" height="16" fill="white" transform="matrix(-1 0 0 1 16 0)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -19,8 +19,10 @@ gpui.workspace = true
|
|||
search.workspace = true
|
||||
settings.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
repl.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -304,7 +304,7 @@ impl Render for QuickActionBar {
|
|||
.child(
|
||||
h_flex()
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
.children(search_button)
|
||||
.children(self.render_repl_menu(cx))
|
||||
.when(
|
||||
AssistantSettings::get_global(cx).enabled
|
||||
&& AssistantSettings::get_global(cx).button,
|
||||
|
@ -314,7 +314,11 @@ impl Render for QuickActionBar {
|
|||
.child(
|
||||
h_flex()
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
.children(self.render_repl_menu(cx))
|
||||
.children(search_button),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(Spacing::Medium.rems(cx))
|
||||
.children(editor_selections_dropdown)
|
||||
.child(editor_settings_dropdown),
|
||||
)
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use gpui::AnyElement;
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation};
|
||||
use repl::{
|
||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, Session,
|
||||
SessionSupport,
|
||||
ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, SessionSupport,
|
||||
};
|
||||
use ui::{prelude::*, ButtonLike, IconWithIndicator, IntoElement, Tooltip};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
|
||||
Tooltip,
|
||||
};
|
||||
|
||||
use gpui::ElementId;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::QuickActionBar;
|
||||
|
||||
|
@ -25,6 +32,19 @@ impl QuickActionBar {
|
|||
return None;
|
||||
};
|
||||
|
||||
let has_nonempty_selection = {
|
||||
editor.update(cx, |this, cx| {
|
||||
this.selections
|
||||
.count()
|
||||
.ne(&0)
|
||||
.then(|| {
|
||||
let latest = this.selections.newest_display(cx);
|
||||
!latest.is_empty()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
};
|
||||
|
||||
let session = repl_panel.update(cx, |repl_panel, cx| {
|
||||
repl_panel.session(editor.downgrade(), cx)
|
||||
});
|
||||
|
@ -32,6 +52,7 @@ impl QuickActionBar {
|
|||
let session = match session {
|
||||
SessionSupport::ActiveSession(session) => session.read(cx),
|
||||
SessionSupport::Inactive(spec) => {
|
||||
let spec = *spec;
|
||||
return self.render_repl_launch_menu(spec, cx);
|
||||
}
|
||||
SessionSupport::RequiresSetup(language) => {
|
||||
|
@ -48,34 +69,270 @@ impl QuickActionBar {
|
|||
.clone()
|
||||
.into();
|
||||
|
||||
let tooltip = |session: &Session| match &session.kernel {
|
||||
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
|
||||
ExecutionState::Idle => {
|
||||
format!("Run code on {} ({})", kernel_name, kernel_language)
|
||||
struct ReplMenuState {
|
||||
tooltip: SharedString,
|
||||
icon: IconName,
|
||||
icon_color: Color,
|
||||
icon_is_animating: bool,
|
||||
popover_disabled: bool,
|
||||
indicator: Option<Indicator>,
|
||||
// TODO: Persist rotation state so the
|
||||
// icon doesn't reset on every state change
|
||||
// current_delta: Duration,
|
||||
}
|
||||
|
||||
impl Default for ReplMenuState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tooltip: "Nothing running".into(),
|
||||
icon: IconName::ReplNeutral,
|
||||
icon_color: Color::Default,
|
||||
icon_is_animating: false,
|
||||
popover_disabled: false,
|
||||
indicator: None,
|
||||
// current_delta: Duration::default(),
|
||||
}
|
||||
ExecutionState::Busy => format!("Interrupt {} ({})", kernel_name, kernel_language),
|
||||
}
|
||||
}
|
||||
|
||||
let menu_state = match &session.kernel {
|
||||
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
|
||||
ExecutionState::Idle => ReplMenuState {
|
||||
tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(),
|
||||
indicator: Some(Indicator::dot().color(Color::Success)),
|
||||
..Default::default()
|
||||
},
|
||||
ExecutionState::Busy => ReplMenuState {
|
||||
tooltip: format!("Interrupt {} ({})", kernel_name, kernel_language).into(),
|
||||
icon_is_animating: true,
|
||||
popover_disabled: false,
|
||||
indicator: None,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
Kernel::StartingKernel(_) => format!("{} is starting", kernel_name),
|
||||
Kernel::ErroredLaunch(e) => format!("Error with kernel {}: {}", kernel_name, e),
|
||||
Kernel::ShuttingDown => format!("{} is shutting down", kernel_name),
|
||||
Kernel::Shutdown => "Nothing running".to_string(),
|
||||
Kernel::StartingKernel(_) => ReplMenuState {
|
||||
tooltip: format!("{} is starting", kernel_name).into(),
|
||||
icon_is_animating: true,
|
||||
popover_disabled: true,
|
||||
icon_color: Color::Muted,
|
||||
indicator: Some(Indicator::dot().color(Color::Muted)),
|
||||
..Default::default()
|
||||
},
|
||||
Kernel::ErroredLaunch(e) => ReplMenuState {
|
||||
tooltip: format!("Error with kernel {}: {}", kernel_name, e).into(),
|
||||
popover_disabled: false,
|
||||
indicator: Some(Indicator::dot().color(Color::Error)),
|
||||
..Default::default()
|
||||
},
|
||||
Kernel::ShuttingDown => ReplMenuState {
|
||||
tooltip: format!("{} is shutting down", kernel_name).into(),
|
||||
popover_disabled: true,
|
||||
icon_color: Color::Muted,
|
||||
indicator: Some(Indicator::dot().color(Color::Muted)),
|
||||
..Default::default()
|
||||
},
|
||||
Kernel::Shutdown => ReplMenuState::default(),
|
||||
};
|
||||
|
||||
let tooltip_text: SharedString = SharedString::from(tooltip(&session).clone());
|
||||
let id = "repl-menu".to_string();
|
||||
|
||||
let button = ButtonLike::new("toggle_repl_icon")
|
||||
.child(
|
||||
IconWithIndicator::new(Icon::new(IconName::Play), Some(session.kernel.dot()))
|
||||
.indicator_border_color(Some(cx.theme().colors().border)),
|
||||
)
|
||||
let element_id = |suffix| ElementId::Name(format!("{}-{}", id, suffix).into());
|
||||
|
||||
let kernel = &session.kernel;
|
||||
let status_borrow = &kernel.status();
|
||||
let status = status_borrow.clone();
|
||||
let panel_clone = repl_panel.clone();
|
||||
let editor_clone = editor.downgrade();
|
||||
let dropdown_menu = PopoverMenu::new(element_id("menu"))
|
||||
.menu(move |cx| {
|
||||
let kernel_name = kernel_name.clone();
|
||||
let kernel_language = kernel_language.clone();
|
||||
let status = status.clone();
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
ContextMenu::build(cx, move |menu, _cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
let panel_clone = panel_clone.clone();
|
||||
let kernel_name = kernel_name.clone();
|
||||
let kernel_language = kernel_language.clone();
|
||||
let status = status.clone();
|
||||
menu.when_else(
|
||||
status.is_connected(),
|
||||
|running| {
|
||||
let status = status.clone();
|
||||
running
|
||||
.custom_row(move |_cx| {
|
||||
h_flex()
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"kernel: {} ({})",
|
||||
kernel_name.clone(),
|
||||
kernel_language.clone()
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.custom_row(move |_cx| {
|
||||
h_flex()
|
||||
.child(
|
||||
Label::new(status.clone().to_string())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
},
|
||||
|not_running| {
|
||||
let status = status.clone();
|
||||
not_running.custom_row(move |_cx| {
|
||||
h_flex()
|
||||
.child(
|
||||
Label::new(format!("{}...", status.clone().to_string()))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
},
|
||||
)
|
||||
.separator()
|
||||
// Run
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new(if has_nonempty_selection {
|
||||
"Run Selection"
|
||||
} else {
|
||||
"Run Line"
|
||||
})
|
||||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.run(editor_clone.clone(), cx).log_err();
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
// Interrupt
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new("Interrupt")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Error)
|
||||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.interrupt(editor_clone, cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
// Clear Outputs
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new("Clear Outputs")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.clear_outputs(editor_clone, cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
.separator()
|
||||
.link(
|
||||
"Change Kernel",
|
||||
Box::new(zed_actions::OpenBrowser {
|
||||
url: format!("{}#change-kernel", ZED_REPL_DOCUMENTATION),
|
||||
}),
|
||||
)
|
||||
// TODO: Add Restart action
|
||||
// .action("Restart", Box::new(gpui::NoAction))
|
||||
// Shut down kernel
|
||||
.custom_entry(
|
||||
move |_cx| {
|
||||
Label::new("Shut Down Kernel")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Error)
|
||||
.into_any_element()
|
||||
},
|
||||
{
|
||||
let panel_clone = panel_clone.clone();
|
||||
let editor_clone = editor_clone.clone();
|
||||
move |cx| {
|
||||
let editor_clone = editor_clone.clone();
|
||||
panel_clone.update(cx, |this, cx| {
|
||||
this.shutdown(editor_clone, cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
// .separator()
|
||||
// TODO: Add shut down all kernels action
|
||||
// .action("Shut Down all Kernels", Box::new(gpui::NoAction))
|
||||
})
|
||||
.into()
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new_rounded_right(element_id("dropdown"))
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDownSmall)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::text("REPL Menu", cx))
|
||||
.width(rems(1.).into())
|
||||
.disabled(menu_state.popover_disabled),
|
||||
);
|
||||
|
||||
let button = ButtonLike::new_rounded_left("toggle_repl_icon")
|
||||
.child(if menu_state.icon_is_animating {
|
||||
Icon::new(menu_state.icon)
|
||||
.color(menu_state.icon_color)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(5)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::ReplNeutral).color(menu_state.icon_color),
|
||||
menu_state.indicator,
|
||||
)
|
||||
.indicator_border_color(Some(cx.theme().colors().toolbar_background))
|
||||
.into_any_element()
|
||||
})
|
||||
.size(ButtonSize::Compact)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip_text.clone(), cx))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.tooltip(move |cx| Tooltip::text(menu_state.tooltip.clone(), cx))
|
||||
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
|
||||
.into_any_element();
|
||||
|
||||
Some(button)
|
||||
Some(
|
||||
h_flex()
|
||||
.child(button)
|
||||
.child(dropdown_menu)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_repl_launch_menu(
|
||||
|
@ -87,7 +344,7 @@ impl QuickActionBar {
|
|||
SharedString::from(format!("Start REPL for {}", kernel_specification.name));
|
||||
|
||||
Some(
|
||||
IconButton::new("toggle_repl_icon", IconName::Play)
|
||||
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
|
@ -104,12 +361,12 @@ impl QuickActionBar {
|
|||
) -> Option<AnyElement> {
|
||||
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
|
||||
Some(
|
||||
IconButton::new("toggle_repl_icon", IconName::Play)
|
||||
IconButton::new("toggle_repl_icon", IconName::ReplNeutral)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_color(Color::Muted)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
|
||||
.on_click(|_, cx| cx.open_url(ZED_REPL_DOCUMENTATION))
|
||||
.on_click(|_, cx| cx.open_url(&format!("{}#installation", ZED_REPL_DOCUMENTATION)))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -81,6 +81,52 @@ pub enum Kernel {
|
|||
Shutdown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum KernelStatus {
|
||||
Idle,
|
||||
Busy,
|
||||
Starting,
|
||||
Error,
|
||||
ShuttingDown,
|
||||
Shutdown,
|
||||
}
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Kernel {
|
||||
pub fn dot(&self) -> Indicator {
|
||||
match self {
|
||||
|
@ -95,6 +141,10 @@ impl Kernel {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> KernelStatus {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn set_execution_state(&mut self, status: &ExecutionState) {
|
||||
match self {
|
||||
Kernel::RunningKernel(running_kernel) => {
|
||||
|
|
|
@ -12,7 +12,7 @@ mod stdio;
|
|||
|
||||
pub use jupyter_settings::JupyterSettings;
|
||||
pub use kernels::{Kernel, KernelSpecification};
|
||||
pub use runtime_panel::Run;
|
||||
pub use runtime_panel::{ClearOutputs, Interrupt, Run, Shutdown};
|
||||
pub use runtime_panel::{RuntimePanel, SessionSupport};
|
||||
pub use runtimelib::ExecutionState;
|
||||
pub use session::Session;
|
||||
|
|
|
@ -23,7 +23,7 @@ use workspace::{
|
|||
Workspace,
|
||||
};
|
||||
|
||||
actions!(repl, [Run, ClearOutputs]);
|
||||
actions!(repl, [Run, ClearOutputs, Interrupt, Shutdown]);
|
||||
actions!(repl_panel, [ToggleFocus]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
|
@ -51,6 +51,8 @@ pub struct RuntimePanel {
|
|||
pub enum ReplEvent {
|
||||
Run(WeakView<Editor>),
|
||||
ClearOutputs(WeakView<Editor>),
|
||||
Interrupt(WeakView<Editor>),
|
||||
Shutdown(WeakView<Editor>),
|
||||
}
|
||||
|
||||
impl RuntimePanel {
|
||||
|
@ -108,6 +110,40 @@ impl RuntimePanel {
|
|||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor = cx.view().downgrade();
|
||||
let repl_editor_event_tx = repl_editor_event_tx.clone();
|
||||
|
||||
move |_: &Interrupt, cx: &mut WindowContext| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
repl_editor_event_tx
|
||||
.unbounded_send(ReplEvent::Interrupt(
|
||||
editor.clone(),
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
editor
|
||||
.register_action({
|
||||
let editor = cx.view().downgrade();
|
||||
let repl_editor_event_tx = repl_editor_event_tx.clone();
|
||||
|
||||
move |_: &Shutdown, cx: &mut WindowContext| {
|
||||
if !JupyterSettings::enabled(cx) {
|
||||
return;
|
||||
}
|
||||
repl_editor_event_tx
|
||||
.unbounded_send(ReplEvent::Shutdown(editor.clone()))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
},
|
||||
),
|
||||
];
|
||||
|
@ -123,6 +159,12 @@ impl RuntimePanel {
|
|||
ReplEvent::ClearOutputs(editor) => {
|
||||
runtime_panel.clear_outputs(editor, cx);
|
||||
}
|
||||
ReplEvent::Interrupt(editor) => {
|
||||
runtime_panel.interrupt(editor, cx);
|
||||
}
|
||||
ReplEvent::Shutdown(editor) => {
|
||||
runtime_panel.shutdown(editor, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
@ -320,11 +362,31 @@ impl RuntimePanel {
|
|||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
|
||||
let entity_id = editor.entity_id();
|
||||
if let Some(session) = self.sessions.get_mut(&entity_id) {
|
||||
session.update(cx, |session, cx| {
|
||||
session.interrupt(cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self, editor: WeakView<Editor>, cx: &mut ViewContext<RuntimePanel>) {
|
||||
let entity_id = editor.entity_id();
|
||||
if let Some(session) = self.sessions.get(&entity_id) {
|
||||
session.update(cx, |session, cx| {
|
||||
session.shutdown(cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(View<Session>),
|
||||
Inactive(KernelSpecification),
|
||||
Inactive(Box<KernelSpecification>),
|
||||
RequiresSetup(Arc<str>),
|
||||
Unsupported,
|
||||
}
|
||||
|
@ -350,7 +412,7 @@ impl RuntimePanel {
|
|||
let kernelspec = self.kernelspec(&language, cx);
|
||||
|
||||
match kernelspec {
|
||||
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
|
||||
Some(kernelspec) => SessionSupport::Inactive(Box::new(kernelspec)),
|
||||
None => {
|
||||
// If no kernelspec but language is one of typescript or python
|
||||
// then we return RequiresSetup
|
||||
|
|
|
@ -370,6 +370,14 @@ impl ButtonLike {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
|
||||
Self::new(id).rounding(ButtonLikeRounding::Left)
|
||||
}
|
||||
|
||||
pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
|
||||
Self::new(id).rounding(ButtonLikeRounding::Right)
|
||||
}
|
||||
|
||||
pub(crate) fn height(mut self, height: DefiniteLength) -> Self {
|
||||
self.height = Some(height);
|
||||
self
|
||||
|
|
|
@ -91,7 +91,7 @@ impl IconSize {
|
|||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Eq, PartialEq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
|
||||
Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum IconName {
|
||||
Ai,
|
||||
|
@ -118,6 +118,8 @@ pub enum IconName {
|
|||
CaseSensitive,
|
||||
Check,
|
||||
ChevronDown,
|
||||
/// This chevron indicates a popover menu.
|
||||
ChevronDownSmall,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
|
@ -160,11 +162,11 @@ pub enum IconName {
|
|||
Font,
|
||||
FontSize,
|
||||
FontWeight,
|
||||
GenericClose,
|
||||
GenericMaximize,
|
||||
GenericMinimize,
|
||||
GenericRestore,
|
||||
Github,
|
||||
GenericMinimize,
|
||||
GenericMaximize,
|
||||
GenericClose,
|
||||
GenericRestore,
|
||||
Hash,
|
||||
HistoryRerun,
|
||||
Indicator,
|
||||
|
@ -194,9 +196,6 @@ pub enum IconName {
|
|||
PullRequest,
|
||||
Quote,
|
||||
Regex,
|
||||
ReplPlay,
|
||||
ReplOff,
|
||||
ReplPause,
|
||||
ReplNeutral,
|
||||
Replace,
|
||||
ReplaceAll,
|
||||
|
@ -235,12 +234,12 @@ pub enum IconName {
|
|||
Trash,
|
||||
TriangleRight,
|
||||
Update,
|
||||
Visible,
|
||||
WholeWord,
|
||||
XCircle,
|
||||
ZedAssistant,
|
||||
ZedAssistantFilled,
|
||||
ZedXCopilot,
|
||||
Visible,
|
||||
}
|
||||
|
||||
impl IconName {
|
||||
|
@ -270,6 +269,7 @@ impl IconName {
|
|||
IconName::CaseSensitive => "icons/case_insensitive.svg",
|
||||
IconName::Check => "icons/check.svg",
|
||||
IconName::ChevronDown => "icons/chevron_down.svg",
|
||||
IconName::ChevronDownSmall => "icons/chevron_down_small.svg",
|
||||
IconName::ChevronLeft => "icons/chevron_left.svg",
|
||||
IconName::ChevronRight => "icons/chevron_right.svg",
|
||||
IconName::ChevronUp => "icons/chevron_up.svg",
|
||||
|
@ -312,11 +312,11 @@ impl IconName {
|
|||
IconName::Font => "icons/font.svg",
|
||||
IconName::FontSize => "icons/font_size.svg",
|
||||
IconName::FontWeight => "icons/font_weight.svg",
|
||||
IconName::GenericClose => "icons/generic_close.svg",
|
||||
IconName::GenericMaximize => "icons/generic_maximize.svg",
|
||||
IconName::GenericMinimize => "icons/generic_minimize.svg",
|
||||
IconName::GenericRestore => "icons/generic_restore.svg",
|
||||
IconName::Github => "icons/github.svg",
|
||||
IconName::GenericMinimize => "icons/generic_minimize.svg",
|
||||
IconName::GenericMaximize => "icons/generic_maximize.svg",
|
||||
IconName::GenericClose => "icons/generic_close.svg",
|
||||
IconName::GenericRestore => "icons/generic_restore.svg",
|
||||
IconName::Hash => "icons/hash.svg",
|
||||
IconName::HistoryRerun => "icons/history_rerun.svg",
|
||||
IconName::Indicator => "icons/indicator.svg",
|
||||
|
@ -346,10 +346,7 @@ impl IconName {
|
|||
IconName::PullRequest => "icons/pull_request.svg",
|
||||
IconName::Quote => "icons/quote.svg",
|
||||
IconName::Regex => "icons/regex.svg",
|
||||
IconName::ReplPlay => "icons/repl_play.svg",
|
||||
IconName::ReplPause => "icons/repl_pause.svg",
|
||||
IconName::ReplNeutral => "icons/repl_neutral.svg",
|
||||
IconName::ReplOff => "icons/repl_off.svg",
|
||||
IconName::Replace => "icons/replace.svg",
|
||||
IconName::ReplaceAll => "icons/replace_all.svg",
|
||||
IconName::ReplaceNext => "icons/replace_next.svg",
|
||||
|
@ -387,12 +384,12 @@ impl IconName {
|
|||
IconName::Trash => "icons/trash.svg",
|
||||
IconName::TriangleRight => "icons/triangle_right.svg",
|
||||
IconName::Update => "icons/update.svg",
|
||||
IconName::Visible => "icons/visible.svg",
|
||||
IconName::WholeWord => "icons/word_search.svg",
|
||||
IconName::XCircle => "icons/error.svg",
|
||||
IconName::ZedAssistant => "icons/zed_assistant.svg",
|
||||
IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
|
||||
IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
|
||||
IconName::Visible => "icons/visible.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,13 @@ This feature is in active development. Details may change. We're delighted to ge
|
|||
|
||||
</div>
|
||||
|
||||
## Getting started
|
||||
|
||||
The built-in REPL for Zed allows you to run code interactively in your editor similarly to a notebook with your own text files.
|
||||
Bring the power of [Jupyter kernels](https://docs.jupyter.org/en/latest/projects/kernels.html) to your editor! The built-in REPL for Zed allows you to run code interactively in your editor similarly to a notebook with your own text files.
|
||||
|
||||
<!-- TODO: Include GIF in action -->
|
||||
|
||||
To start using the REPL, add the following to your Zed `settings.json` to bring the power of [Jupyter kernels](https://docs.jupyter.org/en/latest/projects/kernels.html) to your editor:
|
||||
To start using the REPL, add the following to your Zed `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -23,22 +24,53 @@ To start using the REPL, add the following to your Zed `settings.json` to bring
|
|||
}
|
||||
```
|
||||
|
||||
After that, install any of the supported kernels:
|
||||
## Installation
|
||||
|
||||
* [Python](#python)
|
||||
* [TypeScript via Deno](#deno)
|
||||
Zed supports running code in multiple languages. To get started, you need to install a kernel for the language you want to use.
|
||||
|
||||
## Python
|
||||
**Currently supported languages:**
|
||||
|
||||
* [Python (ipykernel)](#python)
|
||||
* [TypeScript (Deno)](#typescript-deno)
|
||||
|
||||
|
||||
Once installed, you can start using the REPL in the respective language files, or other places those languages are supported, such as Markdown.
|
||||
|
||||
<!-- TODO: Make markdown a link with an example -->
|
||||
|
||||
## Using the REPL
|
||||
|
||||
To start the REPL, open a file with the language you want to use and use the `repl: run` command (defaults to CMD + Enter on macOS). You can also click on the REPL icon in the toolbar.
|
||||
|
||||
The `repl: run` command will be executed on your selection(s), and the result will be displayed below the selection.
|
||||
|
||||
Outputs can be cleared with the `repl: clear outputs` command, or from the REPL menu in the toolbar.
|
||||
|
||||
## Changing Kernels {#changing-kernels}
|
||||
|
||||
Work in Progress!
|
||||
|
||||
## Language specific instructions
|
||||
|
||||
### Python {#python}
|
||||
|
||||
#### Global environment
|
||||
|
||||
<div class="warning">
|
||||
|
||||
On MacOS, your system Python will _not_ work. Either set up [pyenv](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation) or use a virtual environment.
|
||||
|
||||
</div>
|
||||
|
||||
### Global environment
|
||||
|
||||
To setup your current python to have an available kernel, run:
|
||||
|
||||
```
|
||||
pip install ipykernel
|
||||
python -m ipykernel install --user
|
||||
```
|
||||
|
||||
### Conda Environment
|
||||
#### Conda Environment
|
||||
|
||||
```
|
||||
source activate myenv
|
||||
|
@ -47,7 +79,7 @@ python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
|
|||
```
|
||||
|
||||
|
||||
### Virtualenv with pip
|
||||
#### Virtualenv with pip
|
||||
|
||||
```
|
||||
source activate myenv
|
||||
|
@ -55,7 +87,7 @@ pip install ipykernel
|
|||
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
|
||||
```
|
||||
|
||||
## Deno
|
||||
### Typescript: Deno {#typescript-deno}
|
||||
|
||||
[Install Deno](https://docs.deno.com/runtime/manual/getting_started/installation/) and then install the Deno jupyter kernel:
|
||||
|
||||
|
@ -63,10 +95,12 @@ python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
|
|||
deno jupyter --unstable --install
|
||||
```
|
||||
|
||||
## Other languages
|
||||
### Other languages
|
||||
|
||||
* [Julia](https://github.com/JuliaLang/IJulia.jl)
|
||||
The following languages and kernels are also supported. You can help us out by expanding their installation instructions and configuration:
|
||||
|
||||
* [Julia (IJulia)](https://github.com/JuliaLang/IJulia.jl)
|
||||
* R
|
||||
- [Ark Kernel from Positron, formerly RStudio](https://github.com/posit-dev/ark)
|
||||
- [Ark Kernel](https://github.com/posit-dev/ark) - via Positron, formerly RStudio
|
||||
- [Xeus-R](https://github.com/jupyter-xeus/xeus-r)
|
||||
* [Scala](https://almond.sh/docs/quick-start-install)
|
||||
* [Scala (almond)](https://almond.sh/docs/quick-start-install)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue