| 1 | <?php␊ |
| 2 | /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */␊ |
| 3 | /*␊ |
| 4 | # ***** BEGIN LICENSE BLOCK *****␊ |
| 5 | # This file is part of InDefero, an open source project management application.␊ |
| 6 | # Copyright (C) 2008-2011 CĂ©ondo Ltd and contributors.␊ |
| 7 | #␊ |
| 8 | # InDefero is free software; you can redistribute it and/or modify␊ |
| 9 | # it under the terms of the GNU General Public License as published by␊ |
| 10 | # the Free Software Foundation; either version 2 of the License, or␊ |
| 11 | # (at your option) any later version.␊ |
| 12 | #␊ |
| 13 | # InDefero is distributed in the hope that it will be useful,␊ |
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of␊ |
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the␊ |
| 16 | # GNU General Public License for more details.␊ |
| 17 | #␊ |
| 18 | # You should have received a copy of the GNU General Public License␊ |
| 19 | # along with this program; if not, write to the Free Software␊ |
| 20 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA␊ |
| 21 | #␊ |
| 22 | # ***** END LICENSE BLOCK ***** */␊ |
| 23 | ␊ |
| 24 | /**␊ |
| 25 | * Diff parser.␊ |
| 26 | *␊ |
| 27 | */␊ |
| 28 | class IDF_Diff␊ |
| 29 | {␊ |
| 30 | public $path_strip_level = 0;␊ |
| 31 | protected $lines = array();␊ |
| 32 | ␊ |
| 33 | public $files = array();␊ |
| 34 | ␊ |
| 35 | public function __construct($diff, $path_strip_level = 0)␊ |
| 36 | {␊ |
| 37 | $this->path_strip_level = $path_strip_level;␊ |
| 38 | $this->lines = IDF_FileUtil::splitIntoLines($diff, true);␊ |
| 39 | }␊ |
| 40 | ␊ |
| 41 | public function parse()␊ |
| 42 | {␊ |
| 43 | $current_file = '';␊ |
| 44 | $current_chunk = 0;␊ |
| 45 | $lline = 0;␊ |
| 46 | $rline = 0;␊ |
| 47 | $files = array();␊ |
| 48 | $indiff = false; // Used to skip the headers in the git patches␊ |
| 49 | $i = 0; // Used to skip the end of a git patch with --\nversion number␊ |
| 50 | $diffsize = count($this->lines);␊ |
| 51 | while ($i < $diffsize) {␊ |
| 52 | // look for the potential beginning of a diff␊ |
| 53 | if (substr($this->lines[$i], 0, 4) !== '--- ') {␊ |
| 54 | $i++;␊ |
| 55 | continue;␊ |
| 56 | }␊ |
| 57 | ␊ |
| 58 | // we're inside a diff candiate␊ |
| 59 | $oldfileline = $this->lines[$i++];␊ |
| 60 | $newfileline = $this->lines[$i++];␊ |
| 61 | if (substr($newfileline, 0, 4) !== '+++ ') {␊ |
| 62 | // not a valid diff here, move on␊ |
| 63 | continue;␊ |
| 64 | }␊ |
| 65 | ␊ |
| 66 | // use new file name by default␊ |
| 67 | preg_match("/^\+\+\+ ([^\t\n\r]+)/", $newfileline, $m);␊ |
| 68 | $current_file = $m[1];␊ |
| 69 | if ($current_file === '/dev/null') {␊ |
| 70 | // except if it's /dev/null, use the old one instead␊ |
| 71 | // eg. mtn 0.48 and newer␊ |
| 72 | preg_match("/^--- ([^\t\r\n]+)/", $oldfileline, $m);␊ |
| 73 | $current_file = $m[1];␊ |
| 74 | }␊ |
| 75 | if ($this->path_strip_level > 0) {␊ |
| 76 | $fileparts = explode('/', $current_file, $this->path_strip_level+1);␊ |
| 77 | $current_file = array_pop($fileparts);␊ |
| 78 | }␊ |
| 79 | $current_chunk = 0;␊ |
| 80 | $files[$current_file] = array();␊ |
| 81 | $files[$current_file]['chunks'] = array();␊ |
| 82 | $files[$current_file]['chunks_def'] = array();␊ |
| 83 | ␊ |
| 84 | while ($i < $diffsize && substr($this->lines[$i], 0, 3) === '@@ ') {␊ |
| 85 | $elems = preg_match('/@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@.*/',␊ |
| 86 | $this->lines[$i++], $results);␊ |
| 87 | if ($elems != 1) {␊ |
| 88 | // hunk is badly formatted␊ |
| 89 | break;␊ |
| 90 | }␊ |
| 91 | $delstart = $results[1];␊ |
| 92 | $dellines = $results[2] === '' ? 1 : $results[2];␊ |
| 93 | $addstart = $results[3];␊ |
| 94 | $addlines = $results[4] === '' ? 1 : $results[4];␊ |
| 95 | ␊ |
| 96 | $files[$current_file]['chunks_def'][] = array(␊ |
| 97 | array($delstart, $dellines), array($addstart, $addlines)␊ |
| 98 | );␊ |
| 99 | $files[$current_file]['chunks'][] = array();␊ |
| 100 | ␊ |
| 101 | while ($i < $diffsize && ($addlines >= 0 || $dellines >= 0)) {␊ |
| 102 | $linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : false;␊ |
| 103 | $content = substr($this->lines[$i], 1);␊ |
| 104 | switch ($linetype) {␊ |
| 105 | case ' ':␊ |
| 106 | $files[$current_file]['chunks'][$current_chunk][] =␊ |
| 107 | array($delstart, $addstart, $content);␊ |
| 108 | $dellines--;␊ |
| 109 | $addlines--;␊ |
| 110 | $delstart++;␊ |
| 111 | $addstart++;␊ |
| 112 | break;␊ |
| 113 | case '+':␊ |
| 114 | $files[$current_file]['chunks'][$current_chunk][] =␊ |
| 115 | array('', $addstart, $content);␊ |
| 116 | $addlines--;␊ |
| 117 | $addstart++;␊ |
| 118 | break;␊ |
| 119 | case '-':␊ |
| 120 | $files[$current_file]['chunks'][$current_chunk][] =␊ |
| 121 | array($delstart, '', $content);␊ |
| 122 | $dellines--;␊ |
| 123 | $delstart++;␊ |
| 124 | break;␊ |
| 125 | case '\\':␊ |
| 126 | // no new line at the end of this file; remove pseudo new line from last line␊ |
| 127 | $cur = count($files[$current_file]['chunks'][$current_chunk]) - 1;␊ |
| 128 | $files[$current_file]['chunks'][$current_chunk][$cur][2] =␊ |
| 129 | rtrim($files[$current_file]['chunks'][$current_chunk][$cur][2], "\r\n");␊ |
| 130 | continue;␊ |
| 131 | default:␊ |
| 132 | break 2;␊ |
| 133 | }␊ |
| 134 | $i++;␊ |
| 135 | }␊ |
| 136 | $current_chunk++;␊ |
| 137 | }␊ |
| 138 | }␊ |
| 139 | $this->files = $files;␊ |
| 140 | return $files;␊ |
| 141 | }␊ |
| 142 | ␊ |
| 143 | /**␊ |
| 144 | * Return the html version of a parsed diff.␊ |
| 145 | */␊ |
| 146 | public function as_html()␊ |
| 147 | {␊ |
| 148 | $out = '';␊ |
| 149 | foreach ($this->files as $filename => $file) {␊ |
| 150 | $pretty = '';␊ |
| 151 | $fileinfo = IDF_FileUtil::getMimeType($filename);␊ |
| 152 | if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {␊ |
| 153 | $pretty = ' prettyprint';␊ |
| 154 | }␊ |
| 155 | ␊ |
| 156 | $cc = 1;␊ |
| 157 | $offsets = array();␊ |
| 158 | $contents = array();␊ |
| 159 | ␊ |
| 160 | foreach ($file['chunks'] as $chunk) {␊ |
| 161 | foreach ($chunk as $line) {␊ |
| 162 | list($left, $right, $content) = $line;␊ |
| 163 | if ($left and $right) {␊ |
| 164 | $class = 'context';␊ |
| 165 | } elseif ($left) {␊ |
| 166 | $class = 'removed';␊ |
| 167 | } else {␊ |
| 168 | $class = 'added';␊ |
| 169 | }␊ |
| 170 | ␊ |
| 171 | $offsets[] = sprintf('<td>%s</td><td>%s</td>', $left, $right);␊ |
| 172 | $content = IDF_FileUtil::emphasizeControlCharacters(Pluf_esc($content));␊ |
| 173 | $contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $content);␊ |
| 174 | }␊ |
| 175 | if (count($file['chunks']) > $cc) {␊ |
| 176 | $offsets[] = '<td class="next">...</td><td class="next">...</td>';␊ |
| 177 | $contents[] = '<td class="next"></td>';␊ |
| 178 | }␊ |
| 179 | $cc++;␊ |
| 180 | }␊ |
| 181 | ␊ |
| 182 | list($added, $removed) = end($file['chunks_def']);␊ |
| 183 | ␊ |
| 184 | $added = $added[0] + $added[1];␊ |
| 185 | $leftwidth = 0;␊ |
| 186 | if ($added > 0)␊ |
| 187 | $leftwidth = ((ceil(log10($added)) + 1) * 8) + 17;␊ |
| 188 | ␊ |
| 189 | $removed = $removed[0] + $removed[1];␊ |
| 190 | $rightwidth = 0;␊ |
| 191 | if ($removed > 0)␊ |
| 192 | $rightwidth = ((ceil(log10($removed)) + 1) * 8) + 17;␊ |
| 193 | ␊ |
| 194 | // we need to correct the width of a single column a little␊ |
| 195 | // to take less space and to hide the empty one␊ |
| 196 | $class = '';␊ |
| 197 | if ($leftwidth == 0) {␊ |
| 198 | $class = 'left-hidden';␊ |
| 199 | $rightwidth -= floor(log10($removed));␊ |
| 200 | }␊ |
| 201 | else if ($rightwidth == 0) {␊ |
| 202 | $class = 'right-hidden';␊ |
| 203 | $leftwidth -= floor(log10($added));␊ |
| 204 | }␊ |
| 205 | ␊ |
| 206 | $inner_linecounts =␊ |
| 207 | '<table class="diff-linecounts '.$class.'">' ."\n".␊ |
| 208 | '<colgroup><col width="'.$leftwidth.'" /><col width="'. $rightwidth.'" /></colgroup>' ."\n".␊ |
| 209 | '<tr class="line">' .␊ |
| 210 | implode('</tr>'."\n".'<tr class="line">', $offsets).␊ |
| 211 | '</tr>' ."\n".␊ |
| 212 | '</table>' ."\n";␊ |
| 213 | ␊ |
| 214 | ␊ |
| 215 | $inner_contents =␊ |
| 216 | '<table class="diff-contents">' ."\n".␊ |
| 217 | '<tr class="line">' .␊ |
| 218 | implode('</tr>'."\n".'<tr class="line">', $contents) .␊ |
| 219 | '</tr>' ."\n".␊ |
| 220 | '</table>' ."\n";␊ |
| 221 | ␊ |
| 222 | $out .= '<table class="diff unified">' ."\n".␊ |
| 223 | '<colgroup><col width="'.($leftwidth + $rightwidth + 1).'" /><col width="*" /></colgroup>' ."\n".␊ |
| 224 | '<tr id="diff-'.md5($filename).'">'.␊ |
| 225 | '<th colspan="2">'.Pluf_esc($filename).'</th>'.␊ |
| 226 | '</tr>' ."\n".␊ |
| 227 | '<tr>' .␊ |
| 228 | '<td>'. $inner_linecounts .'</td>'. "\n".␊ |
| 229 | '<td><div class="scroll">'. $inner_contents .'</div></td>'.␊ |
| 230 | '</tr>' ."\n".␊ |
| 231 | '</table>' ."\n";␊ |
| 232 | }␊ |
| 233 | ␊ |
| 234 | return Pluf_Template::markSafe($out);␊ |
| 235 | }␊ |
| 236 | ␊ |
| 237 | /**␊ |
| 238 | * Review patch.␊ |
| 239 | *␊ |
| 240 | * Given the original file as a string and the parsed␊ |
| 241 | * corresponding diff chunks, generate a side by side view of the␊ |
| 242 | * original file and new file with added/removed lines.␊ |
| 243 | *␊ |
| 244 | * Example of use:␊ |
| 245 | *␊ |
| 246 | * $diff = new IDF_Diff(file_get_contents($diff_file));␊ |
| 247 | * $orig = file_get_contents($orig_file);␊ |
| 248 | * $diff->parse();␊ |
| 249 | * echo $diff->fileCompare($orig, $diff->files[$orig_file], $diff_file);␊ |
| 250 | *␊ |
| 251 | * @param string Original file␊ |
| 252 | * @param array Chunk description of the diff corresponding to the file␊ |
| 253 | * @param string Original file name␊ |
| 254 | * @param int Number of lines before/after the chunk to be displayed (10)␊ |
| 255 | * @return Pluf_Template_SafeString The table body␊ |
| 256 | */␊ |
| 257 | public function fileCompare($orig, $chunks, $filename, $context=10)␊ |
| 258 | {␊ |
| 259 | $orig_lines = IDF_FileUtil::splitIntoLines($orig);␊ |
| 260 | $new_chunks = $this->mergeChunks($orig_lines, $chunks, $context);␊ |
| 261 | return $this->renderCompared($new_chunks, $filename);␊ |
| 262 | }␊ |
| 263 | ␊ |
| 264 | private function mergeChunks($orig_lines, $chunks, $context=10)␊ |
| 265 | {␊ |
| 266 | $spans = array();␊ |
| 267 | $new_chunks = array();␊ |
| 268 | $min_line = 0;␊ |
| 269 | $max_line = 0;␊ |
| 270 | //if (count($chunks['chunks_def']) == 0) return '';␊ |
| 271 | foreach ($chunks['chunks_def'] as $chunk) {␊ |
| 272 | $start = ($chunk[0][0] > $context) ? $chunk[0][0]-$context : 0;␊ |
| 273 | $end = (($chunk[0][0]+$chunk[0][1]+$context-1) < count($orig_lines)) ? $chunk[0][0]+$chunk[0][1]+$context-1 : count($orig_lines);␊ |
| 274 | $spans[] = array($start, $end);␊ |
| 275 | }␊ |
| 276 | // merge chunks/get the chunk lines␊ |
| 277 | // these are reference lines␊ |
| 278 | $chunk_lines = array();␊ |
| 279 | foreach ($chunks['chunks'] as $chunk) {␊ |
| 280 | foreach ($chunk as $line) {␊ |
| 281 | $chunk_lines[] = $line;␊ |
| 282 | }␊ |
| 283 | }␊ |
| 284 | $i = 0;␊ |
| 285 | foreach ($chunks['chunks'] as $chunk) {␊ |
| 286 | $n_chunk = array();␊ |
| 287 | // add lines before␊ |
| 288 | if ($chunk[0][0] > $spans[$i][0]) {␊ |
| 289 | for ($lc=$spans[$i][0];$lc<$chunk[0][0];$lc++) {␊ |
| 290 | $exists = false;␊ |
| 291 | foreach ($chunk_lines as $line) {␊ |
| 292 | if ($lc == $line[0]␊ |
| 293 | or ($chunk[0][1]-$chunk[0][0]+$lc) == $line[1]) {␊ |
| 294 | $exists = true;␊ |
| 295 | break;␊ |
| 296 | }␊ |
| 297 | }␊ |
| 298 | if (!$exists) {␊ |
| 299 | $orig = isset($orig_lines[$lc-1]) ? $orig_lines[$lc-1] : '';␊ |
| 300 | $n_chunk[] = array(␊ |
| 301 | $lc,␊ |
| 302 | $chunk[0][1]-$chunk[0][0]+$lc,␊ |
| 303 | $orig␊ |
| 304 | );␊ |
| 305 | }␊ |
| 306 | }␊ |
| 307 | }␊ |
| 308 | // add chunk lines␊ |
| 309 | foreach ($chunk as $line) {␊ |
| 310 | $n_chunk[] = $line;␊ |
| 311 | }␊ |
| 312 | // add lines after␊ |
| 313 | $lline = $line;␊ |
| 314 | if (!empty($lline[0]) and $lline[0] < $spans[$i][1]) {␊ |
| 315 | for ($lc=$lline[0];$lc<=$spans[$i][1];$lc++) {␊ |
| 316 | $exists = false;␊ |
| 317 | foreach ($chunk_lines as $line) {␊ |
| 318 | if ($lc == $line[0] or ($lline[1]-$lline[0]+$lc) == $line[1]) {␊ |
| 319 | $exists = true;␊ |
| 320 | break;␊ |
| 321 | }␊ |
| 322 | }␊ |
| 323 | if (!$exists) {␊ |
| 324 | $n_chunk[] = array(␊ |
| 325 | $lc,␊ |
| 326 | $lline[1]-$lline[0]+$lc,␊ |
| 327 | $orig_lines[$lc-1]␊ |
| 328 | );␊ |
| 329 | }␊ |
| 330 | }␊ |
| 331 | }␊ |
| 332 | $new_chunks[] = $n_chunk;␊ |
| 333 | $i++;␊ |
| 334 | }␊ |
| 335 | // Now, each chunk has the right length, we need to merge them␊ |
| 336 | // when needed␊ |
| 337 | $nnew_chunks = array();␊ |
| 338 | $i = 0;␊ |
| 339 | foreach ($new_chunks as $chunk) {␊ |
| 340 | if ($i>0) {␊ |
| 341 | $lline = end($nnew_chunks[$i-1]);␊ |
| 342 | if ($chunk[0][0] <= $lline[0]+1) {␊ |
| 343 | // need merging␊ |
| 344 | foreach ($chunk as $line) {␊ |
| 345 | if ($line[0] > $lline[0] or empty($line[0])) {␊ |
| 346 | $nnew_chunks[$i-1][] = $line;␊ |
| 347 | }␊ |
| 348 | }␊ |
| 349 | } else {␊ |
| 350 | $nnew_chunks[] = $chunk;␊ |
| 351 | $i++;␊ |
| 352 | }␊ |
| 353 | } else {␊ |
| 354 | $nnew_chunks[] = $chunk;␊ |
| 355 | $i++;␊ |
| 356 | }␊ |
| 357 | }␊ |
| 358 | return $nnew_chunks;␊ |
| 359 | }␊ |
| 360 | ␊ |
| 361 | private function renderCompared($chunks, $filename)␊ |
| 362 | {␊ |
| 363 | $fileinfo = IDF_FileUtil::getMimeType($filename);␊ |
| 364 | $pretty = '';␊ |
| 365 | if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {␊ |
| 366 | $pretty = ' prettyprint';␊ |
| 367 | }␊ |
| 368 | ␊ |
| 369 | $cc = 1;␊ |
| 370 | $left_offsets = array();␊ |
| 371 | $left_contents = array();␊ |
| 372 | $right_offsets = array();␊ |
| 373 | $right_contents = array();␊ |
| 374 | ␊ |
| 375 | $max_lineno_left = $max_lineno_right = 0;␊ |
| 376 | ␊ |
| 377 | foreach ($chunks as $chunk) {␊ |
| 378 | foreach ($chunk as $line) {␊ |
| 379 | $left = '';␊ |
| 380 | $right = '';␊ |
| 381 | $content = IDF_FileUtil::emphasizeControlCharacters(Pluf_esc($line[2]));␊ |
| 382 | ␊ |
| 383 | if ($line[0] and $line[1]) {␊ |
| 384 | $class = 'context';␊ |
| 385 | $left = $right = $content;␊ |
| 386 | } elseif ($line[0]) {␊ |
| 387 | $class = 'removed';␊ |
| 388 | $left = $content;␊ |
| 389 | } else {␊ |
| 390 | $class = 'added';␊ |
| 391 | $right = $content;␊ |
| 392 | }␊ |
| 393 | ␊ |
| 394 | $left_offsets[] = sprintf('<td>%s</td>', $line[0]);␊ |
| 395 | $right_offsets[] = sprintf('<td>%s</td>', $line[1]);␊ |
| 396 | $left_contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $left);␊ |
| 397 | $right_contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $right);␊ |
| 398 | ␊ |
| 399 | $max_lineno_left = max($max_lineno_left, $line[0]);␊ |
| 400 | $max_lineno_right = max($max_lineno_right, $line[1]);␊ |
| 401 | }␊ |
| 402 | ␊ |
| 403 | if (count($chunks) > $cc) {␊ |
| 404 | $left_offsets[] = '<td class="next">...</td>';␊ |
| 405 | $right_offsets[] = '<td class="next">...</td>';␊ |
| 406 | $left_contents[] = '<td></td>';␊ |
| 407 | $right_contents[] = '<td></td>';␊ |
| 408 | }␊ |
| 409 | $cc++;␊ |
| 410 | }␊ |
| 411 | ␊ |
| 412 | $leftwidth = 1;␊ |
| 413 | if ($max_lineno_left > 0)␊ |
| 414 | $leftwidth = ((ceil(log10($max_lineno_left)) + 1) * 8) + 17;␊ |
| 415 | ␊ |
| 416 | $rightwidth = 1;␊ |
| 417 | if ($max_lineno_right > 0)␊ |
| 418 | $rightwidth = ((ceil(log10($max_lineno_right)) + 1) * 8) + 17;␊ |
| 419 | ␊ |
| 420 | $inner_linecounts_left =␊ |
| 421 | '<table class="diff-linecounts">' ."\n".␊ |
| 422 | '<colgroup><col width="'.$leftwidth.'" /></colgroup>' ."\n".␊ |
| 423 | '<tr class="line">' .␊ |
| 424 | implode('</tr>'."\n".'<tr class="line">', $left_offsets).␊ |
| 425 | '</tr>' ."\n".␊ |
| 426 | '</table>' ."\n";␊ |
| 427 | ␊ |
| 428 | $inner_linecounts_right =␊ |
| 429 | '<table class="diff-linecounts">' ."\n".␊ |
| 430 | '<colgroup><col width="'.$rightwidth.'" /></colgroup>' ."\n".␊ |
| 431 | '<tr class="line">' .␊ |
| 432 | implode('</tr>'."\n".'<tr class="line">', $right_offsets).␊ |
| 433 | '</tr>' ."\n".␊ |
| 434 | '</table>' ."\n";␊ |
| 435 | ␊ |
| 436 | $inner_contents_left =␊ |
| 437 | '<table class="diff-contents">' ."\n".␊ |
| 438 | '<tr class="line">' .␊ |
| 439 | implode('</tr>'."\n".'<tr class="line">', $left_contents) .␊ |
| 440 | '</tr>' ."\n".␊ |
| 441 | '</table>' ."\n";␊ |
| 442 | ␊ |
| 443 | $inner_contents_right =␊ |
| 444 | '<table class="diff-contents">' ."\n".␊ |
| 445 | '<tr class="line">' .␊ |
| 446 | implode('</tr>'."\n".'<tr class="line">', $right_contents) .␊ |
| 447 | '</tr>' ."\n".␊ |
| 448 | '</table>' ."\n";␊ |
| 449 | ␊ |
| 450 | $out =␊ |
| 451 | '<table class="diff context">' ."\n".␊ |
| 452 | '<colgroup>' .␊ |
| 453 | '<col width="'.($leftwidth + 1).'" /><col width="*" />' .␊ |
| 454 | '<col width="'.($rightwidth + 1).'" /><col width="*" />' .␊ |
| 455 | '</colgroup>' ."\n".␊ |
| 456 | '<tr id="diff-'.md5($filename).'">'.␊ |
| 457 | '<th colspan="4">'.Pluf_esc($filename).'</th>'.␊ |
| 458 | '</tr>' ."\n".␊ |
| 459 | '<tr>' .␊ |
| 460 | '<th colspan="2">'.__('Old').'</th><th colspan="2">'.__('New').'</th>' .␊ |
| 461 | '</tr>'.␊ |
| 462 | '<tr>' .␊ |
| 463 | '<td>'. $inner_linecounts_left .'</td>'. "\n".␊ |
| 464 | '<td><div class="scroll">'. $inner_contents_left .'</div></td>'. "\n".␊ |
| 465 | '<td>'. $inner_linecounts_right .'</td>'. "\n".␊ |
| 466 | '<td><div class="scroll">'. $inner_contents_right .'</div></td>'. "\n".␊ |
| 467 | '</tr>' ."\n".␊ |
| 468 | '</table>' ."\n";␊ |
| 469 | ␊ |
| 470 | return Pluf_Template::markSafe($out);␊ |
| 471 | }␊ |
| 472 | }␊ |
| 473 | |