diff --git a/Cargo.lock b/Cargo.lock index ec0dc8e8b8..3298a1a775 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8310,7 +8310,9 @@ dependencies = [ "search", "settings", "ui", + "util", "workspace", + "zed_actions", ] [[package]] diff --git a/assets/icons/chevron_down_small.svg b/assets/icons/chevron_down_small.svg new file mode 100644 index 0000000000..8f8a99d4b9 --- /dev/null +++ b/assets/icons/chevron_down_small.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/repl_neutral.svg b/assets/icons/repl_neutral.svg index cb0c37d335..db647fe40b 100644 --- a/assets/icons/repl_neutral.svg +++ b/assets/icons/repl_neutral.svg @@ -1,14 +1,13 @@ - - - - - - - + + + + + + - - + + diff --git a/crates/quick_action_bar/Cargo.toml b/crates/quick_action_bar/Cargo.toml index 83dd3ae0f0..e5574a83f8 100644 --- a/crates/quick_action_bar/Cargo.toml +++ b/crates/quick_action_bar/Cargo.toml @@ -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"] } diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index caa36db858..42af8a7e69 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -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), ) diff --git a/crates/quick_action_bar/src/repl_menu.rs b/crates/quick_action_bar/src/repl_menu.rs index 49027a617b..a5f17ddec9 100644 --- a/crates/quick_action_bar/src/repl_menu.rs +++ b/crates/quick_action_bar/src/repl_menu.rs @@ -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, + // 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 { 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(), ) } diff --git a/crates/repl/src/kernels.rs b/crates/repl/src/kernels.rs index c91e4f5247..28c8bb9ba6 100644 --- a/crates/repl/src/kernels.rs +++ b/crates/repl/src/kernels.rs @@ -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) => { diff --git a/crates/repl/src/repl.rs b/crates/repl/src/repl.rs index 3ad8a7a715..3e0c3cead5 100644 --- a/crates/repl/src/repl.rs +++ b/crates/repl/src/repl.rs @@ -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; diff --git a/crates/repl/src/runtime_panel.rs b/crates/repl/src/runtime_panel.rs index 1b3361dea3..2f080699e0 100644 --- a/crates/repl/src/runtime_panel.rs +++ b/crates/repl/src/runtime_panel.rs @@ -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), ClearOutputs(WeakView), + Interrupt(WeakView), + Shutdown(WeakView), } 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, cx: &mut ViewContext) { + 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, cx: &mut ViewContext) { + 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), - Inactive(KernelSpecification), + Inactive(Box), RequiresSetup(Arc), 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 diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index b041860276..de2557f176 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -370,6 +370,14 @@ impl ButtonLike { } } + pub fn new_rounded_left(id: impl Into) -> Self { + Self::new(id).rounding(ButtonLikeRounding::Left) + } + + pub fn new_rounded_right(id: impl Into) -> Self { + Self::new(id).rounding(ButtonLikeRounding::Right) + } + pub(crate) fn height(mut self, height: DefiniteLength) -> Self { self.height = Some(height); self diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index fde5523147..b87d80232c 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -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", } } } diff --git a/docs/src/repl.md b/docs/src/repl.md index e7fdc0c035..9c932ff6ac 100644 --- a/docs/src/repl.md +++ b/docs/src/repl.md @@ -8,12 +8,13 @@ This feature is in active development. Details may change. We're delighted to ge +## 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. -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. + + + +## 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 + +
+ +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. + +
-### 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)