markdown.rs

 1use anyhow::Result;
 2use gpui::{div, prelude::*, App, ClipboardItem, Context, Entity, Task, Window};
 3use language::Buffer;
 4use markdown_preview::{
 5    markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown,
 6    markdown_renderer::render_markdown_block,
 7};
 8use ui::v_flex;
 9
10use crate::outputs::OutputContent;
11
12pub struct MarkdownView {
13    raw_text: String,
14    contents: Option<ParsedMarkdown>,
15    parsing_markdown_task: Option<Task<Result<()>>>,
16}
17
18impl MarkdownView {
19    pub fn from(text: String, cx: &mut Context<Self>) -> Self {
20        let task = cx.spawn(|markdown_view, mut cx| {
21            let text = text.clone();
22            let parsed = cx
23                .background_executor()
24                .spawn(async move { parse_markdown(&text, None, None).await });
25
26            async move {
27                let content = parsed.await;
28
29                markdown_view.update(&mut cx, |markdown, cx| {
30                    markdown.parsing_markdown_task.take();
31                    markdown.contents = Some(content);
32                    cx.notify();
33                })
34            }
35        });
36
37        Self {
38            raw_text: text.clone(),
39            contents: None,
40            parsing_markdown_task: Some(task),
41        }
42    }
43}
44
45impl OutputContent for MarkdownView {
46    fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
47        Some(ClipboardItem::new_string(self.raw_text.clone()))
48    }
49
50    fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
51        true
52    }
53
54    fn has_buffer_content(&self, _window: &Window, _cx: &App) -> bool {
55        true
56    }
57
58    fn buffer_content(&mut self, _: &mut Window, cx: &mut App) -> Option<Entity<Buffer>> {
59        let buffer = cx.new(|cx| {
60            // TODO: Bring in the language registry so we can set the language to markdown
61            let mut buffer = Buffer::local(self.raw_text.clone(), cx)
62                .with_language(language::PLAIN_TEXT.clone(), cx);
63            buffer.set_capability(language::Capability::ReadOnly, cx);
64            buffer
65        });
66        Some(buffer)
67    }
68}
69
70impl Render for MarkdownView {
71    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
72        let Some(parsed) = self.contents.as_ref() else {
73            return div().into_any_element();
74        };
75
76        let mut markdown_render_context =
77            markdown_preview::markdown_renderer::RenderContext::new(None, window, cx);
78
79        v_flex()
80            .gap_3()
81            .py_4()
82            .children(parsed.children.iter().map(|child| {
83                div().relative().child(
84                    div()
85                        .relative()
86                        .child(render_markdown_block(child, &mut markdown_render_context)),
87                )
88            }))
89            .into_any_element()
90    }
91}