markdown: Track code block metadata in parser (#28543)
This allows us to not scan the codeblock content for newlines on every frame in `active_thread` Release Notes: - N/A
This commit is contained in:
parent
73305ce45e
commit
44cb8e582b
3 changed files with 247 additions and 203 deletions
|
@ -18,6 +18,7 @@ use gpui::{
|
|||
TextStyleRefinement, actions, point, quad,
|
||||
};
|
||||
use language::{Language, LanguageRegistry, Rope};
|
||||
use parser::CodeBlockMetadata;
|
||||
use parser::{MarkdownEvent, MarkdownTag, MarkdownTagEnd, parse_links_only, parse_markdown};
|
||||
use pulldown_cmark::Alignment;
|
||||
use sum_tree::TreeMap;
|
||||
|
@ -99,10 +100,19 @@ pub enum CodeBlockRenderer {
|
|||
},
|
||||
}
|
||||
|
||||
pub type CodeBlockRenderFn =
|
||||
Arc<dyn Fn(&CodeBlockKind, &ParsedMarkdown, Range<usize>, &mut Window, &App) -> Div>;
|
||||
pub type CodeBlockRenderFn = Arc<
|
||||
dyn Fn(
|
||||
&CodeBlockKind,
|
||||
&ParsedMarkdown,
|
||||
Range<usize>,
|
||||
CodeBlockMetadata,
|
||||
&mut Window,
|
||||
&App,
|
||||
) -> Div,
|
||||
>;
|
||||
|
||||
pub type CodeBlockTransformFn = Arc<dyn Fn(AnyDiv, Range<usize>, &mut Window, &App) -> AnyDiv>;
|
||||
pub type CodeBlockTransformFn =
|
||||
Arc<dyn Fn(AnyDiv, Range<usize>, CodeBlockMetadata, &mut Window, &App) -> AnyDiv>;
|
||||
|
||||
actions!(markdown, [Copy, CopyAsMarkdown]);
|
||||
|
||||
|
@ -603,6 +613,8 @@ impl Element for MarkdownElement {
|
|||
0
|
||||
};
|
||||
|
||||
let mut current_code_block_metadata = None;
|
||||
|
||||
for (range, event) in parsed_markdown.events.iter() {
|
||||
match event {
|
||||
MarkdownEvent::Start(tag) => {
|
||||
|
@ -641,7 +653,7 @@ impl Element for MarkdownElement {
|
|||
markdown_end,
|
||||
);
|
||||
}
|
||||
MarkdownTag::CodeBlock(kind) => {
|
||||
MarkdownTag::CodeBlock { kind, metadata } => {
|
||||
let language = match kind {
|
||||
CodeBlockKind::Fenced => None,
|
||||
CodeBlockKind::FencedLang(language) => {
|
||||
|
@ -654,6 +666,8 @@ impl Element for MarkdownElement {
|
|||
_ => None,
|
||||
};
|
||||
|
||||
current_code_block_metadata = Some(metadata.clone());
|
||||
|
||||
let is_indented = matches!(kind, CodeBlockKind::Indented);
|
||||
|
||||
match (&self.code_block_renderer, is_indented) {
|
||||
|
@ -686,8 +700,14 @@ impl Element for MarkdownElement {
|
|||
builder.push_div(code_block, range, markdown_end);
|
||||
}
|
||||
(CodeBlockRenderer::Custom { render, .. }, _) => {
|
||||
let parent_container =
|
||||
render(kind, &parsed_markdown, range.clone(), window, cx);
|
||||
let parent_container = render(
|
||||
kind,
|
||||
&parsed_markdown,
|
||||
range.clone(),
|
||||
metadata.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
builder.push_div(parent_container, range, markdown_end);
|
||||
|
||||
|
@ -852,12 +872,22 @@ impl Element for MarkdownElement {
|
|||
builder.pop_text_style();
|
||||
}
|
||||
|
||||
let metadata = current_code_block_metadata.take();
|
||||
|
||||
if let CodeBlockRenderer::Custom {
|
||||
transform: Some(modify),
|
||||
transform: Some(transform),
|
||||
..
|
||||
} = &self.code_block_renderer
|
||||
{
|
||||
builder.modify_current_div(|el| modify(el, range.clone(), window, cx));
|
||||
builder.modify_current_div(|el| {
|
||||
transform(
|
||||
el,
|
||||
range.clone(),
|
||||
metadata.clone().unwrap_or_default(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
if matches!(
|
||||
|
@ -866,9 +896,13 @@ impl Element for MarkdownElement {
|
|||
) {
|
||||
builder.flush_text();
|
||||
builder.modify_current_div(|el| {
|
||||
let code =
|
||||
without_fences(parsed_markdown.source()[range.clone()].trim())
|
||||
.to_string();
|
||||
let content_range = parser::extract_code_block_content_range(
|
||||
parsed_markdown.source()[range.clone()].trim(),
|
||||
);
|
||||
let content_range = content_range.start + range.start
|
||||
..content_range.end + range.start;
|
||||
|
||||
let code = parsed_markdown.source()[content_range].to_string();
|
||||
let codeblock = render_copy_code_block_button(
|
||||
range.end,
|
||||
code,
|
||||
|
@ -1507,43 +1541,3 @@ impl RenderedText {
|
|||
.find(|link| link.source_range.contains(&source_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Some markdown blocks are indented, and others have e.g. ```rust … ``` around them.
|
||||
/// If this block is fenced with backticks, strip them off (and the language name).
|
||||
/// We use this when copying code blocks to the clipboard.
|
||||
pub fn without_fences(mut markdown: &str) -> &str {
|
||||
if let Some(opening_backticks) = markdown.find("```") {
|
||||
markdown = &markdown[opening_backticks..];
|
||||
|
||||
// Trim off the next newline. This also trims off a language name if it's there.
|
||||
if let Some(newline) = markdown.find('\n') {
|
||||
markdown = &markdown[newline + 1..];
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(closing_backticks) = markdown.rfind("```") {
|
||||
markdown = &markdown[..closing_backticks];
|
||||
};
|
||||
|
||||
markdown
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_without_fences() {
|
||||
let input = "```rust\nlet x = 5;\n```";
|
||||
assert_eq!(without_fences(input), "let x = 5;\n");
|
||||
|
||||
let input = " ```\nno language\n``` ";
|
||||
assert_eq!(without_fences(input), "no language\n");
|
||||
|
||||
let input = "plain text";
|
||||
assert_eq!(without_fences(input), "plain text");
|
||||
|
||||
let input = "```python\nprint('hello')\nprint('world')\n```";
|
||||
assert_eq!(without_fences(input), "print('hello')\nprint('world')\n");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue