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 =
23                cx.background_spawn(async move { parse_markdown(&text, None, None).await });
24
25            async move {
26                let content = parsed.await;
27
28                markdown_view.update(&mut cx, |markdown, cx| {
29                    markdown.parsing_markdown_task.take();
30                    markdown.contents = Some(content);
31                    cx.notify();
32                })
33            }
34        });
35
36        Self {
37            raw_text: text.clone(),
38            contents: None,
39            parsing_markdown_task: Some(task),
40        }
41    }
42}
43
44impl OutputContent for MarkdownView {
45    fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
46        Some(ClipboardItem::new_string(self.raw_text.clone()))
47    }
48
49    fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
50        true
51    }
52
53    fn has_buffer_content(&self, _window: &Window, _cx: &App) -> bool {
54        true
55    }
56
57    fn buffer_content(&mut self, _: &mut Window, cx: &mut App) -> Option<Entity<Buffer>> {
58        let buffer = cx.new(|cx| {
59            // TODO: Bring in the language registry so we can set the language to markdown
60            let mut buffer = Buffer::local(self.raw_text.clone(), cx)
61                .with_language(language::PLAIN_TEXT.clone(), cx);
62            buffer.set_capability(language::Capability::ReadOnly, cx);
63            buffer
64        });
65        Some(buffer)
66    }
67}
68
69impl Render for MarkdownView {
70    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
71        let Some(parsed) = self.contents.as_ref() else {
72            return div().into_any_element();
73        };
74
75        let mut markdown_render_context =
76            markdown_preview::markdown_renderer::RenderContext::new(None, window, cx);
77
78        v_flex()
79            .gap_3()
80            .py_4()
81            .children(parsed.children.iter().map(|child| {
82                div().relative().child(
83                    div()
84                        .relative()
85                        .child(render_markdown_block(child, &mut markdown_render_context)),
86                )
87            }))
88            .into_any_element()
89    }
90}