repl: Add notebook command/edit modes and keybindings (#51194)

- Introducing NotebookMode state and handlers (EnterEditMode,
EnterCommandMode, RunAndAdvance).
- Wire up UI to switch modes,focus editors appropriately, and advance
selection while in command mode.
- Update default and vim keymaps to use the new bindings (shift-enter
runs+advances, escape enters command mode,
and enter/up/down work in command mode).
- Reveal and Focus the new Cell when inserted 

Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- N/A
This commit is contained in:
MostlyK 2026-04-08 02:28:03 +05:30 committed by GitHub
parent f2ef43429f
commit fd667f6373
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 250 additions and 119 deletions

View file

@ -1436,7 +1436,7 @@
{ {
"context": "NotebookEditor", "context": "NotebookEditor",
"bindings": { "bindings": {
"shift-enter": "notebook::Run", "shift-enter": "notebook::RunAndAdvance",
"ctrl-enter": "notebook::Run", "ctrl-enter": "notebook::Run",
"ctrl-shift-enter": "notebook::RunAll", "ctrl-shift-enter": "notebook::RunAll",
"alt-up": "notebook::MoveCellUp", "alt-up": "notebook::MoveCellUp",
@ -1447,11 +1447,19 @@
"ctrl-c": "notebook::InterruptKernel", "ctrl-c": "notebook::InterruptKernel",
}, },
}, },
{
"context": "NotebookEditor && notebook_mode == command",
"bindings": {
"enter": "notebook::EnterEditMode",
"down": "menu::SelectNext",
"up": "menu::SelectPrevious",
},
},
{ {
"context": "NotebookEditor > Editor", "context": "NotebookEditor > Editor",
"bindings": { "bindings": {
"enter": "editor::Newline", "enter": "editor::Newline",
"shift-enter": "notebook::Run", "shift-enter": "notebook::RunAndAdvance",
"ctrl-enter": "notebook::Run", "ctrl-enter": "notebook::Run",
"ctrl-shift-enter": "notebook::RunAll", "ctrl-shift-enter": "notebook::RunAll",
"alt-up": "notebook::MoveCellUp", "alt-up": "notebook::MoveCellUp",
@ -1460,6 +1468,7 @@
"ctrl-shift-m": "notebook::AddMarkdownBlock", "ctrl-shift-m": "notebook::AddMarkdownBlock",
"ctrl-shift-r": "notebook::RestartKernel", "ctrl-shift-r": "notebook::RestartKernel",
"ctrl-c": "notebook::InterruptKernel", "ctrl-c": "notebook::InterruptKernel",
"escape": "notebook::EnterCommandMode",
}, },
}, },
{ {

View file

@ -1572,7 +1572,7 @@
{ {
"context": "NotebookEditor", "context": "NotebookEditor",
"bindings": { "bindings": {
"shift-enter": "notebook::Run", "shift-enter": "notebook::RunAndAdvance",
"cmd-enter": "notebook::Run", "cmd-enter": "notebook::Run",
"cmd-shift-enter": "notebook::RunAll", "cmd-shift-enter": "notebook::RunAll",
"alt-up": "notebook::MoveCellUp", "alt-up": "notebook::MoveCellUp",
@ -1583,11 +1583,19 @@
"cmd-c": "notebook::InterruptKernel", "cmd-c": "notebook::InterruptKernel",
}, },
}, },
{
"context": "NotebookEditor && notebook_mode == command",
"bindings": {
"enter": "notebook::EnterEditMode",
"down": "menu::SelectNext",
"up": "menu::SelectPrevious",
},
},
{ {
"context": "NotebookEditor > Editor", "context": "NotebookEditor > Editor",
"bindings": { "bindings": {
"enter": "editor::Newline", "enter": "editor::Newline",
"shift-enter": "notebook::Run", "shift-enter": "notebook::RunAndAdvance",
"cmd-enter": "notebook::Run", "cmd-enter": "notebook::Run",
"cmd-shift-enter": "notebook::RunAll", "cmd-shift-enter": "notebook::RunAll",
"alt-up": "notebook::MoveCellUp", "alt-up": "notebook::MoveCellUp",
@ -1596,6 +1604,7 @@
"cmd-shift-m": "notebook::AddMarkdownBlock", "cmd-shift-m": "notebook::AddMarkdownBlock",
"cmd-shift-r": "notebook::RestartKernel", "cmd-shift-r": "notebook::RestartKernel",
"cmd-c": "notebook::InterruptKernel", "cmd-c": "notebook::InterruptKernel",
"escape": "notebook::EnterCommandMode",
}, },
}, },
] ]

View file

@ -1488,7 +1488,7 @@
{ {
"context": "NotebookEditor", "context": "NotebookEditor",
"bindings": { "bindings": {
"shift-enter": "notebook::Run", "shift-enter": "notebook::RunAndAdvance",
"ctrl-enter": "notebook::Run", "ctrl-enter": "notebook::Run",
"ctrl-shift-enter": "notebook::RunAll", "ctrl-shift-enter": "notebook::RunAll",
"alt-up": "notebook::MoveCellUp", "alt-up": "notebook::MoveCellUp",
@ -1499,11 +1499,19 @@
"ctrl-c": "notebook::InterruptKernel", "ctrl-c": "notebook::InterruptKernel",
}, },
}, },
{
"context": "NotebookEditor && notebook_mode == command",
"bindings": {
"enter": "notebook::EnterEditMode",
"down": "menu::SelectNext",
"up": "menu::SelectPrevious",
},
},
{ {
"context": "NotebookEditor > Editor", "context": "NotebookEditor > Editor",
"bindings": { "bindings": {
"enter": "editor::Newline", "enter": "editor::Newline",
"shift-enter": "notebook::Run", "shift-enter": "notebook::RunAndAdvance",
"ctrl-enter": "notebook::Run", "ctrl-enter": "notebook::Run",
"ctrl-shift-enter": "notebook::RunAll", "ctrl-shift-enter": "notebook::RunAll",
"alt-up": "notebook::MoveCellUp", "alt-up": "notebook::MoveCellUp",
@ -1512,6 +1520,7 @@
"ctrl-shift-m": "notebook::AddMarkdownBlock", "ctrl-shift-m": "notebook::AddMarkdownBlock",
"ctrl-shift-r": "notebook::RestartKernel", "ctrl-shift-r": "notebook::RestartKernel",
"ctrl-c": "notebook::InterruptKernel", "ctrl-c": "notebook::InterruptKernel",
"escape": "notebook::EnterCommandMode",
}, },
}, },
] ]

View file

@ -1110,10 +1110,24 @@
}, },
{ {
"context": "NotebookEditor > Editor && VimControl && vim_mode == normal", "context": "NotebookEditor > Editor && VimControl && vim_mode == normal",
"bindings": { "bindings": {
"j": "notebook::NotebookMoveDown", "j": "notebook::NotebookMoveDown",
"k": "notebook::NotebookMoveUp", "k": "notebook::NotebookMoveUp",
"escape": "notebook::EnterCommandMode",
},
},
{
"context": "NotebookEditor && notebook_mode == command",
"bindings": {
"j": "menu::SelectNext",
"k": "menu::SelectPrevious",
"g g": "menu::SelectFirst",
"shift-g": "menu::SelectLast",
"i": "notebook::EnterEditMode",
"a": "notebook::EnterEditMode",
"enter": "notebook::EnterEditMode",
"shift-enter": "notebook::RunAndAdvance",
"ctrl-enter": "notebook::Run",
}, },
}, },
{ {

View file

@ -10,8 +10,8 @@ use feature_flags::{FeatureFlagAppExt as _, NotebookFeatureFlag};
use futures::FutureExt; use futures::FutureExt;
use futures::future::Shared; use futures::future::Shared;
use gpui::{ use gpui::{
AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, ListScrollEvent, ListState, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, KeyContext, ListScrollEvent,
Point, Task, actions, list, prelude::*, ListState, Point, Task, actions, list, prelude::*,
}; };
use jupyter_protocol::JupyterKernelspec; use jupyter_protocol::JupyterKernelspec;
use language::{Language, LanguageRegistry}; use language::{Language, LanguageRegistry};
@ -41,33 +41,18 @@ use picker::Picker;
use runtimelib::{ExecuteRequest, JupyterMessage, JupyterMessageContent}; use runtimelib::{ExecuteRequest, JupyterMessage, JupyterMessageContent};
use ui::PopoverMenuHandle; use ui::PopoverMenuHandle;
use zed_actions::editor::{MoveDown, MoveUp}; use zed_actions::editor::{MoveDown, MoveUp};
use zed_actions::notebook::{NotebookMoveDown, NotebookMoveUp}; use zed_actions::notebook::{
AddCodeBlock, AddMarkdownBlock, ClearOutputs, EnterCommandMode, EnterEditMode, InterruptKernel,
MoveCellDown, MoveCellUp, NotebookMoveDown, NotebookMoveUp, OpenNotebook, RestartKernel, Run,
RunAll, RunAndAdvance,
};
actions!( /// Whether the notebook is in command mode (navigating cells) or edit mode (editing a cell).
notebook, #[derive(Clone, Copy, PartialEq, Eq)]
[ pub(crate) enum NotebookMode {
/// Opens a Jupyter notebook file. Command,
OpenNotebook, Edit,
/// Runs all cells in the notebook. }
RunAll,
/// Runs the current cell.
Run,
/// Clears all cell outputs.
ClearOutputs,
/// Moves the current cell up.
MoveCellUp,
/// Moves the current cell down.
MoveCellDown,
/// Adds a new markdown cell.
AddMarkdownBlock,
/// Adds a new code cell.
AddCodeBlock,
/// Restarts the kernel.
RestartKernel,
/// Interrupts the current execution.
InterruptKernel,
]
);
pub(crate) const MAX_TEXT_BLOCK_WIDTH: f32 = 9999.0; pub(crate) const MAX_TEXT_BLOCK_WIDTH: f32 = 9999.0;
pub(crate) const SMALL_SPACING_SIZE: f32 = 8.0; pub(crate) const SMALL_SPACING_SIZE: f32 = 8.0;
@ -107,6 +92,7 @@ pub struct NotebookEditor {
remote_id: Option<ViewId>, remote_id: Option<ViewId>,
cell_list: ListState, cell_list: ListState,
notebook_mode: NotebookMode,
selected_cell_index: usize, selected_cell_index: usize,
cell_order: Vec<CellId>, cell_order: Vec<CellId>,
original_cell_order: Vec<CellId>, original_cell_order: Vec<CellId>,
@ -148,18 +134,9 @@ impl NotebookEditor {
match &cell_entity { match &cell_entity {
Cell::Code(code_cell) => { Cell::Code(code_cell) => {
let cell_id_for_focus = cell_id.clone(); let cell_id_for_focus = cell_id.clone();
cx.subscribe(code_cell, move |this, cell, event, cx| match event { cx.subscribe(code_cell, move |this, _cell, event, cx| match event {
CellEvent::Run(cell_id) => this.execute_cell(cell_id.clone(), cx), CellEvent::Run(cell_id) => this.execute_cell(cell_id.clone(), cx),
CellEvent::FocusedIn(_) => { CellEvent::FocusedIn(_) => this.select_cell_by_id(&cell_id_for_focus, cx),
if let Some(index) = this
.cell_order
.iter()
.position(|id| id == &cell_id_for_focus)
{
this.selected_cell_index = index;
cx.notify();
}
}
}) })
.detach(); .detach();
@ -167,20 +144,12 @@ impl NotebookEditor {
let editor = code_cell.read(cx).editor().clone(); let editor = code_cell.read(cx).editor().clone();
cx.subscribe(&editor, move |this, _editor, event, cx| { cx.subscribe(&editor, move |this, _editor, event, cx| {
if let editor::EditorEvent::Focused = event { if let editor::EditorEvent::Focused = event {
if let Some(index) = this this.select_cell_by_id(&cell_id_for_editor, cx);
.cell_order
.iter()
.position(|id| id == &cell_id_for_editor)
{
this.selected_cell_index = index;
cx.notify();
}
} }
}) })
.detach(); .detach();
} }
Cell::Markdown(markdown_cell) => { Cell::Markdown(markdown_cell) => {
let cell_id_for_focus = cell_id.clone();
cx.subscribe( cx.subscribe(
markdown_cell, markdown_cell,
move |_this, cell, event: &MarkdownCellEvent, cx| { move |_this, cell, event: &MarkdownCellEvent, cx| {
@ -206,14 +175,7 @@ impl NotebookEditor {
let editor = markdown_cell.read(cx).editor().clone(); let editor = markdown_cell.read(cx).editor().clone();
cx.subscribe(&editor, move |this, _editor, event, cx| { cx.subscribe(&editor, move |this, _editor, event, cx| {
if let editor::EditorEvent::Focused = event { if let editor::EditorEvent::Focused = event {
if let Some(index) = this this.select_cell_by_id(&cell_id_for_editor, cx);
.cell_order
.iter()
.position(|id| id == &cell_id_for_editor)
{
this.selected_cell_index = index;
cx.notify();
}
} }
}) })
.detach(); .detach();
@ -239,6 +201,7 @@ impl NotebookEditor {
notebook_language, notebook_language,
remote_id: None, remote_id: None,
cell_list, cell_list,
notebook_mode: NotebookMode::Command,
selected_cell_index: 0, selected_cell_index: 0,
cell_order: cell_order.clone(), cell_order: cell_order.clone(),
original_cell_order: cell_order.clone(), original_cell_order: cell_order.clone(),
@ -385,8 +348,7 @@ impl NotebookEditor {
let working_directory = self let working_directory = self
.project .project
.read(cx) .read(cx)
.worktrees(cx) .worktree_for_id(self.worktree_id, cx)
.next()
.map(|worktree| worktree.read(cx).abs_path().to_path_buf()) .map(|worktree| worktree.read(cx).abs_path().to_path_buf())
.unwrap_or_else(std::env::temp_dir); .unwrap_or_else(std::env::temp_dir);
let fs = self.project.read(cx).fs().clone(); let fs = self.project.read(cx).fs().clone();
@ -590,6 +552,31 @@ impl NotebookEditor {
} }
fn run_current_cell(&mut self, _: &Run, window: &mut Window, cx: &mut Context<Self>) { fn run_current_cell(&mut self, _: &Run, window: &mut Window, cx: &mut Context<Self>) {
let Some(cell_id) = self.cell_order.get(self.selected_cell_index).cloned() else {
return;
};
let Some(cell) = self.cell_map.get(&cell_id) else {
return;
};
match cell {
Cell::Code(_) => {
self.execute_cell(cell_id, cx);
}
Cell::Markdown(markdown_cell) => {
// for markdown, finish editing and move to next cell
let is_editing = markdown_cell.read(cx).is_editing();
if is_editing {
markdown_cell.update(cx, |cell, cx| {
cell.run(cx);
});
self.enter_command_mode(window, cx);
}
}
Cell::Raw(_) => {}
}
}
fn run_and_advance(&mut self, _: &RunAndAdvance, window: &mut Window, cx: &mut Context<Self>) {
if let Some(cell_id) = self.cell_order.get(self.selected_cell_index).cloned() { if let Some(cell_id) = self.cell_order.get(self.selected_cell_index).cloned() {
if let Some(cell) = self.cell_map.get(&cell_id) { if let Some(cell) = self.cell_map.get(&cell_id) {
match cell { match cell {
@ -597,25 +584,83 @@ impl NotebookEditor {
self.execute_cell(cell_id, cx); self.execute_cell(cell_id, cx);
} }
Cell::Markdown(markdown_cell) => { Cell::Markdown(markdown_cell) => {
// for markdown, finish editing and move to next cell if markdown_cell.read(cx).is_editing() {
let is_editing = markdown_cell.read(cx).is_editing();
if is_editing {
markdown_cell.update(cx, |cell, cx| { markdown_cell.update(cx, |cell, cx| {
cell.run(cx); cell.run(cx);
}); });
// move to the next cell
// Discussion can be done on this default implementation
self.move_to_next_cell(window, cx);
} }
} }
Cell::Raw(_) => {} Cell::Raw(_) => {}
} }
} }
} }
let is_last_cell = self.selected_cell_index == self.cell_count().saturating_sub(1);
if is_last_cell {
self.add_code_block(window, cx);
self.enter_command_mode(window, cx);
} else {
self.advance_in_command_mode(window, cx);
}
}
fn enter_edit_mode(&mut self, _: &EnterEditMode, window: &mut Window, cx: &mut Context<Self>) {
self.notebook_mode = NotebookMode::Edit;
if let Some(cell_id) = self.cell_order.get(self.selected_cell_index) {
if let Some(cell) = self.cell_map.get(cell_id) {
match cell {
Cell::Code(code_cell) => {
let editor = code_cell.read(cx).editor().clone();
window.focus(&editor.focus_handle(cx), cx);
}
Cell::Markdown(markdown_cell) => {
markdown_cell.update(cx, |cell, cx| {
cell.set_editing(true);
cx.notify();
});
let editor = markdown_cell.read(cx).editor().clone();
window.focus(&editor.focus_handle(cx), cx);
}
Cell::Raw(_) => {}
}
}
}
cx.notify();
}
fn enter_command_mode(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.notebook_mode = NotebookMode::Command;
self.focus_handle.focus(window, cx);
cx.notify();
}
fn handle_enter_command_mode(
&mut self,
_: &EnterCommandMode,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.enter_command_mode(window, cx);
}
/// Advances to the next cell while staying in command mode (used by RunAndAdvance and shift-enter).
fn advance_in_command_mode(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let count = self.cell_count();
if count == 0 {
return;
}
if self.selected_cell_index < count - 1 {
self.selected_cell_index += 1;
self.cell_list
.scroll_to_reveal_item(self.selected_cell_index);
}
self.notebook_mode = NotebookMode::Command;
self.focus_handle.focus(window, cx);
cx.notify();
} }
// Discussion can be done on this default implementation // Discussion can be done on this default implementation
/// Moves focus to the next cell, or creates a new code cell if at the end /// Moves focus to the next cell editor (used when already in edit mode).
fn move_to_next_cell(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn move_to_next_cell(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if !self.cell_order.is_empty() && self.selected_cell_index < self.cell_order.len() - 1 { if !self.cell_order.is_empty() && self.selected_cell_index < self.cell_order.len() - 1 {
self.selected_cell_index += 1; self.selected_cell_index += 1;
@ -666,6 +711,19 @@ impl NotebookEditor {
} }
} }
fn insert_cell_at_current_position(&mut self, cell_id: CellId, cell: Cell) {
let insert_index = if self.cell_order.is_empty() {
0
} else {
self.selected_cell_index + 1
};
self.cell_order.insert(insert_index, cell_id.clone());
self.cell_map.insert(cell_id, cell);
self.selected_cell_index = insert_index;
self.cell_list.splice(insert_index..insert_index, 1);
self.cell_list.scroll_to_reveal_item(insert_index);
}
fn add_markdown_block(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn add_markdown_block(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let new_cell_id: CellId = Uuid::new_v4().into(); let new_cell_id: CellId = Uuid::new_v4().into();
let languages = self.languages.clone(); let languages = self.languages.clone();
@ -683,16 +741,6 @@ impl NotebookEditor {
) )
}); });
let insert_index = if self.cell_order.is_empty() {
0
} else {
self.selected_cell_index + 1
};
self.cell_order.insert(insert_index, new_cell_id.clone());
self.cell_map
.insert(new_cell_id.clone(), Cell::Markdown(markdown_cell.clone()));
self.selected_cell_index = insert_index;
cx.subscribe( cx.subscribe(
&markdown_cell, &markdown_cell,
move |_this, cell, event: &MarkdownCellEvent, cx| match event { move |_this, cell, event: &MarkdownCellEvent, cx| match event {
@ -709,19 +757,19 @@ impl NotebookEditor {
let editor = markdown_cell.read(cx).editor().clone(); let editor = markdown_cell.read(cx).editor().clone();
cx.subscribe(&editor, move |this, _editor, event, cx| { cx.subscribe(&editor, move |this, _editor, event, cx| {
if let editor::EditorEvent::Focused = event { if let editor::EditorEvent::Focused = event {
if let Some(index) = this this.select_cell_by_id(&cell_id_for_editor, cx);
.cell_order
.iter()
.position(|id| id == &cell_id_for_editor)
{
this.selected_cell_index = index;
cx.notify();
}
} }
}) })
.detach(); .detach();
self.cell_list.reset(self.cell_order.len()); self.insert_cell_at_current_position(new_cell_id, Cell::Markdown(markdown_cell.clone()));
markdown_cell.update(cx, |cell, cx| {
cell.set_editing(true);
cx.notify();
});
let editor = markdown_cell.read(cx).editor().clone();
window.focus(&editor.focus_handle(cx), cx);
self.notebook_mode = NotebookMode::Edit;
cx.notify(); cx.notify();
} }
@ -742,25 +790,10 @@ impl NotebookEditor {
) )
}); });
let insert_index = if self.cell_order.is_empty() {
0
} else {
self.selected_cell_index + 1
};
self.cell_order.insert(insert_index, new_cell_id.clone());
self.cell_map
.insert(new_cell_id.clone(), Cell::Code(code_cell.clone()));
self.selected_cell_index = insert_index;
let cell_id_for_run = new_cell_id.clone(); let cell_id_for_run = new_cell_id.clone();
cx.subscribe(&code_cell, move |this, _cell, event, cx| match event { cx.subscribe(&code_cell, move |this, _cell, event, cx| match event {
CellEvent::Run(cell_id) => this.execute_cell(cell_id.clone(), cx), CellEvent::Run(cell_id) => this.execute_cell(cell_id.clone(), cx),
CellEvent::FocusedIn(_) => { CellEvent::FocusedIn(_) => this.select_cell_by_id(&cell_id_for_run, cx),
if let Some(index) = this.cell_order.iter().position(|id| id == &cell_id_for_run) {
this.selected_cell_index = index;
cx.notify();
}
}
}) })
.detach(); .detach();
@ -768,19 +801,15 @@ impl NotebookEditor {
let editor = code_cell.read(cx).editor().clone(); let editor = code_cell.read(cx).editor().clone();
cx.subscribe(&editor, move |this, _editor, event, cx| { cx.subscribe(&editor, move |this, _editor, event, cx| {
if let editor::EditorEvent::Focused = event { if let editor::EditorEvent::Focused = event {
if let Some(index) = this this.select_cell_by_id(&cell_id_for_editor, cx);
.cell_order
.iter()
.position(|id| id == &cell_id_for_editor)
{
this.selected_cell_index = index;
cx.notify();
}
} }
}) })
.detach(); .detach();
self.cell_list.reset(self.cell_order.len()); self.insert_cell_at_current_position(new_cell_id, Cell::Code(code_cell.clone()));
let editor = code_cell.read(cx).editor().clone();
window.focus(&editor.focus_handle(cx), cx);
self.notebook_mode = NotebookMode::Edit;
cx.notify(); cx.notify();
} }
@ -792,6 +821,14 @@ impl NotebookEditor {
self.selected_cell_index self.selected_cell_index
} }
fn select_cell_by_id(&mut self, cell_id: &CellId, cx: &mut Context<Self>) {
if let Some(index) = self.cell_order.iter().position(|id| id == cell_id) {
self.selected_cell_index = index;
self.notebook_mode = NotebookMode::Edit;
cx.notify();
}
}
pub fn set_selected_index( pub fn set_selected_index(
&mut self, &mut self,
index: usize, index: usize,
@ -1216,9 +1253,19 @@ impl NotebookEditor {
impl Render for NotebookEditor { impl Render for NotebookEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("NotebookEditor");
key_context.set(
"notebook_mode",
match self.notebook_mode {
NotebookMode::Command => "command",
NotebookMode::Edit => "edit",
},
);
v_flex() v_flex()
.size_full() .size_full()
.key_context("NotebookEditor") .key_context(key_context)
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.on_action(cx.listener(|this, _: &OpenNotebook, window, cx| { .on_action(cx.listener(|this, _: &OpenNotebook, window, cx| {
this.open_notebook(&OpenNotebook, window, cx) this.open_notebook(&OpenNotebook, window, cx)
@ -1229,6 +1276,9 @@ impl Render for NotebookEditor {
.on_action( .on_action(
cx.listener(|this, _: &Run, window, cx| this.run_current_cell(&Run, window, cx)), cx.listener(|this, _: &Run, window, cx| this.run_current_cell(&Run, window, cx)),
) )
.on_action(
cx.listener(|this, action, window, cx| this.run_and_advance(action, window, cx)),
)
.on_action(cx.listener(|this, _: &RunAll, window, cx| this.run_cells(window, cx))) .on_action(cx.listener(|this, _: &RunAll, window, cx| this.run_cells(window, cx)))
.on_action( .on_action(
cx.listener(|this, _: &MoveCellUp, window, cx| this.move_cell_up(window, cx)), cx.listener(|this, _: &MoveCellUp, window, cx| this.move_cell_up(window, cx)),
@ -1242,6 +1292,20 @@ impl Render for NotebookEditor {
.on_action( .on_action(
cx.listener(|this, _: &AddCodeBlock, window, cx| this.add_code_block(window, cx)), cx.listener(|this, _: &AddCodeBlock, window, cx| this.add_code_block(window, cx)),
) )
.on_action(
cx.listener(|this, action, window, cx| this.enter_edit_mode(action, window, cx)),
)
.on_action(cx.listener(|this, action, window, cx| {
this.handle_enter_command_mode(action, window, cx)
}))
.on_action(cx.listener(|this, action, window, cx| this.select_next(action, window, cx)))
.on_action(
cx.listener(|this, action, window, cx| this.select_previous(action, window, cx)),
)
.on_action(
cx.listener(|this, action, window, cx| this.select_first(action, window, cx)),
)
.on_action(cx.listener(|this, action, window, cx| this.select_last(action, window, cx)))
.on_action(cx.listener(|this, _: &MoveUp, window, cx| { .on_action(cx.listener(|this, _: &MoveUp, window, cx| {
this.select_previous(&menu::SelectPrevious, window, cx); this.select_previous(&menu::SelectPrevious, window, cx);
if let Some(cell_id) = this.cell_order.get(this.selected_cell_index) { if let Some(cell_id) = this.cell_order.get(this.selected_cell_index) {

View file

@ -795,10 +795,36 @@ pub mod notebook {
actions!( actions!(
notebook, notebook,
[ [
/// Move to down in cells /// Opens a Jupyter notebook file.
OpenNotebook,
/// Runs all cells in the notebook.
RunAll,
/// Runs the current cell and stays on it.
Run,
/// Runs the current cell and advances to the next cell.
RunAndAdvance,
/// Clears all cell outputs.
ClearOutputs,
/// Moves the current cell up.
MoveCellUp,
/// Moves the current cell down.
MoveCellDown,
/// Adds a new markdown cell.
AddMarkdownBlock,
/// Adds a new code cell.
AddCodeBlock,
/// Restarts the kernel.
RestartKernel,
/// Interrupts the current execution.
InterruptKernel,
/// Move down in cells.
NotebookMoveDown, NotebookMoveDown,
/// Move to up in cells /// Move up in cells.
NotebookMoveUp, NotebookMoveUp,
/// Enters the current cell's editor (edit mode).
EnterEditMode,
/// Exits the cell editor and returns to cell command mode.
EnterCommandMode,
] ]
); );
} }