# Supporting CJK-friendly emphasis in @lezer/markdown

**URL:** <https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904>\
**Category:** Lezer\
**Created:** [October 2, 2026, 6:29am UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904 "2026-10-02T06:29:14Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![isunjn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/isunjn/32/5562_2.png) [@isunjn](https://discuss.codemirror.net/u/isunjn)\
**Post date:** [October 2, 2026, 6:29am UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/1 "2026-10-02T06:29:15Z")

</div>

CommonMark’s flanking rules work poorly for Chinese, Japanese and Korean text. These languages don’t put spaces between words, so emphasis often ends with punctuation that is directly followed by a letter. The closing delimiter is then not right-flanking:

```markdown
**该星号不会被识别，而是直接显示。** 这是因为它没有被识别为强调符号。

```

The closing `**` is preceded by `。` (punctuation) and followed by `这` (a letter), so both delimiters stay literal text.

The [CJK-friendly amendments](https://github.com/tats-u/markdown-cjk-friendly/blob/main/specification.md) fix this by changing one condition of the flanking rules: a delimiter run next to a CJK character or CJK punctuation still counts as flanking. They are proposed for CommonMark itself in [commonmark-spec#650](https://github.com/commonmark/commonmark-spec/issues/650#issuecomment-4274538796). There are plugins for markdown-it, micromark/remark and marked, and Comrak, goldmark and Markdig implement them. VitePress and Rspress turn them on by default. Text without CJK characters parses exactly as it does under plain CommonMark.

I use @lezer/markdown for the source view of a Markdown editor whose files follow these amendments. I’d like its syntax tree to agree with them, both for highlighting and for commands that read the tree, such as toggling bold.

Replacing the `Emphasis` inline parser by name works, and computing the amended flanking is easy. The problem is the delimiters the replacement adds. `resolveMarkers` applies its emphasis logic (splitting runs by length, the rule of 3, `Emphasis` vs. `StrongEmphasis`) only to delimiters whose type is `EmphasisAsterisk` or `EmphasisUnderscore`, and those types aren’t exported. So a replacement parser can’t produce delimiters that resolve as emphasis.

For now I run a second Markdown parser over each inline section to find which delimiters pair. I then give each pair a `DelimiterType` of its own, with `resolve: "Emphasis"` or `"StrongEmphasis"`. This works, but every inline section is parsed twice, and the second parser repeats work Lezer already does well.

Would you consider exposing the two types, the way `InlineContext.linkStart` and `InlineContext.imageStart` are exposed? For example, as `InlineContext.emphasisAsterisk` and `InlineContext.emphasisUnderscore`. A replacement parser could then be:

```js
import {InlineContext} from "@lezer/markdown"

const CJKFriendlyEmphasis = {
  parseInline: [{
    name: "Emphasis",
    parse(cx, next, start) {
      if (next != 42 /* '*' */ && next != 95 /* '_' */) return -1
      let end = start + 1
      while (cx.char(end) == next) end++
      let {canOpen, canClose} = cjkFriendlyFlanking(cx, start, end, next == 42)
      let type = next == 42 ? InlineContext.emphasisAsterisk : InlineContext.emphasisUnderscore
      return cx.addDelimiter(type, start, end, canOpen, canClose)
    }
  }]
}

```

If you’d rather support the amendments directly, as an optional extension like `Strikethrough`, that would work for me too.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/marijn/32/136_2.png) [@marijn](https://discuss.codemirror.net/u/marijn)\
**Post date:** [October 5, 2026, 8:32am UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/2 "2026-10-05T08:32:12Z")

</div>

I think I prefer the solution of exporting these. Can you check whether the patch below works for you?

> **[Export emphasisAsterisk and emphasisUnderscore · 35e5b937f0](https://code.haverbeke.berlin/lezer/markdown/commit/35e5b937f076dc5dda07a83d7e2059de56f7fdf6)**
>
> FEATURE: Export the delimiter types used for emphasis, so that extensions can
> override their parsing behavior.
> 
> See https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904

---

<div class="post-metadata">

**Author:** ![isunjn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/isunjn/32/5562_2.png) [@isunjn](https://discuss.codemirror.net/u/isunjn)\
**Post date:** [October 5, 2026, 1:44pm UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/3 "2026-10-05T13:44:15Z")

</div>

Thanks, the patch works: the replacement parser from my first post now produces delimiters that resolve as emphasis, and I can drop the second parse.

While testing it, I found an unrelated deviation from the spec. resolveMarkers applies the rule of 3 to what is left of a partially used delimiter, but the spec’s algorithm uses the original lengths of the opening and closing delimiter runs. For example, `a ****b*** c`:

- @lezer/markdown: `a**<strong>b</strong>*c`
- commonmark.js, cmark, markdown-it and most others: `a*<em><strong>b</strong></em>c` ([commonmark.js demo](https://spec.commonmark.org/dingus/?text=a ****b*** c))

After the strong emphasis, the leftover \*\* and \* add up to 3, so Lezer doesn’t pair them, while the original runs (4 and 3) add up to 7. Keeping the original run length on InlineDelimiter and checking that instead fixed it in my tests.

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/marijn/32/136_2.png) [@marijn](https://discuss.codemirror.net/u/marijn)\
**Post date:** [October 6, 2026, 10:17am UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/4 "2026-10-06T10:17:41Z")

</div>

Does the patch below align with how you understand this should work?

> **[Track original size in strong/emphasis inline delimiters · b6d10d27cc](https://code.haverbeke.berlin/lezer/markdown/commit/b6d10d27cce598e527e6b5456d0ce7cc7068ba13)**
>
> FIX: Fix a bug in how partially consumed strong/emphasis marks were matched.
> 
> See https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/3

---

<div class="post-metadata">

**Author:** ![isunjn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/isunjn/32/5562_2.png) [@isunjn](https://discuss.codemirror.net/u/isunjn)\
**Post date:** [October 7, 2026, 6:06am UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/5 "2026-10-07T06:06:35Z")

</div>

Yes, that’s how I understand it, and it works in my tests. Thanks!

---

<div class="post-metadata">

**Author:** ![marijn](https://discuss.codemirror.net/user_avatar/discuss.codemirror.net/marijn/32/136_2.png) [@marijn](https://discuss.codemirror.net/u/marijn)\
**Post date:** [October 7, 2026, 11:29am UTC](https://discuss.codemirror.net/t/supporting-cjk-friendly-emphasis-in-lezer-markdown/9904/6 "2026-10-07T11:29:58Z")

</div>

Great. I’ve tagged a version 1.8.0
