Diff.php generator

Diff boxlabs 27 Views Size: 6.75 KB Posted on: Sep 8, 25 @ 7:26 PM
  1. 1@@ -1,6 +1,6 @@
  2. 2 <?php
  3. 3 /*
  4. 4- * Paste $v3.2 2025/09/07 https://github.com/boxlabss/PASTE
  5. 5+ * Paste $v3.2 2025/09/08 https://github.com/boxlabss/PASTE
  6. 6 * demo: https://paste.boxlabs.uk/
  7. 7 *
  8. 8 * https://phpaste.sourceforge.io/
  9. 9@@ -114,14 +114,15 @@
  10. 10 function _diff_idx_via_xdiff(array $A, array $B): array {
  11. 11 $N = count($A); $M = count($B);
  12. 12
  13. 13- // Join as text; ensure trailing newline to keep line counts exact
  14. 14+ // Join as text; add trailing newline to stabilize EOF handling
  15. 15 $left = ($N ? implode("\n", $A) : '') . "\n";
  16. 16 $right = ($M ? implode("\n", $B) : '') . "\n";
  17. 17
  18. 18- // 0-context unified diff, non-minimal for speed
  19. 19- $ud = xdiff_string_diff($left, $right, 0, false);
  20. 20+ // Use context for stable headers + ' ' lines; non-minimal for speed
  21. 21+ $ctx = 3;
  22. 22+ $ud = xdiff_string_diff($left, $right, $ctx, false);
  23. 23 if ($ud === false) {
  24. 24- // Fallback to trivial equality handling
  25. 25+ // Trivial fallback: align common prefix, then tail adds/dels
  26. 26 $ops = [];
  27. 27 $eq = min($N, $M);
  28. 28 for ($i=0; $i<$eq; $i++) $ops[] = ['op'=>'eq','ai'=>$i,'bi'=>$i];
  29. 29@@ -131,9 +132,9 @@
  30. 30 }
  31. 31 if ($ud === '') {
  32. 32 $ops = [];
  33. 33- for ($i=0; $i<$N && $i<$M; $i++) $ops[]=['op'=>'eq','ai'=>$i,'bi'=>$i];
  34. 34- for ($i=$M; $i<$N; $i++) $ops[]=['op'=>'del','ai'=>$i];
  35. 35- for ($j=$N; $j<$M; $j++) $ops[]=['op'=>'add','bi'=>$j];
  36. 36+ for ($i=0; $i<min($N,$M); $i++) $ops[] = ['op'=>'eq','ai'=>$i,'bi'=>$i];
  37. 37+ for ($i=$M; $i<$N; $i++) $ops[] = ['op'=>'del','ai'=>$i];
  38. 38+ for ($j=$N; $j<$M; $j++) $ops[] = ['op'=>'add','bi'=>$j];
  39. 39 return $ops;
  40. 40 }
  41. 41
  42. 42@@ -144,25 +145,34 @@
  43. 43 foreach ($lines as $ln) {
  44. 44 if ($ln === '' && $ln !== '0') continue;
  45. 45
  46. 46- if (preg_match('/^@@\s+-([0-9]+)(?:,([0-9]+))?\s+\+([0-9]+)(?:,([0-9]+))?\s+@@/', $ln, $m)) {
  47. 47- $startA = max(0, (int)$m[1] - 1); // 0-based
  48. 48+ // Hunk header: @@ -aStart,aLen +bStart,bLen @@
  49. 49+ if (($ln[0] ?? '') === '@' &&
  50. 50+ preg_match('/^@@\s+-([0-9]+)(?:,([0-9]+))?\s+\+([0-9]+)(?:,([0-9]+))?\s+@@/', $ln, $m)) {
  51. 51+
  52. 52+ $startA = max(0, (int)$m[1] - 1); // convert to 0-based
  53. 53 $startB = max(0, (int)$m[3] - 1);
  54. 54- // Emit equal block before this hunk (between current cursors and hunk start)
  55. 55- $eq = min(max(0, $startA - $ai), max(0, $startB - $bi));
  56. 56- for ($k=0; $k<$eq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  57. 57+
  58. 58+ // Between hunks: move forward by equal lines only
  59. 59+ $gap = min(max(0, $startA - $ai), max(0, $startB - $bi));
  60. 60+ for ($k=0; $k<$gap; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  61. 61 continue;
  62. 62 }
  63. 63
  64. 64 $tag = $ln[0] ?? '';
  65. 65- if ($tag === ' ') { $ops[] = ['op'=>'eq', 'ai'=>$ai, 'bi'=>$bi]; $ai++; $bi++; }
  66. 66- elseif ($tag === '-') { $ops[] = ['op'=>'del', 'ai'=>$ai]; $ai++; }
  67. 67- elseif ($tag === '+') { $ops[] = ['op'=>'add', 'bi'=>$bi]; $bi++; }
  68. 68- else {
  69. 69- // headers '---', '+++', empty, etc > ignore
  70. 70+ if ($tag === ' ') { // context (equal)
  71. 71+ $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++;
  72. 72+ } elseif ($tag === '-') { // deletion
  73. 73+ $ops[] = ['op'=>'del','ai'=>$ai]; $ai++;
  74. 74+ } elseif ($tag === '+') { // addition
  75. 75+ $ops[] = ['op'=>'add','bi'=>$bi]; $bi++;
  76. 76+ } elseif ($tag === '\\') {
  77. 77+ // "\ No newline at end of file" > ignore
  78. 78+ } else {
  79. 79+ // headers '---' / '+++' or noise > ignore
  80. 80 }
  81. 81 }
  82. 82
  83. 83- // Trailing equals after last hunk
  84. 84+ // Trailing equals after the last hunk
  85. 85 $tailEq = min($N - $ai, $M - $bi);
  86. 86 for ($k=0; $k<$tailEq; $k++) { $ops[] = ['op'=>'eq','ai'=>$ai,'bi'=>$bi]; $ai++; $bi++; }
  87. 87 for (; $ai<$N; $ai++) $ops[] = ['op'=>'del','ai'=>$ai];
  88. 88@@ -545,6 +555,66 @@
  89. 89
  90. 90 $ops = diff_lines_idx($leftLines, $rightLines, $normalizer);
  91. 91
  92. 92+/* ---------- change counts & exposure ----------
  93. 93+ * Treat an adjacent del-run followed by an add-run (or vice-versa) as "mods".
  94. 94+ * Each paired line counts as 1 change in the total.
  95. 95+ * Returns: [$adds, $dels, $mods, $total, $no_changes]
  96. 96+ */
  97. 97+function compute_change_counts(array $ops): array {
  98. 98+ $adds = 0; $dels = 0; $mods = 0;
  99. 99+ $n = count($ops);
  100. 100+ for ($i = 0; $i < $n; ) {
  101. 101+ $op = $ops[$i]['op'] ?? 'eq';
  102. 102+ if ($op !== 'add' && $op !== 'del') { $i++; continue; }
  103. 103+
  104. 104+ // First run (all adds or all dels)
  105. 105+ $t1 = $op;
  106. 106+ $c1 = 0;
  107. 107+ $j = $i;
  108. 108+ while ($j < $n && ($ops[$j]['op'] ?? 'eq') === $t1) { $c1++; $j++; }
  109. 109+
  110. 110+ // Optional immediately-adjacent opposite run
  111. 111+ $t2 = ($t1 === 'add') ? 'del' : 'add';
  112. 112+ $c2 = 0;
  113. 113+ $k = $j;
  114. 114+ while ($k < $n && ($ops[$k]['op'] ?? 'eq') === $t2) { $c2++; $k++; }
  115. 115+
  116. 116+ // Pair min(c1,c2) as modifications
  117. 117+ $pair = min($c1, $c2);
  118. 118+ $mods += $pair;
  119. 119+
  120. 120+ if ($t1 === 'add') {
  121. 121+ $adds += $c1 - $pair;
  122. 122+ $dels += $c2 - $pair;
  123. 123+ } else {
  124. 124+ $dels += $c1 - $pair;
  125. 125+ $adds += $c2 - $pair;
  126. 126+ }
  127. 127+
  128. 128+ // Advance past both runs
  129. 129+ $i = ($c2 > 0) ? $k : $j;
  130. 130+ }
  131. 131+
  132. 132+ $total = $adds + $dels + $mods; // modified lines count as 1
  133. 133+ $no_changes = ($total === 0);
  134. 134+ return [$adds, $dels, $mods, $total, $no_changes];
  135. 135+}
  136. 136+
  137. 137+/* ---------- change counts (mods collapse -/+ into 1) ---------- */
  138. 138+[$adds, $dels, $mods, $changed_total, $no_changes] = compute_change_counts($ops);
  139. 139+
  140. 140+header('X-Diff-No-Changes: ' . ($no_changes ? '1' : '0'));
  141. 141+header('X-Diff-Change-Add: ' . $adds);
  142. 142+header('X-Diff-Change-Del: ' . $dels);
  143. 143+header('X-Diff-Change-Mod: ' . $mods);
  144. 144+header('X-Diff-Change-Total: ' . $changed_total);
  145. 145+
  146. 146+$GLOBALS['diff_no_changes'] = $no_changes;
  147. 147+$GLOBALS['diff_changes_add'] = $adds;
  148. 148+$GLOBALS['diff_changes_del'] = $dels;
  149. 149+$GLOBALS['diff_changes_mod'] = $mods; // available if you want a separate badge
  150. 150+$GLOBALS['diff_changes_total'] = $changed_total; // theme uses this for ±T
  151. 151+
  152. 152 /* ---------- Build tables server-side ---------- */
  153. 153 [$sideRows, $uniRows] = build_tables_idx($ops, $leftLines, $rightLines);
  154. 154
  155. 155@@ -579,7 +649,7 @@
  156. 156 // expose split pct to the view if needed by JS
  157. 157 $GLOBALS['split_pct'] = $split_pct;
  158. 158
  159. 159-// Also expose new goodies for the toolbar (the theme may choose to use them)
  160. 160+// badges/toggle url for the theme
  161. 161 $GLOBALS['diff_engine_badge'] = $diff_engine_badge;
  162. 162 $GLOBALS['ignore_ws_on'] = $ignore_ws_on;
  163. 163 $GLOBALS['ignore_ws_toggle'] = $ignore_ws_toggle_url;

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA), and — with your consent — to measure usage and show ads. See Privacy.