Implements ignore-whitespace and hilight-syntax on diff.

Improvement experience speed
 * lazy diff rendering ( it is effective if tha diff has a lot of files ).
 * some feature implemented by javascript, to implement by css.
 * some javascript event handlers on each elements move to on parent elements.
This commit is contained in:
nazoking
2015-03-06 03:31:49 +09:00
parent 14becd0bd6
commit aa35498bdd
4 changed files with 400 additions and 156 deletions

View File

@@ -76,41 +76,240 @@ function displayErrors(data, elem){
* @param newTextId {String} element id of new text
* @param outputId {String} element id of output element
* @param viewType {Number} 0: split, 1: unified
* @param ignoreSpace {Number} 0: include, 1: ignore
*/
function diffUsingJS(oldTextId, newTextId, outputId, viewType) {
// get the baseText and newText values from the two textboxes, and split them into lines
var oldText = document.getElementById(oldTextId).value;
var oldLines = [];
if(oldText !== ''){
oldLines = difflib.stringAsLines(oldText);
}
var newText = document.getElementById(newTextId).value;
var newLines = [];
if(newText !== ''){
newLines = difflib.stringAsLines(newText);
}
// create a SequenceMatcher instance that diffs the two sets of lines
var sm = new difflib.SequenceMatcher(oldLines, newLines);
// get the opcodes from the SequenceMatcher instance
// opcodes is a list of 3-tuples describing what changes should be made to the base text
// in order to yield the new text
var opcodes = sm.get_opcodes();
var diffoutputdiv = document.getElementById(outputId);
while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild);
// build the diff view and add it to the current DOM
diffoutputdiv.appendChild(diffview.buildView({
baseTextLines: oldLines,
newTextLines: newLines,
opcodes: opcodes,
contextSize: 4,
viewType: viewType
}));
function diffUsingJS(oldTextId, newTextId, outputId, viewType, ignoreSpace) {
var render = new JsDiffRender({
baseText: document.getElementById(oldTextId).value,
newText: document.getElementById(newTextId).value,
ignoreSpace: ignoreSpace,
contextSize: 4
});
var diff = render[viewType==1 ? "unified" : "split"]();
diff.appendTo($('#'+outputId).html(""));
}
function jqSelectorEscape(val) {
return val.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&');
}
function JsDiffRender(params){
var baseTextLines = (params.baseText==="")?[]:params.baseText.split(/\r\n|\r|\n/);
var headTextLines = (params.headText==="")?[]:params.newText.split(/\r\n|\r|\n/);
var sm, ctx;
if(params.ignoreSpace){
var ignoreSpace = function(a){ return a.replace(/\s+/,' ').replace(/^\s+|\s+$/,''); };
sm = new difflib.SequenceMatcher(
$.map(baseTextLines, ignoreSpace),
$.map(headTextLines, ignoreSpace));
ctx = this.flatten(sm.get_opcodes(), headTextLines, baseTextLines, function(text){ return ignoreSpace(text) === ""; });
}else{
sm = new difflib.SequenceMatcher(baseTextLines, headTextLines);
ctx = this.flatten(sm.get_opcodes(), headTextLines, baseTextLines, function(){ return false; });
}
var oplines = this.fold(ctx, params.contextSize);
function prettyDom(text){
var dom = null;
return function(ln){
if(dom===null){
dom = prettyPrintOne(text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;').replace(/>/g,'&gt;'), null, true);
}
return (new RegExp('<li id="L'+ln+'"[^>]*>(.*?)</li>').exec(dom) || [])[1];
};
}
return this.renders(oplines, prettyDom(params.baseText), prettyDom(params.newText));
}
$.extend(JsDiffRender.prototype,{
renders: function(oplines, baseTextDom, headTextDom){
return {
split:function(){
var table = $('<table class="diff">');
var tbody = $('<tbody>').appendTo(table);
for(var i=0;i<oplines.length;i++){
var o = oplines[i];
switch(o.change){
case 'skip':
$('<tr>').html('<th></th><td colspan="3" class="skip">...</td>').appendTo(tbody);
break;
case 'delete':
case 'insert':
case 'equal':
$('<tr>').append(
lineNum('old',o.base),
$('<td class="body">').html(o.base ? baseTextDom(o.base): "").addClass(o.change),
lineNum('old',o.head),
$('<td class="body">').html(o.head ? headTextDom(o.head): "").addClass(o.change)
).appendTo(tbody);
break;
case 'replace':
var ld = lineDiff(baseTextDom(o.base), headTextDom(o.head));
$('<tr>').append(
lineNum('old',o.base),
$('<td class="body">').append(ld.base).addClass('delete'),
lineNum('old',o.head),
$('<td class="body">').append(ld.head).addClass('insert')
).appendTo(tbody);
break;
}
}
return table;
},
unified:function(){
var table = $('<table class="diff inlinediff">');
var tbody = $('<tbody>').appendTo(table);
for(var i=0;i<oplines.length;i++){
var o = oplines[i];
switch(o.change){
case 'skip':
tbody.append($('<tr>').html('<th colspan="2"></th><td class="skip"></td>'));
break;
case 'delete':
case 'insert':
case 'equal':
tbody.append($('<tr>').append(
lineNum('old',o.base),
lineNum('new',o.head),
$('<td class="body">').addClass(o.change).html(o.head ? headTextDom(o.head) : baseTextDom(o.base))));
break;
case 'replace':
var deletes = [];
while(oplines[i] && oplines[i].change == 'replace'){
if(oplines[i].base && oplines[i].head){
var ld = lineDiff(baseTextDom(oplines[i].base), headTextDom(oplines[i].head));
tbody.append($('<tr>').append(lineNum('old',oplines[i].base),'<th>',$('<td class="body delete">').append(ld.base)));
deletes.push($('<tr>').append('<th>',lineNum('new',oplines[i].head),$('<td class="body insert">').append(ld.head)));
}else if(oplines[i].base){
tbody.append($('<tr>').append(lineNum('old',oplines[i].base),'<th>',$('<td class="body delete">').html(baseTextDom(oplines[i].base))));
}else if(oplines[i].head){
deletes.push($('<tr>').append('<th>',lineNum('new',oplines[i].head),$('<td class="body insert">').html(headTextDom(oplines[i].head))));
}
i++;
}
tbody.append(deletes);
i--;
break;
}
}
return table;
}
};
function lineNum(type,num){
var cell = $('<th class="line-num">').addClass(type+'line');
if(num){
cell.attr('line-number',num);
}
return cell;
}
function lineDiff(b,n){
var bc = $('<diff>').html(b).children();
var nc = $('<diff>').html(n).children();
var textE = function(){ return $(this).text(); };
var sm = new difflib.SequenceMatcher(bc.map(textE), nc.map(textE));
var op = sm.get_opcodes();
if(op.length==1 || sm.ratio()<0.5){
return {base:bc,head:nc};
}
var ret = { base : [], head: []};
for(var i=0;i<op.length;i++){
var o = op[i];
switch(o[0]){
case 'equal':
ret.base=ret.base.concat(bc.slice(o[1],o[2]));
ret.head=ret.head.concat(nc.slice(o[3],o[4]));
break;
case 'delete':
case 'insert':
case 'replace':
if(o[2]!=o[1]){
ret.base.push($('<del>').append(bc.slice(o[1],o[2])));
}
if(o[4]!=o[3]){
ret.head.push($('<ins>').append(nc.slice(o[3],o[4])));
}
break;
}
}
return ret;
}
},
flatten: function(opcodes, headTextLines, baseTextLines, isIgnoreLine){
var ret = [];
for (var idx = 0; idx < opcodes.length; idx++) {
var code = opcodes[idx];
var change = code[0];
var b = code[1];
var n = code[3];
var rowcnt = Math.max(code[2] - b, code[4] - n);
for (var i = 0; i < rowcnt; i++) {
switch(change){
case 'insert':
ret.push({
change:(isIgnoreLine(headTextLines[n]) ? 'equal' : change),
head: ++n
});
break;
case 'delete':
ret.push({
change: (isIgnoreLine(baseTextLines[b]) ? 'equal' : change),
base: ++b
});
break;
case 'replace':
var r = {change: change};
if(n<code[4]){
r.head = ++n;
}
if(b<code[2]){
r.base = ++b;
}
ret.push(r);
break;
default:
ret.push({
change:change,
head: ++n,
base: ++b
});
}
}
}
return ret;
},
fold: function(oplines, contextSize){
var ret = [], skips=[], bskip = contextSize;
for(var i=0;i<oplines.length;i++){
var o = oplines[i];
if(o.change=='equal'){
if(bskip < contextSize){
bskip ++;
ret.push(o);
}else{
skips.push(o);
}
}else{
if(skips.length > contextSize){
ret.push({
change:'skip',
start:skips[0],
end:skips[skips.length-contextSize]
});
}
ret = ret.concat(skips.splice(- contextSize));
ret.push(o);
skips = [];
bskip = 0;
}
}
if(skips.length > contextSize){
ret.push({
change:'skip',
start:skips[0],
end:skips[skips.length-contextSize]
});
}
return ret;
}
});