741 lines · 24.5 KB · Rust
RawDownload
#![allow(unused, dead_code)]use std::sync::Arc; use editor::{Editor, EditorMode, MultiBuffer};use futures::future::Shared;use gpui::{prelude::*, App, Entity, Hsla, 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};use settings::Settings as _;use theme::ThemeSettings;use ui::{prelude::*, IconButtonShape};use util::ResultExt; use crate::{    notebook::{CODE_BLOCK_INSET, GUTTER_WIDTH},    outputs::{plain::TerminalOutput, user_error::ErrorView, Output},}; #[derive(Copy, Clone, PartialEq, PartialOrd)]pub enum CellPosition {    First,    Middle,    Last,} pub enum CellControlType {    RunCell,    RerunCell,    ClearCell,    CellOptions,    CollapseCell,    ExpandCell,} impl CellControlType {    fn icon_name(&self) -> IconName {        match self {            CellControlType::RunCell => IconName::Play,            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        .into_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 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| {                    let markdown_parsing_task = {                        let languages = languages.clone();                        let source = source.clone();                         cx.spawn_in(window, |this, mut cx| async move {                            let parsed_markdown = cx                                .background_executor()                                .spawn(async move {                                    parse_markdown(&source, None, Some(languages)).await                                })                                .await;                             this.update(&mut cx, |cell: &mut MarkdownCell, _| {                                cell.parsed_markdown = Some(parsed_markdown);                            })                            .log_err();                        })                    };                     MarkdownCell {                        markdown_parsing_task,                        languages: languages.clone(),                        id: id.clone(),                        metadata: metadata.clone(),                        source: source.clone(),                        parsed_markdown: None,                        selected: false,                        cell_position: None,                    }                });                 Cell::Markdown(entity)            }            nbformat::v4::Cell::Code {                id,                metadata,                execution_count,                source,                outputs,            } => Cell::Code(cx.new(|cx| {                let text = source.join("");                 let buffer = cx.new(|cx| Buffer::local(text.clone(), cx));                let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));                 let editor_view = cx.new(|cx| {                    let mut editor = Editor::new(                        EditorMode::AutoHeight { max_lines: 1024 },                        multi_buffer,                        None,                        false,                        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.into()),                        color: Some(cx.theme().colors().editor_foreground),                        background_color: Some(gpui::transparent_black()),                        ..Default::default()                    };                     editor.set_text(text, window, cx);                    editor.set_show_gutter(false, cx);                    editor.set_text_style_refinement(refinement);                     // editor.set_read_only(true);                    editor                });                 let buffer = buffer.clone();                let language_task = cx.spawn_in(window, |this, mut cx| async move {                    let language = notebook_language.await;                     buffer.update(&mut cx, |buffer, cx| {                        buffer.set_language(language.clone(), cx);                    });                });                 CodeCell {                    id: id.clone(),                    metadata: metadata.clone(),                    execution_count: *execution_count,                    source: source.join(""),                    editor: editor_view,                    outputs: convert_outputs(outputs, window, cx),                    selected: false,                    language_task,                    cell_position: None,                }            })),            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().icon_accent;            color.fade_out(0.9);            color        } else {            // TODO: this is wrong            cx.theme().colors().tab_bar_background        }    }    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,    source: String,    parsed_markdown: Option<markdown_preview::markdown_elements::ParsedMarkdown>,    markdown_parsing_task: Task<()>,    selected: bool,    cell_position: Option<CellPosition>,    languages: Arc<LanguageRegistry>,} 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    }     fn set_selected(&mut self, selected: bool) -> &mut Self {        self.selected = selected;        self    }     fn control(&self, _window: &mut Window, _: &mut Context<Self>) -> Option<CellControl> {        None    }     fn cell_position(&self) -> Option<&CellPosition> {        self.cell_position.as_ref()    }     fn set_cell_position(&mut self, cell_position: CellPosition) -> &mut Self {        self.cell_position = Some(cell_position);        self    }} 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 mut markdown_render_context =            markdown_preview::markdown_renderer::RenderContext::new(None, window, cx);         v_flex()            .size_full()            // TODO: Move base cell render into trait impl so we don't have to repeat this            .children(self.cell_position_spacer(true, window, cx))            .child(                h_flex()                    .w_full()                    .pr_6()                    .rounded_sm()                    .items_start()                    .gap(DynamicSpacing::Base08.rems(cx))                    .bg(self.selected_bg_color(window, cx))                    .child(self.gutter(window, cx))                    .child(                        v_flex()                            .size_full()                            .flex_1()                            .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),                                ))                            })),                    ),            )            // TODO: Move base cell render into trait impl so we don't have to repeat this            .children(self.cell_position_spacer(false, window, cx))    }} pub struct CodeCell {    id: CellId,    metadata: CellMetadata,    execution_count: Option<i32>,    source: String,    editor: Entity<editor::Editor>,    outputs: Vec<Output>,    selected: bool,    cell_position: Option<CellPosition>,    language_task: Task<()>,} impl CodeCell {    pub fn is_dirty(&self, cx: &App) -> bool {        self.editor.read(cx).buffer().read(cx).is_dirty(cx)    }    pub fn has_outputs(&self) -> bool {        !self.outputs.is_empty()    }     pub fn clear_outputs(&mut self) {        self.outputs.clear();    }     fn output_control(&self) -> Option<CellControlType> {        if self.has_outputs() {            Some(CellControlType::ClearCell)        } else {            None        }    }     pub fn gutter_output(&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(self.has_outputs(), |this| {                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(IconButton::new("control", IconName::Ellipsis)),                )            })    }} impl RenderableCell for CodeCell {

Lines 1–500 of 741.