1use anyhow::Result;
2use gpui::{div, prelude::*, ClipboardItem, Task, ViewContext, WindowContext};
3use markdown_preview::{
4 markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown,
5 markdown_renderer::render_markdown_block,
6};
7use ui::v_flex;
8
9use crate::outputs::SupportsClipboard;
10
11pub struct MarkdownView {
12 raw_text: String,
13 contents: Option<ParsedMarkdown>,
14 parsing_markdown_task: Option<Task<Result<()>>>,
15}
16
17impl MarkdownView {
18 pub fn from(text: String, cx: &mut ViewContext<Self>) -> Self {
19 let task = cx.spawn(|markdown_view, mut cx| {
20 let text = text.clone();
21 let parsed = cx
22 .background_executor()
23 .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 SupportsClipboard for MarkdownView {
45 fn clipboard_content(&self, _cx: &WindowContext) -> Option<ClipboardItem> {
46 Some(ClipboardItem::new_string(self.raw_text.clone()))
47 }
48
49 fn has_clipboard_content(&self, _cx: &WindowContext) -> bool {
50 true
51 }
52}
53
54impl Render for MarkdownView {
55 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
56 let Some(parsed) = self.contents.as_ref() else {
57 return div().into_any_element();
58 };
59
60 let mut markdown_render_context =
61 markdown_preview::markdown_renderer::RenderContext::new(None, cx);
62
63 v_flex()
64 .gap_3()
65 .py_4()
66 .children(parsed.children.iter().map(|child| {
67 div().relative().child(
68 div()
69 .relative()
70 .child(render_markdown_block(child, &mut markdown_render_context)),
71 )
72 }))
73 .into_any_element()
74 }
75}