#![allow(unused, dead_code)]use std::sync::Arc;use std::time::{Duration, Instant}; use editor::{Editor, EditorMode, MultiBuffer};use futures::future::Shared;use gpui::{ App, Entity, EventEmitter, Focusable, Hsla, InteractiveElement, RetainAllImageCache, StatefulInteractiveElement, Task, TextStyleRefinement, image_cache, prelude::*,};use language::{Buffer, Language, LanguageRegistry};use markdown::{Markdown, MarkdownElement, MarkdownStyle};use nbformat::v4::{CellId, CellMetadata, CellType};use runtimelib::{JupyterMessage, JupyterMessageContent};use settings::Settings as _;use theme::ThemeSettings;use ui::{CommonAnimationExt, IconButtonShape, prelude::*};use util::ResultExt; use crate::{ notebook::{CODE_BLOCK_INSET, GUTTER_WIDTH}, outputs::{Output, plain, plain::TerminalOutput, user_error::ErrorView}, repl_settings::ReplSettings,}; #[derive(Copy, Clone, PartialEq, PartialOrd)]pub enum CellPosition { First, Middle, Last,} pub enum CellControlType { RunCell, RerunCell, ClearCell, CellOptions, CollapseCell, ExpandCell,} pub enum CellEvent { Run(CellId), FocusedIn(CellId),} pub enum MarkdownCellEvent { FinishedEditing, Run(CellId),} impl CellControlType { fn icon_name(&self) -> IconName { match self { CellControlType::RunCell => IconName::PlayFilled, CellControlType::RerunCell => IconName::ArrowCircle, CellControlType::ClearCell => IconName::ListX, CellControlType::CellOptions => IconName::Ellipsis, CellControlType::CollapseCell => IconName::ChevronDown, CellControlType::ExpandCell => IconName::ChevronRight, } }} pub struct CellControl { button: IconButton,} impl CellControl { fn new(id: impl Into<SharedString>, control_type: CellControlType) -> Self { let icon_name = control_type.icon_name(); let id = id.into(); let button = IconButton::new(id, icon_name) .icon_size(IconSize::Small) .shape(IconButtonShape::Square); Self { button } }} impl Clickable for CellControl { fn on_click( self, handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static, ) -> Self { let button = self.button.on_click(handler); Self { button } } fn cursor_style(self, _cursor_style: gpui::CursorStyle) -> Self { self }} /// A notebook cell#[derive(Clone)]pub enum Cell { Code(Entity<CodeCell>), Markdown(Entity<MarkdownCell>), Raw(Entity<RawCell>),} fn convert_outputs( outputs: &Vec<nbformat::v4::Output>, window: &mut Window, cx: &mut App,) -> Vec<Output> { outputs .iter() .map(|output| match output { nbformat::v4::Output::Stream { text, .. } => Output::Stream { content: cx.new(|cx| TerminalOutput::from(&text.0, window, cx)), }, nbformat::v4::Output::DisplayData(display_data) => { Output::new(&display_data.data, None, window, cx) } nbformat::v4::Output::ExecuteResult(execute_result) => { Output::new(&execute_result.data, None, window, cx) } nbformat::v4::Output::Error(error) => Output::ErrorOutput(ErrorView { ename: error.ename.clone(), evalue: error.evalue.clone(), traceback: cx .new(|cx| TerminalOutput::from(&error.traceback.join("\n"), window, cx)), }), }) .collect()} impl Cell { pub fn id(&self, cx: &App) -> CellId { match self { Cell::Code(code_cell) => code_cell.read(cx).id().clone(), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).id().clone(), Cell::Raw(raw_cell) => raw_cell.read(cx).id().clone(), } } pub fn current_source(&self, cx: &App) -> String { match self { Cell::Code(code_cell) => code_cell.read(cx).current_source(cx), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).current_source(cx), Cell::Raw(raw_cell) => raw_cell.read(cx).source.clone(), } } pub fn to_nbformat_cell(&self, cx: &App) -> nbformat::v4::Cell { match self { Cell::Code(code_cell) => code_cell.read(cx).to_nbformat_cell(cx), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).to_nbformat_cell(cx), Cell::Raw(raw_cell) => raw_cell.read(cx).to_nbformat_cell(), } } pub fn is_dirty(&self, cx: &App) -> bool { match self { Cell::Code(code_cell) => code_cell.read(cx).is_dirty(cx), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).is_dirty(cx), Cell::Raw(_) => false, } } pub fn load( cell: &nbformat::v4::Cell, languages: &Arc<LanguageRegistry>, notebook_language: Shared<Task<Option<Arc<Language>>>>, window: &mut Window, cx: &mut App, ) -> Self { match cell { nbformat::v4::Cell::Markdown { id, metadata, source, .. } => { let source = source.join(""); let entity = cx.new(|cx| { MarkdownCell::new( id.clone(), metadata.clone(), source, languages.clone(), window, cx, ) }); Cell::Markdown(entity) } nbformat::v4::Cell::Code { id, metadata, execution_count, source, outputs, } => { let text = source.join(""); let outputs = convert_outputs(outputs, window, cx); Cell::Code(cx.new(|cx| { CodeCell::load( id.clone(), metadata.clone(), *execution_count, text, outputs, notebook_language, window, cx, ) })) } nbformat::v4::Cell::Raw { id, metadata, source, } => Cell::Raw(cx.new(|_| RawCell { id: id.clone(), metadata: metadata.clone(), source: source.join(""), selected: false, cell_position: None, })), } }} pub trait RenderableCell: Render { const CELL_TYPE: CellType; fn id(&self) -> &CellId; fn cell_type(&self) -> CellType; fn metadata(&self) -> &CellMetadata; fn source(&self) -> &String; fn selected(&self) -> bool; fn set_selected(&mut self, selected: bool) -> &mut Self; fn selected_bg_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla { if self.selected() { let mut color = cx.theme().colors().element_hover; color.fade_out(0.5); color } else { // Not sure if this is correct, previous was TODO: this is wrong gpui::transparent_black() } } fn control(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<CellControl> { None } fn cell_position_spacer( &self, is_first: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Option<impl IntoElement> { let cell_position = self.cell_position(); if (cell_position == Some(&CellPosition::First) && is_first) || (cell_position == Some(&CellPosition::Last) && !is_first) { Some(div().flex().w_full().h(DynamicSpacing::Base12.px(cx))) } else { None } } fn gutter(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let is_selected = self.selected(); div() .relative() .h_full() .w(px(GUTTER_WIDTH)) .child( div() .w(px(GUTTER_WIDTH)) .flex() .flex_none() .justify_center() .h_full() .child( div() .flex_none() .w(px(1.)) .h_full() .when(is_selected, |this| this.bg(cx.theme().colors().icon_accent)) .when(!is_selected, |this| this.bg(cx.theme().colors().border)), ), ) .when_some(self.control(window, cx), |this, control| { this.child( div() .absolute() .top(px(CODE_BLOCK_INSET - 2.0)) .left_0() .flex() .flex_none() .w(px(GUTTER_WIDTH)) .h(px(GUTTER_WIDTH + 12.0)) .items_center() .justify_center() .bg(cx.theme().colors().tab_bar_background) .child(control.button), ) }) } fn cell_position(&self) -> Option<&CellPosition>; fn set_cell_position(&mut self, position: CellPosition) -> &mut Self;} pub trait RunnableCell: RenderableCell { fn execution_count(&self) -> Option<i32>; fn set_execution_count(&mut self, count: i32) -> &mut Self; fn run(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ();} pub struct MarkdownCell { id: CellId, metadata: CellMetadata, image_cache: Entity<RetainAllImageCache>, source: String, editor: Entity<Editor>, markdown: Entity<Markdown>, editing: bool, selected: bool, cell_position: Option<CellPosition>, languages: Arc<LanguageRegistry>, _editor_subscription: gpui::Subscription,} impl EventEmitter<MarkdownCellEvent> for MarkdownCell {} impl MarkdownCell { pub fn new( id: CellId, metadata: CellMetadata, source: String, languages: Arc<LanguageRegistry>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let buffer = cx.new(|cx| Buffer::local(source.clone(), cx)); let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); let markdown_language = languages.language_for_name("Markdown"); cx.spawn_in(window, async move |_this, cx| { if let Some(markdown) = markdown_language.await.log_err() { buffer.update(cx, |buffer, cx| { buffer.set_language(Some(markdown), cx); }); } }) .detach(); let editor = cx.new(|cx| { let mut editor = Editor::new( EditorMode::AutoHeight { min_lines: 1, max_lines: Some(1024), }, multi_buffer, None, window, cx, ); let theme = ThemeSettings::get_global(cx); let refinement = TextStyleRefinement { font_family: Some(theme.buffer_font.family.clone()), font_size: Some(theme.buffer_font_size(cx).into()), color: Some(cx.theme().colors().editor_foreground), background_color: Some(gpui::transparent_black()), ..Default::default() }; editor.set_show_gutter(false, cx); editor.set_text_style_refinement(refinement); editor }); let markdown = cx.new(|cx| Markdown::new(source.clone().into(), None, None, cx)); let cell_id = id.clone(); let editor_subscription = cx.subscribe(&editor, move |this, _editor, event, cx| match event { editor::EditorEvent::Blurred => { if this.editing { this.editing = false; cx.emit(MarkdownCellEvent::FinishedEditing); cx.notify(); } } _ => {} }); let start_editing = source.is_empty(); Self { id, metadata, image_cache: RetainAllImageCache::new(cx), source, editor, markdown, editing: start_editing, selected: false, cell_position: None, languages, _editor_subscription: editor_subscription, } } pub fn editor(&self) -> &Entity<Editor> { &self.editor } pub fn current_source(&self, cx: &App) -> String { let editor = self.editor.read(cx); let buffer = editor.buffer().read(cx); buffer .as_singleton() .map(|b| b.read(cx).text()) .unwrap_or_default() } pub fn is_dirty(&self, cx: &App) -> bool { self.editor.read(cx).buffer().read(cx).is_dirty(cx) } pub fn to_nbformat_cell(&self, cx: &App) -> nbformat::v4::Cell { let source = self.current_source(cx); let source_lines: Vec<String> = source.lines().map(|l| format!("{}\n", l)).collect(); nbformat::v4::Cell::Markdown { id: self.id.clone(), metadata: self.metadata.clone(), source: source_lines, attachments: None, } } pub fn is_editing(&self) -> bool { self.editing } pub fn set_editing(&mut self, editing: bool) { self.editing = editing; } pub fn reparse_markdown(&mut self, cx: &mut Context<Self>) { let editor = self.editor.read(cx); let buffer = editor.buffer().read(cx); let source = buffer .as_singleton() .map(|b| b.read(cx).text()) .unwrap_or_default(); self.source = source.clone(); let languages = self.languages.clone(); self.markdown.update(cx, |markdown, cx| { markdown.reset(source.into(), cx); }); } /// Called when user presses Shift+Enter or Ctrl+Enter while editing. /// Finishes editing and signals to move to the next cell. pub fn run(&mut self, cx: &mut Context<Self>) { if self.editing { self.editing = false; cx.emit(MarkdownCellEvent::FinishedEditing); cx.emit(MarkdownCellEvent::Run(self.id.clone())); cx.notify(); } }} impl RenderableCell for MarkdownCell { const CELL_TYPE: CellType = CellType::Markdown; fn id(&self) -> &CellId { &self.id } fn cell_type(&self) -> CellType { CellType::Markdown } fn metadata(&self) -> &CellMetadata { &self.metadata } fn source(&self) -> &String { &self.source } fn selected(&self) -> bool { self.selectedLines 1–500 of 1379. Load more
#![allow(unused, dead_code)] use std::sync::Arc; use std::time::{Duration, Instant}; use editor::{Editor, EditorMode, MultiBuffer}; use futures::future::Shared; use gpui::{ App, Entity, EventEmitter, Focusable, Hsla, InteractiveElement, RetainAllImageCache, StatefulInteractiveElement, Task, TextStyleRefinement, image_cache, prelude::*, }; use language::{Buffer, Language, LanguageRegistry}; use markdown::{Markdown, MarkdownElement, MarkdownStyle}; use nbformat::v4::{CellId, CellMetadata, CellType}; use runtimelib::{JupyterMessage, JupyterMessageContent}; use settings::Settings as _; use theme::ThemeSettings; use ui::{CommonAnimationExt, IconButtonShape, prelude::*}; use util::ResultExt; use crate::{ notebook::{CODE_BLOCK_INSET, GUTTER_WIDTH}, outputs::{Output, plain, plain::TerminalOutput, user_error::ErrorView}, repl_settings::ReplSettings, }; #[derive(Copy, Clone, PartialEq, PartialOrd)] pub enum CellPosition { First, Middle, Last, } pub enum CellControlType { RunCell, RerunCell, ClearCell, CellOptions, CollapseCell, ExpandCell, } pub enum CellEvent { Run(CellId), FocusedIn(CellId), } pub enum MarkdownCellEvent { FinishedEditing, Run(CellId), } impl CellControlType { fn icon_name(&self) -> IconName { match self { CellControlType::RunCell => IconName::PlayFilled, CellControlType::RerunCell => IconName::ArrowCircle, CellControlType::ClearCell => IconName::ListX, CellControlType::CellOptions => IconName::Ellipsis, CellControlType::CollapseCell => IconName::ChevronDown, CellControlType::ExpandCell => IconName::ChevronRight, } } } pub struct CellControl { button: IconButton, } impl CellControl { fn new(id: impl Into<SharedString>, control_type: CellControlType) -> Self { let icon_name = control_type.icon_name(); let id = id.into(); let button = IconButton::new(id, icon_name) .icon_size(IconSize::Small) .shape(IconButtonShape::Square); Self { button } } } impl Clickable for CellControl { fn on_click( self, handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static, ) -> Self { let button = self.button.on_click(handler); Self { button } } fn cursor_style(self, _cursor_style: gpui::CursorStyle) -> Self { self } } /// A notebook cell #[derive(Clone)] pub enum Cell { Code(Entity<CodeCell>), Markdown(Entity<MarkdownCell>), Raw(Entity<RawCell>), } fn convert_outputs( outputs: &Vec<nbformat::v4::Output>, window: &mut Window, cx: &mut App, ) -> Vec<Output> { outputs .iter() .map(|output| match output { nbformat::v4::Output::Stream { text, .. } => Output::Stream { content: cx.new(|cx| TerminalOutput::from(&text.0, window, cx)), }, nbformat::v4::Output::DisplayData(display_data) => { Output::new(&display_data.data, None, window, cx) } nbformat::v4::Output::ExecuteResult(execute_result) => { Output::new(&execute_result.data, None, window, cx) } nbformat::v4::Output::Error(error) => Output::ErrorOutput(ErrorView { ename: error.ename.clone(), evalue: error.evalue.clone(), traceback: cx .new(|cx| TerminalOutput::from(&error.traceback.join("\n"), window, cx)), }), }) .collect() } impl Cell { pub fn id(&self, cx: &App) -> CellId { match self { Cell::Code(code_cell) => code_cell.read(cx).id().clone(), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).id().clone(), Cell::Raw(raw_cell) => raw_cell.read(cx).id().clone(), } } pub fn current_source(&self, cx: &App) -> String { match self { Cell::Code(code_cell) => code_cell.read(cx).current_source(cx), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).current_source(cx), Cell::Raw(raw_cell) => raw_cell.read(cx).source.clone(), } } pub fn to_nbformat_cell(&self, cx: &App) -> nbformat::v4::Cell { match self { Cell::Code(code_cell) => code_cell.read(cx).to_nbformat_cell(cx), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).to_nbformat_cell(cx), Cell::Raw(raw_cell) => raw_cell.read(cx).to_nbformat_cell(), } } pub fn is_dirty(&self, cx: &App) -> bool { match self { Cell::Code(code_cell) => code_cell.read(cx).is_dirty(cx), Cell::Markdown(markdown_cell) => markdown_cell.read(cx).is_dirty(cx), Cell::Raw(_) => false, } } pub fn load( cell: &nbformat::v4::Cell, languages: &Arc<LanguageRegistry>, notebook_language: Shared<Task<Option<Arc<Language>>>>, window: &mut Window, cx: &mut App, ) -> Self { match cell { nbformat::v4::Cell::Markdown { id, metadata, source, .. } => { let source = source.join(""); let entity = cx.new(|cx| { MarkdownCell::new( id.clone(), metadata.clone(), source, languages.clone(), window, cx, ) }); Cell::Markdown(entity) } nbformat::v4::Cell::Code { id, metadata, execution_count, source, outputs, } => { let text = source.join(""); let outputs = convert_outputs(outputs, window, cx); Cell::Code(cx.new(|cx| { CodeCell::load( id.clone(), metadata.clone(), *execution_count, text, outputs, notebook_language, window, cx, ) })) } nbformat::v4::Cell::Raw { id, metadata, source, } => Cell::Raw(cx.new(|_| RawCell { id: id.clone(), metadata: metadata.clone(), source: source.join(""), selected: false, cell_position: None, })), } } } pub trait RenderableCell: Render { const CELL_TYPE: CellType; fn id(&self) -> &CellId; fn cell_type(&self) -> CellType; fn metadata(&self) -> &CellMetadata; fn source(&self) -> &String; fn selected(&self) -> bool; fn set_selected(&mut self, selected: bool) -> &mut Self; fn selected_bg_color(&self, window: &mut Window, cx: &mut Context<Self>) -> Hsla { if self.selected() { let mut color = cx.theme().colors().element_hover; color.fade_out(0.5); color } else { // Not sure if this is correct, previous was TODO: this is wrong gpui::transparent_black() } } fn control(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<CellControl> { None } fn cell_position_spacer( &self, is_first: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Option<impl IntoElement> { let cell_position = self.cell_position(); if (cell_position == Some(&CellPosition::First) && is_first) || (cell_position == Some(&CellPosition::Last) && !is_first) { Some(div().flex().w_full().h(DynamicSpacing::Base12.px(cx))) } else { None } } fn gutter(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let is_selected = self.selected(); div() .relative() .h_full() .w(px(GUTTER_WIDTH)) .child( div() .w(px(GUTTER_WIDTH)) .flex() .flex_none() .justify_center() .h_full() .child( div() .flex_none() .w(px(1.)) .h_full() .when(is_selected, |this| this.bg(cx.theme().colors().icon_accent)) .when(!is_selected, |this| this.bg(cx.theme().colors().border)), ), ) .when_some(self.control(window, cx), |this, control| { this.child( div() .absolute() .top(px(CODE_BLOCK_INSET - 2.0)) .left_0() .flex() .flex_none() .w(px(GUTTER_WIDTH)) .h(px(GUTTER_WIDTH + 12.0)) .items_center() .justify_center() .bg(cx.theme().colors().tab_bar_background) .child(control.button), ) }) } fn cell_position(&self) -> Option<&CellPosition>; fn set_cell_position(&mut self, position: CellPosition) -> &mut Self; } pub trait RunnableCell: RenderableCell { fn execution_count(&self) -> Option<i32>; fn set_execution_count(&mut self, count: i32) -> &mut Self; fn run(&mut self, window: &mut Window, cx: &mut Context<Self>) -> (); } pub struct MarkdownCell { id: CellId, metadata: CellMetadata, image_cache: Entity<RetainAllImageCache>, source: String, editor: Entity<Editor>, markdown: Entity<Markdown>, editing: bool, selected: bool, cell_position: Option<CellPosition>, languages: Arc<LanguageRegistry>, _editor_subscription: gpui::Subscription, } impl EventEmitter<MarkdownCellEvent> for MarkdownCell {} impl MarkdownCell { pub fn new( id: CellId, metadata: CellMetadata, source: String, languages: Arc<LanguageRegistry>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let buffer = cx.new(|cx| Buffer::local(source.clone(), cx)); let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); let markdown_language = languages.language_for_name("Markdown"); cx.spawn_in(window, async move |_this, cx| { if let Some(markdown) = markdown_language.await.log_err() { buffer.update(cx, |buffer, cx| { buffer.set_language(Some(markdown), cx); }); } }) .detach(); let editor = cx.new(|cx| { let mut editor = Editor::new( EditorMode::AutoHeight { min_lines: 1, max_lines: Some(1024), }, multi_buffer, None, window, cx, ); let theme = ThemeSettings::get_global(cx); let refinement = TextStyleRefinement { font_family: Some(theme.buffer_font.family.clone()), font_size: Some(theme.buffer_font_size(cx).into()), color: Some(cx.theme().colors().editor_foreground), background_color: Some(gpui::transparent_black()), ..Default::default() }; editor.set_show_gutter(false, cx); editor.set_text_style_refinement(refinement); editor }); let markdown = cx.new(|cx| Markdown::new(source.clone().into(), None, None, cx)); let cell_id = id.clone(); let editor_subscription = cx.subscribe(&editor, move |this, _editor, event, cx| match event { editor::EditorEvent::Blurred => { if this.editing { this.editing = false; cx.emit(MarkdownCellEvent::FinishedEditing); cx.notify(); } } _ => {} }); let start_editing = source.is_empty(); Self { id, metadata, image_cache: RetainAllImageCache::new(cx), source, editor, markdown, editing: start_editing, selected: false, cell_position: None, languages, _editor_subscription: editor_subscription, } } pub fn editor(&self) -> &Entity<Editor> { &self.editor } pub fn current_source(&self, cx: &App) -> String { let editor = self.editor.read(cx); let buffer = editor.buffer().read(cx); buffer .as_singleton() .map(|b| b.read(cx).text()) .unwrap_or_default() } pub fn is_dirty(&self, cx: &App) -> bool { self.editor.read(cx).buffer().read(cx).is_dirty(cx) } pub fn to_nbformat_cell(&self, cx: &App) -> nbformat::v4::Cell { let source = self.current_source(cx); let source_lines: Vec<String> = source.lines().map(|l| format!("{}\n", l)).collect(); nbformat::v4::Cell::Markdown { id: self.id.clone(), metadata: self.metadata.clone(), source: source_lines, attachments: None, } } pub fn is_editing(&self) -> bool { self.editing } pub fn set_editing(&mut self, editing: bool) { self.editing = editing; } pub fn reparse_markdown(&mut self, cx: &mut Context<Self>) { let editor = self.editor.read(cx); let buffer = editor.buffer().read(cx); let source = buffer .as_singleton() .map(|b| b.read(cx).text()) .unwrap_or_default(); self.source = source.clone(); let languages = self.languages.clone(); self.markdown.update(cx, |markdown, cx| { markdown.reset(source.into(), cx); }); } /// Called when user presses Shift+Enter or Ctrl+Enter while editing. /// Finishes editing and signals to move to the next cell. pub fn run(&mut self, cx: &mut Context<Self>) { if self.editing { self.editing = false; cx.emit(MarkdownCellEvent::FinishedEditing); cx.emit(MarkdownCellEvent::Run(self.id.clone())); cx.notify(); } } } impl RenderableCell for MarkdownCell { const CELL_TYPE: CellType = CellType::Markdown; fn id(&self) -> &CellId { &self.id } fn cell_type(&self) -> CellType { CellType::Markdown } fn metadata(&self) -> &CellMetadata { &self.metadata } fn source(&self) -> &String { &self.source } fn selected(&self) -> bool { self.selected