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