Skip to content

Inline Rules ​

An inline rule matches text inside a block with a regular expression, adds a CSS class to the match, and optionally gives it a ⌘-click (Ctrl-click) action and a list in the detail panel.

Inline rules are decorations. They never change the document, the Markdown, or what is saved — #123 stays #123 in the file.

json
"contributes": {
  "inlineRules": [
    {
      "id": "issue",
      "pattern": "(?<![\\w/&#])#(\\d+)\\b",
      "class": "md-ext-issue",
      "title": "Issue #$1 (⌘-click to open)",
      "action": { "openUrl": "https://github.com/typora/typora-issues/issues/$1" }
    },
    {
      "id": "tag",
      "pattern": "(?<=^|\\s)#([\\p{L}_][\\p{L}\\p{N}_/-]*)",
      "class": "md-ext-tag",
      "title": "Tag #$1 (⌘-click to search)",
      "action": { "command": "search.global", "args": { "query": "#$1" } },
      "index": { "title": "Tags", "icon": "tags", "sfSymbol": "tag", "groupBy": "#$1" }
    }
  ]
}

Fields ​

FieldRequiredDescription
idyesLocal id of the rule.
patternyesJavaScript regular expression source. It always runs with the u flag, so \p{L} and lookbehind work. Remember to escape \ in JSON.
flagsExtra flags: any of i, m, s.
excludeInline kinds to skip. Default: ["code", "link", "url", "math", "html"].
classCSS class added to the match. Every match also gets md-ext-rule.
titleTooltip. $1…$9 are capture groups.
actionWhat ⌘-click (Ctrl-click on Windows and Linux) does. See Actions.
indexAdds a detail view listing every match. See Index views.

exclude values ​

ValueSkips
codeInline code
linkLinks, reference links, wiki links and embeds, <a> tags
urlBare URLs and autolinks
mathInline math
htmlInline HTML, tags, comments, <img>, entities
imageImages
footnoteFootnote references
citationCitations
emojiEmoji shortcodes

Pass [] to match everywhere.

Actions ​

json
{ "openUrl": "https://example.com/issues/$1" }
{ "command": "search.global", "args": { "query": "#$1" } }
  • openUrl opens an http/https URL in the browser. $n capture groups are URL-encoded. The template must start with http://, https:// or a setting reference {config.key}, which inserts the value of one of your configuration properties.
  • command runs a Typora command. search.global opens search across the workspace with args.query (capture groups are substituted into string arguments).

Index views ​

With index, the rule also contributes a detail-panel tab listing every match in the document:

FieldDescription
titleTab title, e.g. Tags.
iconOptional icon name.
sfSymbolOptional SF Symbol name for the macOS menus, as in detail views.
groupByGroup the list by a template of capture groups, e.g. "#$1" gives one group per tag.

Clicking an item selects the match in the editor.

Styling ​

Every match is a span with the class md-ext-rule (which Typora styles) plus your own class. Custom styling will come with theme contributions; until then, pick a descriptive class name so a theme can target it later.

Semantics and limits ​

  • Rules run over the Markdown source of each block, skipping the exclude ranges. A heading's leading # is followed by a space, so a tag pattern that requires a character after # doesn't match it.
  • Rules apply in declaration order, then extension load order. Matches can't overlap; the first one wins.
  • At most 200 matches per rule per block are decorated.
  • Rules are matched in the background, so a decoration can appear a moment after the text it marks. While you type inside or at the end of a match, the decoration grows with it until the new match arrives.
  • A rule that takes longer than 20 ms on one block, or hangs (a pattern that backtracks catastrophically, such as (a+)+$), is switched off for the session. Typora shows a notice and writes an error to the extension's log, which you can open from Settings ▸ Extensions ▸ View Log. Editing is never blocked: rules are matched in the background.

See the Issue Links & Tags sample.

Theme adapted from Nimbus (MIT).