Merge pull request #643 from team-lab/feature/diff-ignore-space

Improvement diff
This commit is contained in:
Naoki Takezoe
2015-03-22 05:59:16 -07:00
6 changed files with 501 additions and 170 deletions

View File

@@ -10,7 +10,7 @@
@import gitbucket.core.view.helpers._
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
@if(showIndex){
<div>
<div style="overflow: hidden;">
<div class="pull-right" style="margin-bottom: 10px;">
<div class="btn-group" data-toggle="buttons-radio">
<input type="button" id="btn-unified" class="btn btn-default btn-small active" value="Unified">
@@ -22,6 +22,7 @@
<ul id="commit-file-list" style="display: none;">
@diffs.zipWithIndex.map { case (diff, i) =>
<li@if(i > 0){ class="border"}>
<span class="pull-right diffstat" data-diff-id="@i"></span>
<a href="#diff-@i">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
@@ -42,37 +43,46 @@
}
@diffs.zipWithIndex.map { case (diff, i) =>
<a name="diff-@i"></a>
<table class="table table-bordered" commitId="@newCommitId" fileName="@diff.newPath">
<table class="table table-bordered diff-outside" commitId="@newCommitId" fileName="@diff.newPath" data-diff-id="@i">
<tr>
<th style="font-weight: normal; line-height: 27px;" class="box-header">
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
<span class="diffstat">
<img src="@assets/common/images/diff_move.png"/>
</span> @diff.oldPath -> @diff.newPath
@if(newCommitId.isDefined){
<div class="pull-right align-right">
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label>
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small">View file @@ @newCommitId.get.substring(0, 10)</a>
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
</div>
}
}
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
<span class="diffstat">
@if(diff.changeType == ChangeType.ADD){
<img src="@assets/common/images/diff_add.png"/>
}else{
<img src="@assets/common/images/diff_edit.png"/>
} @diff.newPath
}
</span>
@diff.newPath
@if(newCommitId.isDefined){
<div class="pull-right align-right">
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label>
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small">View file @@ @newCommitId.get.substring(0, 10)</a>
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
</div>
}
}
@if(diff.changeType == ChangeType.DELETE){
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
<span class="diffstat">
<img src="@assets/common/images/diff_delete.png"/>
</span> @diff.oldPath
@if(oldCommitId.isDefined){
<div class="pull-right align-right">
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-small">View file @@ @oldCommitId.get.substring(0, 10)</a>
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-small" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
</div>
}
}
@@ -81,9 +91,9 @@
<tr>
<td style="padding: 0;">
@if(diff.newContent != None || diff.oldContent != None){
<div id="diffText-@i"></div>
<textarea id="newText-@i" style="display: none;">@diff.newContent.getOrElse("")</textarea>
<textarea id="oldText-@i" style="display: none;">@diff.oldContent.getOrElse("")</textarea>
<div id="diffText-@i" class="diffText"></div>
<textarea id="newText-@i" style="display: none;" data-file-name="@diff.oldPath">@diff.newContent.getOrElse("")</textarea>
<textarea id="oldText-@i" style="display: none;" data-file-name="@diff.newPath">@diff.oldContent.getOrElse("")</textarea>
} else {
Not supported
}
@@ -92,7 +102,6 @@
</table>
}
<script type="text/javascript" src="@assets/vendors/jsdifflib/difflib.js"></script>
<script type="text/javascript" src="@assets/vendors/jsdifflib/diffview.js"></script>
<link href="@assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script>
$(function(){
@@ -108,16 +117,26 @@ $(function(){
}
// Render diffs as unified mode initially
renderDiffs(1);
if(("&"+location.search.substring(1)).indexOf("&w=1")!=-1){
$('.ignore-whitespace').prop('checked',true);
}
window.viewType=1;
if(("&"+location.search.substring(1)).indexOf("&diff=split")!=-1){
$('.container').removeClass('container').addClass('container-wide');
window.viewType=0;
}
renderDiffs();
$('#btn-unified').click(function(){
window.viewType=1;
$('.container-wide').removeClass('container-wide').addClass('container');
renderDiffs(1);
renderDiffs();
});
$('#btn-split').click(function(){
window.viewType=0;
$('.container').removeClass('container').addClass('container-wide');
renderDiffs(0);
renderDiffs();
});
$('.toggle-notes').change(function() {
@@ -126,126 +145,154 @@ $(function(){
}
$(this).closest('table').find('.not-diff').toggle();
});
$('.ignore-whitespace').change(function() {
renderOneDiff($(this).closest("table").find(".diffText"), viewType);
});
function renderDiffs(viewType){
window.viewType = viewType;
@diffs.zipWithIndex.map { case (diff, i) =>
@if(diff.newContent != None || diff.oldContent != None){
if($('#oldText-@i').length > 0){
diffUsingJS('oldText-@i', 'newText-@i', 'diffText-@i', viewType);
function getInlineContainer(where) {
if (viewType == 0) {
if (where === 'new') {
return $('<tr class="not-diff"><td colspan="2"></td><td colspan="2" class="comment-box-container"></td></tr>');
} else if (where === 'old') {
return $('<tr class="not-diff"><td colspan="2" class="comment-box-container"></td><td colspan="2"></td></tr>');
}
}
return $('<tr class="not-diff"><td colspan="3" class="comment-box-container"></td></tr>');
}
if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
$('.inline-comment').hide();
}
$('.diff-outside').on('click','table.diff .add-comment',function() {
var $this = $(this),
$tr = $this.closest('tr'),
$check = $this.closest('table:not(.diff)').find('.toggle-notes');
if (!$check.prop('checked')) {
$check.prop('checked', true).trigger('change');
}
if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) {
var commitId = $this.closest('.table-bordered').attr('commitId'),
fileName = $this.closest('.table-bordered').attr('fileName'),
oldLineNumber, newLineNumber,
url = '@url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
if (viewType == 0) {
oldLineNumber = $this.parent().prev('.oldline').attr('line-number');
newLineNumber = $this.parent().prev('.newline').attr('line-number');
} else {
oldLineNumber = $this.parent().prevAll('.oldline').attr('line-number');
newLineNumber = $this.parent().prevAll('.newline').attr('line-number');
}
if (!isNaN(oldLineNumber) && oldLineNumber) {
url += ('&oldLineNumber=' + oldLineNumber)
}
if (!isNaN(newLineNumber) && newLineNumber) {
url += ('&newLineNumber=' + newLineNumber)
}
$.get(
url,
{
dataType : 'html'
},
function(responseContent) {
$this.hide();
var tmp;
if (!isNaN(oldLineNumber) && oldLineNumber) {
if (!isNaN(newLineNumber) && newLineNumber) {
tmp = getInlineContainer();
} else {
tmp = getInlineContainer('old');
}
} else {
tmp = getInlineContainer('new');
}
tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent);
$tr.nextAll(':not(.not-diff):first').before(tmp);
}
);
}
}).on('click', 'table.diff .btn-default', function() {
$(this).closest('.inline-comment-form').remove();
});
function renderOneCommitCommentIntoDiff($v, diff){
var filename = $v.attr('filename'),
oldline = $v.attr('oldline'), newline = $v.attr('newline');
var tmp;
var diff;
if (typeof oldline !== 'undefined') {
if (typeof newline !== 'undefined') {
tmp = getInlineContainer();
} else {
tmp = getInlineContainer('old');
}
tmp.children('td:first').html($v.clone().show());
diff.find('table.diff').find('.oldline[line-number=' + oldline + ']')
.parent().nextAll(':not(.not-diff):first').before(tmp);
} else {
tmp = getInlineContainer('new');
tmp.children('td:last').html($v.clone().show());
diff.find('table.diff').find('.newline[line-number=' + newline + ']')
.parent().nextAll(':not(.not-diff):first').before(tmp);
}
if (!diff.find('.toggle-notes').prop('checked')) {
tmp.hide();
}
}
function renderStatBar(add,del){
if(add+del>5){
if(add){
if(add<del){
add = Math.floor(1 + (add * 4 / (add+del)));
}else{
add = Math.ceil(1 + (add * 4 / (add+del)));
}
}
del = 5-add;
}
var ret = $('<div class="diffstat-bar">');
for(var i=0;i<5;i++){
if(add){
ret.append('<span class="text-diff-added">■</span>');
add --;
}else if(del){
ret.append('<span class="text-diff-deleted">■</span>');
del --;
}else{
ret.append('■');
}
}
return ret;
}
function renderOneDiff(diffText, viewType){
var table = diffText.closest("table[data-diff-id]");
var i = table.data("diff-id");
var ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked');
diffUsingJS('oldText-'+i, 'newText-'+i, diffText.attr('id'), viewType, ignoreWhiteSpace);
var add = diffText.find("table").attr("add")*1;
var del = diffText.find("table").attr("del")*1;
table.find(".diffstat").text(add+del+" ").append(renderStatBar(add,del)).attr("title",add+" additions & "+del+" deletions").tooltip();
$('span.diffstat[data-diff-id="'+i+'"]').html('<span class="text-diff-added">+'+add+'</span><span class="text-diff-deleted">-'+del+'</span>').append(renderStatBar(add,del).attr('title',(add+del)+" lines changed").tooltip());
@if(hasWritePermission) {
diffText.find('.body').each(function(){ $('<b class="add-comment">+</b>').prependTo(this); });
}
@if(showLineNotes){
function getInlineContainer(where) {
if (viewType == 0) {
if (where === 'new') {
return $('<tr class="not-diff"><td colspan="2"></td><td colspan="2" class="comment-box-container"></td></tr>');
} else if (where === 'old') {
return $('<tr class="not-diff"><td colspan="2" class="comment-box-container"></td><td colspan="2"></td></tr>');
}
}
return $('<tr class="not-diff"><td colspan="3" class="comment-box-container"></td></tr>');
}
var fileName = table.attr('filename');
$('.inline-comment').each(function(i, v) {
var $v = $(v), filename = $v.attr('filename'),
oldline = $v.attr('oldline'), newline = $v.attr('newline');
if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
$(this).hide();
}
var tmp;
var diff;
if (typeof oldline !== 'undefined') {
if (typeof newline !== 'undefined') {
tmp = getInlineContainer();
} else {
tmp = getInlineContainer('old');
}
tmp.children('td:first').html($(this).clone().show());
diff = $('table[filename="' + filename + '"]');
diff.find('table.diff').find('.oldline[line-number=' + oldline + ']')
.parent().nextAll(':not(.not-diff):first').before(tmp);
} else {
tmp = getInlineContainer('new');
tmp.children('td:last').html($(this).clone().show());
diff = $('table[filename="' + filename + '"]');
diff.find('table.diff').find('.newline[line-number=' + newline + ']')
.parent().nextAll(':not(.not-diff):first').before(tmp);
}
if (!diff.find('.toggle-notes').prop('checked')) {
tmp.hide();
if($(this).attr('filename')==fileName){
renderOneCommitCommentIntoDiff($(this), table);
}
});
@if(hasWritePermission) {
$('table.diff td').hover(
function() {
$(this).find('b').css('display', 'inline-block');
},
function() {
$(this).find('b').css('display', 'none');
}
);
$('table.diff th').hover(
function() {
$(this).nextAll().find('b').first().css('display', 'inline-block');
},
function() {
$(this).nextAll().find('b').first().css('display', 'none');
}
);
$('.add-comment').click(function() {
var $this = $(this),
$tr = $this.closest('tr'),
$check = $this.closest('table:not(.diff)').find('.toggle-notes');
if (!$check.prop('checked')) {
$check.prop('checked', true).trigger('change');
}
if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) {
var commitId = $this.closest('.table-bordered').attr('commitId'),
fileName = $this.closest('.table-bordered').attr('fileName'),
oldLineNumber, newLineNumber,
url = '@url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
if (viewType == 0) {
oldLineNumber = $this.parent().prev('.oldline').attr('line-number');
newLineNumber = $this.parent().prev('.newline').attr('line-number');
} else {
oldLineNumber = $this.parent().prevAll('.oldline').attr('line-number');
newLineNumber = $this.parent().prevAll('.newline').attr('line-number');
}
if (!isNaN(oldLineNumber) && oldLineNumber) {
url += ('&oldLineNumber=' + oldLineNumber)
}
if (!isNaN(newLineNumber) && newLineNumber) {
url += ('&newLineNumber=' + newLineNumber)
}
$.get(
url,
{
dataType : 'html'
},
function(responseContent) {
$this.hide();
var tmp;
if (!isNaN(oldLineNumber) && oldLineNumber) {
if (!isNaN(newLineNumber) && newLineNumber) {
tmp = getInlineContainer();
} else {
tmp = getInlineContainer('old');
}
} else {
tmp = getInlineContainer('new');
}
tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent);
$tr.nextAll(':not(.not-diff):first').before(tmp);
}
);
}
});
$('table.diff').on('click', '.btn-default', function() {
$(this).closest('.inline-comment-form').remove();
});
}
}
function renderDiffs(){
var i=0, diffs = $('.diffText');
function render(){
if(diffs[i]){
renderOneDiff($(diffs[i]), viewType);
i++;
setTimeout(render);
}
}
render();
}
});
</script>

View File

@@ -28,8 +28,8 @@
<tr>
<td>
<div id="diffText"></div>
<textarea id="newText" style="display: none;"></textarea>
<textarea id="oldText" style="display: none;">@content.content</textarea>
<textarea id="newText" style="display: none;" data-file-name="@fileName"></textarea>
<textarea id="oldText" style="display: none;" data-file-name="@fileName">@content.content</textarea>
</td>
</tr>
</table>
@@ -52,7 +52,6 @@
}
}
<script type="text/javascript" src="@assets/vendors/jsdifflib/difflib.js"></script>
<script type="text/javascript" src="@assets/vendors/jsdifflib/diffview.js"></script>
<link href="@assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script>
$(function(){

View File

@@ -71,7 +71,6 @@
}
<script src="@assets/vendors/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="@assets/vendors/jsdifflib/difflib.js"></script>
<script type="text/javascript" src="@assets/vendors/jsdifflib/diffview.js"></script>
<link href="@assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script>
$(function(){
@@ -138,8 +137,8 @@ $(function(){
// Show diff
$('#preview').empty()
.append($('<div id="diffText">'))
.append($('<textarea id="newText" style="display: none;">').html(editor.getValue()))
.append($('<textarea id="oldText" style="display: none;">').html($('#initial').val()));
.append($('<textarea id="newText" style="display: none;">').data('file-name',$("#newFileName").val()).html(editor.getValue()))
.append($('<textarea id="oldText" style="display: none;">').data('file-name',$("#oldFileName").val()).html($('#initial').val()));
diffUsingJS('oldText', 'newText', 'diffText', 1);
}
});

View File

@@ -1027,16 +1027,25 @@ td.insert, td.equal, td.delete, td.empty {
width: 50%;
}
table.diff td.body{
position: relative;
}
table.diff th.line-num{
min-width: 20px;
}
table.diff .add-comment {
position: absolute;
background: blue;
background: #4183c4;
top: 0;
left: -7px;
color: white;
padding: 2px 4px;
border: solid 1px blue;
border: solid 1px #4183c4;
border-radius: 3px;
z-index: 99;
cursor: pointer;
}
table.diff .add-comment:hover {
@@ -1044,6 +1053,24 @@ table.diff .add-comment:hover {
top: -1px;
}
table.diff tr td.body b.add-comment{
display: none;
}
table.diff tr:hover td.body b.add-comment{
display: inline-block;
}
.container-wide table.diff tr:hover td.body b.add-comment{
display: none;
}
.container-wide table.diff tr:hover td.body:hover b.add-comment,
.container-wide table.diff tr:hover th.line-num:hover + td b.add-comment{
display: inline-block;
}
table.diff tbody tr.not-diff {
font-family: '"Helvetica Neue", Helvetica, Arial, sans-serif';
}
@@ -1074,6 +1101,29 @@ table.diff tbody tr.not-diff:hover td{
content: "..."
}
.diffstat-bar {
display: inline-block;
margin-left: 3px;
font-size: 16px;
color: #ddd;
letter-spacing: 1px;
text-align: left;
text-decoration: none;
-webkit-font-smoothing: antialiased;
}
.text-diff-added {
color: #55a532;
}
.text-diff-deleted {
color: #bd2c00;
}
.diffstat {
font-size: 12px;
font-weight: bold;
color: #666;
white-space: nowrap;
letter-spacing: 0px;
}
/****************************************************************************/
/* Repository Settings */
/****************************************************************************/

View File

@@ -76,41 +76,256 @@ 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 old = $('#'+oldTextId), head = $('#'+newTextId);
var render = new JsDiffRender({
oldText: old.val(),
oldTextName: old.data('file-name'),
newText: head.val(),
newTextName: head.data('file-name'),
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.oldText==="")?[]:params.oldText.split(/\r\n|\r|\n/);
var headTextLines = (params.newText==="")?[]: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, fileName){
var dom = null;
return function(ln){
if(dom===null){
dom = prettyPrintOne(
text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;').replace(/>/g,'&gt;'),
(/\.([^.]*)$/.exec(fileName)||[])[1],
true);
}
return (new RegExp('<li id="L'+ln+'"[^>]*>(.*?)</li>').exec(dom) || [])[1];
};
}
return this.renders(oplines, prettyDom(params.oldText, params.oldTextName), prettyDom(params.newText, params.newTextName));
}
$.extend(JsDiffRender.prototype,{
renders: function(oplines, baseTextDom, headTextDom){
return {
split:function(){
var table = $('<table class="diff">');
table.attr({add:oplines.add, del:oplines.del});
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 class="skip"></th><td colspan="3" class="skip">...</td>').appendTo(tbody);
break;
case 'delete':
case 'insert':
case 'equal':
$('<tr>').append(
lineNum('old',o.base, o.change),
$('<td class="body">').html(o.base ? baseTextDom(o.base): "").addClass(o.change),
lineNum('old',o.head, o.change),
$('<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, 'delete'),
$('<td class="body">').append(ld.base).addClass('delete'),
lineNum('old',o.head, 'insert'),
$('<td class="body">').append(ld.head).addClass('insert')
).appendTo(tbody);
break;
}
}
return table;
},
unified:function(){
var table = $('<table class="diff inlinediff">');
table.attr({add:oplines.add, del:oplines.del});
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" class="skip"></th><td class="skip"></td>'));
break;
case 'delete':
case 'insert':
case 'equal':
tbody.append($('<tr>').append(
lineNum('old',o.base, o.change),
lineNum('new',o.head, o.change),
$('<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, 'delete'),'<th class="delete">',$('<td class="body delete">').append(ld.base)));
deletes.push($('<tr>').append('<th class="insert">',lineNum('new',oplines[i].head, 'insert'),$('<td class="body insert">').append(ld.head)));
}else if(oplines[i].base){
tbody.append($('<tr>').append(lineNum('old', oplines[i].base, 'delete'),'<th class="delete">',$('<td class="body delete">').html(baseTextDom(oplines[i].base))));
}else if(oplines[i].head){
deletes.push($('<tr>').append('<th class="insert">',lineNum('new',oplines[i].head, 'insert'),$('<td class="body insert">').html(headTextDom(oplines[i].head))));
}
i++;
}
tbody.append(deletes);
i--;
break;
}
}
return table;
}
};
function lineNum(type, num, klass){
var cell = $('<th class="line-num">').addClass(type+'line').addClass(klass);
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 = [], add=0, del=0;
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':
add++;
ret.push({
change:(isIgnoreLine(headTextLines[n]) ? 'equal' : change),
head: ++n
});
break;
case 'delete':
del++;
ret.push({
change: (isIgnoreLine(baseTextLines[b]) ? 'equal' : change),
base: ++b
});
break;
case 'replace':
add++;
del++;
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
});
}
}
}
ret.add=add;
ret.del=del;
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]
});
}
ret.add = oplines.add;
ret.del = oplines.del;
return ret;
}
});

View File

@@ -51,7 +51,7 @@ table.diff tbody th {
font-size:11px;
font-weight:normal;
border-top:none; /* for overriding bootstrap */
color:#886;
color: rgba(0,0,0,0.3);;
padding:.3em .5em .1em 2em;
text-align:right;
vertical-align:top
@@ -77,19 +77,40 @@ table.diff .replace {
background-color:#FD8
}
table.diff .delete {
background-color:#FFDDDD;
background-color:#ffecec;
}
table.diff .skip {
background-color: #F8F8FF;
background-color: #f8fafd;
}
table.diff th.skip {
background-color: #f0f5fa;
}
table.diff .skip:before {
content: " ...";
content: " ";
}
table.diff .insert {
background-color:#DDFFDD
background-color:#eaffea
}
table.diff th.insert {
background-color: #c1e9c1;
background-color: #dbffdb;
}
table.diff th.delete {
background-color: #ffdddd;
border-color: #f1c0c0;
}
table.diff th.author {
text-align:right;
border-top:1px solid #BBC;
background:#EFEFEF
}
table.diff ins{
background-color: #a6f3a6;
text-decoration: none;
}
table.diff del{
background-color: #f8cbcb;
text-decoration: none;
}