thunderbird-patch-review
Easy-to-use patch review interface for Thunderbird
Contribute: ~marcc/thunderbird-review-plugin@lists.sr.ht
git clone git://mccd.space/thunderbird-patch-review
| Log | Files | Refs | README | LICENSE |
intraline.js (2653B)
1 // Intra-line ("word level") diff highlighting: within a replacement block —
2 // a run of deleted lines followed by a run of added lines — pair the lines
3 // index-wise and mark the span that actually changed, computed as everything
4 // between the common prefix and common suffix of the pair.
5
6 /**
7 * Changed spans for one del/add line pair, as ranges over the line text
8 * (without the +/- prefix). Returns null when the lines share too little
9 * (under ~25% of the longer line) for a partial highlight to mean anything.
10 * A side whose range is empty (pure insertion/removal) should not be
11 * highlighted; computeIntraline already filters those out.
12 */
13 export function pairRanges(delText, addText) {
14 const max = Math.min(delText.length, addText.length);
15 let prefix = 0;
16 while (prefix < max && delText[prefix] === addText[prefix]) {
17 prefix++;
18 }
19 let suffix = 0;
20 while (
21 suffix < max - prefix &&
22 delText[delText.length - 1 - suffix] === addText[addText.length - 1 - suffix]
23 ) {
24 suffix++;
25 }
26 if ((prefix + suffix) * 4 < Math.max(delText.length, addText.length)) {
27 return null;
28 }
29 return {
30 del: [prefix, delText.length - suffix],
31 add: [prefix, addText.length - suffix],
32 };
33 }
34
35 const isNoNewlineMarker = (line) => line.origin === "ctx" && line.raw.startsWith("\\");
36
37 /**
38 * @param {Array<{origin: string, text: string, raw: string}>} lines hunk lines
39 * @returns {Array<{start: number, end: number}|null>} per-line changed range
40 * over line.text, null where nothing (or everything) changed
41 */
42 export function computeIntraline(lines) {
43 const result = new Array(lines.length).fill(null);
44
45 let i = 0;
46 while (i < lines.length) {
47 if (lines[i].origin !== "del") {
48 i++;
49 continue;
50 }
51 const dels = [];
52 while (i < lines.length && lines[i].origin === "del") {
53 dels.push(i);
54 i++;
55 }
56 // A "\ No newline at end of file" marker may sit between the runs.
57 let j = i;
58 while (j < lines.length && isNoNewlineMarker(lines[j])) {
59 j++;
60 }
61 const adds = [];
62 while (j < lines.length && lines[j].origin === "add") {
63 adds.push(j);
64 j++;
65 }
66
67 const pairs = Math.min(dels.length, adds.length);
68 for (let k = 0; k < pairs; k++) {
69 const ranges = pairRanges(lines[dels[k]].text, lines[adds[k]].text);
70 if (!ranges) {
71 continue;
72 }
73 if (ranges.del[1] > ranges.del[0]) {
74 result[dels[k]] = { start: ranges.del[0], end: ranges.del[1] };
75 }
76 if (ranges.add[1] > ranges.add[0]) {
77 result[adds[k]] = { start: ranges.add[0], end: ranges.add[1] };
78 }
79 }
80 }
81
82 return result;
83 }