From 8677759c7aedcd842f02351160dd17dfd34777c6 Mon Sep 17 00:00:00 2001 From: Finn Eitreim <48069764+feitreim@users.noreply.github.com> Date: Sun, 19 Jul 2026 23:52:32 -0400 Subject: [PATCH] markdown: Fix inline HTML block highlighting (#61212) # Objective Fixes #59304 Right now inline html tags don't render with any kind of highlighting, despite the fact that non-inline html tags do get highlighting. ## Solution Right now zed already has tree-sitter rules that identifies the inline blocks so we can add a new tree-sitter grammar rule, ``` ((html_tag) @injection.content (#set! injection.language "html") (#set! injection.combined)) ``` that passes these blocks to the html language server. ## Testing I did some manual testing: https://github.com/user-attachments/assets/92e5b322-bbc1-48c3-a615-0d32acc2e7d6 And also added a test: test_markdown_inline_html_highlighting ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - markdown: Fixed inline html block highlighting. --- .../src/markdown-inline/injections.scm | 4 + crates/language/src/buffer_tests.rs | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/crates/grammars/src/markdown-inline/injections.scm b/crates/grammars/src/markdown-inline/injections.scm index 074b08fd874..3b82d3697c6 100644 --- a/crates/grammars/src/markdown-inline/injections.scm +++ b/crates/grammars/src/markdown-inline/injections.scm @@ -1,2 +1,6 @@ +((html_tag) @injection.content + (#set! injection.language "html") + (#set! injection.combined)) + ((latex_block) @injection.content (#set! injection.language "latex")) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index ea4c49d920c..a5dd8f4b07e 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2981,6 +2981,104 @@ fn test_language_at_for_markdown_code_block(cx: &mut App) { }); } +#[gpui::test] +async fn test_markdown_inline_html_highlighting(cx: &mut TestAppContext) { + let markdown_language = markdown_lang(); + let markdown_inline_language = Arc::new( + Language::new( + LanguageConfig { + name: "markdown-inline".into(), + grammar: Some("markdown-inline".into()), + ..Default::default() + }, + Some(tree_sitter_md::INLINE_LANGUAGE.into()), + ) + .with_highlights_query(include_str!( + "../../grammars/src/markdown-inline/highlights.scm" + )) + .unwrap() + .with_injection_query(include_str!( + "../../grammars/src/markdown-inline/injections.scm" + )) + .unwrap(), + ); + let html_language = Arc::new( + Language::new( + LanguageConfig { + name: "HTML".into(), + ..Default::default() + }, + Some(tree_sitter_html::LANGUAGE.into()), + ) + .with_highlights_query("(comment) @comment (tag_name) @tag") + .unwrap(), + ); + let syntax_theme = SyntaxTheme::new([ + ("comment".to_string(), gpui::rgba(0xffffffff).into()), + ("tag".to_string(), gpui::rgba(0xff0000ff).into()), + ]); + markdown_language.set_theme(&syntax_theme); + markdown_inline_language.set_theme(&syntax_theme); + html_language.set_theme(&syntax_theme); + let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor.clone())); + language_registry.add(markdown_language.clone()); + language_registry.add(markdown_inline_language); + language_registry.add(html_language); + + let text = "\n\n\ + Annotation in the middle \n\n\ + An inline comment can span within a paragraph.\n\n\ + Ordinary inline HTML: emphasized."; + let buffer = cx.new(|cx| { + let mut buffer = Buffer::local(text, cx); + buffer.set_language_registry(language_registry); + buffer.set_language(Some(markdown_language), cx); + buffer + }); + + cx.run_until_parked(); + + buffer.read_with(cx, |buffer, _cx| { + let snapshot = buffer.snapshot(); + let highlighted_text = |capture_name: &str| { + let highlight_id = syntax_theme + .highlight_id(capture_name) + .map(HighlightId::new); + assert!(highlight_id.is_some(), "{capture_name} not in test theme"); + let mut runs: Vec = Vec::new(); + let mut previous_chunk_matched = false; + let chunks = snapshot.chunks( + 0..snapshot.len(), + LanguageAwareStyling { + tree_sitter: true, + diagnostics: false, + }, + ); + for chunk in chunks { + let chunk_matches = chunk.syntax_highlight_id == highlight_id; + if chunk_matches { + match runs.last_mut() { + Some(last_run) if previous_chunk_matched => last_run.push_str(chunk.text), + _ => runs.push(chunk.text.to_string()), + } + } + previous_chunk_matched = chunk_matches; + } + runs + }; + + assert_eq!( + highlighted_text("comment"), + vec![ + "", + "", + "", + ] + ); + assert_eq!(highlighted_text("tag"), vec!["em", "em"]); + }); +} + #[gpui::test] fn test_syntax_layer_at_for_combined_injections(cx: &mut App) { init_settings(cx, |_| {});