repl: Refactor editor registration (#14819)

Kyle Kelley and Conrad created

Cleans up action registration with the editors and also fixes a major
bug where only one workspace's panel was getting session info (due to my
not understanding that `cx.observe_new_views` is for the whole app).

Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>

Change summary

crates/repl/src/runtime_panel.rs | 202 ++++++++++++++++-----------------
1 file changed, 96 insertions(+), 106 deletions(-)

Detailed changes

crates/repl/src/runtime_panel.rs 🔗

@@ -6,7 +6,6 @@ use crate::{
 use anyhow::{Context as _, Result};
 use collections::HashMap;
 use editor::{Anchor, Editor, RangeToAnchorExt};
-use futures::StreamExt as _;
 use gpui::{
     actions, prelude::*, AppContext, AsyncWindowContext, EntityId, EventEmitter, FocusHandle,
     FocusOutEvent, FocusableView, Subscription, Task, View, WeakView,
@@ -46,6 +45,102 @@ pub fn init(cx: &mut AppContext) {
         },
     )
     .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 {
@@ -56,14 +151,6 @@ pub struct RuntimePanel {
     sessions: HashMap<EntityId, View<Session>>,
     kernel_specifications: Vec<KernelSpecification>,
     _subscriptions: Vec<Subscription>,
-    _editor_events_task: Task<()>,
-}
-
-pub enum ReplEvent {
-    Run(WeakView<Editor>),
-    ClearOutputs(WeakView<Editor>),
-    Interrupt(WeakView<Editor>),
-    Shutdown(WeakView<Editor>),
 }
 
 impl RuntimePanel {
@@ -78,110 +165,14 @@ impl RuntimePanel {
 
                     let fs = workspace.app_state().fs.clone();
 
-                    // Make a channel that we receive editor events on (for repl::Run, repl::ClearOutputs)
-                    // This allows us to inject actions on the editor from the repl panel without requiring the editor to
-                    // depend on the `repl` crate.
-                    let (repl_editor_event_tx, mut repl_editor_event_rx) =
-                        futures::channel::mpsc::unbounded::<ReplEvent>();
-
                     let subscriptions = vec![
                         cx.on_focus_in(&focus_handle, Self::focus_in),
                         cx.on_focus_out(&focus_handle, Self::focus_out),
                         cx.observe_global::<SettingsStore>(move |this, cx| {
                             this.set_enabled(JupyterSettings::enabled(cx), cx);
                         }),
-                        cx.observe_new_views(
-                            move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
-                                let editor_view = cx.view().downgrade();
-                                let run_event_tx = repl_editor_event_tx.clone();
-                                let clear_event_tx = repl_editor_event_tx.clone();
-                                editor
-                                    .register_action(move |_: &Run, cx: &mut WindowContext| {
-                                        if !JupyterSettings::enabled(cx) {
-                                            return;
-                                        }
-                                        run_event_tx
-                                            .unbounded_send(ReplEvent::Run(editor_view.clone()))
-                                            .ok();
-                                    })
-                                    .detach();
-
-                                let editor_view = cx.view().downgrade();
-                                editor
-                                    .register_action(
-                                        move |_: &ClearOutputs, cx: &mut WindowContext| {
-                                            if !JupyterSettings::enabled(cx) {
-                                                return;
-                                            }
-                                            clear_event_tx
-                                                .unbounded_send(ReplEvent::ClearOutputs(
-                                                    editor_view.clone(),
-                                                ))
-                                                .ok();
-                                        },
-                                    )
-                                    .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();
-                            },
-                        ),
                     ];
 
-                    // Listen for events from the editor on the `repl_editor_event_rx` channel
-                    let _editor_events_task = cx.spawn(
-                        move |this: WeakView<RuntimePanel>, mut cx: AsyncWindowContext| async move {
-                            while let Some(event) = repl_editor_event_rx.next().await {
-                                this.update(&mut cx, |runtime_panel, cx| match event {
-                                    ReplEvent::Run(editor) => {
-                                        runtime_panel.run(editor, cx).log_err();
-                                    }
-                                    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();
-                            }
-                        },
-                    );
-
                     let runtime_panel = Self {
                         fs: fs.clone(),
                         width: None,
@@ -190,7 +181,6 @@ impl RuntimePanel {
                         sessions: Default::default(),
                         _subscriptions: subscriptions,
                         enabled: JupyterSettings::enabled(cx),
-                        _editor_events_task,
                     };
 
                     runtime_panel