Skip to content

Commit

Permalink
Add support for code blocks in Markdown (PrismJS#1562)
Browse files Browse the repository at this point in the history
It also supports syntax highlighting!

The highlighting is done in two steps:

1. Add an alias `language-****` containing the given language to the `code-block`
token.
    This happens in the `after-tokenize` hook.
2. Highlight the code with the `wrap` hook.

This is to get around the encoding (`util.encode`) of tokens in `Prism.highlight`.

By using this procedure we get the correct execution of the `before-tokenize`,
`after-tokenize`, and `wrap` hook for all included languages.
  • Loading branch information
RunDevelopment authored and ggrossetie committed Mar 11, 2019
1 parent 67b9157 commit 706c969
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 3 deletions.
88 changes: 88 additions & 0 deletions components/prism-markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ Prism.languages.insertBefore('markdown', 'prolog', {
// ``code``
pattern: /``.+?``|`[^`\n]+`/,
alias: 'keyword'
},
{
// ```optional language
// code block
// ```
pattern: /^```[\s\S]*?^```$/m,
greedy: true,
inside: {
'code-block': {
pattern: /^(```.*(?:\r?\n|\r))[\s\S]+?(?=(?:\r?\n|\r)^```$)/m,
lookbehind: true
},
'code-language': {
pattern: /^(```).+/,
lookbehind: true
},
'punctuation': /```/
}
}
],
'title': [
Expand Down Expand Up @@ -137,3 +155,73 @@ Prism.languages.markdown['italic'].inside['bold'] = Prism.languages.markdown['bo
Prism.languages.markdown['italic'].inside['strike'] = Prism.languages.markdown['strike'];
Prism.languages.markdown['strike'].inside['bold'] = Prism.languages.markdown['bold'];
Prism.languages.markdown['strike'].inside['italic'] = Prism.languages.markdown['italic'];

Prism.hooks.add('after-tokenize', function (env) {
if (env.language !== 'markdown') {
return;
}

function walkTokens(tokens) {
if (!tokens || typeof tokens === 'string') {
return;
}

for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];

if (token.type !== 'code') {
walkTokens(token.content);
continue;
}

var codeLang = token.content[1];
var codeBlock = token.content[3];

if (codeLang && codeBlock &&
codeLang.type === 'code-language' && codeBlock.type === 'code-block' &&
typeof codeLang.content === 'string') {

// this might be a language that Prism does not support
var alias = 'language-' + codeLang.content.trim().split(/\s+/)[0].toLowerCase();

// add alias
if (!codeBlock.alias) {
codeBlock.alias = [alias];
} else if (typeof codeBlock.alias === 'string') {
codeBlock.alias = [codeBlock.alias, alias];
} else {
codeBlock.alias.push(alias);
}
}
}
}

walkTokens(env.tokens);
});

Prism.hooks.add('wrap', function (env) {
if (env.type !== 'code-block') {
return;
}

var codeLang = '';
for (var i = 0, l = env.classes.length; i < l; i++) {
var cls = env.classes[i];
var match = /language-(\w+)/.exec(cls);
if (match) {
codeLang = match[1];
break;
}
}

var grammar = Prism.languages[codeLang];

if (!grammar) {
return;
}

// reverse Prism.util.encode
var code = env.content.replace(/&lt;/g, '<').replace(/&amp;/g, '&');

env.content = Prism.highlight(code, grammar, codeLang);
});
2 changes: 1 addition & 1 deletion components/prism-markdown.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions tests/languages/markdown/code_feature.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@

foobar

``` js
var a = 0;
```

----------------------------------------------------

[
["code", "`foo bar baz`"],
["code", "``foo `bar` baz``"],
["code", " foobar"],
["code", "\tfoobar"]
["code", "\tfoobar"],

["code", [
["punctuation", "```"],
["code-language", " js"],
["code-block", "var a = 0;"],
["punctuation", "```"]
]]
]

----------------------------------------------------

Checks for code blocks and inline code. The first code block is
indented with 4 spaces, the second one is indented with 1 tab.
The initial dot is necessary because of the first part being trimmed
by the test runner.
by the test runner.

0 comments on commit 706c969

Please sign in to comment.