1use std::{
2 any::Any,
3 collections::HashMap,
4 fmt,
5 sync::{
6 Arc,
7 atomic::{AtomicU64, Ordering},
8 },
9};
10
11use gpui::{AnyElement, App, IntoElement, SharedString, Window};
12use markdown::{ParseOptions, mdast};
13
14use crate::text::node::Span;
15
16static MARKDOWN_EXTENSIONS_REVISION: AtomicU64 = AtomicU64::new(1);
17
18pub use markdown::mdast as markdown_ast;
20
21pub type MarkdownBlockParserFn =
27 dyn for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode> + Send + Sync;
28
29pub type MarkdownBlockRenderFn =
31 dyn Fn(&MarkdownNode, &mut Window, &mut App) -> AnyElement + Send + Sync;
32
33pub trait MarkdownPlugin: Send + Sync + 'static {
35 fn is_block(&self) -> bool {
40 false
41 }
42
43 fn name(&self) -> &str;
45
46 fn parse(&self, node: &mdast::Node, cx: &MarkdownParseContext<'_>) -> Option<MarkdownNode>;
48
49 fn render(&self, node: &MarkdownNode, window: &mut Window, cx: &mut App) -> impl IntoElement;
51}
52
53pub struct MarkdownParseContext<'a> {
55 source: &'a str,
56 offset: usize,
57}
58
59impl<'a> MarkdownParseContext<'a> {
60 pub(crate) fn new(source: &'a str, offset: usize) -> Self {
61 Self { source, offset }
62 }
63
64 pub fn source(&self) -> &'a str {
66 self.source
67 }
68
69 pub fn offset(&self) -> usize {
72 self.offset
73 }
74
75 pub fn node_source(&self, node: &mdast::Node) -> Option<&'a str> {
77 let position = node.position()?;
78 self.source.get(position.start.offset..position.end.offset)
79 }
80}
81
82#[derive(Clone)]
84pub struct MarkdownNode {
85 name: SharedString,
86 text: SharedString,
87 markdown: SharedString,
88 data: Arc<dyn Any + Send + Sync>,
89 pub(crate) span: Option<Span>,
90}
91
92impl MarkdownNode {
93 pub fn new<T>(name: impl Into<SharedString>, data: T) -> Self
95 where
96 T: Any + Send + Sync + 'static,
97 {
98 Self {
99 name: name.into(),
100 text: SharedString::default(),
101 markdown: SharedString::default(),
102 data: Arc::new(data),
103 span: None,
104 }
105 }
106
107 pub fn name(&self) -> &str {
109 &self.name
110 }
111
112 pub fn as_text(&self) -> &str {
114 &self.text
115 }
116
117 pub fn as_markdown(&self) -> &str {
119 &self.markdown
120 }
121
122 pub fn text(mut self, text: impl Into<SharedString>) -> Self {
124 self.text = text.into();
125 self
126 }
127
128 pub fn markdown(mut self, markdown: impl Into<SharedString>) -> Self {
130 self.markdown = markdown.into();
131 self
132 }
133
134 pub fn data<T>(&self) -> Option<&T>
136 where
137 T: Any + Send + Sync + 'static,
138 {
139 self.data.downcast_ref()
140 }
141
142 pub(crate) fn set_span(&mut self, span: Option<Span>) {
143 self.span = span;
144 }
145
146 pub(crate) fn to_markdown(&self) -> String {
147 if self.markdown.is_empty() {
148 self.text.to_string()
149 } else {
150 self.markdown.to_string()
151 }
152 }
153}
154
155impl fmt::Debug for MarkdownNode {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.debug_struct("MarkdownNode")
158 .field("name", &self.name)
159 .field("text", &self.text)
160 .field("markdown", &self.markdown)
161 .field("span", &self.span)
162 .finish_non_exhaustive()
163 }
164}
165
166impl PartialEq for MarkdownNode {
167 fn eq(&self, other: &Self) -> bool {
168 self.name == other.name
169 && self.text == other.text
170 && self.markdown == other.markdown
171 && self.span == other.span
172 }
173}
174
175#[derive(Clone, Default)]
177pub struct MarkdownExtensions {
178 enable_mdx: bool,
179 enable_frontmatter: bool,
180 block_parsers: Vec<Arc<MarkdownBlockParserFn>>,
181 block_renderers: HashMap<SharedString, Arc<MarkdownBlockRenderFn>>,
182 revision: u64,
183}
184
185impl MarkdownExtensions {
186 pub fn frontmatter(mut self) -> Self {
192 self.enable_frontmatter = true;
193 self.bump_revision();
194 self
195 }
196
197 pub fn mdx(mut self) -> Self {
202 self.enable_mdx = true;
203 self.bump_revision();
204 self
205 }
206
207 pub fn block_parser<F>(mut self, parser: F) -> Self
209 where
210 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
211 + Send
212 + Sync
213 + 'static,
214 {
215 self.push_block_parser(parser);
216 self
217 }
218
219 pub fn block_renderer<F, E>(mut self, name: impl Into<SharedString>, renderer: F) -> Self
221 where
222 F: Fn(&MarkdownNode, &mut Window, &mut App) -> E + Send + Sync + 'static,
223 E: IntoElement,
224 {
225 self.push_block_renderer(name, renderer);
226 self
227 }
228
229 pub fn plugin<P>(self, plugin: P) -> Self
231 where
232 P: MarkdownPlugin,
233 {
234 let plugin = Arc::new(plugin);
235 let name = SharedString::from(plugin.name().to_string());
236 let parser = plugin.clone();
237 let renderer = plugin;
238
239 if parser.is_block() {
240 let mut extensions = self.block_parser(move |node, cx| parser.parse(node, cx));
241 extensions.push_block_renderer(name, move |node, window, cx| {
242 renderer.render(node, window, cx).into_any_element()
243 });
244 extensions
245 } else {
246 panic!("inline Markdown plugins are not supported by TextView yet")
247 }
248 }
249
250 pub(crate) fn revision(&self) -> u64 {
251 self.revision
252 }
253
254 pub(crate) fn has_same_parser_configuration(&self, other: &Self) -> bool {
260 self.enable_mdx == other.enable_mdx
261 && self.enable_frontmatter == other.enable_frontmatter
262 && self.block_parsers.len() == other.block_parsers.len()
263 && self.block_renderers.len() == other.block_renderers.len()
264 && self
265 .block_renderers
266 .keys()
267 .all(|name| other.block_renderers.contains_key(name))
268 }
269
270 pub(crate) fn push_block_parser<F>(&mut self, parser: F)
271 where
272 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
273 + Send
274 + Sync
275 + 'static,
276 {
277 self.block_parsers.push(Arc::new(parser));
278 self.bump_revision();
279 }
280
281 pub(crate) fn push_block_renderer<F, E>(&mut self, name: impl Into<SharedString>, renderer: F)
282 where
283 F: Fn(&MarkdownNode, &mut Window, &mut App) -> E + Send + Sync + 'static,
284 E: IntoElement,
285 {
286 self.block_renderers.insert(
287 name.into(),
288 Arc::new(move |node, window, cx| renderer(node, window, cx).into_any_element()),
289 );
290 self.bump_revision();
291 }
292
293 pub(crate) fn parse_options(&self) -> ParseOptions {
294 let mut options = ParseOptions::gfm();
295 options.constructs.frontmatter = self.enable_frontmatter;
296 if self.enable_mdx {
297 options.constructs.html_flow = false;
298 options.constructs.html_text = false;
299 options.constructs.mdx_expression_flow = true;
300 options.constructs.mdx_expression_text = true;
301 options.constructs.mdx_jsx_flow = true;
302 options.constructs.mdx_jsx_text = true;
303 }
304 options
305 }
306
307 pub(crate) fn parse_block(
308 &self,
309 node: &mdast::Node,
310 cx: &MarkdownParseContext<'_>,
311 ) -> Option<MarkdownNode> {
312 for parser in &self.block_parsers {
313 if let Some(node) = parser(node, cx) {
314 return Some(node);
315 }
316 }
317 None
318 }
319
320 pub(crate) fn render_block(
321 &self,
322 node: &MarkdownNode,
323 window: &mut Window,
324 cx: &mut App,
325 ) -> Option<AnyElement> {
326 self.block_renderers
327 .get(node.name())
328 .map(|render| render(node, window, cx))
329 }
330
331 fn bump_revision(&mut self) {
332 self.revision = MARKDOWN_EXTENSIONS_REVISION.fetch_add(1, Ordering::Relaxed);
333 }
334}