From 189f9d6727ca86147d4086254ce3dd53f8c0e9d7 Mon Sep 17 00:00:00 2001 From: Benjamin Canac Date: Thu, 10 Sep 2026 12:25:27 +0200 Subject: [PATCH 01/12] feat(shiki): highlight inline code Inline code that declares a language with `{lang="ts"}` rendered unhighlighted, because the walk only collected `
`. It now collects inline ``
in the same pass, reading `lang` first and `language` second.

Adds grammar contexts, so a fragment can seed the grammar before it is
tokenized. `ts-type` and `vue-html` ship by default, mirroring the `@nuxtjs/mdc`
conventions. Without the `let a:` seed, `Ref` tokenizes
as an expression and the type names fall through to plain text. Add or override
with `grammarContexts`, or set an entry to `false` to drop a built-in.

Inline code naming a grammar that is not registered is left exactly as written,
with no class and no spans, because `lang` is a real HTML attribute for natural
language. A fence still falls back to an unhighlighted `.shiki` block, since a
`
` is unambiguously code.

Inline output is flat spans with no line wrapper, and `transformers` and
`preStyles` stay block-only.

Also stops the ` . ` class sentinel leaking into rendered HTML. It is a
markdown-stringify encoding, but `htmlAttributes` emitted it verbatim, so a
highlighted `
` with a user class rendered a bogus `.` class token.
---
 docs/content/4.plugins/1.built-in/rangi.md    |   2 +
 docs/content/4.plugins/1.built-in/shiki.md    |  76 +++++
 .../attributes/wrapped-pre-highlighted.md     |   2 +-
 .../SPEC/COMARK/shiki-inline-code-lang.md     | 123 ++++++++
 .../SPEC/COMARK/shiki-inline-code-plain.md    |  88 ++++++
 packages/comark/src/internal/shiki.ts         | 298 ++++++++++++++----
 .../src/internal/stringify/attributes.ts      |  30 +-
 .../src/internal/stringify/handlers/code.ts   |   8 +-
 .../src/internal/stringify/handlers/html.ts   |  12 +-
 packages/comark/src/plugins/shiki.ts          |   3 +-
 packages/comark/src/plugins/shiki/core.ts     |   3 +-
 .../comark/test/plugins/shiki-inline.test.ts  | 162 ++++++++++
 packages/comark/test/plugins/shiki.test.ts    |  45 +++
 13 files changed, 770 insertions(+), 82 deletions(-)
 create mode 100644 packages/comark/SPEC/COMARK/shiki-inline-code-lang.md
 create mode 100644 packages/comark/SPEC/COMARK/shiki-inline-code-plain.md
 create mode 100644 packages/comark/test/plugins/shiki-inline.test.ts

diff --git a/docs/content/4.plugins/1.built-in/rangi.md b/docs/content/4.plugins/1.built-in/rangi.md
index f656f353..a69ca942 100644
--- a/docs/content/4.plugins/1.built-in/rangi.md
+++ b/docs/content/4.plugins/1.built-in/rangi.md
@@ -35,6 +35,8 @@ npm install rangi
 Use [`comark/plugins/shiki`](/plugins/built-in/shiki) when you need transformers, Twoslash, or the full VS Code grammar set. Use `rangi` for a faster, smaller highlighter with built-in light/dark themes.
 ::
 
+Inline code highlighting with `{lang="…"}` is currently shiki only. Rangi highlights fenced blocks.
+
 ## Usage
 
 ```typescript
diff --git a/docs/content/4.plugins/1.built-in/shiki.md b/docs/content/4.plugins/1.built-in/shiki.md
index 9ef4539b..b895a397 100644
--- a/docs/content/4.plugins/1.built-in/shiki.md
+++ b/docs/content/4.plugins/1.built-in/shiki.md
@@ -196,6 +196,46 @@ const x: number = 42
 ```
 ````
 
+### Inline code
+
+Inline code is highlighted when it declares a language with the attributes syntax. `lang` wins over `language`:
+
+```markdown
+The type is `Ref`{lang="ts-type"} and the component is ``{lang="vue-html"}.
+```
+
+Inline code uses the fast token path only, so `transformers` and `preStyles` are block-only and are not applied. The node gets the same `shiki` class a `
` gets, so the dual-theme CSS in [Styling](#styling) covers it without extra rules.
+
+Inline code naming a grammar that is not registered is left exactly as it was written, with no class and no spans. That is deliberate: `lang` is a real HTML attribute for natural language, so `` `Bonjour`{lang="fr"} `` must not be treated as code. A fenced block behaves differently and still falls back to an unhighlighted `.shiki` block, because a `
` is unambiguously code.
+
+Set `inlineCode: false` to turn this off.
+
+### Grammar contexts
+
+Some inline snippets are fragments rather than whole statements, so the grammar needs seeding before it tokenizes them correctly. A grammar context maps a name you write in `{lang="…"}` onto a real grammar plus source that is tokenized and then discarded.
+
+Two ship by default, mirroring the `@nuxtjs/mdc` conventions:
+
+| Name | Grammar | Seed |
+|---|---|---|
+| `ts-type` | `typescript` | `let a:` |
+| `vue-html` | `vue` | `