Refactor repl context menu (#14587)

Kyle Kelley created

Change summary

crates/quick_action_bar/src/repl_menu.rs | 198 ++++++++++++++-----------
crates/repl/src/repl.rs                  |   2 
2 files changed, 112 insertions(+), 88 deletions(-)

Detailed changes

crates/quick_action_bar/src/repl_menu.rs 🔗

@@ -1,8 +1,9 @@
 use std::time::Duration;
 
-use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation};
+use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View};
 use repl::{
-    ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, SessionSupport,
+    ExecutionState, JupyterSettings, Kernel, KernelSpecification, KernelStatus, RuntimePanel,
+    Session, SessionSupport,
 };
 use ui::{
     prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu,
@@ -16,6 +17,22 @@ use crate::QuickActionBar;
 
 const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl";
 
+struct ReplMenuState {
+    tooltip: SharedString,
+    icon: IconName,
+    icon_color: Color,
+    icon_is_animating: bool,
+    popover_disabled: bool,
+    indicator: Option<Indicator>,
+
+    status: KernelStatus,
+    kernel_name: SharedString,
+    kernel_language: SharedString,
+    // TODO: Persist rotation state so the
+    // icon doesn't reset on every state change
+    // current_delta: Duration,
+}
+
 impl QuickActionBar {
     pub fn render_repl_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
         if !JupyterSettings::enabled(cx) {
@@ -50,7 +67,7 @@ impl QuickActionBar {
         });
 
         let session = match session {
-            SessionSupport::ActiveSession(session) => session.read(cx),
+            SessionSupport::ActiveSession(session) => session,
             SessionSupport::Inactive(spec) => {
                 let spec = *spec;
                 return self.render_repl_launch_menu(spec, cx);
@@ -61,101 +78,25 @@ impl QuickActionBar {
             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();
-
-        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(),
-                }
-            }
-        }
-
-        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(_) => 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 menu_state = session_state(session.clone(), cx);
 
         let id = "repl-menu".to_string();
 
         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 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 kernel_name = kernel_name.clone();
-                    let kernel_language = kernel_language.clone();
-                    let status = status.clone();
+
                     menu.when_else(
                         status.is_connected(),
                         |running| {
@@ -166,8 +107,8 @@ impl QuickActionBar {
                                         .child(
                                             Label::new(format!(
                                                 "kernel: {} ({})",
-                                                kernel_name.clone(),
-                                                kernel_language.clone()
+                                                menu_state.kernel_name.clone(),
+                                                menu_state.kernel_language.clone()
                                             ))
                                             .size(LabelSize::Small)
                                             .color(Color::Muted),
@@ -371,3 +312,86 @@ impl QuickActionBar {
         )
     }
 }
+
+fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
+    let session = session.read(cx);
+
+    let kernel_name: SharedString = session.kernel_specification.name.clone().into();
+    let kernel_language: SharedString = session
+        .kernel_specification
+        .kernelspec
+        .language
+        .clone()
+        .into();
+
+    let fill_fields = || {
+        ReplMenuState {
+            tooltip: "Nothing running".into(),
+            icon: IconName::ReplNeutral,
+            icon_color: Color::Default,
+            icon_is_animating: false,
+            popover_disabled: false,
+            indicator: None,
+            kernel_name: kernel_name.clone(),
+            kernel_language: kernel_language.clone(),
+            // todo!(): Technically not shutdown, but indeterminate
+            status: KernelStatus::Shutdown,
+            // current_delta: Duration::default(),
+        }
+    };
+
+    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)),
+                status: session.kernel.status(),
+                ..fill_fields()
+            },
+            ExecutionState::Busy => ReplMenuState {
+                tooltip: format!("Interrupt {} ({})", kernel_name, kernel_language).into(),
+                icon_is_animating: true,
+                popover_disabled: false,
+                indicator: None,
+                status: session.kernel.status(),
+                ..fill_fields()
+            },
+        },
+        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)),
+            status: session.kernel.status(),
+            ..fill_fields()
+        },
+        Kernel::ErroredLaunch(e) => ReplMenuState {
+            tooltip: format!("Error with kernel {}: {}", kernel_name, e).into(),
+            popover_disabled: false,
+            indicator: Some(Indicator::dot().color(Color::Error)),
+            status: session.kernel.status(),
+            ..fill_fields()
+        },
+        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)),
+            status: session.kernel.status(),
+            ..fill_fields()
+        },
+        Kernel::Shutdown => ReplMenuState {
+            tooltip: "Nothing running".into(),
+            icon: IconName::ReplNeutral,
+            icon_color: Color::Default,
+            icon_is_animating: false,
+            popover_disabled: false,
+            indicator: None,
+            status: KernelStatus::Shutdown,
+            ..fill_fields()
+        },
+    };
+
+    menu_state
+}

crates/repl/src/repl.rs 🔗

@@ -11,7 +11,7 @@ mod session;
 mod stdio;
 
 pub use jupyter_settings::JupyterSettings;
-pub use kernels::{Kernel, KernelSpecification};
+pub use kernels::{Kernel, KernelSpecification, KernelStatus};
 pub use runtime_panel::{ClearOutputs, Interrupt, Run, Shutdown};
 pub use runtime_panel::{RuntimePanel, SessionSupport};
 pub use runtimelib::ExecutionState;