markdown: Restore fallback language highlighting for untagged code blocks (#63023)

Restores fallback highlights lost in
https://github.com/zed-industries/zed/pull/28217

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-08-21 16:00:50 +00:00
1 parent 9b5e282df8
commit 7dcda48fdf
5 files changed
+93 -14

No files matched your search

Generated
-1
View File
@@ -10460,7 +10460,6 @@ dependencies = [
"collections",
"env_logger 0.11.8",
"fs",
"futures 0.3.32",
"gpui",
"gpui_platform",
"html5ever 0.27.0",
-1
View File
@@ -46,4 +46,3 @@ Zed is developed by **Zed Industries, Inc.**, a for-profit company.
If you’d like to financially support the project, you can do so via GitHub Sponsors.
Sponsorships go directly to Zed Industries and are used as general company revenue.
There are no perks or entitlements associated with sponsorship.
-1
View File
@@ -22,7 +22,6 @@ test-support = [
anyhow.workspace = true
base64.workspace = true
collections.workspace = true
futures.workspace = true
gpui.workspace = true
html5ever.workspace = true
language.workspace = true
+89 -11
View File
@@ -5,7 +5,7 @@ mod path_range;
mod selection;
use base64::Engine as _;
use futures::FutureExt as _;
use gpui::EdgesRefinement;
use gpui::HitboxBehavior;
use gpui::UnderlineStyle;
@@ -958,7 +958,8 @@ impl Markdown {
.languages_by_path
.get(&path_range.path)
.cloned(),
CodeBlockKind::Fenced | CodeBlockKind::Indented => None,
CodeBlockKind::Fenced => self.parsed_markdown.fallback_code_block_language.clone(),
CodeBlockKind::Indented => None,
}
})
}
@@ -1226,6 +1227,7 @@ impl Markdown {
heading_slugs: HashMap::default(),
footnote_definitions: HashMap::default(),
link_definition_spans: Arc::default(),
fallback_code_block_language: None,
},
Default::default(),
);
@@ -1246,6 +1248,7 @@ impl Markdown {
let heading_slugs = parsed.heading_slugs;
let footnote_definitions = parsed.footnote_definitions;
let link_definition_spans = parsed.link_definition_spans;
let has_untagged_code_block = parsed.has_untagged_code_block;
let mermaid_diagrams = if should_render_mermaid_diagrams {
extract_mermaid_diagrams(&source, &events)
} else {
@@ -1254,16 +1257,10 @@ impl Markdown {
let mut images_by_source_offset = HashMap::default();
let mut languages_by_name = TreeMap::default();
let mut languages_by_path = TreeMap::default();
let mut fallback_code_block_language = None;
if let Some(registry) = language_registry.as_ref() {
for name in language_names {
let language = if !name.is_empty() {
registry.language_for_name_or_extension(&name).left_future()
} else if let Some(fallback) = &fallback {
registry.language_for_name(fallback.as_ref()).right_future()
} else {
continue;
};
if let Ok(language) = language.await {
if let Ok(language) = registry.language_for_name_or_extension(&name).await {
languages_by_name.insert(name, language);
}
}
@@ -1276,6 +1273,11 @@ impl Markdown {
languages_by_path.insert(path, language);
}
}
if has_untagged_code_block && let Some(fallback) = &fallback {
fallback_code_block_language =
registry.language_for_name(fallback.as_ref()).await.ok();
}
}
for (range, event) in &events {
@@ -1316,6 +1318,7 @@ impl Markdown {
heading_slugs,
footnote_definitions,
link_definition_spans: Arc::from(link_definition_spans),
fallback_code_block_language,
},
images_by_source_offset,
)
@@ -1463,6 +1466,7 @@ pub struct ParsedMarkdown {
pub heading_slugs: HashMap<SharedString, usize>,
pub footnote_definitions: HashMap<SharedString, usize>,
pub(crate) link_definition_spans: Arc<[Range<usize>]>,
pub(crate) fallback_code_block_language: Option<Arc<Language>>,
}
impl ParsedMarkdown {
@@ -2622,7 +2626,9 @@ impl Element for MarkdownElement {
}
let language = match kind {
CodeBlockKind::Fenced => None,
CodeBlockKind::Fenced => {
parsed_markdown.fallback_code_block_language.clone()
}
CodeBlockKind::FencedLang(language) => {
parsed_markdown.languages_by_name.get(language).cloned()
}
@@ -5014,6 +5020,78 @@ mod tests {
});
}
#[gpui::test]
fn test_fallback_language_highlights_untagged_code_blocks(cx: &mut TestAppContext) {
let language = language::rust_lang();
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
language_registry.add(language.clone());
let source = indoc::indoc! {"
```
fn main() {}
```
"};
let markdown = cx.new(|cx| {
Markdown::new(
source.into(),
Some(language_registry),
Some(language.name()),
cx,
)
});
cx.run_until_parked();
markdown.read_with(cx, |markdown, _| {
let fallback = markdown
.parsed_markdown()
.fallback_code_block_language
.as_ref()
.expect("the fallback language must be resolved for untagged code blocks");
assert_eq!(fallback.name(), language.name());
assert_eq!(
markdown
.first_code_block_language()
.map(|language| language.name()),
Some(language.name()),
"untagged code blocks must resolve to the fallback language"
);
});
}
#[gpui::test]
fn test_no_fallback_language_without_untagged_code_blocks(cx: &mut TestAppContext) {
let language = language::rust_lang();
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
language_registry.add(language.clone());
let source = indoc::indoc! {"
```rust
fn main() {}
```
"};
let markdown = cx.new(|cx| {
Markdown::new(
source.into(),
Some(language_registry),
Some(language.name()),
cx,
)
});
cx.run_until_parked();
markdown.read_with(cx, |markdown, _| {
assert_eq!(
markdown
.parsed_markdown()
.fallback_code_block_language
.as_ref()
.map(|language| language.name()),
None,
"The fallback language must not be loaded when no code block needs it"
);
});
}
#[gpui::test]
fn test_frontmatter_renders_without_delimiters(cx: &mut TestAppContext) {
let rendered = render_markdown_with_options(
+4
View File
@@ -43,6 +43,7 @@ pub(crate) struct ParsedMarkdownData {
/// Source spans of link reference definitions (`[id]: https://example.com`), which are
/// consumed by the parser and never appear in any event range.
pub link_definition_spans: Vec<Range<usize>>,
pub has_untagged_code_block: bool,
}
#[derive(Clone, Debug, PartialEq)]
@@ -240,6 +241,7 @@ pub(crate) fn parse_markdown_with_options(
let mut state = ParseState::default();
let mut language_names = HashSet::default();
let mut language_paths = HashSet::default();
let mut has_untagged_code_block = false;
let mut html_blocks = BTreeMap::default();
let mut metadata_blocks = BTreeMap::default();
let mut within_link = false;
@@ -359,6 +361,7 @@ pub(crate) fn parse_markdown_with_options(
let info = info.trim();
let kind = if info.is_empty() {
has_untagged_code_block = true;
CodeBlockKind::Fenced
// Languages should never contain a slash, and PathRanges always should.
// (Models are told to specify them relative to a workspace root.)
@@ -686,6 +689,7 @@ pub(crate) fn parse_markdown_with_options(
heading_slugs,
footnote_definitions,
link_definition_spans,
has_untagged_code_block,
}
}