Add line numbers to side-by-side diffs
authorChuck Hagenbuch <chuck@horde.org>
Mon, 29 Nov 2010 02:39:57 +0000 (21:39 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Sun, 5 Dec 2010 03:21:45 +0000 (22:21 -0500)
chora/app/helpers/Diff.php
chora/app/views/diff/added.html.php [deleted file]
chora/app/views/diff/change.html.php [deleted file]
chora/app/views/diff/context.html.php [deleted file]
chora/app/views/diff/diff.html.php
chora/app/views/diff/removed.html.php [deleted file]
chora/themes/default/screen.css

index ae2d6ca..0e04ab6 100644 (file)
 class Chora_Diff_Helper extends Horde_View_Helper_Base
 {
     /**
-     * @var string
+     * @var array
      */
-    protected $_context = '';
+    protected $_context = array();
+
+    /**
+     * @var array
+     */
+    protected $_leftLines = array();
+    protected $_rightLines = array();
+    protected $_leftNumbers = array();
+    protected $_rightNumbers = array();
+
+    /**
+     * @var integer
+     */
+    protected $_leftLine = 0;
+    protected $_rightLine = 0;
 
     public function diff(Horde_Vcs_File $file, $r1, $r2, $id = null)
     {
@@ -23,40 +37,122 @@ class Chora_Diff_Helper extends Horde_View_Helper_Base
             return '<div class="diff"><p>' . sprintf(_("There was an error generating the diff: %s"), $e->getMessage()) . '</p></div>';
         }
 
-        return $this->render('app/views/diff/diff.html.php', array('diff' => $diff, 'file' => $file, 'r1' => $r1, 'r2' => $r2, 'id' => $id));
+        $this->_leftLines = array();
+        $this->_rightLines = array();
+        $this->_leftNumbers = array();
+        $this->_rightNumbers = array();
+
+        $firstSection = true;
+        foreach ($diff as $section) {
+            if (!$firstSection) {
+                $this->_leftLines[] = array('type' => 'separator', 'lines' => array(''));
+                $this->_rightLines[] = array('type' => 'separator', 'lines' => array(''));
+                $this->_leftNumbers[] = '…';
+                $this->_rightNumbers[] = '…';
+            }
+            $firstSection = false;
+
+            $this->_leftLine = (int)$section['oldline'];
+            $this->_rightLine = (int)$section['newline'];
+
+            foreach ($section['contents'] as $change) {
+                if ($this->hasContext() && $change['type'] != 'empty') {
+                    $this->diffContext();
+                }
+
+                $method = 'diff' . ucfirst($change['type']);
+                $this->$method($change);
+            }
+
+            if ($this->hasContext()) {
+                $this->diffContext();
+            }
+        }
+
+        return $this->render('app/views/diff/diff.html.php', array(
+            'leftLines' => $this->_leftLines,
+            'rightLines' => $this->_rightLines,
+            'leftNumbers' => $this->_leftNumbers,
+            'rightNumbers' => $this->_rightNumbers,
+            'file' => $file,
+            'r1' => $r1,
+            'r2' => $r2,
+            'id' => $id,
+        ));
     }
 
     public function diffAdd($change)
     {
+        $leftSection = array();
+        $rightSection = array();
+        foreach ($change['lines'] as $addedLine) {
+            $leftSection[] = '';
+            $rightSection[] = $addedLine;
+            $this->_leftNumbers[] = '';
+            $this->_rightNumbers[] = $this->_rightLine++;
+        }
+        $this->_leftLines[] = array('type' => 'added-empty', 'lines' => $leftSection);
+        $this->_rightLines[] = array('type' => 'added', 'lines' => $rightSection);
+        /*
         return $this->render('app/views/diff/added.html.php', array(
             'lines' => $change['lines'],
         ));
+        */
     }
 
     public function diffRemove($change)
     {
+        $leftSection = array();
+        $rightSection = array();
+        foreach ($change['lines'] as $removedLine) {
+            $leftSection[] = $removedLine;
+            $rightSection[] = '';
+            $this->_leftNumbers[] = $this->_leftLine++;
+            $this->_rightNumbers[] = '';
+        }
+        $this->_leftLines[] = array('type' => 'removed', 'lines' => $leftSection);
+        $this->_rightLines[] = array('type' => 'removed-empty', 'lines' => $rightSection);
+        /*
         return $this->render('app/views/diff/removed.html.php', array(
             'lines' => $change['lines'],
         ));
+        */
     }
 
     public function diffEmpty($change)
     {
-        $this->_context .= $this->escape($change['line']) . '<br>';
+        $this->_context[] = array('left' => $this->_leftLine++, 'right' => $this->_rightLine++, 'text' => $change['line']);
         return '';
     }
 
     public function diffChange($change)
     {
+        $leftSection = array();
+        $rightSection = array();
+
         // Pop the old/new stacks one by one, until both are empty.
         $oldsize = count($change['old']);
         $newsize = count($change['new']);
-        $left = $right = '';
         for ($row = 0, $rowMax = max($oldsize, $newsize); $row < $rowMax; ++$row) {
-            $left .= (isset($change['old'][$row]) ? $this->escape($change['old'][$row]) : '') . '<br>';
-            $right .= (isset($change['new'][$row]) ? $this->escape($change['new'][$row]) : '') . '<br>';
-        }
+            if (isset($change['old'][$row])) {
+                $leftSection[] = $change['old'][$row];
+                $this->_leftNumbers[] = $this->_leftLine++;
+            } else {
+                $leftSection[] = '';
+                $this->_leftNumbers[] = '';
+            }
 
+            if (isset($change['new'][$row])) {
+                $rightSection[] = $change['new'][$row];
+                $this->_rightNumbers[] = $this->_rightLine++;
+            } else {
+                $rightSection[] = '';
+                $this->_rightNumbers[] = '';
+            }
+        }
+        $this->_leftLines[] = array('type' => 'modified', 'lines' => $leftSection);
+        $this->_rightLines[] = array('type' => 'modified', 'lines' => $rightSection);
+        /*
         return $this->render('app/views/diff/change.html.php', array(
             'left' => $left,
             'right' => $right,
@@ -64,6 +160,7 @@ class Chora_Diff_Helper extends Horde_View_Helper_Base
             'newsize' => $newsize,
             'row' => $row,
         ));
+        */
     }
 
     public function hasContext()
@@ -74,11 +171,23 @@ class Chora_Diff_Helper extends Horde_View_Helper_Base
     public function diffContext()
     {
         $context = $this->_context;
-        $this->_context = '';
+        $this->_context = array();
 
+        $leftSection = array();
+        $rightSection = array();
+        foreach ($context as $contextLine) {
+            $leftSection[] = $contextLine['text'];
+            $rightSection[] = $contextLine['text'];
+            $this->_leftNumbers[] = $contextLine['left'];
+            $this->_rightNumbers[] = $contextLine['right'];
+        }
+        $this->_leftLines[] = array('type' => 'unmodified', 'lines' => $leftSection);
+        $this->_rightLines[] = array('type' => 'unmodified', 'lines' => $rightSection);
+        /*
         return $this->render('app/views/diff/context.html.php', array(
             'context' => $context,
         ));
+        */
     }
 
     public function diffCaption()
diff --git a/chora/app/views/diff/added.html.php b/chora/app/views/diff/added.html.php
deleted file mode 100644 (file)
index 6180f9c..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<div class="diff-container diff-added-empty">
- <div class="diff-left"></div>
- <div class="diff-right diff-added"><pre><?php foreach ($lines as $l) echo $this->escape($l) . '<br>' ?></pre></div>
-</div>
diff --git a/chora/app/views/diff/change.html.php b/chora/app/views/diff/change.html.php
deleted file mode 100644 (file)
index c73cab4..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<div class="diff-container">
-<?php if (!empty($left)): ?>
- <div class="diff-left diff-modified"><pre><?php echo $left ?></pre></div>
-<?php elseif ($row < $oldsize): ?>
- <div class="diff-left diff-modified">&nbsp;</div>
-<?php else: ?>
- <div class="diff-left diff-unmodified">&nbsp;</div>
-<?php endif; ?>
-
-<?php if (!empty($right)): ?>
- <div class="diff-right diff-modified"><pre><?php echo $right ?></pre></div>
-<?php elseif ($row < $newsize): ?>
- <div class="diff-right diff-modified">&nbsp;</div>
-<?php else: ?>
- <div class="diff-right diff-unmodified">&nbsp;</div>
-<?php endif; ?>
-</div>
diff --git a/chora/app/views/diff/context.html.php b/chora/app/views/diff/context.html.php
deleted file mode 100644 (file)
index cd92d04..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<div class="diff-container">
- <div class="diff-left diff-unmodified"><pre><?php echo $context ?></pre></div>
- <div class="diff-right diff-unmodified"><pre><?php echo $context ?></pre></div>
-</div>
index 1ca1ceb..5067524 100644 (file)
@@ -1,24 +1,47 @@
 <div class="diff"<?php if ($id) echo ' id="' . $id . '"' ?>>
-<div class="diff-header"><h4><a href="<?php echo Chora::url('co', $file->queryModulePath(), array('onb' => $r1)) ?>"><?php echo $file->queryModulePath() ?></a></h4></div>
-<?php foreach ($diff as $section): ?>
-<div class="diff-container diff-section">
- <div class="diff-left"><h3><?php printf(_("Line %s"), $section['oldline']) ?></h3></div>
- <div class="diff-right"><h3><?php printf(_("Line %s"), $section['newline']) ?></h3></div>
-</div>
-<?php
-foreach ($section['contents'] as $change) {
-    if ($this->hasContext() && $change['type'] != 'empty') {
-        echo $this->diffContext();
-    }
+ <div class="diff-header"><h4><a href="<?php echo Chora::url('co', $file->queryModulePath(), array('onb' => $r1)) ?>"><?php echo $file->queryModulePath() ?></a></h4></div>
+ <!--
+ <div class="diff-container">
+  <div class="diff-left"><?php echo $this->escape($r1) ?></div>
+  <div class="diff-right"><?php echo $this->escape($r2) ?></div>
+ </div>
+ -->
+
+ <div class="diff-container">
+  <div class="diff-left">
+   <div class="diff-linenumbers">
+    <ol>
+    <?php foreach ($leftNumbers as $ln): ?>
+     <li><?php echo $ln ? $ln : '&nbsp;' ?></li>
+    <?php endforeach ?>
+    </ol>
+   </div>
 
-    $method = 'diff' . ucfirst($change['type']);
-    echo $this->$method($change);
-}
+   <div class="diff-listing">
+    <?php foreach ($leftLines as $leftSection): ?>
+    <pre class="diff-<?php echo $leftSection['type'] ?>"><?php foreach ($leftSection['lines'] as $ll):
+      echo ($ll ? $this->escape($ll) : '&nbsp;') . "\n";
+      endforeach ?></pre>
+    <?php endforeach ?>
+   </div>
+  </div>
 
-if ($this->hasContext()) {
-    echo $this->diffContext();
-}
+  <div class="diff-right">
+   <div class="diff-linenumbers">
+    <ol>
+    <?php foreach ($rightNumbers as $rn): ?>
+     <li><?php echo $rn ? $rn : '&nbsp;' ?></li>
+    <?php endforeach ?>
+    </ol>
+   </div>
 
-endforeach;
-?>
+   <div class="diff-listing">
+    <?php foreach ($rightLines as $rightSection): ?>
+    <pre class="diff-<?php echo $rightSection['type'] ?>"><?php foreach ($rightSection['lines'] as $rl):
+      echo ($rl ? $this->escape($rl) : '&nbsp;') . "\n";
+      endforeach ?></pre>
+    <?php endforeach ?>
+   </div>
+  </div>
+ </div>
 </div>
diff --git a/chora/app/views/diff/removed.html.php b/chora/app/views/diff/removed.html.php
deleted file mode 100644 (file)
index f8090ef..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<div class="diff-container diff-removed-empty">
- <div class="diff-left diff-removed"><pre><?php foreach ($lines as $l) echo $this->escape($l) . '<br>' ?></pre></div>
- <div class="diff-right"></div>
-</div>
index 6f019ce..1b43837 100644 (file)
@@ -286,9 +286,6 @@ div.diff-header h4 {
     font-family: Menlo,Consolas,"Lucida Console","DejaVu Sans Mono",monospace;
     font-size: 110%;
 }
-div.diff-section {
-    margin-top: 1em;
-}
 div.diff-container:after {
     content: ".";
     display: block;
@@ -296,19 +293,14 @@ div.diff-container:after {
     clear: both;
     visibility: hidden;
 }
-div.diff-container h3 {
-    font-weight: bold;
-}
 
 div.diff-left {
     float: left;
     width: 50%;
-    overflow-y: auto;
 }
 div.diff-right {
     float: right;
     width: 50%;
-    overflow-y: auto;
 }
 
 .diff-unmodified {
@@ -334,6 +326,34 @@ div.diff-right {
     background: -webkit-gradient(linear, right top, left top, from(#fff), to(#fcc), color-stop(0.5, #fcc));
     background: -moz-linear-gradient(right, #fff, #fcc 50%);
 }
+.diff-separator {
+    background: #eee;
+}
+
+div.diff-linenumbers {
+    float: left;
+    font-family: Menlo,Consolas,"Lucida Console","DejaVu Sans Mono",monospace;
+    text-align: right;
+    padding: 0 .5em 0 .1em;
+     width: 4em;
+    color: #333;
+    background: #eee;
+    border-right: thin solid #ddd;
+}
+div.diff-linenumbers ol {
+    list-style: none;
+}
+div.diff-right div.diff-linenumbers {
+    border-left: thin solid #ddd;
+}
+
+div.diff-listing {
+    font-family: Menlo,Consolas,"Lucida Console","DejaVu Sans Mono",monospace;
+    overflow-y: auto;
+}
+div.diff-listing pre {
+    padding-left: .3em;
+}
 
 .diff-caption {
     text-align: left;