Commit 62b2a16d authored by Astor Bizard's avatar Astor Bizard
Browse files

Fixed similarity diff memory consumption and display. Minor refactor.

parent 5e88d4ac
define(["jquery","mod_vpl/vplutil"],function(a,b){return function(){var c=this,d=null,e=null,f=c.getFileManager().readOnly,g=this.getContent;this.getContent=function(){return this.isOpen()?d.getValue():g.call(this)};var h=this.setContent;this.setContent=function(a){h.call(this,a),this.isOpen()&&d.setValue(a)};var i=this.destroy;this.destroy=function(){this.isOpen()&&d.destroy(),i.call(this)},this.setFontSize=function(a){this.isOpen()&&d.setFontSize(a)};var j=this.adjustSize;this.adjustSize=function(){return!!j.call(this)&&(d.resize(!0),!0)},this.gotoLine=function(a){this.isOpen()&&(d.gotoLine(a,0),d.scrollToLine(a,!0),d.focus())},this.setReadOnly=function(a){f=a,this.isOpen()&&d.setReadOnly(a)},this.focus=function(){if(this.isOpen()){var b=this.getTId();a(b).removeClass("ui-widget-content ui-tabs-panel"),a(b).addClass("ui-corner-bottom"),this.adjustSize(),d.focus()}},this.blur=function(){this.isOpen()&&d.blur()},this.undo=function(){this.isOpen()&&(d.undo(),d.focus())},this.redo=function(){this.isOpen()&&(d.redo(),d.focus())},this.selectAll=function(){this.isOpen()&&(d.selectAll(),d.focus())},this.hasUndo=function(){return!!this.isOpen()&&e.getUndoManager().hasUndo()},this.hasRedo=function(){return!!this.isOpen()&&e.getUndoManager().hasRedo()},this.hasSelectAll=b.returnTrue,this.hasFind=b.returnTrue,this.hasFindReplace=b.returnTrue,this.hasNext=b.returnTrue,this.find=function(){this.isOpen()&&d.execCommand("find")},this.replace=function(){this.isOpen()&&d.execCommand("replace")},this.next=function(){this.isOpen()&&d.execCommand("findnext")},this.getAnnotations=function(){return this.isOpen()?e.getAnnotations():[]},this.setAnnotations=function(a){return!!this.isOpen()&&e.setAnnotations(a)},this.clearAnnotations=function(){return!!this.isOpen()&&e.clearAnnotations()},this.langSelection=function(){if(this.isOpen()){var a=b.fileExtension(this.getFileName()),c="text";""!==a&&(c=b.langType(a)),e.setMode("ace/mode/"+c)}},this.getEditor=function(){return!!this.isOpen()&&d},this.setTheme=function(a){this.isOpen()&&d.setTheme("ace/theme/"+a)},this.check_difference=function(){},this.open=function(){if(this.showFileName(),"undefined"==typeof ace)return b.loadScript(["../editor/ace9/ace.js","../editor/ace9/ext-language_tools.js"],function(){c.open()}),!1;if(this.isOpen())return d;var g=this.getFileManager(),h=this.getTId();a(h).removeClass("ui-widget-content ui-tabs-panel"),a(h).addClass("ui-corner-bottom"),ace.require("ext/language_tools"),d=ace.edit("vpl_file"+this.getId()),e=d.getSession(),d.setOptions({enableBasicAutocompletion:!0,enableSnippets:!0}),d.setValue(this.getContent()),d.setFontSize(g.getFontSize()),d.setTheme("ace/theme/"+g.getTheme()),d.$blockScrolling=1/0,d.gotoLine(0,0),d.setReadOnly(f),e.setUseSoftTabs(!0),e.setTabSize(4),e.setUndoManager(new ace.UndoManager),this.setOpen(!0),this.langSelection(),d.execCommand("replace");var i=function(){var b=a(h+" div.ace_search");b.length?(b.on("drop",g.dropHandler),a(".ace_searchbtn_close").trigger("click")):setTimeout(i,50)};this.check_difference();var j=null;d.on("change",function(){c.change(),null!==j&&clearTimeout(j),j=setTimeout(()=>{j=null,c.check_difference()},500)}),setTimeout(i,5);var k=d.onPaste;return d.onPaste=function(a){g.restrictedEdit?d.insert(g.getClipboard()):k.call(d,a)},d.on("copy",function(a){g.setClipboard(a)}),a(h).on("paste","*",g.restrictedPaste),a(h+" div.ace_content").on("drop",g.dropHandler),a(h+" div.ace_content").on("dragover",g.dragoverHandler),this.adjustSize(),d},this.close=function(){this.setOpen(!1),null!==d&&(this.setContent(d.getValue()),d.destroy(),d=null,e=null)}}});
\ No newline at end of file
define(["jquery","core/log","mod_vpl/vplutil"],function(a,b,c){return function(){var d=this,e=null,f=null,g=d.getFileManager().readOnly,h=this.getContent;this.getContent=function(){return this.isOpen()?e.getValue():h.call(this)};var i=this.setContent;this.setContent=function(a){i.call(this,a),this.isOpen()&&e.setValue(a)};var j=this.destroy;this.destroy=function(){this.isOpen()&&e.destroy(),j.call(this)},this.setFontSize=function(a){this.isOpen()&&e.setFontSize(a)};var k=this.adjustSize;this.adjustSize=function(){return!!k.call(this)&&(e.resize(!0),!0)},this.gotoLine=function(a){this.isOpen()&&(e.gotoLine(a,0),e.scrollToLine(a,!0),e.focus())},this.setReadOnly=function(a){g=a,this.isOpen()&&e.setReadOnly(a)},this.focus=function(){if(this.isOpen()){var b=this.getTId();a(b).removeClass("ui-widget-content ui-tabs-panel"),a(b).addClass("ui-corner-bottom"),this.adjustSize(),e.focus()}},this.blur=function(){this.isOpen()&&e.blur()},this.undo=function(){this.isOpen()&&(e.undo(),e.focus())},this.redo=function(){this.isOpen()&&(e.redo(),e.focus())},this.selectAll=function(){this.isOpen()&&(e.selectAll(),e.focus())},this.hasUndo=function(){return!!this.isOpen()&&f.getUndoManager().hasUndo()},this.hasRedo=function(){return!!this.isOpen()&&f.getUndoManager().hasRedo()},this.hasSelectAll=c.returnTrue,this.hasFind=c.returnTrue,this.hasFindReplace=c.returnTrue,this.hasNext=c.returnTrue,this.find=function(){this.isOpen()&&e.execCommand("find")},this.replace=function(){this.isOpen()&&e.execCommand("replace")},this.next=function(){this.isOpen()&&e.execCommand("findnext")},this.getAnnotations=function(){return this.isOpen()?f.getAnnotations():[]},this.setAnnotations=function(a){return!!this.isOpen()&&f.setAnnotations(a)},this.clearAnnotations=function(){return!!this.isOpen()&&f.clearAnnotations()},this.langSelection=function(){if(this.isOpen()){var a=c.fileExtension(this.getFileName()),b="text";""!==a&&(b=c.langType(a)),f.setMode("ace/mode/"+b)}},this.getEditor=function(){return!!this.isOpen()&&e},this.setTheme=function(a){this.isOpen()&&e.setTheme("ace/theme/"+a)},this.check_difference=function(){a.ajax({method:"POST",url:"../similarity/diff_check_requested_files.php",data:{id:this.getCurrentIdVpl(),name:this.getFileName(),val:e.getValue()}}).done(function(a){if(a.success){a.response.diff.forEach(function(a){"="!=a.type?(e.session.removeGutterDecoration(a.ln2-1,"ace-changed"),e.session.addGutterDecoration(a.ln2-1,"ace-changed")):e.session.removeGutterDecoration(a.ln2-1,"ace-changed")})}}).fail(function(a,c,d){b.error(d)})},this.open=function(){if(this.showFileName(),"undefined"==typeof ace)return c.loadScript(["../editor/ace9/ace.js","../editor/ace9/ext-language_tools.js"],function(){d.open()}),!1;if(this.isOpen())return e;var b=this.getFileManager(),h=this.getTId();a(h).removeClass("ui-widget-content ui-tabs-panel"),a(h).addClass("ui-corner-bottom"),ace.require("ext/language_tools"),e=ace.edit("vpl_file"+this.getId()),f=e.getSession(),e.setOptions({enableBasicAutocompletion:!0,enableSnippets:!0}),e.setValue(this.getContent()),e.setFontSize(b.getFontSize()),e.setTheme("ace/theme/"+b.getTheme()),e.$blockScrolling=1/0,e.gotoLine(0,0),e.setReadOnly(g),f.setUseSoftTabs(!0),f.setTabSize(4),f.setUndoManager(new ace.UndoManager),this.setOpen(!0),this.langSelection(),e.execCommand("replace");var i=function(){var c=a(h+" div.ace_search");c.length?(c.on("drop",b.dropHandler),a(".ace_searchbtn_close").trigger("click")):setTimeout(i,50)};this.check_difference();var j=null;e.on("change",function(){d.change(),null!==j&&clearTimeout(j),j=setTimeout(()=>{j=null,d.check_difference()},500)}),setTimeout(i,5);var k=e.onPaste;return e.onPaste=function(a){b.restrictedEdit?e.insert(b.getClipboard()):k.call(e,a)},e.on("copy",function(a){b.setClipboard(a)}),a(h).on("paste","*",b.restrictedPaste),a(h+" div.ace_content").on("drop",b.dropHandler),a(h+" div.ace_content").on("dragover",b.dragoverHandler),this.adjustSize(),e},this.close=function(){this.setOpen(!1),null!==e&&(this.setContent(e.getValue()),e.destroy(),e=null,f=null)}}});
\ No newline at end of file
......@@ -27,9 +27,10 @@
define(
[
'jquery',
'core/log',
'mod_vpl/vplutil',
],
function($, VPLUtil) {
function($, log, VPLUtil) {
return function() {
var self = this;
var editor = null;
......@@ -196,9 +197,7 @@ define(
editor.setTheme("ace/theme/" + theme);
};
this.check_difference = function() {
/*
var URL = "../similarity/diff_check_requested_files.php";
$.ajax({
method: "POST",
url: URL,
......@@ -217,8 +216,9 @@ define(
}
});
}
}).fail(function(jqxhr, status, error) {
log.error(error);
});
*/
};
this.open = function() {
this.showFileName();
......
......@@ -32,74 +32,29 @@ require_once(dirname(__FILE__).'/similarity_factory.class.php');
require_once(dirname(__FILE__).'/similarity_sources.class.php');
class vpl_diff {
/**
* Remove chars and digits
*
* @param $line string
* to process
* @return string without chars and digits
*/
static public function removealphanum($line) {
$ret = '';
$l = strlen( $line );
// Parse line to remove alphanum chars.
for ($i = 0; $i < $l; $i ++) {
$c = $line [$i];
if (! ctype_alnum( $c ) && $c != ' ') {
$ret .= $c;
}
}
return $ret;
}
/**
* Calculate the similarity of two lines
*
* @param
* $line1
* @param
* $line2
* @param string $line1
* @param string $line2
* @return int (3 => trimmed equal, 2 =>removealphanum , 1 => start of line , 0 => not equal)
*/
static public function diffline($line1, $line2) {
// TODO Refactor.
// This is a bad solution that must be rebuild to consider diferent languages.
// Compare trimed text.
$line1 = trim( $line1 );
$line2 = trim( $line2 );
if ($line1 == $line2) {
if (strlen( $line1 ) > 0) {
return 3;
} else {
return 1;
}
}
// Compare filtered text (removing alphanum).
$ran1 = self::removealphanum( $line1 );
$limit = strlen( $ran1 );
if ($limit > 0) {
if ($limit > 3) {
$limit = 3;
}
if (strncmp( $ran1, self::removealphanum( $line2 ), $limit ) == 0) {
return 2;
}
return 3;
}
// Compare start of line.
$l = 4;
if ($l > strlen( $line1 )) {
$l = strlen( $line1 );
if (self::similine($line1, $line2, '/(\s|[0-9]|[a-z])/i')) {
return 2;
}
if ($l > strlen( $line2 )) {
$l = strlen( $line2 );
if (substr($line1, 0, 4) == substr($line2, 0, 4)) {
return 1;
}
for ($i = 0; $i < $l; ++ $i) {
if ($line1 [$i] != $line2 [$i]) {
break;
}
}
return $i > 0 ? 1 : 0;
return 0;
}
static public function newlineinfo($type, $ln1, $ln2 = 0) {
$ret = new StdClass();
$ret->type = $type;
......@@ -108,31 +63,35 @@ class vpl_diff {
return $ret;
}
// Due to PHP arrays architecture, the calculatediff function consumes a lot of memory.
// To reduce that memory consumption, we put information on the "prev" on the 29th and 30th bits of integers.
static private $PREVX = 2**29;
static private $PREVY = 2**30;
static private function trueValue($value) {
return $value & ~(self::$PREVX + self::$PREVY);
}
/**
* Initialize used matrix
*
* @param array $matrix of arrays to initialize
* @param array $prev of arrays to initialize
* @param $nl1 number of rows
* @param $nl2 number of columns
* @return void
*/
static public function initauxiliarmatrices(&$matrix, &$prev, $nl1, $nl2) {
static public function initauxiliarmatrices(&$matrix, $nl1, $nl2) {
// Set the matrix[0..nl1+1][0..nl2+1] to 0.
$row = array_pad( array (), $nl2 + 1, 0 );
$matrix = array_pad( array (), $nl1 + 1, $row );
// Set the prev matrix [0..nl1+1][0..nl2+1] to 0.
$prev = $matrix;
// Update first column.
for ($i = 0; $i <= $nl1; $i ++) {
$matrix [$i] [0] = 0;
$prev [$i] [0] = - 1;
$matrix[$i] = new SplFixedArray($nl2 + 1);
for ($j = 0; $j <= $nl2; $j ++) {
$matrix[$i][$j] = 0;
}
// Set prev for first column.
$matrix[$i][0] = self::$PREVY;
}
// Update first row.
for ($j = 1; $j <= $nl2; $j ++) {
$matrix [0] [$j] = 0;
$prev [0] [$j] = 1;
// Set prev for first row.
for ($j = 0; $j <= $nl2; $j ++) {
$matrix[0][$j] = self::$PREVX;
}
}
......@@ -170,32 +129,31 @@ class vpl_diff {
}
return $ret;
}
$matrix = [];
$prev = [];
self::initauxiliarmatrices( $matrix, $prev, $nl1, $nl2 );
$matrix = new SplFixedArray($nl1 + 1);
self::initauxiliarmatrices( $matrix, $nl1, $nl2 );
// Matrix processing.
for ($i = 1; $i <= $nl1; $i ++) {
$line = $lines1 [$i - 1];
for ($j = 1; $j <= $nl2; $j ++) {
if ($matrix [$i] [$j - 1] > $matrix [$i - 1] [$j]) {
$max = $matrix [$i] [$j - 1];
$best = 1;
if (self::trueValue($matrix[$i][$j - 1]) > self::trueValue($matrix[$i - 1][$j])) {
$max = self::trueValue($matrix[$i][$j - 1]);
$best = self::$PREVY;
} else {
$max = $matrix [$i - 1] [$j];
$best = - 1;
$max = self::trueValue($matrix[$i - 1][$j]);
$best = self::$PREVX;
}
if ($detaileddiff) {
$prize = self::diffline( $line, $lines2 [$j - 1] );
} else {
$prize = $line === $lines2 [$j - 1] ? 1 : 0;
}
if ($matrix [$i - 1] [$j - 1] + $prize >= $max) {
$max = $matrix [$i - 1] [$j - 1] + $prize;
if (self::trueValue($matrix[$i - 1][$j - 1]) + $prize >= $max) {
$max = self::trueValue($matrix[$i - 1][$j - 1]) + $prize;
$best = 0;
}
$matrix [$i] [$j] = $max;
$prev [$i] [$j] = $best;
$matrix [$i] [$j] = $max + $best;
}
}
......@@ -209,16 +167,13 @@ class vpl_diff {
$pair->i = $pi;
$pair->j = $pj;
$pairs [] = $pair;
$p = $prev [$pi] [$pj];
if ($p == 0) {
$pi --;
if (($matrix[$pi][$pj] & self::$PREVY) > 0) {
$pj --;
} else if ($p == - 1) {
} else if (($matrix[$pi][$pj] & self::$PREVX) > 0) {
$pi --;
} else if ($p == 1) {
$pj --;
} else {
debbuging('error');
$pi --;
$pj --;
}
$limit --;
}
......@@ -231,13 +186,17 @@ class vpl_diff {
if ($pair->i == $prevpair->i + 1 && $pair->j == $prevpair->j + 1) { // Regular advance.
$l1 = $lines1 [$pair->i - 1];
$l2 = $lines2 [$pair->j - 1];
if ($l1 == $l2) { // Equals.
if ($l1 == $l2) {
// Equals.
$ret [] = self::newlineinfo( '=', $pair->i, $pair->j );
} else if ( self::similine($l1, $l2, '/\s/')) {
// Equal once whitespaces have been removed.
$ret [] = self::newlineinfo( '1', $pair->i, $pair->j );
} else if ( self::similine($l1, $l2, '/(\s|[0-9]|[a-z])/i')) {
// Equal once whitespaces and alphanumeric characters have been removed.
$ret [] = self::newlineinfo( '2', $pair->i, $pair->j );
} else {
// Different even once whitespaces and alphanumeric characters have been removed.
$ret [] = self::newlineinfo( '#', $pair->i, $pair->j );
}
} else if ($pair->i == $prevpair->i + 1) { // Removed next line.
......@@ -303,38 +262,37 @@ class vpl_diff {
$datal2 .= $emptyline;
}
}
echo '<div style="width: 100%;min-width: 950px; overflow: auto">';
echo '<div id="sim_details" style="width: 100%; overflow: auto; white-space: nowrap;">';
// Header.
echo '<div style="float:left; width: 445px">';
echo $htmlheader1;
echo '</div>';
echo '<div style="float:left; width: 445px">';
echo $htmlheader2;
echo '</div>';
echo '<div class="sim-col">&nbsp;</div>';
echo '<div class="sim-file">' . $htmlheader1 . '</div>';
echo '<div class="sim-col">&nbsp;</div>';
echo '<div class="sim-col">&nbsp;</div>';
echo '<div class="sim-file">' . $htmlheader2 . '</div>';
echo '<div style="clear:both;"></div>';
// Files.
echo '<div style="float:left; text-align: right; width: 3em">';
$shower = vpl_sh_factory::get_sh( 'a.txt' );
$shower->print_file( 'a.txt', $datal1, false, count($diff) + 1, false );
$simpledisplayer = vpl_sh_factory::get_sh( '' );
$nl = count($diff) + 1;
echo '<div class="sim-col sim-gutter">';
$simpledisplayer->print_file( '', $datal1, false, $nl, false );
echo '</div>';
echo '<div style="float:left; width: 390px; overflow:auto">';
echo '<div class="sim-file">';
$shower = vpl_sh_factory::get_sh( $filename1 );
$shower->print_file( $filename1, $data1, false, count($diff) + 1, false );
$shower->print_file( $filename1, $data1, false, $nl, false );
echo '</div>';
echo '<div style="float:left; width: 3em">';
$shower = vpl_sh_factory::get_sh( 'b.txt' );
$shower->print_file( 'b.txt', $diffl, false, count($diff) + 1, false );
echo '<div class="sim-col">';
$simpledisplayer->print_file( '', $diffl, false, $nl, false );
echo '</div>';
echo '<div style="float:left; text-align: right; width: 3em">';
$shower = vpl_sh_factory::get_sh( 'b.txt' );
$shower->print_file( 'b.txt', $datal2, false, count($diff) + 1, false );
echo '<div class="sim-col sim-gutter">';
$simpledisplayer->print_file( '', $datal2, false, $nl, false );
echo '</div>';
echo '<div style="float:left; width: 390px; overflow:auto">';
echo '<div class="sim-file">';
$shower = vpl_sh_factory::get_sh( $filename2 );
$shower->print_file( $filename2, $data2, false, count($diff) + 1, false );
$shower->print_file( $filename2, $data2, false, $nl, false );
echo '</div>';
echo '</div>';
echo '<div style="clear:both;"></div>';
vpl_sh_factory::syntaxhighlight();
}
static public function vpl_get_similfile($f, &$htmlheader, &$filename, &$data) {
......@@ -393,82 +351,6 @@ class vpl_diff {
}
}
/**
* Computes diff between two lines.
* @param string $line1
* @param string $line2
* @return int The diff in number of chars.
*/
static public function compute_linediff($line1, $line2) {
$n1 = strlen($line1);
$n2 = strlen($line2);
if ($n1 == 0 || $n2 == 0) {
return $n1 + $n2;
}
$queue = array_fill(0, $n1 + $n2 + 1, array());
$done = array_fill(0, $n1 + 1, array());
$z = new stdClass();
$z->i1 = 0;
$z->i2 = 0;
$z->d = 0;
$queue[0][] = $z;
$priority = 0;
// A-star search of shortest diff.
while (true) {
while (count($queue[$priority]) == 0) {
$priority++;
}
$z = array_pop($queue[$priority]);
$i1 = $z->i1;
$i2 = $z->i2;
if (isset($done[$i1][$i2])) {
continue;
} else {
$done[$i1][$i2] = true;
}
// Identical substring: 0 distance.
while ($i1 < $n1 && $i2 < $n2 && $line1{$i1} == $line2{$i2}) {
$i1++;
$i2++;
$done[$i1][$i2] = true;
}
if ($i1 == $n1 && $i2 == $n2) {
// Found shortest diff.
return $z->d;
}
// Character addition in line1.
if ($i1 < $n1 && !isset($done[$i1 + 1][$i2])) {
$z1 = new stdClass();
$z1->i1 = $i1 + 1;
$z1->i2 = $i2;
$z1->d = $z->d + 1;
$queue[$z1->d + abs(($n1 - $z1->i1) - ($n2 - $z1->i2))][] = $z1;
}
// Character addition in line2.
if ($i2 < $n2 && !isset($done[$i1][$i2 + 1])) {
$z2 = new stdClass();
$z2->i1 = $i1;
$z2->i2 = $i2 + 1;
$z2->d = $z->d + 1;
$queue[$z2->d + abs(($n1 - $z2->i1) - ($n2 - $z2->i2))][] = $z2;
}
// Character change.
if ($i1 < $n1 && $i2 < $n2 && !isset($done[$i1 + 1][$i2 + 1])) {
$z3 = new stdClass();
$z3->i1 = $i1 + 1;
$z3->i2 = $i2 + 1;
$z3->d = $z->d + 1;
$queue[$z3->d + abs(($n1 - $z3->i1) - ($n2 - $z3->i2))][] = $z3;
}
}
}
/**
* Computes diff between two files.
* @param string $filedata1
......@@ -481,11 +363,64 @@ class vpl_diff {
if (strlen($filedata1) == 0 || strlen($filedata2) == 0) {
return strlen($filedata1) + strlen($filedata2);
}
$starttime = microtime(true);
$lines1 = explode("\n", $filedata1);
$lines2 = explode("\n", $filedata2);
$n1 = count($lines1);
$n2 = count($lines2);
$prev = self::compute_diff($lines1, $lines2, $timelimit)->prev;
// Backtrack to compute total diff as a sum of lines diff.
$totaldiff = 0;
$i1 = count($lines1);
$i2 = count($lines2);
while ($i1 > 0 || $i2 > 0) {
if (!isset($prev[$i1][$i2])) {
$i1--;
$i2--;
} else {
switch ($prev[$i1][$i2]) {
case 1 :
$totaldiff += strlen( $lines1[$i1 - 1] ) + 1;
$i1--;
break;
case 2 :
$totaldiff += strlen( $lines2[$i2 - 1] ) + 1;
$i2--;
break;
case 3 :
$totaldiff += self::compute_diff($lines1[$i1 - 1], $lines2[$i2 - 1])->diff;
case 0 :
$i1--;
$i2--;
break;
}
}
}
return $totaldiff;
}
/**
* Computes diff between two arrays or strings, by equality comparison between elements at corresponding positions.
* @param string|array $array1
* @param string|array $array2
* @param int $timelimit The maximum amount of time to spend on this computation (in milliseconds).
* If provided and the time limit is exceeded, a moodle_exception with code 'difftoolarge' will be thrown.
* @return stdClass An object with a 'diff' field containing the diff in number of elements
* and a 'prev' field containing a 2-dimensional array of diff data, indexed on [array1][array2] :
* 0 means identical elements, 1 means element addition in array1,
* 2 means element addition in array2, 3 means element change.
*/
public static function compute_diff($array1, $array2, $timelimit = 0) {
if (is_string($array1)) {
$n1 = strlen($array1);
$n2 = strlen($array2);
} else {
$n1 = count($array1);
$n2 = count($array2);
}
if ($n1 == 0 || $n2 == 0) {
$result = new stdClass();
$result->diff = $n1 + $n2;
$result->prev = null;
return $result;
}
$queue = array_fill(0, $n1 + $n2 + 1, array());
$prev = array_fill(0, $n1 + 1, array());
$z = new stdClass();
......@@ -495,6 +430,7 @@ class vpl_diff {
$z->prev = 0;
$queue[0][] = $z;
$priority = 0;
$starttime = microtime(true);
// A-star search of shortest diff.
while (true) {
......@@ -514,44 +450,22 @@ class vpl_diff {
$prev[$i1][$i2] = $z->prev;
}
// Identical substring: 0 distance.
while ($i1 < $n1 && $i2 < $n2 && $lines1[$i1] == $lines2[$i2]) {
// Identical subarray: 0 distance.
while ($i1 < $n1 && $i2 < $n2 && $array1[$i1] == $array2[$i2]) {
$i1++;
$i2++;
$prev[$i1][$i2] = 0;
}
if ($i1 == $n1 && $i2 == $n2) {
// Found shortest diff amongst lines.
// Backtrack to compute total diff as a sum of lines diff.
$totaldiff = 0;
while ($i1 > 0 || $i2 > 0) {
if (!isset($prev[$i1][$i2])) {
$i1--;
$i2--;
} else {
switch ($prev[$i1][$i2]) {
case 1 :
$totaldiff += strlen( $lines1[$i1 - 1] ) + 1;
$i1--;
break;
case 2 :
$totaldiff += strlen( $lines2[$i2 - 1] ) + 1;
$i2--;
break;
case 3 :
$totaldiff += self::compute_linediff( $lines1[$i1 - 1], $lines2[$i2 - 1] );
case 0 :
$i1--;
$i2--;
break;
}
}
}
return $totaldiff;
// Found shortest diff amongst arrays.
$result = new stdClass();
$result->diff = $z->d;
$result->prev = $prev;
return $result;
}
// Line addition in file1.
// Element addition in array1.
if ($i1 < $n1 && !isset($prev[$i1 + 1][$i2])) {
$z1 = new stdClass();
$z1->i1 = $i1 + 1;
......@@ -561,7 +475,7 @@ class vpl_diff {
$queue[$z1->d + abs(($n1 - $z1->i1) - ($n2 - $z1->i2))][] = $z1;
}
// Line addition in file2.
// Element addition in array2.
if ($i2 < $n2 && !isset($prev[$i1][$i2 + 1])) {
$z2 = new stdClass();
$z2->i1 = $i1;
......@@ -571,7 +485,7 @@ class vpl_diff {
$queue[$z2->d + abs(($n1 - $z2->i1) - ($n2 - $z2->i2))][] = $z2;
}
// Line change.
// Element change.
if ($i1 < $n1 && $i2 < $n2 && !isset($prev[$i1 + 1][$i2 + 1])) {
$z3 = new stdClass();
$z3->i1 = $i1 + 1;
......
......@@ -504,6 +504,32 @@
word-wrap: normal;
}
/* ############################## */
/* ######### Similarity ######### */
/* ############################## */
.path-mod-vpl #sim_details .ace_cursor {
display: none;
}
.path-mod-vpl #sim_details .sim-gutter .ace_scroller {
background-color: #ddd;
}
.path-mod-vpl #sim_details .sim-col,
.path-mod-vpl #sim_details .sim-file {
display: inline-block;
vertical-align: top;
}
.path-mod-vpl #sim_details .sim-col {
width: 3em;
}