Fix highlight for multiline elements

* Now highlight should be perfect except for cases where the diff
context is not enough to understant what is the code
This commit is contained in:
Rodrigo Fernandes 2014-05-09 00:07:50 +01:00
parent 1e242ded9a
commit affd71eca0
No known key found for this signature in database
GPG key ID: 08E3C5F38969078E
2 changed files with 158 additions and 26 deletions

View file

@ -24,6 +24,20 @@
<script> <script>
var lineDiffExample = var lineDiffExample =
'diff --git a/src/core/init.java b/src/core/init.java\n' +
'index e49196a..50f310c 100644\n' +
'--- a/src/core/init.java\n' +
'+++ b/src/core/init.java\n' +
'@@ -101,7 +101,7 @@\n' +
' /**\n' +
' * Setter for property filesize.\n' +
' *\n' +
" * @param filesize value of property 'filesize'.\n" +
' */\n' +
' public void setFilesize(int filesize) {\n' +
'- this.filesizeOld = filesizeOld;\n' +
'+ this.filesizeNew = filesizeNew;\n' +
' }\n' +
'diff --git a/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/coverage.init b/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/coverage.init\n' + 'diff --git a/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/coverage.init b/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/coverage.init\n' +
'index fc56817..e8e7e49 100644\n' + 'index fc56817..e8e7e49 100644\n' +
'--- a/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/coverage.init\n' + '--- a/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/src/very/long/file/path/coverage.init\n' +

View file

@ -8,7 +8,7 @@
* *
*/ */
/*global $, hljs*/ /*global $, hljs, Diff2Html*/
(function() { (function() {
@ -69,15 +69,28 @@
var $target = that._getTarget(targetId); var $target = that._getTarget(targetId);
var languages = that._getLanguages($target); // collect all the diff files and execute the highlight on their lines
var $files = $target.find(".d2h-file-wrapper");
$files.map(function(i, file) {
var state;
var $file = $(file);
var language = $file.data("lang");
// pass the languages to the highlightjs plugin // collect all the code lines and execute the highlight on them
hljs.configure({languages: languages}); var $codeLines = $file.find(".d2h-code-line-ctn");
$codeLines.map(function(i, line) {
var text = line.textContent;
var result = hljs.getLanguage(language) ? hljs.highlight(language, text, true, state) : hljs.highlightAuto(text);
state = result.top;
// collect all the code lines and execute the highlight on them var originalStream = nodeStream(line);
var $codeLines = $target.find(".d2h-code-line-ctn"); if (originalStream.length) {
$codeLines.map(function(i, line) { var resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
hljs.highlightBlock(line); resultNode.innerHTML = result.value;
result.value = mergeStreams(originalStream, nodeStream(resultNode), text);
}
$(line).html(result.value);
});
}); });
}; };
@ -97,24 +110,6 @@
return $target; return $target;
}; };
Diff2HtmlUI.prototype._getLanguages = function($target) {
var allFileLanguages = [];
if (diffJson) {
// collect all the file extensions in the json
allFileLanguages = diffJson.map(function(line) {
return line.language;
});
} else {
$target.find(".d2h-file-wrapper").map(function(i, file) {
allFileLanguages.push($(file).data("lang"));
});
}
// return only distinct languages
return this._distinct(allFileLanguages);
};
Diff2HtmlUI.prototype._getHashTag = function() { Diff2HtmlUI.prototype._getHashTag = function() {
var docUrl = document.URL; var docUrl = document.URL;
var hashTagIndex = docUrl.indexOf('#'); var hashTagIndex = docUrl.indexOf('#');
@ -181,6 +176,129 @@
return text; return text;
}; };
/*
* Copied from Highlight.js Private API
* Will be removed when this part of the API is exposed
*/
/* Utility functions */
function escape(value) {
return value.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
}
function tag(node) {
return node.nodeName.toLowerCase();
}
/* Stream merging */
function nodeStream(node) {
var result = [];
(function _nodeStream(node, offset) {
for (var child = node.firstChild; child; child = child.nextSibling) {
if (child.nodeType == 3)
offset += child.nodeValue.length;
else if (child.nodeType == 1) {
result.push({
event: 'start',
offset: offset,
node: child
});
offset = _nodeStream(child, offset);
// Prevent void elements from having an end tag that would actually
// double them in the output. There are more void elements in HTML
// but we list only those realistically expected in code display.
if (!tag(child).match(/br|hr|img|input/)) {
result.push({
event: 'stop',
offset: offset,
node: child
});
}
}
}
return offset;
})(node, 0);
return result;
}
function mergeStreams(original, highlighted, value) {
var processed = 0;
var result = '';
var nodeStack = [];
function selectStream() {
if (!original.length || !highlighted.length) {
return original.length ? original : highlighted;
}
if (original[0].offset != highlighted[0].offset) {
return (original[0].offset < highlighted[0].offset) ? original : highlighted;
}
/*
To avoid starting the stream just before it should stop the order is
ensured that original always starts first and closes last:
if (event1 == 'start' && event2 == 'start')
return original;
if (event1 == 'start' && event2 == 'stop')
return highlighted;
if (event1 == 'stop' && event2 == 'start')
return original;
if (event1 == 'stop' && event2 == 'stop')
return highlighted;
... which is collapsed to:
*/
return highlighted[0].event == 'start' ? original : highlighted;
}
function open(node) {
function attrStr(a) {
return ' ' + a.nodeName + '="' + escape(a.value) + '"';
}
result += '<' + tag(node) + Array.prototype.map.call(node.attributes, attrStr).join('') + '>';
}
function close(node) {
result += '</' + tag(node) + '>';
}
function render(event) {
(event.event == 'start' ? open : close)(event.node);
}
while (original.length || highlighted.length) {
var stream = selectStream();
result += escape(value.substr(processed, stream[0].offset - processed));
processed = stream[0].offset;
if (stream == original) {
/*
On any opening or closing tag of the original markup we first close
the entire highlighted node stack, then render the original tag along
with all the following original tags at the same offset and then
reopen all the tags on the highlighted stack.
*/
nodeStack.reverse().forEach(close);
do {
render(stream.splice(0, 1)[0]);
stream = selectStream();
} while (stream == original && stream.length && stream[0].offset == processed);
nodeStack.reverse().forEach(open);
} else {
if (stream[0].event == 'start') {
nodeStack.push(stream[0].node);
} else {
nodeStack.pop();
}
render(stream.splice(0, 1)[0]);
}
}
return result + escape(value.substr(processed));
}
/* **** Highlight.js Private API **** */
module.exports.Diff2HtmlUI = Diff2HtmlUI; module.exports.Diff2HtmlUI = Diff2HtmlUI;
// Expose diff2html in the browser // Expose diff2html in the browser