Compare commits
6 Commits
scan-code
...
notebook--
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe6d10fc0 | ||
|
|
c1040f54e8 | ||
|
|
b09dc3ba6c | ||
|
|
4e55791e1c | ||
|
|
c9c581a77e | ||
|
|
40f948ad93 |
@@ -1,9 +1,14 @@
|
||||
#![allow(unused, dead_code)]
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
default,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use editor::{Editor, EditorMode, MultiBuffer};
|
||||
use futures::future::Shared;
|
||||
use gpui::{prelude::*, App, Entity, Hsla, Task, TextStyleRefinement};
|
||||
use gpui::{prelude::*, App, ClickEvent, Entity, Hsla, MouseDownEvent, Task, TextStyleRefinement};
|
||||
use language::{Buffer, Language, LanguageRegistry};
|
||||
use markdown_preview::{markdown_parser::parse_markdown, markdown_renderer::render_markdown_block};
|
||||
use nbformat::v4::{CellId, CellMetadata, CellType};
|
||||
@@ -75,6 +80,22 @@ impl Clickable for CellControl {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_cell_metadata() -> CellMetadata {
|
||||
CellMetadata {
|
||||
id: None,
|
||||
collapsed: None,
|
||||
scrolled: None,
|
||||
deletable: None,
|
||||
editable: None,
|
||||
format: None,
|
||||
name: None,
|
||||
tags: None,
|
||||
jupyter: None,
|
||||
execution: None,
|
||||
additional: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A notebook cell
|
||||
#[derive(Clone)]
|
||||
pub enum Cell {
|
||||
@@ -146,6 +167,11 @@ impl Cell {
|
||||
})
|
||||
};
|
||||
|
||||
let editor = cx.new(|cx| Editor::multi_line(window, cx));
|
||||
editor.update(cx, |this, cx| {
|
||||
this.set_text(source.clone(), window, cx);
|
||||
});
|
||||
|
||||
MarkdownCell {
|
||||
markdown_parsing_task,
|
||||
languages: languages.clone(),
|
||||
@@ -155,6 +181,10 @@ impl Cell {
|
||||
parsed_markdown: None,
|
||||
selected: false,
|
||||
cell_position: None,
|
||||
editing: false,
|
||||
editor,
|
||||
on_click: None,
|
||||
on_secondary_mouse_down: None,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -336,6 +366,69 @@ pub struct MarkdownCell {
|
||||
selected: bool,
|
||||
cell_position: Option<CellPosition>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
editing: bool,
|
||||
editor: Entity<Editor>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl MarkdownCell {
|
||||
pub fn new(
|
||||
id: CellId,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let selected = false;
|
||||
let cell_position = None;
|
||||
|
||||
let editor = cx.new(|cx| Editor::multi_line(window, cx));
|
||||
|
||||
MarkdownCell {
|
||||
id,
|
||||
metadata: default_cell_metadata(),
|
||||
source: "".to_string(),
|
||||
parsed_markdown: None,
|
||||
markdown_parsing_task: Task::ready(()),
|
||||
selected,
|
||||
cell_position,
|
||||
languages,
|
||||
editing: false,
|
||||
editor,
|
||||
on_click: None,
|
||||
on_secondary_mouse_down: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor
|
||||
.update(cx, |this, cx| this.set_text(text, window, cx));
|
||||
}
|
||||
|
||||
pub fn is_editing(&self) -> bool {
|
||||
self.editing
|
||||
}
|
||||
|
||||
pub fn editing(&mut self, editing: bool) -> &mut Self {
|
||||
self.editing = editing;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_secondary_mouse_down(
|
||||
mut self,
|
||||
handler: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_secondary_mouse_down = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderableCell for MarkdownCell {
|
||||
@@ -382,9 +475,10 @@ impl RenderableCell for MarkdownCell {
|
||||
|
||||
impl Render for MarkdownCell {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let Some(parsed) = self.parsed_markdown.as_ref() else {
|
||||
return div();
|
||||
};
|
||||
let parsed = self.parsed_markdown.as_ref();
|
||||
|
||||
let source_lines = self.source.lines();
|
||||
let source_line_count = source_lines.count();
|
||||
|
||||
let mut markdown_render_context =
|
||||
markdown_preview::markdown_renderer::RenderContext::new(None, window, cx);
|
||||
@@ -409,12 +503,36 @@ impl Render for MarkdownCell {
|
||||
.p_3()
|
||||
.font_ui(cx)
|
||||
.text_size(TextSize::Default.rems(cx))
|
||||
//
|
||||
.children(parsed.children.iter().map(|child| {
|
||||
div().relative().child(div().relative().child(
|
||||
render_markdown_block(child, &mut markdown_render_context),
|
||||
))
|
||||
})),
|
||||
.when(!self.is_editing(), |this| {
|
||||
this.map(|this| {
|
||||
if let Some(parsed) = parsed {
|
||||
this.children(parsed.children.iter().map(|child| {
|
||||
div().relative().child(div().relative().child(
|
||||
render_markdown_block(
|
||||
child,
|
||||
&mut markdown_render_context,
|
||||
),
|
||||
))
|
||||
}))
|
||||
} else {
|
||||
this.child(
|
||||
Label::new(
|
||||
"Empty markdown block. Double click to edit.",
|
||||
)
|
||||
.color(Color::Placeholder),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.when(self.is_editing(), |this| {
|
||||
this.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_h(window.line_height() * px(source_line_count as f32))
|
||||
.w_full()
|
||||
.child(self.editor.clone()),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
// TODO: Move base cell render into trait impl so we don't have to repeat this
|
||||
|
||||
@@ -9,8 +9,8 @@ use feature_flags::{FeatureFlagAppExt as _, NotebookFeatureFlag};
|
||||
use futures::future::Shared;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
actions, list, prelude::*, AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
ListScrollEvent, ListState, Point, Task,
|
||||
actions, list, prelude::*, AnyElement, App, ClickEvent, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, ListScrollEvent, ListState, Point, Task, WeakEntity,
|
||||
};
|
||||
use language::{Language, LanguageRegistry};
|
||||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
@@ -20,10 +20,10 @@ use workspace::searchable::SearchableItemHandle;
|
||||
use workspace::{Item, ItemHandle, ProjectItem, ToolbarItemLocation};
|
||||
use workspace::{ToolbarItemEvent, ToolbarItemView};
|
||||
|
||||
use super::{Cell, CellPosition, RenderableCell};
|
||||
use super::{Cell, CellPosition, CodeCell, MarkdownCell, RawCell, RenderableCell};
|
||||
|
||||
use nbformat::v4::CellId;
|
||||
use nbformat::v4::Metadata as NotebookMetadata;
|
||||
use nbformat::v4::{CellId, CellType};
|
||||
|
||||
actions!(
|
||||
notebook,
|
||||
@@ -35,6 +35,8 @@ actions!(
|
||||
MoveCellDown,
|
||||
AddMarkdownBlock,
|
||||
AddCodeBlock,
|
||||
ToggleEditMode,
|
||||
EditCell,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -45,6 +47,7 @@ pub(crate) const LARGE_SPACING_SIZE: f32 = 16.0;
|
||||
pub(crate) const GUTTER_WIDTH: f32 = 19.0;
|
||||
pub(crate) const CODE_BLOCK_INSET: f32 = MEDIUM_SPACING_SIZE;
|
||||
pub(crate) const CONTROL_SIZE: f32 = 20.0;
|
||||
const MINIMUM_NOTEBOOK_VER: f32 = 4.1;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
if cx.has_flag::<NotebookFeatureFlag>() || std::env::var("LOCAL_NOTEBOOK_DEV").is_ok() {
|
||||
@@ -64,6 +67,36 @@ pub fn init(cx: &mut App) {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn update_cell_list(
|
||||
cell_count: usize,
|
||||
notebook_handle: WeakEntity<NotebookEditor>,
|
||||
cx: &mut Context<NotebookEditor>,
|
||||
) -> ListState {
|
||||
ListState::new(
|
||||
cell_count,
|
||||
gpui::ListAlignment::Top,
|
||||
px(1000.),
|
||||
move |ix, window, cx| {
|
||||
notebook_handle
|
||||
.upgrade()
|
||||
.and_then(|notebook_handle| {
|
||||
notebook_handle.update(cx, |notebook, cx| {
|
||||
notebook
|
||||
.cell_order
|
||||
.get(ix)
|
||||
.and_then(|cell_id| notebook.cell_map.get(cell_id))
|
||||
.map(|cell| {
|
||||
notebook
|
||||
.render_cell(ix, cell, window, cx)
|
||||
.into_any_element()
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| div().into_any())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub struct NotebookEditor {
|
||||
languages: Arc<LanguageRegistry>,
|
||||
project: Entity<Project>,
|
||||
@@ -75,6 +108,7 @@ pub struct NotebookEditor {
|
||||
cell_list: ListState,
|
||||
|
||||
selected_cell_index: usize,
|
||||
|
||||
cell_order: Vec<CellId>,
|
||||
cell_map: HashMap<CellId, Cell>,
|
||||
}
|
||||
@@ -117,29 +151,7 @@ impl NotebookEditor {
|
||||
let cell_count = cell_order.len();
|
||||
|
||||
let this = cx.entity();
|
||||
let cell_list = ListState::new(
|
||||
cell_count,
|
||||
gpui::ListAlignment::Top,
|
||||
px(1000.),
|
||||
move |ix, window, cx| {
|
||||
notebook_handle
|
||||
.upgrade()
|
||||
.and_then(|notebook_handle| {
|
||||
notebook_handle.update(cx, |notebook, cx| {
|
||||
notebook
|
||||
.cell_order
|
||||
.get(ix)
|
||||
.and_then(|cell_id| notebook.cell_map.get(cell_id))
|
||||
.map(|cell| {
|
||||
notebook
|
||||
.render_cell(ix, cell, window, cx)
|
||||
.into_any_element()
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| div().into_any())
|
||||
},
|
||||
);
|
||||
let cell_list = update_cell_list(cell_count, notebook_handle, cx);
|
||||
|
||||
Self {
|
||||
project,
|
||||
@@ -154,6 +166,12 @@ impl NotebookEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn has_executable_cells(&self) -> bool {
|
||||
self.cell_map
|
||||
.values()
|
||||
.any(|cell| matches!(cell, Cell::Code(_)))
|
||||
}
|
||||
|
||||
fn has_outputs(&self, window: &mut Window, cx: &mut Context<Self>) -> bool {
|
||||
self.cell_map.values().any(|cell| {
|
||||
if let Cell::Code(code_cell) = cell {
|
||||
@@ -182,20 +200,89 @@ impl NotebookEditor {
|
||||
println!("Open notebook triggered");
|
||||
}
|
||||
|
||||
fn swap_cells(
|
||||
&mut self,
|
||||
index1: usize,
|
||||
index2: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if index1 < self.cell_order.len() && index2 < self.cell_order.len() {
|
||||
let cell_count = self.cell_count();
|
||||
let notebook_handle = cx.entity().downgrade();
|
||||
|
||||
self.cell_order.swap(index1, index2);
|
||||
|
||||
self.cell_list = update_cell_list(cell_count, notebook_handle, cx);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn move_cell_up(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
println!("Move cell up triggered");
|
||||
if self.selected_cell_index > 0 {
|
||||
let current_index = self.selected_cell_index;
|
||||
let new_index = current_index - 1;
|
||||
|
||||
self.swap_cells(current_index, new_index, window, cx);
|
||||
self.selected_cell_index = new_index;
|
||||
self.cell_list.scroll_to_reveal_item(new_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn move_cell_down(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
println!("Move cell down triggered");
|
||||
if self.selected_cell_index < self.cell_order.len() - 1 {
|
||||
let current_index = self.selected_cell_index;
|
||||
let new_index = current_index + 1;
|
||||
|
||||
self.swap_cells(current_index, new_index, window, cx);
|
||||
self.selected_cell_index = new_index;
|
||||
self.cell_list.scroll_to_reveal_item(new_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_cell(&mut self, cell_type: CellType, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let new_cell_id = CellId::new(&uuid::Uuid::new_v4().to_string()).unwrap();
|
||||
let insert_index = self.selected_cell_index.saturating_add(1);
|
||||
|
||||
let languages = self.languages.clone();
|
||||
|
||||
let cell = match cell_type {
|
||||
CellType::Markdown => {
|
||||
let markdown_cell = cx.new(|cx| {
|
||||
MarkdownCell::new(new_cell_id.clone(), languages.clone(), window, cx)
|
||||
});
|
||||
Cell::Markdown(markdown_cell)
|
||||
}
|
||||
_ => return, // CellType::Code => {
|
||||
// let code_cell =
|
||||
// cx.new(|_| CodeCell::new(new_cell_id.clone(), self.languages.clone()));
|
||||
// Cell::Code(code_cell)
|
||||
// }
|
||||
// CellType::Raw => {
|
||||
// let raw_cell = cx.new(|_| RawCell::new(new_cell_id.clone()));
|
||||
// Cell::Raw(raw_cell)
|
||||
// }
|
||||
};
|
||||
|
||||
self.cell_order.insert(insert_index, new_cell_id.clone());
|
||||
self.cell_map.insert(new_cell_id.clone(), cell);
|
||||
|
||||
let notebook_handle = cx.entity().downgrade();
|
||||
self.cell_list = update_cell_list(self.cell_count(), notebook_handle, cx);
|
||||
|
||||
self.set_selected_index(insert_index, true, window, cx);
|
||||
|
||||
self.cell_list.scroll_to_reveal_item(insert_index);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn add_markdown_block(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
println!("Add markdown block triggered");
|
||||
self.add_cell(CellType::Markdown, window, cx);
|
||||
}
|
||||
|
||||
fn add_code_block(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
println!("Add code block triggered");
|
||||
self.add_cell(CellType::Code, window, cx);
|
||||
}
|
||||
|
||||
fn cell_count(&self) -> usize {
|
||||
@@ -284,6 +371,45 @@ impl NotebookEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, 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::Markdown(markdown_cell)) = self.cell_map.get_mut(&cell_id) {
|
||||
markdown_cell.update(cx, |cell, _| {
|
||||
println!("Confirming cell, {}", cell.id());
|
||||
cell.editing(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn edit_cell(&mut self, index: usize, cx: &mut Context<Self>) {
|
||||
if let Some(cell_id) = self.cell_order.get(index).cloned() {
|
||||
if let Some(Cell::Markdown(markdown_cell)) = self.cell_map.get_mut(&cell_id) {
|
||||
markdown_cell.update(cx, |cell, _| {
|
||||
println!("Editing cell, {}", cell.id());
|
||||
cell.editing(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_edit_mode(
|
||||
&mut self,
|
||||
_: &ToggleEditMode,
|
||||
_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::Markdown(markdown_cell)) = self.cell_map.get_mut(&cell_id) {
|
||||
markdown_cell.update(cx, |cell, _| {
|
||||
cell.editing(!cell.is_editing());
|
||||
});
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn jump_to_cell(&mut self, index: usize, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.cell_list.scroll_to_reveal_item(index);
|
||||
}
|
||||
@@ -317,8 +443,17 @@ impl NotebookEditor {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let has_outputs = self.has_outputs(window, cx);
|
||||
let selected_cell_id = &self.cell_order[self.selected_cell_index];
|
||||
let selected_is_editing =
|
||||
self.cell_map
|
||||
.get(selected_cell_id)
|
||||
.map_or(false, |cell| match cell {
|
||||
Cell::Markdown(markdown_cell) => markdown_cell.read(cx).is_editing(),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id("notebook-controls")
|
||||
.max_w(px(CONTROL_SIZE + 4.0))
|
||||
.items_center()
|
||||
.gap(DynamicSpacing::Base16.rems(cx))
|
||||
@@ -338,12 +473,16 @@ impl NotebookEditor {
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.disabled(!self.has_executable_cells())
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Execute all cells", &RunAll, window, cx)
|
||||
})
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(RunAll), cx);
|
||||
}),
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(RunAll), cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Self::render_notebook_control(
|
||||
@@ -361,9 +500,12 @@ impl NotebookEditor {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(ClearOutputs), cx);
|
||||
}),
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(ClearOutputs), cx);
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -378,9 +520,12 @@ impl NotebookEditor {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Move cell up", &MoveCellUp, window, cx)
|
||||
})
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(MoveCellUp), cx);
|
||||
}),
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(MoveCellUp), cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Self::render_notebook_control(
|
||||
@@ -392,9 +537,12 @@ impl NotebookEditor {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Move cell down", &MoveCellDown, window, cx)
|
||||
})
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(MoveCellDown), cx);
|
||||
}),
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(MoveCellDown), cx);
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -414,9 +562,12 @@ impl NotebookEditor {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(AddMarkdownBlock), cx);
|
||||
}),
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(AddMarkdownBlock), cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Self::render_notebook_control(
|
||||
@@ -425,28 +576,53 @@ impl NotebookEditor {
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.disabled(true)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Add code block", &AddCodeBlock, window, cx)
|
||||
})
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(Box::new(AddCodeBlock), cx);
|
||||
}),
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(AddCodeBlock), cx);
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Self::button_group(window, cx).child(
|
||||
Self::render_notebook_control(
|
||||
"edit-cell",
|
||||
if selected_is_editing {
|
||||
IconName::Eye
|
||||
} else {
|
||||
IconName::Pencil
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action("Edit Cell", &EditCell, window, cx)
|
||||
})
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
window.focus(&this.focus_handle);
|
||||
window.dispatch_action(Box::new(ToggleEditMode), cx);
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.items_center()
|
||||
.child(Self::render_notebook_control(
|
||||
"more-menu",
|
||||
IconName::Ellipsis,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
Self::render_notebook_control("more-menu", IconName::Ellipsis, window, cx)
|
||||
.disabled(true),
|
||||
)
|
||||
.child(
|
||||
Self::button_group(window, cx)
|
||||
.child(IconButton::new("repl", IconName::ReplNeutral)),
|
||||
.child(IconButton::new("repl", IconName::ReplNeutral).disabled(true)),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -504,6 +680,14 @@ impl Render for NotebookEditor {
|
||||
.on_action(cx.listener(|this, &OpenNotebook, window, cx| {
|
||||
this.open_notebook(&OpenNotebook, window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(|this, &EditCell, window, cx| {
|
||||
let selected_cell_index = this.selected_cell_index.clone();
|
||||
|
||||
this.edit_cell(selected_cell_index.clone(), cx)
|
||||
}))
|
||||
.on_action(cx.listener(|this, &ToggleEditMode, window, cx| {
|
||||
this.toggle_edit_mode(&ToggleEditMode, window, cx)
|
||||
}))
|
||||
.on_action(
|
||||
cx.listener(|this, &ClearOutputs, window, cx| this.clear_outputs(window, cx)),
|
||||
)
|
||||
@@ -590,7 +774,11 @@ impl project::ProjectItem for NotebookItem {
|
||||
}
|
||||
// Bad notebooks and notebooks v4.0 and below are not supported
|
||||
Err(e) => {
|
||||
anyhow::bail!("Failed to parse notebook: {:?}", e);
|
||||
anyhow::bail!(
|
||||
"Unsupported notebook version. This notebook requires version {} or later. Error details: {}",
|
||||
MINIMUM_NOTEBOOK_VER,
|
||||
e
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user