Finely scope repl events for runs and output clearing (#13872)

Sets up the `cmd-enter` keybinding for the jupyter repl to only apply
when enabled.

Release Notes:

- N/A

---------

Co-authored-by: Kirill <kirill@zed.dev>
This commit is contained in:
Kyle Kelley 2024-07-05 13:38:44 -07:00 committed by GitHub
parent 750df6c93d
commit 1c1fd6aaa1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 183 additions and 100 deletions

View file

@ -171,7 +171,6 @@
"enter": "editor::Newline", "enter": "editor::Newline",
"shift-enter": "editor::Newline", "shift-enter": "editor::Newline",
"cmd-shift-enter": "editor::NewlineAbove", "cmd-shift-enter": "editor::NewlineAbove",
"cmd-enter": "editor::NewlineBelow",
"alt-z": "editor::ToggleSoftWrap", "alt-z": "editor::ToggleSoftWrap",
"cmd-f": "buffer_search::Deploy", "cmd-f": "buffer_search::Deploy",
"cmd-alt-f": [ "cmd-alt-f": [
@ -197,6 +196,12 @@
"cmd-alt-e": "editor::SelectEnclosingSymbol" "cmd-alt-e": "editor::SelectEnclosingSymbol"
} }
}, },
{
"context": "Editor && mode == full && !jupyter",
"bindings": {
"cmd-enter": "editor::NewlineBelow"
}
},
{ {
"context": "Editor && mode == full && inline_completion", "context": "Editor && mode == full && inline_completion",
"bindings": { "bindings": {
@ -637,6 +642,12 @@
"space": "project_panel::Open" "space": "project_panel::Open"
} }
}, },
{
"context": "Editor && jupyter",
"bindings": {
"cmd-enter": "repl::Run"
}
},
{ {
"context": "CollabPanel && not_editing", "context": "CollabPanel && not_editing",
"bindings": { "bindings": {

View file

@ -1927,6 +1927,11 @@ impl Editor {
EditorMode::AutoHeight { .. } => "auto_height", EditorMode::AutoHeight { .. } => "auto_height",
EditorMode::Full => "full", EditorMode::Full => "full",
}; };
if EditorSettings::get_global(cx).jupyter.enabled {
key_context.add("jupyter");
}
key_context.set("mode", mode); key_context.set("mode", mode);
if self.pending_rename.is_some() { if self.pending_rename.is_some() {
key_context.add("renaming"); key_context.add("renaming");

View file

@ -25,6 +25,8 @@ pub struct EditorSettings {
pub expand_excerpt_lines: u32, pub expand_excerpt_lines: u32,
#[serde(default)] #[serde(default)]
pub double_click_in_multibuffer: DoubleClickInMultibuffer, pub double_click_in_multibuffer: DoubleClickInMultibuffer,
#[serde(default)]
pub jupyter: Jupyter,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@ -64,6 +66,15 @@ pub enum DoubleClickInMultibuffer {
Open, Open,
} }
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct Jupyter {
/// Whether the Jupyter feature is enabled.
///
/// Default: `false`
pub enabled: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Toolbar { pub struct Toolbar {
pub breadcrumbs: bool, pub breadcrumbs: bool,
@ -217,6 +228,9 @@ pub struct EditorSettingsContent {
/// ///
/// Default: select /// Default: select
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>, pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
/// Jupyter REPL settings.
pub jupyter: Option<Jupyter>,
} }
// Toolbar related settings // Toolbar related settings

View file

@ -35,6 +35,7 @@ theme.workspace = true
terminal_view.workspace = true terminal_view.workspace = true
ui.workspace = true ui.workspace = true
uuid.workspace = true uuid.workspace = true
util.workspace = true
workspace.workspace = true workspace.workspace = true
[dev-dependencies] [dev-dependencies]

View file

@ -1,5 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use editor::EditorSettings;
use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources};
@ -16,18 +18,22 @@ pub enum JupyterDockPosition {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct JupyterSettings { pub struct JupyterSettings {
pub enabled: bool,
pub dock: JupyterDockPosition, pub dock: JupyterDockPosition,
pub default_width: Pixels, pub default_width: Pixels,
pub kernel_selections: HashMap<String, String>, pub kernel_selections: HashMap<String, String>,
} }
impl JupyterSettings {
pub fn enabled(cx: &AppContext) -> bool {
// In order to avoid a circular dependency between `editor` and `repl` crates,
// we put the `enable` flag on its settings.
// This allows the editor to set up context for key bindings/actions.
EditorSettings::get_global(cx).jupyter.enabled
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] #[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
pub struct JupyterSettingsContent { pub struct JupyterSettingsContent {
/// Whether the Jupyter feature is enabled.
///
/// Default: `false`
enabled: Option<bool>,
/// Where to dock the Jupyter panel. /// Where to dock the Jupyter panel.
/// ///
/// Default: `right` /// Default: `right`
@ -51,7 +57,6 @@ impl JupyterSettingsContent {
impl Default for JupyterSettingsContent { impl Default for JupyterSettingsContent {
fn default() -> Self { fn default() -> Self {
JupyterSettingsContent { JupyterSettingsContent {
enabled: Some(false),
dock: Some(JupyterDockPosition::Right), dock: Some(JupyterDockPosition::Right),
default_width: Some(640.0), default_width: Some(640.0),
kernel_selections: Some(HashMap::new()), kernel_selections: Some(HashMap::new()),
@ -74,9 +79,6 @@ impl Settings for JupyterSettings {
let mut settings = JupyterSettings::default(); let mut settings = JupyterSettings::default();
for value in sources.defaults_and_customizations() { for value in sources.defaults_and_customizations() {
if let Some(enabled) = value.enabled {
settings.enabled = enabled;
}
if let Some(dock) = value.dock { if let Some(dock) = value.dock {
settings.dock = dock; settings.dock = dock;
} }
@ -108,9 +110,10 @@ mod tests {
let store = settings::SettingsStore::test(cx); let store = settings::SettingsStore::test(cx);
cx.set_global(store); cx.set_global(store);
EditorSettings::register(cx);
JupyterSettings::register(cx); JupyterSettings::register(cx);
assert_eq!(JupyterSettings::get_global(cx).enabled, false); assert_eq!(JupyterSettings::enabled(cx), false);
assert_eq!( assert_eq!(
JupyterSettings::get_global(cx).dock, JupyterSettings::get_global(cx).dock,
JupyterDockPosition::Right JupyterDockPosition::Right
@ -136,7 +139,7 @@ mod tests {
.unwrap(); .unwrap();
}); });
assert_eq!(JupyterSettings::get_global(cx).enabled, true); assert_eq!(JupyterSettings::enabled(cx), true);
assert_eq!( assert_eq!(
JupyterSettings::get_global(cx).dock, JupyterSettings::get_global(cx).dock,
JupyterDockPosition::Left JupyterDockPosition::Left

View file

@ -160,7 +160,11 @@ impl RunningKernel {
kernel_name: Some(format!("zed-{}", kernel_specification.name)), kernel_name: Some(format!("zed-{}", kernel_specification.name)),
}; };
let connection_path = dirs::runtime_dir().join(format!("kernel-zed-{entity_id}.json")); let runtime_dir = dirs::runtime_dir();
fs.create_dir(&runtime_dir)
.await
.with_context(|| format!("Failed to create jupyter runtime dir {runtime_dir:?}"))?;
let connection_path = runtime_dir.join(format!("kernel-zed-{entity_id}.json"));
let content = serde_json::to_string(&connection_info)?; let content = serde_json::to_string(&connection_info)?;
// write out file to disk for kernel // write out file to disk for kernel
fs.atomic_write(connection_path.clone(), content).await?; fs.atomic_write(connection_path.clone(), content).await?;

View file

@ -6,31 +6,31 @@ use crate::{
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use collections::HashMap; use collections::HashMap;
use editor::{Anchor, Editor, RangeToAnchorExt}; use editor::{Anchor, Editor, RangeToAnchorExt};
use futures::StreamExt as _;
use gpui::{ use gpui::{
actions, prelude::*, AppContext, AsyncWindowContext, Entity, EntityId, EventEmitter, actions, prelude::*, AppContext, AsyncWindowContext, EntityId, EventEmitter, FocusHandle,
FocusHandle, FocusOutEvent, FocusableView, Subscription, Task, View, WeakView, FocusOutEvent, FocusableView, Subscription, Task, View, WeakView,
}; };
use language::Point; use language::Point;
use project::Fs; use project::Fs;
use settings::{Settings as _, SettingsStore}; use settings::{Settings as _, SettingsStore};
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding}; use ui::{prelude::*, ButtonLike, ElevationIndex, KeyBinding};
use util::ResultExt as _;
use workspace::{ use workspace::{
dock::{Panel, PanelEvent}, dock::{Panel, PanelEvent},
Workspace, Workspace,
}; };
actions!(repl, [Run, ToggleFocus, ClearOutputs]); actions!(repl, [Run, ClearOutputs]);
actions!(repl_panel, [ToggleFocus]);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.observe_new_views( cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| { |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace workspace.register_action(|workspace, _: &ToggleFocus, cx| {
.register_action(|workspace, _: &ToggleFocus, cx| { workspace.toggle_panel_focus::<RuntimePanel>(cx);
workspace.toggle_panel_focus::<RuntimePanel>(cx); });
})
.register_action(run)
.register_action(clear_outputs);
}, },
) )
.detach(); .detach();
@ -44,6 +44,12 @@ pub struct RuntimePanel {
sessions: HashMap<EntityId, View<Session>>, sessions: HashMap<EntityId, View<Session>>,
kernel_specifications: Vec<KernelSpecification>, kernel_specifications: Vec<KernelSpecification>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
_editor_events_task: Task<()>,
}
pub enum ReplEvent {
Run(WeakView<Editor>),
ClearOutputs(WeakView<Editor>),
} }
impl RuntimePanel { impl RuntimePanel {
@ -58,26 +64,82 @@ impl RuntimePanel {
let fs = workspace.app_state().fs.clone(); 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![ let subscriptions = vec![
cx.on_focus_in(&focus_handle, Self::focus_in), cx.on_focus_in(&focus_handle, Self::focus_in),
cx.on_focus_out(&focus_handle, Self::focus_out), cx.on_focus_out(&focus_handle, Self::focus_out),
cx.observe_global::<SettingsStore>(move |this, cx| { cx.observe_global::<SettingsStore>(move |this, cx| {
let settings = JupyterSettings::get_global(cx); this.set_enabled(JupyterSettings::enabled(cx), cx);
this.set_enabled(settings.enabled, 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();
},
),
]; ];
let enabled = JupyterSettings::get_global(cx).enabled; // 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);
}
})
.ok();
}
},
);
Self { let runtime_panel = Self {
fs, fs: fs.clone(),
width: None, width: None,
focus_handle, focus_handle,
kernel_specifications: Vec::new(), kernel_specifications: Vec::new(),
sessions: Default::default(), sessions: Default::default(),
_subscriptions: subscriptions, _subscriptions: subscriptions,
enabled, enabled: JupyterSettings::enabled(cx),
} _editor_events_task,
};
runtime_panel
}) })
})?; })?;
@ -152,9 +214,11 @@ impl RuntimePanel {
pub fn snippet( pub fn snippet(
&self, &self,
editor: View<Editor>, editor: WeakView<Editor>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<(String, Arc<str>, Range<Anchor>)> { ) -> Option<(String, Arc<str>, Range<Anchor>)> {
let editor = editor.upgrade()?;
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx); let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
let anchor_range = self.selection(editor, cx); let anchor_range = self.selection(editor, cx);
@ -214,8 +278,7 @@ impl RuntimePanel {
pub fn run( pub fn run(
&mut self, &mut self,
editor: View<Editor>, editor: WeakView<Editor>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if !self.enabled { if !self.enabled {
@ -234,7 +297,8 @@ impl RuntimePanel {
.with_context(|| format!("No kernel found for language: {language_name}"))?; .with_context(|| format!("No kernel found for language: {language_name}"))?;
let session = self.sessions.entry(entity_id).or_insert_with(|| { let session = self.sessions.entry(entity_id).or_insert_with(|| {
let view = cx.new_view(|cx| Session::new(editor, fs.clone(), kernel_specification, cx)); let view =
cx.new_view(|cx| Session::new(editor, self.fs.clone(), kernel_specification, cx));
cx.notify(); cx.notify();
let subscription = cx.subscribe( let subscription = cx.subscribe(
@ -261,7 +325,7 @@ impl RuntimePanel {
anyhow::Ok(()) anyhow::Ok(())
} }
pub fn clear_outputs(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) { pub fn clear_outputs(&mut self, editor: WeakView<Editor>, cx: &mut ViewContext<Self>) {
let entity_id = editor.entity_id(); let entity_id = editor.entity_id();
if let Some(session) = self.sessions.get_mut(&entity_id) { if let Some(session) = self.sessions.get_mut(&entity_id) {
session.update(cx, |session, cx| { session.update(cx, |session, cx| {
@ -272,42 +336,6 @@ impl RuntimePanel {
} }
} }
pub fn run(workspace: &mut Workspace, _: &Run, cx: &mut ViewContext<Workspace>) {
let settings = JupyterSettings::get_global(cx);
if !settings.enabled {
return;
}
let editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx));
if let (Some(editor), Some(runtime_panel)) = (editor, workspace.panel::<RuntimePanel>(cx)) {
runtime_panel.update(cx, |runtime_panel, cx| {
runtime_panel
.run(editor, workspace.app_state().fs.clone(), cx)
.ok();
});
}
}
pub fn clear_outputs(workspace: &mut Workspace, _: &ClearOutputs, cx: &mut ViewContext<Workspace>) {
let settings = JupyterSettings::get_global(cx);
if !settings.enabled {
return;
}
let editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx));
if let (Some(editor), Some(runtime_panel)) = (editor, workspace.panel::<RuntimePanel>(cx)) {
runtime_panel.update(cx, |runtime_panel, cx| {
runtime_panel.clear_outputs(editor, cx);
});
}
}
impl Panel for RuntimePanel { impl Panel for RuntimePanel {
fn persistent_name() -> &'static str { fn persistent_name() -> &'static str {
"RuntimePanel" "RuntimePanel"

View file

@ -10,7 +10,7 @@ use editor::{
Anchor, AnchorRangeExt as _, Editor, Anchor, AnchorRangeExt as _, Editor,
}; };
use futures::{FutureExt as _, StreamExt as _}; use futures::{FutureExt as _, StreamExt as _};
use gpui::{div, prelude::*, Entity, EventEmitter, Render, Task, View, ViewContext}; use gpui::{div, prelude::*, EventEmitter, Render, Task, View, ViewContext, WeakView};
use project::Fs; use project::Fs;
use runtimelib::{ use runtimelib::{
ExecuteRequest, InterruptRequest, JupyterMessage, JupyterMessageContent, KernelInfoRequest, ExecuteRequest, InterruptRequest, JupyterMessage, JupyterMessageContent, KernelInfoRequest,
@ -22,16 +22,15 @@ use theme::{ActiveTheme, ThemeSettings};
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label}; use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label};
pub struct Session { pub struct Session {
editor: View<Editor>, editor: WeakView<Editor>,
kernel: Kernel, kernel: Kernel,
blocks: HashMap<String, EditorBlock>, blocks: HashMap<String, EditorBlock>,
messaging_task: Task<()>, messaging_task: Task<()>,
kernel_specification: KernelSpecification, kernel_specification: KernelSpecification,
} }
#[derive(Debug)]
struct EditorBlock { struct EditorBlock {
editor: View<Editor>, editor: WeakView<Editor>,
code_range: Range<Anchor>, code_range: Range<Anchor>,
block_id: BlockId, block_id: BlockId,
execution_view: View<ExecutionView>, execution_view: View<ExecutionView>,
@ -39,11 +38,11 @@ struct EditorBlock {
impl EditorBlock { impl EditorBlock {
fn new( fn new(
editor: View<Editor>, editor: WeakView<Editor>,
code_range: Range<Anchor>, code_range: Range<Anchor>,
status: ExecutionStatus, status: ExecutionStatus,
cx: &mut ViewContext<Session>, cx: &mut ViewContext<Session>,
) -> Self { ) -> anyhow::Result<Self> {
let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx)); let execution_view = cx.new_view(|cx| ExecutionView::new(status, cx));
let block_id = editor.update(cx, |editor, cx| { let block_id = editor.update(cx, |editor, cx| {
@ -56,14 +55,14 @@ impl EditorBlock {
}; };
editor.insert_blocks([block], None, cx)[0] editor.insert_blocks([block], None, cx)[0]
}); })?;
Self { anyhow::Ok(Self {
editor, editor,
code_range, code_range,
block_id, block_id,
execution_view, execution_view,
} })
} }
fn handle_message(&mut self, message: &JupyterMessage, cx: &mut ViewContext<Session>) { fn handle_message(&mut self, message: &JupyterMessage, cx: &mut ViewContext<Session>) {
@ -71,17 +70,19 @@ impl EditorBlock {
execution_view.push_message(&message.content, cx); execution_view.push_message(&message.content, cx);
}); });
self.editor.update(cx, |editor, cx| { self.editor
let mut replacements = HashMap::default(); .update(cx, |editor, cx| {
replacements.insert( let mut replacements = HashMap::default();
self.block_id, replacements.insert(
( self.block_id,
Some(self.execution_view.num_lines(cx).saturating_add(1)), (
Self::create_output_area_render(self.execution_view.clone()), Some(self.execution_view.num_lines(cx).saturating_add(1)),
), Self::create_output_area_render(self.execution_view.clone()),
); ),
editor.replace_blocks(replacements, None, cx); );
}) editor.replace_blocks(replacements, None, cx);
})
.ok();
} }
fn create_output_area_render(execution_view: View<ExecutionView>) -> RenderBlock { fn create_output_area_render(execution_view: View<ExecutionView>) -> RenderBlock {
@ -118,7 +119,7 @@ impl EditorBlock {
impl Session { impl Session {
pub fn new( pub fn new(
editor: View<Editor>, editor: WeakView<Editor>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
kernel_specification: KernelSpecification, kernel_specification: KernelSpecification,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
@ -200,14 +201,22 @@ impl Session {
let blocks_to_remove: HashSet<BlockId> = let blocks_to_remove: HashSet<BlockId> =
self.blocks.values().map(|block| block.block_id).collect(); self.blocks.values().map(|block| block.block_id).collect();
self.editor.update(cx, |editor, cx| { self.editor
editor.remove_blocks(blocks_to_remove, None, cx); .update(cx, |editor, cx| {
}); editor.remove_blocks(blocks_to_remove, None, cx);
})
.ok();
self.blocks.clear(); self.blocks.clear();
} }
pub fn execute(&mut self, code: &str, anchor_range: Range<Anchor>, cx: &mut ViewContext<Self>) { pub fn execute(&mut self, code: &str, anchor_range: Range<Anchor>, cx: &mut ViewContext<Self>) {
let editor = if let Some(editor) = self.editor.upgrade() {
editor
} else {
return;
};
let execute_request = ExecuteRequest { let execute_request = ExecuteRequest {
code: code.to_string(), code: code.to_string(),
..ExecuteRequest::default() ..ExecuteRequest::default()
@ -217,7 +226,7 @@ impl Session {
let mut blocks_to_remove: HashSet<BlockId> = HashSet::default(); let mut blocks_to_remove: HashSet<BlockId> = HashSet::default();
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx); let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
self.blocks.retain(|_key, block| { self.blocks.retain(|_key, block| {
if anchor_range.overlaps(&block.code_range, &buffer) { if anchor_range.overlaps(&block.code_range, &buffer) {
@ -228,9 +237,11 @@ impl Session {
} }
}); });
self.editor.update(cx, |editor, cx| { self.editor
editor.remove_blocks(blocks_to_remove, None, cx); .update(cx, |editor, cx| {
}); editor.remove_blocks(blocks_to_remove, None, cx);
})
.ok();
let status = match &self.kernel { let status = match &self.kernel {
Kernel::RunningKernel(_) => ExecutionStatus::Queued, Kernel::RunningKernel(_) => ExecutionStatus::Queued,
@ -240,7 +251,13 @@ impl Session {
Kernel::Shutdown => ExecutionStatus::Shutdown, Kernel::Shutdown => ExecutionStatus::Shutdown,
}; };
let editor_block = EditorBlock::new(self.editor.clone(), anchor_range, status, cx); let editor_block = if let Ok(editor_block) =
EditorBlock::new(self.editor.clone(), anchor_range, status, cx)
{
editor_block
} else {
return;
};
self.blocks self.blocks
.insert(message.header.msg_id.clone(), editor_block); .insert(message.header.msg_id.clone(), editor_block);
@ -341,7 +358,7 @@ impl Session {
} }
pub enum SessionEvent { pub enum SessionEvent {
Shutdown(View<Editor>), Shutdown(WeakView<Editor>),
} }
impl EventEmitter<SessionEvent> for Session {} impl EventEmitter<SessionEvent> for Session {}