Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diff: Added support for syntax highlighting inside diffs #1889

Merged
merged 14 commits into from Jul 21, 2019
142 changes: 122 additions & 20 deletions components/prism-diff.js
@@ -1,20 +1,122 @@
Prism.languages.diff = {
'coord': [
// Match all kinds of coord lines (prefixed by "+++", "---" or "***").
/^(?:\*{3}|-{3}|\+{3}).*$/m,
// Match "@@ ... @@" coord lines in unified diff.
/^@@.*@@$/m,
// Match coord lines in normal diff (starts with a number).
/^\d+.*$/m
],

// Match inserted and deleted lines. Support both +/- and >/< styles.
'deleted': /^[-<].*$/m,
'inserted': /^[+>].*$/m,

// Match "different" lines (prefixed with "!") in context diff.
'diff': {
'pattern': /^!(?!!).+$/m,
'alias': 'important'
}
};
(function (Prism) {

Prism.languages.diff = {
'coord': [
// Match all kinds of coord lines (prefixed by "+++", "---" or "***").
/^(?:\*{3}|-{3}|\+{3}).*$/m,
// Match "@@ ... @@" coord lines in unified diff.
/^@@.*@@$/m,
// Match coord lines in normal diff (starts with a number).
/^\d+.*$/m
]

// deleted, inserted, unchanged, diff
};

/**
* A map from the name of a block to its line prefix.
*
* @type {Object<string, string>}
*/
var prefixes = {
'deleted-sign': '-',
'deleted-arrow': '<',
'inserted-sign': '+',
'inserted-arrow': '>',
'unchanged': ' ',
'diff': '!',
};

// add a token for each prefix
Object.keys(prefixes).forEach(function (name) {
var prefix = prefixes[name];

var alias = [];
if (!/^\w+$/.test(name)) { // "deleted-sign" -> "deleted"
alias.push(/\w+/.exec(name)[0]);
}
if (name === "diff") {
alias.push("bold");
}

Prism.languages.diff[name] = {
// pattern: /^(?:[_].*(?:\r\n?|\n|(?![\s\S])))+/m
pattern: RegExp('^(?:[' + prefix + '].*(?:\r\n?|\n|(?![\\s\\S])))+', 'm'),
alias: alias
};
});


var LANGUAGE_REGEX = /diff-([\w-]+)/i;
var HTML_TAG = /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/gi;
//this will match a line plus the line break while ignoring the line breaks HTML tags may contain.
var HTML_LINE = RegExp(/(?:__|[^\r\n<])*(?:\r\n?|\n|(?:__|[^\r\n<])(?![^\r\n]))/.source.replace(/__/g, HTML_TAG.source), 'gi');

Prism.hooks.add('before-sanity-check', function (env) {
var lang = env.language;
if (LANGUAGE_REGEX.test(lang) && !env.grammar) {
env.grammar = Prism.languages[lang] = Prism.languages.diff;
}
})
Prism.hooks.add('before-tokenize', function (env) {
var lang = env.language;
if (LANGUAGE_REGEX.test(lang) && !Prism.languages[lang]) {
Prism.languages[lang] = Prism.languages.diff;
}
})

Prism.hooks.add('wrap', function (env) {
var diffLanguage, diffGrammar;

if (env.language !== 'diff') {
var langMatch = LANGUAGE_REGEX.exec(env.language);
if (!langMatch) {
return; // not a language specific diff
}

diffLanguage = langMatch[1];
diffGrammar = Prism.languages[diffLanguage];
}

// one of the diff tokens without any nested tokens
if (env.type in prefixes) {
/** @type {string} */
var content = env.content.replace(HTML_TAG, ''); // remove all HTML tags

/** @type {string} */
var decoded = content.replace(/&amp;/g, '&').replace(/&lt;/g, '<');
RunDevelopment marked this conversation as resolved.
Show resolved Hide resolved

// remove any one-character prefix
var code = decoded.replace(/(^|[\r\n])./g, '$1');

// highlight, if possible
var highlighted;
if (diffGrammar) {
highlighted = Prism.highlight(code, diffGrammar, diffLanguage);
} else {
highlighted = Prism.util.encode(code);
}

// get the HTML source of the prefix token
var prefixToken = new Prism.Token('prefix', prefixes[env.type], [/\w+/.exec(env.type)[0]]);
var prefix = Prism.Token.stringify(prefixToken, env.language);

// add prefix
var lines = [], m;
HTML_LINE.lastIndex = 0;
while (m = HTML_LINE.exec(highlighted)) {
lines.push(prefix + m[0]);
}
if (/(?:^|[\r\n]).$/.test(decoded)) {
// because both "+a\n+" and "+a\n" will map to "a\n" after the line prefixes are removed
lines.push(prefix);
}
env.content = lines.join('');

if (diffGrammar) {
env.classes.push('language-' + diffLanguage);
}
}
});

}(Prism));
2 changes: 1 addition & 1 deletion components/prism-diff.min.js

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

10 changes: 9 additions & 1 deletion examples/prism-diff.html
Expand Up @@ -30,4 +30,12 @@ <h2>Unified Diff</h2>
headers: "src/*.h"
- qt: core
+ qt: core gui
public_headers: "src/*.h"</code></pre>
public_headers: "src/*.h"</code></pre>

<h2>Language specific Diff</h2>
<p>Using the <code class="language-none">language-diff-xxxx</code> class for your code elements will enable language specific highlighting for your diffs.</p>
<pre><code class="language-diff-javascript">@@ -4,6 +4,5 @@
- let foo = bar.baz([1, 2, 3]);
- foo = foo + 1;
+ const foo = bar.baz([1, 2, 3]) + 1;
console.log(`foo: ${foo}`);</code></pre>
15 changes: 9 additions & 6 deletions tests/languages/diff/diff_feature.test
@@ -1,5 +1,7 @@
! qt: core

unchanged

- qt: core
+ qt: core gui

Expand All @@ -9,13 +11,14 @@
----------------------------------------------------

[
["diff", "! qt: core"],
["deleted", "- qt: core"],
["inserted", "+ qt: core gui"],
["deleted", "< qt: core"],
["inserted", "> qt: core quick"]
["diff", "! qt: core\r\n"],
["unchanged", " unchanged\r\n"],
["deleted-sign", "- qt: core\r\n"],
["inserted-sign", "+ qt: core gui\r\n"],
["deleted-arrow", "< qt: core\r\n"],
["inserted-arrow", "> qt: core quick"]
]

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

Checks for deleted, inserted and different lines.
Checks for deleted, inserted and different lines.
RunDevelopment marked this conversation as resolved.
Show resolved Hide resolved