markdown.rs

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