Change the unidiff rendering by letting the actual content be rendered

into a separate container that can overflow and side-scroll for long lines.

This effectively removes the need for all kinds of line-breaking hacks
that have been applied before and only worked when the browser was
actually able to break a word group apart somewhere.

Lines are now always rendered as-is; as a nice side effect the line numbers
are always visible, independently how far one scrolled into one direction,
so the context is always clear. If the rendering area is made smaller, the
table rendering also degrades gracefully and provides horizontal scrolling
for views that did not need them before.

The size that is occupied by the number display is now also automatically
determined by the size that is needed to render the biggest line number
in a column. Empty columns are rendered with a zero size.

Currently all this works nicely with a recent version of Chrome, Firefox
still needs some fine tuning for the vertical positioning. Other browsers
are untested as of now.
This commit is contained in:
Thomas Keller 2011-10-06 02:06:51 +02:00
parent 83761c66c5
commit f19f07ec59
2 changed files with 112 additions and 56 deletions

View File

@ -168,41 +168,73 @@ class IDF_Diff
public function as_html() public function as_html()
{ {
$out = ''; $out = '';
foreach ($this->files as $filename=>$file) { foreach ($this->files as $filename => $file) {
$pretty = ''; $pretty = '';
$fileinfo = IDF_FileUtil::getMimeType($filename); $fileinfo = IDF_FileUtil::getMimeType($filename);
if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) { if (IDF_FileUtil::isSupportedExtension($fileinfo[2])) {
$pretty = ' prettyprint'; $pretty = ' prettyprint';
} }
$out .= "\n".'<table class="diff" summary="">'."\n";
$out .= '<tr id="diff-'.md5($filename).'"><th colspan="3">'.Pluf_esc($filename).'</th></tr>'."\n";
$cc = 1; $cc = 1;
$offsets = array();
$contents = array();
$maxlinenum = 0;
foreach ($file['chunks'] as $chunk) { foreach ($file['chunks'] as $chunk) {
foreach ($chunk as $line) { foreach ($chunk as $line) {
if ($line[0] and $line[1]) { list($left, $right, $content) = $line;
if ($left and $right) {
$class = 'diff diff-c'; $class = 'diff diff-c';
} elseif ($line[0]) { } elseif ($left) {
$class = 'diff diff-r'; $class = 'diff diff-r';
} else { } else {
$class = 'diff diff-a'; $class = 'diff diff-a';
} }
$line_content = Pluf_esc($line[2]);
$line_content = preg_replace("/\t/", " ", $line_content); $offsets[] = sprintf('<td class="diff-lc">%s</td><td class="diff-lc">%s</td>', $left, $right);
$line_content = self::makeNonPrintableCharsVisible($line_content); $content = Pluf_esc($content);
$out .= sprintf('<tr class="diff-line"><td class="diff-lc">%s</td><td class="diff-lc">%s</td><td class="%s%s mono">%s</td></tr>'."\n", $line[0], $line[1], $class, $pretty, $line_content); $content = self::makeNonPrintableCharsVisible($content);
$contents[] = sprintf('<td class="%s%s mono">%s</td>', $class, $pretty, $content);
$maxlinenum = max($maxlinenum, max($left, $right));
}
if (count($file['chunks']) > $cc) {
$offsets[] = '<td class="next">...</td><td class="next">...</td>';
$contents[] = '<td class="next">&nbsp;</td>';
} }
if (count($file['chunks']) > $cc)
$out .= '<tr class="diff-next"><td>...</td><td>...</td><td>&nbsp;</td></tr>'."\n";
$cc++; $cc++;
} }
$out .= '</table>';
$inner = '<table class="diff-content">' ."\n".
'<tr class="diff-line">' .
implode('</tr>'."\n".'<tr class="diff-line">', $contents) .
'</tr>' ."\n".
'</table>' ."\n";
$rows = count($offsets);
$colwidth = (ceil(log10($maxlinenum)) + 1) * 10;
$first = array_shift($offsets);
$out .= '<table class="diff" summary="">' ."\n".
'<colgroup><col width="'.$colwidth.'" /><col width="'.$colwidth.'" /><col width="*" /></colgroup>' ."\n".
'<tr id="diff-'.md5($filename).'">'.
'<th colspan="3">'.Pluf_esc($filename).'</th>'.
'</tr>' ."\n".
'<tr class="line">' .
$first . sprintf('<td rowspan="%d"><div class="diff-content">%s</div></td>', $rows, $inner) .
'</tr>' ."\n".
'<tr class="line">' .
implode('</tr>'."\n".'<tr class="line">', $offsets) .
'</tr>' ."\n".
'</table>' ."\n";
} }
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
private static function makeNonPrintableCharsVisible($line) private static function makeNonPrintableCharsVisible($line)
{ {
return preg_replace('/([^[:print:]])/e', return preg_replace('/([^[:print:]\t])/e',
'"<span class=\"non-printable\" title=\"0x".strtoupper(bin2hex("\\1"))."\">".bin2hex("\\1")."</span>"', '"<span class=\"non-printable\" title=\"0x".strtoupper(bin2hex("\\1"))."\">".bin2hex("\\1")."</span>"',
$line); $line);
} }

View File

@ -574,6 +574,7 @@ table.commit table.changes table.properties td.removed {
table.diff { table.diff {
border-bottom: 1px solid #d3d7cf; border-bottom: 1px solid #d3d7cf;
width: 100%; width: 100%;
table-layout: fixed;
} }
table.diff th { table.diff th {
@ -583,45 +584,63 @@ table.diff th {
} }
table.diff tr { table.diff tr.line {
border-left: 1px solid #d3d7cf; border: 1px solid #d3d7cf;
border-right: 1px solid #d3d7cf; }
border-bottom: none;
border-top: none; table.diff tr.line td.diff-lc {
font-size: 90%;
padding: 1px 10px;
text-align: right;
width: 20px;
}
table.diff tr.line div.diff-content {
overflow: auto;
display: block;
} }
table.diff td { table.diff td {
font-size: 90%;
vertical-align: top; vertical-align: top;
padding: 1px;
border-color: inherit; border-color: inherit;
padding: 0;
} }
table.diff td.diff-lc { table.diff td.next {
background-color: #e4e8E0;
vertical-align: top;
text-align: right; text-align: right;
padding: 1px 5px; border-color: #d3d7cf;
border-color: inherit; padding: 1px 10px;
border-top: 1px solid #d3d7cf;
border-bottom: 1px solid #d3d7cf;
width: 3em;
} }
td.diff-a { table.diff-content {
border: 0;
/* setting this to 100% sometimes triggers the overflow of the parent container,
when it is not actually needed. we try to prevent that by taking not the
complete available space... */
width: 99.99%;
margin: 0;
}
table.diff-content td.diff {
line-height: 12px;
padding: 2px;
font-size: 90%;
border: none;
white-space: pre;
}
table.diff-content td.diff-a {
background-color: #dfd; background-color: #dfd;
} }
td.diff-r { table.diff-content td.diff-r {
background-color: #fdd; background-color: #fdd;
} }
td.diff { table.diff-content td.diff > span.non-printable {
border-bottom: none; visibility: hidden;
border-top: none;
white-space: pre;
}
td.diff > span.non-printable {
visibility: hidden;
color: white; color: white;
text-transform: uppercase; text-transform: uppercase;
float: none; float: none;
@ -638,39 +657,44 @@ td.diff > span.non-printable {
vertical-align: 10%; vertical-align: 10%;
} }
td.diff:hover > span.non-printable { table.diff-content td.diff:hover > span.non-printable {
visibility: visible; visibility: visible;
} }
td.diff-a > span.non-printable { table.diff-content td.diff-a > span.non-printable {
background: #0A0; background: #0A0;
} }
td.diff-r > span.non-printable { table.diff-content td.diff-r > span.non-printable {
background: #A00; background: #A00;
} }
td.diff-c > span.non-printable { table.diff-content td.diff-c > span.non-printable {
background: black; background: black;
} }
/* override prettify css rule */ /* override prettify css rule */
td.diff > span.non-printable > * { table.diff-content td.diff > span.non-printable > * {
color: white; color: white;
} }
table.diff tr.diff-next { /*
background-color: #e4e8E0; This is a special hack: the outer td.next has
vertical-align: top; top/bottom border and padding and comes to a total
text-align: right; height of 20px - BUT it shares the upper border
border-color: #d3d7cf; with the previous row, so the height is actually
only 19px. The inner table has no lines between rows,
so the upper border is counted and we have 20px
in the interior, which is one pixel too much for
every occurrence.
What we now do is to remove the 1px top padding and
therefor lower the total height of the inner one
again by one to match the outer.
*/
table.diff-content td.next {
padding-top: 0;
} }
table.diff tr.diff-next td {
padding: 1px 5px;
}
/** /**
* view file content * view file content
*/ */