1use std::{
2 any::Any,
3 collections::HashMap,
4 fmt,
5 ops::Range,
6 sync::{
7 Arc,
8 atomic::{AtomicU64, Ordering},
9 },
10};
11
12use gpui::{AnyElement, App, Div, IntoElement, SharedString, Window};
13use markdown::{ParseOptions, mdast};
14
15use super::{InlineElement, InlineRenderContext};
16use crate::text::node::Span;
17
18static MARKDOWN_EXTENSIONS_REVISION: AtomicU64 = AtomicU64::new(1);
19
20pub use markdown::mdast as markdown_ast;
22
23pub type MarkdownBlockParserFn =
29 dyn for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode> + Send + Sync;
30
31pub type MarkdownBlockRenderFn =
33 dyn Fn(&MarkdownNode, &mut Window, &mut App) -> AnyElement + Send + Sync;
34
35pub type BlockSurfaceRenderFn = dyn Fn(&mut Window, &mut App) -> Div + Send + Sync;
37
38type MarkdownInlineParserFn = MarkdownBlockParserFn;
40
41type MarkdownInlineRenderFn = dyn Fn(&MarkdownNode, &InlineRenderContext, &mut Window, &mut App) -> Option<InlineElement>
44 + Send
45 + Sync;
46
47pub trait MarkdownPlugin: Send + Sync + 'static {
49 fn is_block(&self) -> bool {
53 false
54 }
55
56 fn name(&self) -> &str;
58
59 fn parse(&self, node: &mdast::Node, cx: &MarkdownParseContext<'_>) -> Option<MarkdownNode>;
61
62 fn render(&self, node: &MarkdownNode, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
64 node.as_text().to_string()
65 }
66
67 fn render_inline(
70 &self,
71 node: &MarkdownNode,
72 _context: &InlineRenderContext,
73 window: &mut Window,
74 cx: &mut App,
75 ) -> Option<InlineElement> {
76 Some(InlineElement::new(self.render(node, window, cx)))
77 }
78}
79
80pub struct MarkdownParseContext<'a> {
82 source: &'a str,
83 offset: usize,
84}
85
86impl<'a> MarkdownParseContext<'a> {
87 pub(crate) fn new(source: &'a str, offset: usize) -> Self {
88 Self { source, offset }
89 }
90
91 pub fn source(&self) -> &'a str {
93 self.source
94 }
95
96 pub fn offset(&self) -> usize {
99 self.offset
100 }
101
102 pub fn node_source(&self, node: &mdast::Node) -> Option<&'a str> {
104 let position = node.position()?;
105 self.source.get(position.start.offset..position.end.offset)
106 }
107}
108
109#[derive(Clone)]
111pub struct MarkdownNode {
112 name: SharedString,
113 text: SharedString,
114 markdown: SharedString,
115 accessibility_label: Option<SharedString>,
116 data: Arc<dyn Any + Send + Sync>,
117 pub(crate) span: Option<Span>,
118}
119
120impl MarkdownNode {
121 pub fn new<T>(name: impl Into<SharedString>, data: T) -> Self
123 where
124 T: Any + Send + Sync + 'static,
125 {
126 Self {
127 name: name.into(),
128 text: SharedString::default(),
129 markdown: SharedString::default(),
130 accessibility_label: None,
131 data: Arc::new(data),
132 span: None,
133 }
134 }
135
136 pub fn name(&self) -> &str {
138 &self.name
139 }
140
141 pub fn as_text(&self) -> &str {
143 &self.text
144 }
145
146 pub fn as_markdown(&self) -> &str {
148 &self.markdown
149 }
150
151 pub fn source_range(&self) -> Option<Range<usize>> {
153 self.span.map(|span| span.start..span.end)
154 }
155
156 pub fn accessibility_name(&self) -> &str {
158 self.accessibility_label.as_deref().unwrap_or(&self.text)
159 }
160
161 pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
162 let label = label.into();
163 self.accessibility_label = (!label.is_empty()).then_some(label);
164 self
165 }
166
167 pub(crate) fn shared_text(&self) -> SharedString {
173 self.text.clone()
174 }
175
176 pub(crate) fn shared_accessibility_name(&self) -> SharedString {
178 self.accessibility_label
179 .clone()
180 .unwrap_or_else(|| self.text.clone())
181 }
182
183 pub(crate) fn with_inline_source(mut self, source: &str) -> Self {
184 if self.text.is_empty() {
185 self.text = source.to_string().into();
186 }
187 if self.markdown.is_empty() {
188 self.markdown = source.to_string().into();
189 }
190 self
191 }
192
193 pub fn text(mut self, text: impl Into<SharedString>) -> Self {
195 self.text = text.into();
196 self
197 }
198
199 pub fn markdown(mut self, markdown: impl Into<SharedString>) -> Self {
201 self.markdown = markdown.into();
202 self
203 }
204
205 pub fn data<T>(&self) -> Option<&T>
207 where
208 T: Any + Send + Sync + 'static,
209 {
210 self.data.downcast_ref()
211 }
212
213 pub(crate) fn set_span(&mut self, span: Option<Span>) {
214 self.span = span;
215 }
216
217 pub(crate) fn to_markdown(&self) -> String {
218 if self.markdown.is_empty() {
219 self.text.to_string()
220 } else {
221 self.markdown.to_string()
222 }
223 }
224}
225
226impl fmt::Debug for MarkdownNode {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.debug_struct("MarkdownNode")
229 .field("name", &self.name)
230 .field("text", &self.text)
231 .field("markdown", &self.markdown)
232 .field("span", &self.span)
233 .finish_non_exhaustive()
234 }
235}
236
237impl PartialEq for MarkdownNode {
238 fn eq(&self, other: &Self) -> bool {
239 self.name == other.name
240 && self.text == other.text
241 && self.markdown == other.markdown
242 && self.accessibility_label == other.accessibility_label
243 && self.span == other.span
244 }
245}
246
247#[derive(Clone, Default)]
249pub struct MarkdownExtensions {
250 enable_mdx: bool,
251 enable_frontmatter: bool,
252 block_parsers: Vec<Arc<MarkdownBlockParserFn>>,
253 block_renderers: HashMap<SharedString, Arc<MarkdownBlockRenderFn>>,
254 inline_parsers: Vec<Arc<MarkdownInlineParserFn>>,
255 inline_renderers: HashMap<SharedString, Arc<MarkdownInlineRenderFn>>,
256 block_surfaces: HashMap<SharedString, Arc<BlockSurfaceRenderFn>>,
257 revision: u64,
258 parser_revision: u64,
259}
260
261impl MarkdownExtensions {
262 pub fn parser_revision(mut self, revision: u64) -> Self {
266 self.parser_revision = revision;
267 self.bump_revision();
268 self
269 }
270
271 pub fn frontmatter(mut self) -> Self {
277 self.enable_frontmatter = true;
278 self.bump_revision();
279 self
280 }
281
282 pub fn mdx(mut self) -> Self {
287 self.enable_mdx = true;
288 self.bump_revision();
289 self
290 }
291
292 pub fn block_parser<F>(mut self, parser: F) -> Self
294 where
295 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
296 + Send
297 + Sync
298 + 'static,
299 {
300 self.push_block_parser(parser);
301 self
302 }
303
304 pub fn block_renderer<F, E>(mut self, name: impl Into<SharedString>, renderer: F) -> Self
306 where
307 F: Fn(&MarkdownNode, &mut Window, &mut App) -> E + Send + Sync + 'static,
308 E: IntoElement,
309 {
310 self.push_block_renderer(name, renderer);
311 self
312 }
313
314 pub fn block_surface<F>(mut self, name: impl Into<SharedString>, render: F) -> Self
356 where
357 F: Fn(&mut Window, &mut App) -> Div + Send + Sync + 'static,
358 {
359 let name = name.into();
360 let name = if name.bytes().any(|byte| byte.is_ascii_uppercase()) {
361 name.to_ascii_lowercase().into()
362 } else {
363 name
364 };
365 self.block_surfaces.insert(name, Arc::new(render));
366 self.bump_revision();
367 self
368 }
369
370 pub fn plugin<P>(self, plugin: P) -> Self
372 where
373 P: MarkdownPlugin,
374 {
375 let plugin = Arc::new(plugin);
376 let name = SharedString::from(plugin.name().to_string());
377 let parser = plugin.clone();
378 let renderer = plugin;
379
380 if parser.is_block() {
381 let mut extensions = self.block_parser(move |node, cx| parser.parse(node, cx));
382 extensions.push_block_renderer(name, move |node, window, cx| {
383 renderer.render(node, window, cx).into_any_element()
384 });
385 extensions
386 } else {
387 let mut extensions = self;
388 extensions
389 .inline_parsers
390 .push(Arc::new(move |node, cx| parser.parse(node, cx)));
391 extensions.inline_renderers.insert(
392 name,
393 Arc::new(move |node, context, window, cx| {
394 renderer.render_inline(node, context, window, cx)
395 }),
396 );
397 extensions.bump_revision();
398 extensions
399 }
400 }
401
402 pub(crate) fn revision(&self) -> u64 {
403 self.revision
404 }
405
406 pub(crate) fn has_same_parser_configuration(&self, other: &Self) -> bool {
412 self.parser_revision == other.parser_revision
413 && self.enable_mdx == other.enable_mdx
414 && self.enable_frontmatter == other.enable_frontmatter
415 && self.block_parsers.len() == other.block_parsers.len()
416 && self.block_renderers.len() == other.block_renderers.len()
417 && self.inline_parsers.len() == other.inline_parsers.len()
418 && self.inline_renderers.len() == other.inline_renderers.len()
419 && self.block_surfaces.len() == other.block_surfaces.len()
420 && self
421 .block_surfaces
422 .keys()
423 .all(|name| other.block_surfaces.contains_key(name))
424 && self
425 .inline_renderers
426 .keys()
427 .all(|name| other.inline_renderers.contains_key(name))
428 && self
429 .block_renderers
430 .keys()
431 .all(|name| other.block_renderers.contains_key(name))
432 }
433
434 pub(crate) fn push_block_parser<F>(&mut self, parser: F)
435 where
436 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
437 + Send
438 + Sync
439 + 'static,
440 {
441 self.block_parsers.push(Arc::new(parser));
442 self.bump_revision();
443 }
444
445 pub(crate) fn push_block_renderer<F, E>(&mut self, name: impl Into<SharedString>, renderer: F)
446 where
447 F: Fn(&MarkdownNode, &mut Window, &mut App) -> E + Send + Sync + 'static,
448 E: IntoElement,
449 {
450 self.block_renderers.insert(
451 name.into(),
452 Arc::new(move |node, window, cx| renderer(node, window, cx).into_any_element()),
453 );
454 self.bump_revision();
455 }
456
457 pub(crate) fn parse_options(&self) -> ParseOptions {
458 let mut options = ParseOptions::gfm();
459 options.constructs.frontmatter = self.enable_frontmatter;
460 options.constructs.math_text = true;
461 options.constructs.math_flow = true;
465 if self.enable_mdx {
466 options.constructs.html_flow = false;
467 options.constructs.html_text = false;
468 options.constructs.mdx_expression_flow = true;
469 options.constructs.mdx_expression_text = true;
470 options.constructs.mdx_jsx_flow = true;
471 options.constructs.mdx_jsx_text = true;
472 }
473 options
474 }
475
476 pub(crate) fn has_block_surfaces(&self) -> bool {
477 !self.block_surfaces.is_empty()
478 }
479
480 pub(crate) fn block_surface_name(&self, written: &str) -> Option<SharedString> {
482 let name = if written.bytes().any(|byte| byte.is_ascii_uppercase()) {
483 self.block_surfaces
484 .get_key_value(written.to_ascii_lowercase().as_str())
485 } else {
486 self.block_surfaces.get_key_value(written)
487 };
488 name.map(|(name, _)| name.clone())
489 }
490
491 pub(crate) fn render_block_surface(
492 &self,
493 name: &str,
494 window: &mut Window,
495 cx: &mut App,
496 ) -> Option<Div> {
497 self.block_surfaces
498 .get(name)
499 .map(|render| render(window, cx))
500 }
501
502 pub(crate) fn parse_block(
503 &self,
504 node: &mdast::Node,
505 cx: &MarkdownParseContext<'_>,
506 ) -> Option<MarkdownNode> {
507 for parser in &self.block_parsers {
508 if let Some(node) = parser(node, cx) {
509 return Some(node);
510 }
511 }
512 None
513 }
514
515 pub(crate) fn parse_inline(
516 &self,
517 node: &mdast::Node,
518 cx: &MarkdownParseContext<'_>,
519 ) -> Option<MarkdownNode> {
520 self.inline_parsers
521 .iter()
522 .find_map(|parser| parser(node, cx))
523 }
524
525 pub(crate) fn render_inline(
526 &self,
527 node: &MarkdownNode,
528 context: &InlineRenderContext,
529 window: &mut Window,
530 cx: &mut App,
531 ) -> Option<InlineElement> {
532 self.inline_renderers
533 .get(node.name())
534 .and_then(|render| render(node, context, window, cx))
535 }
536
537 pub(crate) fn render_block(
538 &self,
539 node: &MarkdownNode,
540 window: &mut Window,
541 cx: &mut App,
542 ) -> Option<AnyElement> {
543 self.block_renderers
544 .get(node.name())
545 .map(|render| render(node, window, cx))
546 }
547
548 fn bump_revision(&mut self) {
549 self.revision = MARKDOWN_EXTENSIONS_REVISION.fetch_add(1, Ordering::Relaxed);
550 }
551}
552
553#[cfg(test)]
555pub(super) struct TestInlinePlugin {
556 name: &'static str,
557 parser: Option<Arc<MarkdownInlineParserFn>>,
558 renderer: Option<Arc<MarkdownInlineRenderFn>>,
559}
560
561#[cfg(test)]
562impl TestInlinePlugin {
563 pub(super) fn new(name: &'static str) -> Self {
564 Self {
565 name,
566 parser: None,
567 renderer: None,
568 }
569 }
570
571 pub(super) fn parse_with<F>(mut self, parser: F) -> Self
572 where
573 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
574 + Send
575 + Sync
576 + 'static,
577 {
578 self.parser = Some(Arc::new(parser));
579 self
580 }
581
582 pub(super) fn render_with<F>(mut self, renderer: F) -> Self
583 where
584 F: Fn(&MarkdownNode, &InlineRenderContext, &mut Window, &mut App) -> Option<InlineElement>
585 + Send
586 + Sync
587 + 'static,
588 {
589 self.renderer = Some(Arc::new(renderer));
590 self
591 }
592}
593
594#[cfg(test)]
595impl MarkdownPlugin for TestInlinePlugin {
596 fn name(&self) -> &str {
597 self.name
598 }
599
600 fn parse(&self, node: &mdast::Node, cx: &MarkdownParseContext<'_>) -> Option<MarkdownNode> {
601 self.parser.as_ref().and_then(|parse| parse(node, cx))
602 }
603
604 fn render_inline(
605 &self,
606 node: &MarkdownNode,
607 context: &InlineRenderContext,
608 window: &mut Window,
609 cx: &mut App,
610 ) -> Option<InlineElement> {
611 self.renderer
612 .as_ref()
613 .and_then(|render| render(node, context, window, cx))
614 }
615}