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 block_parsers: Vec<Arc<MarkdownBlockParserFn>>,
180 block_renderers: HashMap<SharedString, Arc<MarkdownBlockRenderFn>>,
181 revision: u64,
182}
183
184impl MarkdownExtensions {
185 pub fn mdx(mut self) -> Self {
190 self.enable_mdx = true;
191 self.bump_revision();
192 self
193 }
194
195 pub fn block_parser<F>(mut self, parser: F) -> Self
197 where
198 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
199 + Send
200 + Sync
201 + 'static,
202 {
203 self.push_block_parser(parser);
204 self
205 }
206
207 pub fn block_renderer<F, E>(mut self, name: impl Into<SharedString>, renderer: F) -> Self
209 where
210 F: Fn(&MarkdownNode, &mut Window, &mut App) -> E + Send + Sync + 'static,
211 E: IntoElement,
212 {
213 self.push_block_renderer(name, renderer);
214 self
215 }
216
217 pub fn plugin<P>(self, plugin: P) -> Self
219 where
220 P: MarkdownPlugin,
221 {
222 let plugin = Arc::new(plugin);
223 let name = SharedString::from(plugin.name().to_string());
224 let parser = plugin.clone();
225 let renderer = plugin;
226
227 if parser.is_block() {
228 let mut extensions = self.block_parser(move |node, cx| parser.parse(node, cx));
229 extensions.push_block_renderer(name, move |node, window, cx| {
230 renderer.render(node, window, cx).into_any_element()
231 });
232 extensions
233 } else {
234 panic!("inline Markdown plugins are not supported by TextView yet")
235 }
236 }
237
238 pub(crate) fn revision(&self) -> u64 {
239 self.revision
240 }
241
242 pub(crate) fn has_same_parser_configuration(&self, other: &Self) -> bool {
248 self.enable_mdx == other.enable_mdx
249 && self.block_parsers.len() == other.block_parsers.len()
250 && self.block_renderers.len() == other.block_renderers.len()
251 && self
252 .block_renderers
253 .keys()
254 .all(|name| other.block_renderers.contains_key(name))
255 }
256
257 pub(crate) fn push_block_parser<F>(&mut self, parser: F)
258 where
259 F: for<'a> Fn(&mdast::Node, &MarkdownParseContext<'a>) -> Option<MarkdownNode>
260 + Send
261 + Sync
262 + 'static,
263 {
264 self.block_parsers.push(Arc::new(parser));
265 self.bump_revision();
266 }
267
268 pub(crate) fn push_block_renderer<F, E>(&mut self, name: impl Into<SharedString>, renderer: F)
269 where
270 F: Fn(&MarkdownNode, &mut Window, &mut App) -> E + Send + Sync + 'static,
271 E: IntoElement,
272 {
273 self.block_renderers.insert(
274 name.into(),
275 Arc::new(move |node, window, cx| renderer(node, window, cx).into_any_element()),
276 );
277 self.bump_revision();
278 }
279
280 pub(crate) fn parse_options(&self) -> ParseOptions {
281 let mut options = ParseOptions::gfm();
282 if self.enable_mdx {
283 options.constructs.html_flow = false;
284 options.constructs.html_text = false;
285 options.constructs.mdx_expression_flow = true;
286 options.constructs.mdx_expression_text = true;
287 options.constructs.mdx_jsx_flow = true;
288 options.constructs.mdx_jsx_text = true;
289 }
290 options
291 }
292
293 pub(crate) fn parse_block(
294 &self,
295 node: &mdast::Node,
296 cx: &MarkdownParseContext<'_>,
297 ) -> Option<MarkdownNode> {
298 for parser in &self.block_parsers {
299 if let Some(node) = parser(node, cx) {
300 return Some(node);
301 }
302 }
303 None
304 }
305
306 pub(crate) fn render_block(
307 &self,
308 node: &MarkdownNode,
309 window: &mut Window,
310 cx: &mut App,
311 ) -> Option<AnyElement> {
312 self.block_renderers
313 .get(node.name())
314 .map(|render| render(node, window, cx))
315 }
316
317 fn bump_revision(&mut self) {
318 self.revision = MARKDOWN_EXTENSIONS_REVISION.fetch_add(1, Ordering::Relaxed);
319 }
320}