Stick REPL icon in quick action bar (#14064)

Kyle Kelley and Nate Butler created

REPL Quick Actions

<img width="325" alt="image"
src="https://github.com/zed-industries/zed/assets/836375/faaf4c8f-ef12-4417-a9dd-158d5beae8ba">

When the Jupyter REPL is enabled and a kernel is available, show the
status in the editor bar:

![quick action bar
repl](https://github.com/zed-industries/zed/assets/836375/f3445283-f1fc-4714-895b-7aa842d4ab76)


Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>

Change summary

Cargo.lock                                      |   1 
assets/icons/repl_neutral.svg                   |  14 ++
assets/icons/repl_off.svg                       |  20 +++
assets/icons/repl_pause.svg                     |  15 ++
assets/icons/repl_play.svg                      |  14 ++
crates/quick_action_bar/Cargo.toml              |   1 
crates/quick_action_bar/src/quick_action_bar.rs |   8 +
crates/quick_action_bar/src/repl_menu.rs        | 116 +++++++++++++++++++
crates/repl/src/kernels.rs                      |   2 
crates/repl/src/repl.rs                         |   6 
crates/repl/src/runtime_panel.rs                |  55 +++++++++
crates/repl/src/session.rs                      |  12 
crates/ui/src/components/icon.rs                |  24 ++-
docs/src/SUMMARY.md                             |   1 
docs/src/repl.md                                |  72 +++++++++++
15 files changed, 345 insertions(+), 16 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -8274,6 +8274,7 @@ dependencies = [
  "assistant",
  "editor",
  "gpui",
+ "repl",
  "search",
  "settings",
  "ui",

assets/icons/repl_neutral.svg 🔗

@@ -0,0 +1,14 @@
+<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"/>
+</g>
+<defs>
+<clipPath id="clip0_32_58">
+<rect width="24" height="24" fill="white"/>
+</clipPath>
+</defs>
+</svg>

assets/icons/repl_off.svg 🔗

@@ -0,0 +1,20 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_39_129)">
+<path d="M22.0209 11.9553C22.0059 10.0068 21.4219 8.10512 20.3408 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.1001 2.18C11.355 1.93537 12.1493 1.93674 13.5027 2.10594" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M21.8198 10.1C22.0644 11.3548 22.0644 12.6451 21.8198 13.9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M20.2898 17.6C19.5716 18.6622 18.6548 19.5757 17.5898 20.29" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.9008 21.82C12.6459 22.0644 11.6432 22.1543 10.3883 21.91" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.18005 13.9C1.93543 12.6451 1.93543 11.3548 2.18005 10.1" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.70996 6.40002C4.42822 5.33775 5.34503 4.42433 6.40996 3.71002" 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"/>
+<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="M1.99072 12.0748C2.00804 14.0118 2.58758 15.9021 3.65891 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<defs>
+<clipPath id="clip0_39_129">
+<rect width="24" height="24" fill="white"/>
+</clipPath>
+</defs>
+</svg>

assets/icons/repl_pause.svg 🔗

@@ -0,0 +1,15 @@
+<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_70)">
+<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="M10 15V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M14 15V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<defs>
+<clipPath id="clip0_32_70">
+<rect width="24" height="24" fill="white"/>
+</clipPath>
+</defs>
+</svg>

assets/icons/repl_play.svg 🔗

@@ -0,0 +1,14 @@
+<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_64)">
+<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="M10 8.56055C10 8.32095 10.267 8.17803 10.4664 8.31094L15.6256 11.7504C15.8037 11.8691 15.8037 12.1309 15.6256 12.2496L10.4664 15.6891C10.267 15.822 10 15.6791 10 15.4394V8.56055Z" fill="white" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+<defs>
+<clipPath id="clip0_32_64">
+<rect width="24" height="24" fill="white"/>
+</clipPath>
+</defs>
+</svg>

crates/quick_action_bar/Cargo.toml 🔗

@@ -20,6 +20,7 @@ search.workspace = true
 settings.workspace = true
 ui.workspace = true
 workspace.workspace = true
+repl.workspace = true
 
 [dev-dependencies]
 editor = { workspace = true, features = ["test-support"] }

crates/quick_action_bar/src/quick_action_bar.rs 🔗

@@ -20,8 +20,11 @@ use workspace::{
     item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 };
 
+mod repl_menu;
+
 pub struct QuickActionBar {
     buffer_search_bar: View<BufferSearchBar>,
+    repl_menu: Option<View<ContextMenu>>,
     toggle_settings_menu: Option<View<ContextMenu>>,
     toggle_selections_menu: Option<View<ContextMenu>>,
     active_item: Option<Box<dyn ItemHandle>>,
@@ -40,6 +43,7 @@ impl QuickActionBar {
             buffer_search_bar,
             toggle_settings_menu: None,
             toggle_selections_menu: None,
+            repl_menu: None,
             active_item: None,
             _inlay_hints_enabled_subscription: None,
             workspace: workspace.weak_handle(),
@@ -290,9 +294,13 @@ impl Render for QuickActionBar {
             .child(
                 h_flex()
                     .gap(Spacing::Medium.rems(cx))
+                    .children(self.render_repl_menu(cx))
                     .children(editor_selections_dropdown)
                     .child(editor_settings_dropdown),
             )
+            .when_some(self.repl_menu.as_ref(), |el, repl_menu| {
+                el.child(Self::render_menu_overlay(repl_menu))
+            })
             .when_some(
                 self.toggle_settings_menu.as_ref(),
                 |el, toggle_settings_menu| {

crates/quick_action_bar/src/repl_menu.rs 🔗

@@ -0,0 +1,116 @@
+use gpui::AnyElement;
+use repl::{
+    ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, Session,
+    SessionSupport,
+};
+use ui::{prelude::*, ButtonLike, IconWithIndicator, IntoElement, Tooltip};
+
+use crate::QuickActionBar;
+
+const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl";
+
+impl QuickActionBar {
+    pub fn render_repl_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
+        if !JupyterSettings::enabled(cx) {
+            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 session = repl_panel.update(cx, |repl_panel, cx| {
+            repl_panel.session(editor.downgrade(), cx)
+        });
+
+        let session = match session {
+            SessionSupport::ActiveSession(session) => session.read(cx),
+            SessionSupport::Inactive(spec) => {
+                return self.render_repl_launch_menu(spec, cx);
+            }
+            SessionSupport::RequiresSetup(language) => {
+                return self.render_repl_setup(&language, cx);
+            }
+            SessionSupport::Unsupported => return None,
+        };
+
+        let kernel_name: SharedString = session.kernel_specification.name.clone().into();
+        let kernel_language: SharedString = session
+            .kernel_specification
+            .kernelspec
+            .language
+            .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)
+                }
+                ExecutionState::Busy => format!("Interrupt {} ({})", kernel_name, kernel_language),
+            },
+            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(),
+        };
+
+        let tooltip_text: SharedString = SharedString::from(tooltip(&session).clone());
+
+        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)),
+            )
+            .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 {})))
+            .on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
+            .into_any_element();
+
+        Some(button)
+    }
+
+    pub fn render_repl_launch_menu(
+        &self,
+        kernel_specification: KernelSpecification,
+        _cx: &mut ViewContext<Self>,
+    ) -> Option<AnyElement> {
+        let tooltip: SharedString =
+            SharedString::from(format!("Start REPL for {}", kernel_specification.name));
+
+        Some(
+            IconButton::new("toggle_repl_icon", IconName::Play)
+                .size(ButtonSize::Compact)
+                .icon_color(Color::Muted)
+                .style(ButtonStyle::Subtle)
+                .tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
+                .on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
+                .into_any_element(),
+        )
+    }
+
+    pub fn render_repl_setup(
+        &self,
+        language: &str,
+        _cx: &mut ViewContext<Self>,
+    ) -> Option<AnyElement> {
+        let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
+        Some(
+            IconButton::new("toggle_repl_icon", IconName::Play)
+                .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))
+                .into_any_element(),
+        )
+    }
+}

crates/repl/src/kernels.rs 🔗

@@ -82,7 +82,7 @@ pub enum Kernel {
 }
 
 impl Kernel {
-    pub fn dot(&mut self) -> Indicator {
+    pub fn dot(&self) -> Indicator {
         match self {
             Kernel::RunningKernel(kernel) => match kernel.execution_state {
                 ExecutionState::Idle => Indicator::dot().color(Color::Success),

crates/repl/src/repl.rs 🔗

@@ -11,7 +11,11 @@ mod session;
 mod stdio;
 
 pub use jupyter_settings::JupyterSettings;
-pub use runtime_panel::RuntimePanel;
+pub use kernels::{Kernel, KernelSpecification};
+pub use runtime_panel::Run;
+pub use runtime_panel::{RuntimePanel, SessionSupport};
+pub use runtimelib::ExecutionState;
+pub use session::Session;
 
 fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
     struct ZedDispatcher {

crates/repl/src/runtime_panel.rs 🔗

@@ -241,6 +241,17 @@ impl RuntimePanel {
         Some((selected_text, language_name, anchor_range))
     }
 
+    pub fn language(
+        &self,
+        editor: WeakView<Editor>,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Arc<str>> {
+        match self.snippet(editor, cx) {
+            Some((_, language, _)) => Some(language),
+            None => None,
+        }
+    }
+
     pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
         let kernel_specifications = kernel_specifications(self.fs.clone());
         cx.spawn(|this, mut cx| async move {
@@ -336,6 +347,50 @@ impl RuntimePanel {
     }
 }
 
+pub enum SessionSupport {
+    ActiveSession(View<Session>),
+    Inactive(KernelSpecification),
+    RequiresSetup(String),
+    Unsupported,
+}
+
+impl RuntimePanel {
+    pub fn session(
+        &mut self,
+        editor: WeakView<Editor>,
+        cx: &mut ViewContext<Self>,
+    ) -> SessionSupport {
+        let entity_id = editor.entity_id();
+        let session = self.sessions.get(&entity_id).cloned();
+
+        match session {
+            Some(session) => SessionSupport::ActiveSession(session),
+            None => {
+                let language = self.language(editor, cx);
+                let language = match language {
+                    Some(language) => language,
+                    None => return SessionSupport::Unsupported,
+                };
+                // Check for kernelspec
+                let kernelspec = self.kernelspec(&language, cx);
+
+                match kernelspec {
+                    Some(kernelspec) => SessionSupport::Inactive(kernelspec),
+                    None => {
+                        let language: String = language.to_lowercase();
+                        // If no kernelspec but language is one of typescript, python, r, or julia
+                        // then we return RequiresSetup
+                        match language.as_str() {
+                            "typescript" | "python" => SessionSupport::RequiresSetup(language),
+                            _ => SessionSupport::Unsupported,
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
 impl Panel for RuntimePanel {
     fn persistent_name() -> &'static str {
         "RuntimePanel"

crates/repl/src/session.rs 🔗

@@ -22,11 +22,11 @@ use theme::{ActiveTheme, ThemeSettings};
 use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label};
 
 pub struct Session {
-    editor: WeakView<Editor>,
-    kernel: Kernel,
+    pub editor: WeakView<Editor>,
+    pub kernel: Kernel,
     blocks: HashMap<String, EditorBlock>,
-    messaging_task: Task<()>,
-    kernel_specification: KernelSpecification,
+    pub messaging_task: Task<()>,
+    pub kernel_specification: KernelSpecification,
 }
 
 struct EditorBlock {
@@ -310,7 +310,7 @@ impl Session {
         }
     }
 
-    fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
+    pub fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
         match &mut self.kernel {
             Kernel::RunningKernel(_kernel) => {
                 self.send(InterruptRequest {}.into(), cx).ok();
@@ -322,7 +322,7 @@ impl Session {
         }
     }
 
-    fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
+    pub fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
         let kernel = std::mem::replace(&mut self.kernel, Kernel::ShuttingDown);
 
         match kernel {

crates/ui/src/components/icon.rs 🔗

@@ -160,11 +160,11 @@ pub enum IconName {
     Font,
     FontSize,
     FontWeight,
-    Github,
-    GenericMinimize,
-    GenericMaximize,
     GenericClose,
+    GenericMaximize,
+    GenericMinimize,
     GenericRestore,
+    Github,
     Hash,
     HistoryRerun,
     Indicator,
@@ -194,6 +194,10 @@ pub enum IconName {
     PullRequest,
     Quote,
     Regex,
+    ReplPlay,
+    ReplOff,
+    ReplPause,
+    ReplNeutral,
     Replace,
     ReplaceAll,
     ReplaceNext,
@@ -231,12 +235,12 @@ pub enum IconName {
     Trash,
     TriangleRight,
     Update,
+    Visible,
     WholeWord,
     XCircle,
     ZedAssistant,
     ZedAssistantFilled,
     ZedXCopilot,
-    Visible,
 }
 
 impl IconName {
@@ -308,11 +312,11 @@ impl IconName {
             IconName::Font => "icons/font.svg",
             IconName::FontSize => "icons/font_size.svg",
             IconName::FontWeight => "icons/font_weight.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::GenericMaximize => "icons/generic_maximize.svg",
+            IconName::GenericMinimize => "icons/generic_minimize.svg",
             IconName::GenericRestore => "icons/generic_restore.svg",
+            IconName::Github => "icons/github.svg",
             IconName::Hash => "icons/hash.svg",
             IconName::HistoryRerun => "icons/history_rerun.svg",
             IconName::Indicator => "icons/indicator.svg",
@@ -342,6 +346,10 @@ 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",
@@ -379,12 +387,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",
         }
     }
 }

docs/src/SUMMARY.md 🔗

@@ -22,6 +22,7 @@
 - [Collaboration](./collaboration.md)
 - [Tasks](./tasks.md)
 - [Remote Development](./remote-development.md)
+- [Repl](./repl.md)
 
 # Language Support
 

docs/src/repl.md 🔗

@@ -0,0 +1,72 @@
+# REPL
+
+Read. Eval. Print. Loop.
+
+<div class="warning">
+
+This feature is in active development. Details may change. We're delighted to get feedback as the REPL feature evolves.
+
+</div>
+
+
+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:
+
+```json
+{
+  "jupyter": {
+    "enabled": true
+  }
+}
+```
+
+After that, install any of the supported kernels:
+
+* [Python](#python)
+* [TypeScript via Deno](#deno)
+
+## Python
+
+### Global environment
+
+To setup your current python to have an available kernel, run:
+
+```
+python -m ipykernel install --user
+```
+
+### Conda Environment
+
+```
+source activate myenv
+conda install ipykernel
+python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
+```
+
+
+### Virtualenv with pip
+
+```
+source activate myenv
+pip install ipykernel
+python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
+```
+
+## Deno
+
+[Install Deno](https://docs.deno.com/runtime/manual/getting_started/installation/) and then install the Deno jupyter kernel:
+
+```
+deno jupyter --unstable --install
+```
+
+## Other languages
+
+* [Julia](https://github.com/JuliaLang/IJulia.jl)
+* R
+  - [Ark Kernel from Positron, formerly RStudio](https://github.com/posit-dev/ark)
+  - [Xeus-R](https://github.com/jupyter-xeus/xeus-r)
+* [Scala](https://almond.sh/docs/quick-start-install)