fix: color Markdown heading markers with heading text

This commit is contained in:
2026-10-09 22:52:07 +02:00
parent d418d670ee
commit fcfdc6516d
3 changed files with 40 additions and 1 deletions
+2
View File
@@ -176,4 +176,6 @@ Buffers highlight automatically using [Tree-sitter](https://tree-sitter.github.i
Colors retain the terminal palette: comments dark gray, strings green, keywords magenta, numbers yellow, types/markup cyan, and functions light blue. Grammar queries handle multiline/nested constructs, even when their opening is above the viewport. Markdown uses its inline grammar and named fenced-code injections; HTML script/style content uses JavaScript/CSS. Known tagged-template and raw-string injections use the same selected grammar registry; unknown injected languages retain host highlighting or plain text. Highlighting styles whole graphemes and expanded tab cells; partially clipped wide placeholders remain unchanged. Search, Visual selections, and secondary carets retain foreground/background precedence. Colors retain the terminal palette: comments dark gray, strings green, keywords magenta, numbers yellow, types/markup cyan, and functions light blue. Grammar queries handle multiline/nested constructs, even when their opening is above the viewport. Markdown uses its inline grammar and named fenced-code injections; HTML script/style content uses JavaScript/CSS. Known tagged-template and raw-string injections use the same selected grammar registry; unknown injected languages retain host highlighting or plain text. Highlighting styles whole graphemes and expanded tab cells; partially clipped wide placeholders remain unchanged. Search, Visual selections, and secondary carets retain foreground/background precedence.
Markdown heading markers (`#` through `######`, or Setext underlines) share the heading text's markup color. Plain-text hashes and other punctuation keep their existing styling.
An enabled supporting LSP server supplies semantic tokens automatically, overriding syntax colors only on recognized spans. Variables/parameters use light cyan, properties blue, and readonly variables/properties yellow; declarations/definitions are bold, documentation comments italic, and deprecated symbols struck through. Tree-sitter fills gaps and remains available while new tokens are pending after edits. Search, Visual selections, diagnostics, and carets keep their existing precedence. See [native LSP configuration](configuration.md#native-lsp) for provider support and server-stack routing. An enabled supporting LSP server supplies semantic tokens automatically, overriding syntax colors only on recognized spans. Variables/parameters use light cyan, properties blue, and readonly variables/properties yellow; declarations/definitions are bold, documentation comments italic, and deprecated symbols struck through. Tree-sitter fills gaps and remains available while new tokens are pending after edits. Search, Visual selections, diagnostics, and carets keep their existing precedence. See [native LSP configuration](configuration.md#native-lsp) for provider support and server-stack routing.
+19
View File
@@ -1263,6 +1263,25 @@ mod tests {
assert_token(&cache, source, 3, "42", SyntaxKind::Number); assert_token(&cache, source, 3, "42", SyntaxKind::Number);
} }
#[test]
fn markdown_heading_markers_share_title_colors_without_coloring_plain_hashes() {
let source = "# One\n## Two\n### Three\n#### Four\n##### Five\n###### Six\n\nSetext\n======\n\nplain # text";
let cache = self::cache(source, "headings.md");
for row in 0..6 {
assert_token(
&cache,
source,
row,
&"#".repeat(row + 1),
SyntaxKind::Markup,
);
}
assert_token(&cache, source, 0, "One", SyntaxKind::Markup);
assert_token(&cache, source, 7, "Setext", SyntaxKind::Markup);
assert_token(&cache, source, 8, "======", SyntaxKind::Markup);
assert_eq!(kind_at(&cache, 10, 6), None);
}
#[test] #[test]
fn markdown_inline_and_fences_inject_known_languages_and_update_incrementally() { fn markdown_inline_and_fences_inject_known_languages_and_update_incrementally() {
let original = "A **bold** and `code` link.\n\n```rust\nfn main() { let n = 42; }\n```\n\n```python\ndef hello():\n return 7\n```\n\n```not-a-language\nfn not_rust() { 99; }\n```"; let original = "A **bold** and `code` link.\n\n```rust\nfn main() { let n = 42; }\n```\n\n```python\ndef hello():\n return 7\n```\n\n```not-a-language\nfn not_rust() { 99; }\n```";
+19 -1
View File
@@ -46,6 +46,24 @@ static CPP_HIGHLIGHTS: LazyLock<String> = LazyLock::new(|| {
] ]
.join("\n") .join("\n")
}); });
static MD_HIGHLIGHTS: LazyLock<String> = LazyLock::new(|| {
[
tree_sitter_md::HIGHLIGHT_QUERY_BLOCK,
"
[
(atx_h1_marker)
(atx_h2_marker)
(atx_h3_marker)
(atx_h4_marker)
(atx_h5_marker)
(atx_h6_marker)
(setext_h1_underline)
(setext_h2_underline)
] @text.title
",
]
.join("\n")
});
// The MIT-licensed upstream C++ crate bundles, but does not export, injections.scm. // The MIT-licensed upstream C++ crate bundles, but does not export, injections.scm.
// This minimal AST query uses its raw-string delimiter/content nodes: // This minimal AST query uses its raw-string delimiter/content nodes:
@@ -129,7 +147,7 @@ static LANGUAGES: LazyLock<[LanguageSpec; 18]> = LazyLock::new(|| {
spec( spec(
"Markdown", "Markdown",
tree_sitter_md::LANGUAGE.into(), tree_sitter_md::LANGUAGE.into(),
tree_sitter_md::HIGHLIGHT_QUERY_BLOCK, MD_HIGHLIGHTS.as_str(),
tree_sitter_md::INJECTION_QUERY_BLOCK, tree_sitter_md::INJECTION_QUERY_BLOCK,
"", "",
), ),