mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
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:
parent
f2ef43429f
commit
fd667f6373
6 changed files with 250 additions and 119 deletions
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue