1379 lines · 48.8 KB · Rust
RawDownload
#![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

Lines 1–500 of 1379.