reslop 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/README.md +1 -1
- package/bin/reslop.js +3 -1
- package/lib/capabilities.js +106 -0
- package/lib/cli.js +18 -14
- package/lib/deps/changes.js +331 -0
- package/lib/deps/items.js +131 -0
- package/lib/deps/manifest.js +145 -0
- package/lib/deps/merge.js +120 -0
- package/lib/deps/proposals.js +164 -0
- package/lib/deps/reports.js +180 -0
- package/lib/deps/rows.js +177 -0
- package/lib/deps/scan.js +138 -0
- package/lib/deps/usage.js +68 -0
- package/lib/deps/versions.js +119 -0
- package/lib/deps.js +65 -1348
- package/lib/diff/align.js +226 -0
- package/lib/diff/display.js +101 -0
- package/lib/diff/edit.js +47 -0
- package/lib/diff/inline.js +300 -0
- package/lib/diff/items.js +101 -0
- package/lib/diff/parse.js +156 -0
- package/lib/diff/patch.js +103 -0
- package/lib/diff.js +13 -932
- package/lib/editor.js +37 -0
- package/lib/files.js +88 -3
- package/lib/git/branches.js +291 -0
- package/lib/git/changes.js +164 -0
- package/lib/git/dependencies.js +489 -0
- package/lib/git/proc.js +162 -0
- package/lib/git/read.js +253 -0
- package/lib/git/run.js +100 -0
- package/lib/git.js +82 -1193
- package/lib/github.js +92 -222
- package/lib/gitlab.js +72 -216
- package/lib/highlight/bash.js +5 -9
- package/lib/highlight/core.js +1 -1
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/csv.js +1 -3
- package/lib/highlight/dart.js +6 -20
- package/lib/highlight/dot.js +13 -13
- package/lib/highlight/html.js +1 -5
- package/lib/highlight/js.js +151 -115
- package/lib/highlight/json.js +1 -3
- package/lib/highlight/log.js +1 -3
- package/lib/highlight/txt.js +5 -10
- package/lib/highlight.js +4 -4
- package/lib/keys.js +59 -167
- package/lib/npm/commands.js +100 -0
- package/lib/remote/changes.js +37 -0
- package/lib/remote/headers.js +36 -0
- package/lib/remote/http.js +122 -0
- package/lib/remote/notes.js +94 -0
- package/lib/render/branches.js +218 -0
- package/lib/render/chrome.js +575 -0
- package/lib/render/diff.js +307 -0
- package/lib/render/files.js +166 -0
- package/lib/render/frame.js +197 -0
- package/lib/render/notes.js +201 -0
- package/lib/render/present.js +25 -0
- package/lib/render/primitives.js +200 -0
- package/lib/render.js +13 -1687
- package/lib/review/format.js +434 -0
- package/lib/review/storage.js +150 -0
- package/lib/review/store.js +178 -0
- package/lib/review/templates.js +49 -0
- package/lib/review.js +14 -747
- package/lib/select.js +3 -5
- package/lib/session/accessors.js +25 -0
- package/lib/session/actions.js +275 -0
- package/lib/session/branches.js +304 -0
- package/lib/session/change-actions.js +320 -0
- package/lib/session/commit.js +124 -0
- package/lib/session/compose.js +540 -0
- package/lib/session/dispatch.js +105 -0
- package/lib/session/files-pane.js +234 -0
- package/lib/session/items.js +177 -0
- package/lib/session/lifecycle.js +476 -0
- package/lib/session/load.js +85 -0
- package/lib/session/navigation.js +93 -0
- package/lib/session/ops.js +110 -0
- package/lib/session/origins.js +7 -0
- package/lib/session/pointer.js +154 -0
- package/lib/session/progress.js +29 -0
- package/lib/session/review.js +88 -0
- package/lib/session/templates.js +64 -0
- package/lib/session/terminal.js +169 -0
- package/lib/session/todos.js +269 -0
- package/lib/session/update.js +125 -0
- package/lib/session/view.js +129 -0
- package/lib/session/watch.js +219 -0
- package/lib/session.js +574 -2354
- package/lib/source.js +11 -11
- package/lib/sys.js +25 -4
- package/lib/wrap.js +4 -2
- package/package.json +2 -2
package/lib/diff.js
CHANGED
|
@@ -1,937 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const LINE_MARK = {
|
|
15
|
-
add: '+',
|
|
16
|
-
del: '-',
|
|
17
|
-
ctx: ' ',
|
|
18
|
-
warn: ' ',
|
|
19
|
-
note: ' ',
|
|
20
|
-
noteSep: ' ',
|
|
21
|
-
};
|
|
22
|
-
const CTX_TYPES = ['ctx', 'warn', 'note', 'noteSep'];
|
|
23
|
-
const LINE_TYPE = {
|
|
24
|
-
'+': 'add',
|
|
25
|
-
'-': 'del',
|
|
26
|
-
' ': 'ctx',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const isCtxType = (type) => CTX_TYPES.includes(type);
|
|
30
|
-
const INDEX_SIDE = {
|
|
31
|
-
staged: 'new',
|
|
32
|
-
unstaged: 'old',
|
|
33
|
-
untracked: 'old',
|
|
34
|
-
commit: 'old',
|
|
35
|
-
pr: 'old',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const collapseSpans = (parts) => {
|
|
39
|
-
const spans = [];
|
|
40
|
-
for (const part of parts) {
|
|
41
|
-
const last = spans[spans.length - 1];
|
|
42
|
-
if (last && last.changed === part.changed) {
|
|
43
|
-
last.text += part.text;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
spans.push({ text: part.text, changed: part.changed });
|
|
47
|
-
}
|
|
48
|
-
return spans;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const isNoisy = (spans) => {
|
|
52
|
-
let tiny = 0;
|
|
53
|
-
for (const span of spans) {
|
|
54
|
-
if (span.changed && graphemes(span.text).length <= 2) tiny += 1;
|
|
55
|
-
}
|
|
56
|
-
return tiny > NOISY_TINY_SPANS;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const wholeChanged = (a, b) => ({
|
|
60
|
-
oldSpans: a.length ? [{ text: a.join(''), changed: true }] : [],
|
|
61
|
-
newSpans: b.length ? [{ text: b.join(''), changed: true }] : [],
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const lcsLength = (a, b) => {
|
|
65
|
-
const n = a.length;
|
|
66
|
-
const m = b.length;
|
|
67
|
-
if (n === 0 || m === 0) return 0;
|
|
68
|
-
if (n * m > LCS_CELL_LIMIT) {
|
|
69
|
-
let lo = 0;
|
|
70
|
-
while (lo < n && lo < m && a[lo] === b[lo]) lo += 1;
|
|
71
|
-
let hiA = n;
|
|
72
|
-
let hiB = m;
|
|
73
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
74
|
-
hiA -= 1;
|
|
75
|
-
hiB -= 1;
|
|
76
|
-
}
|
|
77
|
-
return lo + (n - hiA);
|
|
78
|
-
}
|
|
79
|
-
let prev = new Uint32Array(m + 1);
|
|
80
|
-
let cur = new Uint32Array(m + 1);
|
|
81
|
-
for (let i = 1; i <= n; i++) {
|
|
82
|
-
const ai = a[i - 1];
|
|
83
|
-
for (let j = 1; j <= m; j++) {
|
|
84
|
-
if (ai === b[j - 1]) cur[j] = prev[j - 1] + 1;
|
|
85
|
-
else cur[j] = prev[j] > cur[j - 1] ? prev[j] : cur[j - 1];
|
|
86
|
-
}
|
|
87
|
-
const swap = prev;
|
|
88
|
-
prev = cur;
|
|
89
|
-
cur = swap;
|
|
90
|
-
cur.fill(0);
|
|
91
|
-
}
|
|
92
|
-
return prev[m];
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const similarity = (left, right) => {
|
|
96
|
-
const a = graphemes(left);
|
|
97
|
-
const b = graphemes(right);
|
|
98
|
-
if (a.length === 0 && b.length === 0) return 1;
|
|
99
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
100
|
-
const lcs = lcsLength(a, b);
|
|
101
|
-
return (2 * lcs) / (a.length + b.length);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const lineTokens = (text) => {
|
|
105
|
-
TOKEN_RE.lastIndex = 0;
|
|
106
|
-
return text.match(TOKEN_RE) ?? [];
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const tokenSimilarity = (a, b) => {
|
|
110
|
-
if (a.length === 0 && b.length === 0) return 1;
|
|
111
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
112
|
-
const lcs = lcsLength(a, b);
|
|
113
|
-
return (2 * lcs) / (a.length + b.length);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const pairScore = (left, right) => {
|
|
117
|
-
const a = lineTokens(left);
|
|
118
|
-
const b = lineTokens(right);
|
|
119
|
-
const token = tokenSimilarity(a, b);
|
|
120
|
-
const headA = a[0];
|
|
121
|
-
const headB = b[0];
|
|
122
|
-
const head = headA && headA === headB ? 1 : 0;
|
|
123
|
-
return PAIR_HEAD_WEIGHT * head + PAIR_TOKEN_WEIGHT * token;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const comparePair = (left, right) => {
|
|
127
|
-
if (right.score !== left.score) return right.score - left.score;
|
|
128
|
-
if (left.dist !== right.dist) return left.dist - right.dist;
|
|
129
|
-
if (left.i !== right.i) return left.i - right.i;
|
|
130
|
-
return left.j - right.j;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const pairCandidates = (delTexts, addTexts) => {
|
|
134
|
-
const candidates = [];
|
|
135
|
-
for (let i = 0; i < delTexts.length; i++) {
|
|
136
|
-
for (let j = 0; j < addTexts.length; j++) {
|
|
137
|
-
const score = pairScore(delTexts[i], addTexts[j]);
|
|
138
|
-
if (score < PAIR_MIN_SIM) continue;
|
|
139
|
-
const dist = Math.abs(i - j);
|
|
140
|
-
candidates.push({ i, j, score, dist });
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
candidates.sort(comparePair);
|
|
144
|
-
return candidates;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const fillLcsTable = (a, b) => {
|
|
148
|
-
const n = a.length;
|
|
149
|
-
const m = b.length;
|
|
150
|
-
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
151
|
-
for (let i = 1; i <= n; i++) {
|
|
152
|
-
const ai = a[i - 1];
|
|
153
|
-
const row = dp[i];
|
|
154
|
-
const prev = dp[i - 1];
|
|
155
|
-
for (let j = 1; j <= m; j++) {
|
|
156
|
-
if (ai === b[j - 1]) row[j] = prev[j - 1] + 1;
|
|
157
|
-
else row[j] = prev[j] > row[j - 1] ? prev[j] : row[j - 1];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return dp;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const tracebackLcs = (a, b, dp) => {
|
|
164
|
-
const rev = [];
|
|
165
|
-
let i = a.length;
|
|
166
|
-
let j = b.length;
|
|
167
|
-
while (i > 0 && j > 0) {
|
|
168
|
-
if (a[i - 1] === b[j - 1]) {
|
|
169
|
-
rev.push({ type: 'eq', text: a[i - 1] });
|
|
170
|
-
i -= 1;
|
|
171
|
-
j -= 1;
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
175
|
-
rev.push({ type: 'del', text: a[i - 1] });
|
|
176
|
-
i -= 1;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
rev.push({ type: 'add', text: b[j - 1] });
|
|
180
|
-
j -= 1;
|
|
181
|
-
}
|
|
182
|
-
while (i > 0) {
|
|
183
|
-
rev.push({ type: 'del', text: a[i - 1] });
|
|
184
|
-
i -= 1;
|
|
185
|
-
}
|
|
186
|
-
while (j > 0) {
|
|
187
|
-
rev.push({ type: 'add', text: b[j - 1] });
|
|
188
|
-
j -= 1;
|
|
189
|
-
}
|
|
190
|
-
rev.reverse();
|
|
191
|
-
return rev;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const lcsOps = (a, b) => tracebackLcs(a, b, fillLcsTable(a, b));
|
|
195
|
-
|
|
196
|
-
const lcsSpans = (a, b) => {
|
|
197
|
-
const oldParts = [];
|
|
198
|
-
const newParts = [];
|
|
199
|
-
for (const op of lcsOps(a, b)) {
|
|
200
|
-
if (op.type === 'eq') {
|
|
201
|
-
oldParts.push({ text: op.text, changed: false });
|
|
202
|
-
newParts.push({ text: op.text, changed: false });
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (op.type === 'del') {
|
|
206
|
-
oldParts.push({ text: op.text, changed: true });
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
newParts.push({ text: op.text, changed: true });
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
oldSpans: collapseSpans(oldParts),
|
|
213
|
-
newSpans: collapseSpans(newParts),
|
|
214
|
-
};
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const trimSharedEnds = (a, b) => {
|
|
218
|
-
let lo = 0;
|
|
219
|
-
while (lo < a.length && lo < b.length && a[lo] === b[lo]) lo += 1;
|
|
220
|
-
let hiA = a.length;
|
|
221
|
-
let hiB = b.length;
|
|
222
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
223
|
-
hiA -= 1;
|
|
224
|
-
hiB -= 1;
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
prefix: a.slice(0, lo),
|
|
228
|
-
suffix: a.slice(hiA),
|
|
229
|
-
midA: a.slice(lo, hiA),
|
|
230
|
-
midB: b.slice(lo, hiB),
|
|
231
|
-
};
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const diffTokenMiddle = (midA, midB) => {
|
|
235
|
-
if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
236
|
-
return wholeChanged(midA, midB);
|
|
237
|
-
}
|
|
238
|
-
if (midA.length === 0 && midB.length === 0) {
|
|
239
|
-
return { oldSpans: [], newSpans: [] };
|
|
240
|
-
}
|
|
241
|
-
if (midA.length === 0 || midB.length === 0) return wholeChanged(midA, midB);
|
|
242
|
-
return lcsSpans(midA, midB);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const joinAffix = (prefix, mid, suffix) => {
|
|
246
|
-
const parts = [];
|
|
247
|
-
if (prefix.length) {
|
|
248
|
-
parts.push({ text: prefix.join(''), changed: false });
|
|
249
|
-
}
|
|
250
|
-
for (const span of mid) parts.push(span);
|
|
251
|
-
if (suffix.length) {
|
|
252
|
-
parts.push({ text: suffix.join(''), changed: false });
|
|
253
|
-
}
|
|
254
|
-
return collapseSpans(parts);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const joinTrimmedSpans = (prefix, mid, suffix) => ({
|
|
258
|
-
oldSpans: joinAffix(prefix, mid.oldSpans, suffix),
|
|
259
|
-
newSpans: joinAffix(prefix, mid.newSpans, suffix),
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
const diffTokens = (a, b) => {
|
|
263
|
-
if (a.length === 0 && b.length === 0) {
|
|
264
|
-
return { oldSpans: [], newSpans: [] };
|
|
265
|
-
}
|
|
266
|
-
if (a.length === 0 || b.length === 0) return wholeChanged(a, b);
|
|
267
|
-
const trimmed = trimSharedEnds(a, b);
|
|
268
|
-
const mid = diffTokenMiddle(trimmed.midA, trimmed.midB);
|
|
269
|
-
const joined = joinTrimmedSpans(trimmed.prefix, mid, trimmed.suffix);
|
|
270
|
-
if (isNoisy(joined.oldSpans) || isNoisy(joined.newSpans)) {
|
|
271
|
-
return wholeChanged(a, b);
|
|
272
|
-
}
|
|
273
|
-
return joined;
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const splitIdentWords = (part) => {
|
|
277
|
-
const words = [];
|
|
278
|
-
for (const word of part.split(/(_+)/)) {
|
|
279
|
-
if (word === '') continue;
|
|
280
|
-
words.push(word);
|
|
281
|
-
}
|
|
282
|
-
return words;
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const splitPieces = (text) => {
|
|
286
|
-
if (text === '') return [];
|
|
287
|
-
PIECE_RE.lastIndex = 0;
|
|
288
|
-
const coarse = text.match(PIECE_RE) ?? [];
|
|
289
|
-
const pieces = [];
|
|
290
|
-
for (const part of coarse) {
|
|
291
|
-
for (const word of splitIdentWords(part)) pieces.push(word);
|
|
292
|
-
}
|
|
293
|
-
return pieces;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const spansFromWordOps = (ops) => {
|
|
297
|
-
const oldParts = [];
|
|
298
|
-
const newParts = [];
|
|
299
|
-
let i = 0;
|
|
300
|
-
while (i < ops.length) {
|
|
301
|
-
const op = ops[i];
|
|
302
|
-
if (op.type === 'eq') {
|
|
303
|
-
oldParts.push({ text: op.text, changed: false });
|
|
304
|
-
newParts.push({ text: op.text, changed: false });
|
|
305
|
-
i += 1;
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
const dels = [];
|
|
309
|
-
const adds = [];
|
|
310
|
-
while (i < ops.length && ops[i].type !== 'eq') {
|
|
311
|
-
if (ops[i].type === 'del') dels.push(ops[i].text);
|
|
312
|
-
else adds.push(ops[i].text);
|
|
313
|
-
i += 1;
|
|
314
|
-
}
|
|
315
|
-
const one = dels.length === 1 && adds.length === 1;
|
|
316
|
-
const close = one && similarity(dels[0], adds[0]) >= WORD_REFINE_MIN;
|
|
317
|
-
if (close) {
|
|
318
|
-
const inner = diffTokens(graphemes(dels[0]), graphemes(adds[0]));
|
|
319
|
-
for (const span of inner.oldSpans) oldParts.push(span);
|
|
320
|
-
for (const span of inner.newSpans) newParts.push(span);
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
if (dels.length) {
|
|
324
|
-
oldParts.push({ text: dels.join(''), changed: true });
|
|
325
|
-
}
|
|
326
|
-
if (adds.length) {
|
|
327
|
-
newParts.push({ text: adds.join(''), changed: true });
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return {
|
|
331
|
-
oldSpans: collapseSpans(oldParts),
|
|
332
|
-
newSpans: collapseSpans(newParts),
|
|
333
|
-
};
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const diffChars = (oldText, newText) => {
|
|
337
|
-
const a = splitPieces(oldText);
|
|
338
|
-
const b = splitPieces(newText);
|
|
339
|
-
if (a.length === 0 && b.length === 0) {
|
|
340
|
-
return { oldSpans: [], newSpans: [] };
|
|
341
|
-
}
|
|
342
|
-
if (a.length === 0 || b.length === 0) return wholeChanged(a, b);
|
|
343
|
-
const { prefix, suffix, midA, midB } = trimSharedEnds(a, b);
|
|
344
|
-
let mid;
|
|
345
|
-
if (midA.length === 0 && midB.length === 0) {
|
|
346
|
-
mid = { oldSpans: [], newSpans: [] };
|
|
347
|
-
} else if (midA.length === 0 || midB.length === 0) {
|
|
348
|
-
mid = wholeChanged(midA, midB);
|
|
349
|
-
} else if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
350
|
-
mid = wholeChanged(midA, midB);
|
|
351
|
-
} else {
|
|
352
|
-
mid = spansFromWordOps(lcsOps(midA, midB));
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
oldSpans: joinAffix(prefix, mid.oldSpans, suffix),
|
|
356
|
-
newSpans: joinAffix(prefix, mid.newSpans, suffix),
|
|
357
|
-
};
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const pairIndices = (delTexts, addTexts) => {
|
|
361
|
-
const delToAdd = new Array(delTexts.length).fill(-1);
|
|
362
|
-
const addUsed = new Array(addTexts.length).fill(false);
|
|
363
|
-
const candidates = pairCandidates(delTexts, addTexts);
|
|
364
|
-
for (const pair of candidates) {
|
|
365
|
-
if (delToAdd[pair.i] >= 0 || addUsed[pair.j]) continue;
|
|
366
|
-
delToAdd[pair.i] = pair.j;
|
|
367
|
-
addUsed[pair.j] = true;
|
|
368
|
-
}
|
|
369
|
-
if (delTexts.length !== addTexts.length) return delToAdd;
|
|
370
|
-
for (let i = 0; i < delTexts.length; i++) {
|
|
371
|
-
if (delToAdd[i] >= 0 || addUsed[i]) continue;
|
|
372
|
-
delToAdd[i] = i;
|
|
373
|
-
addUsed[i] = true;
|
|
374
|
-
}
|
|
375
|
-
return delToAdd;
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const changeSides = (block) => {
|
|
379
|
-
const dels = [];
|
|
380
|
-
const adds = [];
|
|
381
|
-
for (const line of block) {
|
|
382
|
-
if (line.type === 'del') dels.push(line);
|
|
383
|
-
if (line.type === 'add') adds.push(line);
|
|
384
|
-
}
|
|
385
|
-
return { dels, adds };
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
const applyInlineSpans = (dels, adds, delToAdd) => {
|
|
389
|
-
const addSeen = new Set();
|
|
390
|
-
for (let i = 0; i < dels.length; i++) {
|
|
391
|
-
const j = delToAdd[i];
|
|
392
|
-
if (j < 0) {
|
|
393
|
-
dels[i].spans = [{ text: dels[i].text, changed: true }];
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
addSeen.add(j);
|
|
397
|
-
const diff = diffChars(dels[i].text, adds[j].text);
|
|
398
|
-
dels[i].spans = diff.oldSpans;
|
|
399
|
-
adds[j].spans = diff.newSpans;
|
|
400
|
-
}
|
|
401
|
-
for (let j = 0; j < adds.length; j++) {
|
|
402
|
-
if (addSeen.has(j)) continue;
|
|
403
|
-
adds[j].spans = [{ text: adds[j].text, changed: true }];
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
const attachInline = (lines) => {
|
|
408
|
-
const { dels, adds } = changeSides(lines);
|
|
409
|
-
const delToAdd = pairIndices(
|
|
410
|
-
dels.map((line) => line.text),
|
|
411
|
-
adds.map((line) => line.text),
|
|
412
|
-
);
|
|
413
|
-
applyInlineSpans(dels, adds, delToAdd);
|
|
414
|
-
for (const line of lines) {
|
|
415
|
-
if (isCtxType(line.type)) {
|
|
416
|
-
line.spans = [{ text: line.text, changed: false }];
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return lines;
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
const addPartners = (delToAdd, addCount) => {
|
|
423
|
-
const addToDel = new Array(addCount).fill(-1);
|
|
424
|
-
for (let i = 0; i < delToAdd.length; i++) {
|
|
425
|
-
const j = delToAdd[i];
|
|
426
|
-
if (j >= 0) addToDel[j] = i;
|
|
427
|
-
}
|
|
428
|
-
return addToDel;
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const walkAligned = (dels, adds, delToAdd, emit) => {
|
|
432
|
-
const addToDel = addPartners(delToAdd, adds.length);
|
|
433
|
-
const usedAdd = new Array(adds.length).fill(false);
|
|
434
|
-
let addPos = 0;
|
|
435
|
-
const takeUnpairedAdds = (limit) => {
|
|
436
|
-
while (addPos < limit) {
|
|
437
|
-
const free = addToDel[addPos] < 0 && !usedAdd[addPos];
|
|
438
|
-
if (free) {
|
|
439
|
-
emit.unpairedAdd(adds[addPos]);
|
|
440
|
-
usedAdd[addPos] = true;
|
|
441
|
-
}
|
|
442
|
-
addPos += 1;
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
for (let i = 0; i < dels.length; i++) {
|
|
446
|
-
const j = delToAdd[i];
|
|
447
|
-
if (j < 0) {
|
|
448
|
-
emit.unpairedDel(dels[i]);
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
takeUnpairedAdds(j);
|
|
452
|
-
emit.pair(dels[i], adds[j]);
|
|
453
|
-
usedAdd[j] = true;
|
|
454
|
-
if (addPos <= j) addPos = j + 1;
|
|
455
|
-
}
|
|
456
|
-
takeUnpairedAdds(adds.length);
|
|
457
|
-
for (let j = 0; j < adds.length; j++) {
|
|
458
|
-
if (!usedAdd[j]) emit.unpairedAdd(adds[j]);
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
const alignChangeBlock = (dels, adds, delToAdd) => {
|
|
463
|
-
const out = [];
|
|
464
|
-
walkAligned(dels, adds, delToAdd, {
|
|
465
|
-
unpairedDel: (del) => {
|
|
466
|
-
out.push(del);
|
|
467
|
-
},
|
|
468
|
-
unpairedAdd: (add) => {
|
|
469
|
-
out.push(add);
|
|
470
|
-
},
|
|
471
|
-
pair: (del, add) => {
|
|
472
|
-
out.push(del);
|
|
473
|
-
out.push(add);
|
|
474
|
-
},
|
|
475
|
-
});
|
|
476
|
-
return out;
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
const mapHunkBlocks = (lines, onCtx, onChange) => {
|
|
480
|
-
const out = [];
|
|
481
|
-
let i = 0;
|
|
482
|
-
while (i < lines.length) {
|
|
483
|
-
if (isCtxType(lines[i].type)) {
|
|
484
|
-
out.push(onCtx(lines[i]));
|
|
485
|
-
i += 1;
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
const start = i;
|
|
489
|
-
while (i < lines.length && !isCtxType(lines[i].type)) i += 1;
|
|
490
|
-
const block = lines.slice(start, i);
|
|
491
|
-
const { dels, adds } = changeSides(block);
|
|
492
|
-
const delToAdd = pairIndices(
|
|
493
|
-
dels.map((line) => line.text),
|
|
494
|
-
adds.map((line) => line.text),
|
|
495
|
-
);
|
|
496
|
-
for (const row of onChange(dels, adds, delToAdd)) out.push(row);
|
|
497
|
-
}
|
|
498
|
-
return out;
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const alignPairs = (lines) =>
|
|
502
|
-
mapHunkBlocks(lines, (line) => line, alignChangeBlock);
|
|
503
|
-
|
|
504
|
-
const alignSplitBlock = (dels, adds, delToAdd) => {
|
|
505
|
-
const out = [];
|
|
506
|
-
walkAligned(dels, adds, delToAdd, {
|
|
507
|
-
unpairedDel: (del) => {
|
|
508
|
-
out.push({ left: del, right: null });
|
|
509
|
-
},
|
|
510
|
-
unpairedAdd: (add) => {
|
|
511
|
-
out.push({ left: null, right: add });
|
|
512
|
-
},
|
|
513
|
-
pair: (del, add) => {
|
|
514
|
-
out.push({ left: del, right: add });
|
|
515
|
-
},
|
|
516
|
-
});
|
|
517
|
-
return out;
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
const alignSplit = (lines) =>
|
|
521
|
-
mapHunkBlocks(
|
|
522
|
-
lines,
|
|
523
|
-
(line) => ({ left: line, right: line }),
|
|
524
|
-
alignSplitBlock,
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
const parseGitPaths = (line) => {
|
|
528
|
-
const rest = line.slice('diff --git '.length);
|
|
529
|
-
const mid = rest.lastIndexOf(' b/');
|
|
530
|
-
if (mid < 0 || !rest.startsWith('a/')) return { oldPath: '', newPath: '' };
|
|
531
|
-
const oldPath = rest.slice(2, mid);
|
|
532
|
-
const newPath = rest.slice(mid + 3);
|
|
533
|
-
return { oldPath, newPath };
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
const stripAb = (raw) => {
|
|
537
|
-
if (raw === '/dev/null') return raw;
|
|
538
|
-
if (raw.startsWith('a/') || raw.startsWith('b/')) return raw.slice(2);
|
|
539
|
-
return raw;
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
const parsePathLine = (line) => {
|
|
543
|
-
const space = line.indexOf(' ');
|
|
544
|
-
if (space < 0) return '';
|
|
545
|
-
let rest = line.slice(space + 1);
|
|
546
|
-
if (rest.startsWith('"') && rest.endsWith('"')) {
|
|
547
|
-
rest = rest.slice(1, -1);
|
|
548
|
-
}
|
|
549
|
-
const tab = rest.indexOf('\t');
|
|
550
|
-
if (tab >= 0) rest = rest.slice(0, tab);
|
|
551
|
-
return stripAb(rest);
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
const parseHunkHeader = (line) => {
|
|
555
|
-
const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/.exec(line);
|
|
556
|
-
if (!match) return null;
|
|
557
|
-
const oldStart = parseInt(match[1], 10);
|
|
558
|
-
const oldCount = match[2] === undefined ? 1 : parseInt(match[2], 10);
|
|
559
|
-
const newStart = parseInt(match[3], 10);
|
|
560
|
-
const newCount = match[4] === undefined ? 1 : parseInt(match[4], 10);
|
|
561
|
-
return { oldStart, oldCount, newStart, newCount, header: line };
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
const closeFile = (file, files) => {
|
|
565
|
-
if (file) files.push(file);
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
const createFile = (line) => {
|
|
569
|
-
const paths = parseGitPaths(line);
|
|
570
|
-
return {
|
|
571
|
-
oldPath: paths.oldPath,
|
|
572
|
-
newPath: paths.newPath,
|
|
573
|
-
isNew: false,
|
|
574
|
-
isDeleted: false,
|
|
575
|
-
isBinary: false,
|
|
576
|
-
preamble: [line],
|
|
577
|
-
hunks: [],
|
|
578
|
-
};
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
const startGitFile = (line, ctx) => {
|
|
582
|
-
ctx.hunk = null;
|
|
583
|
-
closeFile(ctx.file, ctx.files);
|
|
584
|
-
ctx.file = createFile(line);
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
const markBinary = (line, ctx) => {
|
|
588
|
-
ctx.file.isBinary = true;
|
|
589
|
-
ctx.file.preamble.push(line);
|
|
590
|
-
ctx.hunk = null;
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
const markNewFile = (line, ctx) => {
|
|
594
|
-
ctx.file.isNew = true;
|
|
595
|
-
ctx.file.preamble.push(line);
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
const markDeleted = (line, ctx) => {
|
|
599
|
-
ctx.file.isDeleted = true;
|
|
600
|
-
ctx.file.preamble.push(line);
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
const setOldPath = (line, ctx) => {
|
|
604
|
-
const parsed = parsePathLine(line);
|
|
605
|
-
if (parsed === '/dev/null') ctx.file.isNew = true;
|
|
606
|
-
else ctx.file.oldPath = parsed;
|
|
607
|
-
ctx.file.preamble.push(line);
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
const setNewPath = (line, ctx) => {
|
|
611
|
-
const parsed = parsePathLine(line);
|
|
612
|
-
if (parsed === '/dev/null') ctx.file.isDeleted = true;
|
|
613
|
-
else ctx.file.newPath = parsed;
|
|
614
|
-
ctx.file.preamble.push(line);
|
|
615
|
-
};
|
|
616
|
-
|
|
617
|
-
const startHunk = (line, ctx) => {
|
|
618
|
-
const meta = parseHunkHeader(line);
|
|
619
|
-
if (!meta) return;
|
|
620
|
-
ctx.hunk = { ...meta, lines: [] };
|
|
621
|
-
ctx.file.hunks.push(ctx.hunk);
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
const DIFF_PREFIX = [
|
|
625
|
-
{ prefix: 'diff --git ', apply: startGitFile, needFile: false },
|
|
626
|
-
{ prefix: 'Binary files ', apply: markBinary, needFile: true },
|
|
627
|
-
{ prefix: 'GIT binary', apply: markBinary, needFile: true },
|
|
628
|
-
{ prefix: 'new file mode', apply: markNewFile, needFile: true },
|
|
629
|
-
{ prefix: 'deleted file mode', apply: markDeleted, needFile: true },
|
|
630
|
-
{ prefix: '--- ', apply: setOldPath, needFile: true },
|
|
631
|
-
{ prefix: '+++ ', apply: setNewPath, needFile: true },
|
|
632
|
-
{ prefix: '@@ ', apply: startHunk, needFile: true },
|
|
633
|
-
];
|
|
634
|
-
|
|
635
|
-
const takeHunkLine = (line, hunk) => {
|
|
636
|
-
if (line.startsWith('\\')) {
|
|
637
|
-
const prev = hunk.lines[hunk.lines.length - 1];
|
|
638
|
-
if (prev) prev.noNl = true;
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
const type = LINE_TYPE[line[0]];
|
|
642
|
-
if (type) {
|
|
643
|
-
hunk.lines.push({ type, text: line.slice(1), noNl: false });
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
hunk.lines.push({ type: 'ctx', text: line, noNl: false });
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
const parseDiff = (text) => {
|
|
650
|
-
const files = [];
|
|
651
|
-
if (!text) return files;
|
|
652
|
-
const rawLines = text.split('\n');
|
|
653
|
-
if (rawLines.at(-1) === '') rawLines.pop();
|
|
654
|
-
const ctx = { files, file: null, hunk: null };
|
|
655
|
-
for (const raw of rawLines) {
|
|
656
|
-
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
657
|
-
const rule = DIFF_PREFIX.find((entry) => line.startsWith(entry.prefix));
|
|
658
|
-
if (rule) {
|
|
659
|
-
if (rule.needFile && !ctx.file) continue;
|
|
660
|
-
rule.apply(line, ctx);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
if (!ctx.file) continue;
|
|
664
|
-
if (!ctx.hunk) {
|
|
665
|
-
ctx.file.preamble.push(line);
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
takeHunkLine(line, ctx.hunk);
|
|
669
|
-
}
|
|
670
|
-
closeFile(ctx.file, files);
|
|
671
|
-
return files;
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
const splitHunk = (hunk) => {
|
|
675
|
-
const { oldStart, oldCount, newStart, newCount, header } = hunk;
|
|
676
|
-
const blocks = [];
|
|
677
|
-
let current = null;
|
|
678
|
-
let blockId = -1;
|
|
679
|
-
const lines = hunk.lines.map((line) => {
|
|
680
|
-
if (isCtxType(line.type)) {
|
|
681
|
-
current = null;
|
|
682
|
-
return { ...line, blockId: null };
|
|
683
|
-
}
|
|
684
|
-
if (!current) {
|
|
685
|
-
blockId += 1;
|
|
686
|
-
current = { id: blockId };
|
|
687
|
-
blocks.push(current);
|
|
688
|
-
}
|
|
689
|
-
return { ...line, blockId };
|
|
690
|
-
});
|
|
691
|
-
const nextHunk = { oldStart, oldCount, newStart, newCount, header, lines };
|
|
692
|
-
return { hunk: nextHunk, blocks };
|
|
693
|
-
};
|
|
694
|
-
|
|
695
|
-
const otherContext = (contextSide, blockId) => {
|
|
696
|
-
if (contextSide === 'new') return 'new';
|
|
697
|
-
if (contextSide === 'old') return 'old';
|
|
698
|
-
return contextSide[blockId] ?? 'old';
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
const flattenBlock = (hunk, blockId, contextSide) => {
|
|
702
|
-
const out = [];
|
|
703
|
-
for (const line of hunk.lines) {
|
|
704
|
-
if (line.blockId === blockId || isCtxType(line.type)) {
|
|
705
|
-
out.push(line);
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
const side = otherContext(contextSide, line.blockId);
|
|
709
|
-
if (side === 'old' && line.type === 'del') {
|
|
710
|
-
out.push({ type: 'ctx', text: line.text, noNl: line.noNl });
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
if (side === 'new' && line.type === 'add') {
|
|
714
|
-
out.push({ type: 'ctx', text: line.text, noNl: line.noNl });
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
return out;
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
const countSides = (lines) => {
|
|
721
|
-
let oldCount = 0;
|
|
722
|
-
let newCount = 0;
|
|
723
|
-
for (const line of lines) {
|
|
724
|
-
if (isCtxType(line.type) || line.type === 'del') oldCount += 1;
|
|
725
|
-
if (isCtxType(line.type) || line.type === 'add') newCount += 1;
|
|
726
|
-
}
|
|
727
|
-
return { oldCount, newCount };
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
const formatHunkHeader = (oldStart, oldCount, newStart, newCount) =>
|
|
731
|
-
`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`;
|
|
732
|
-
|
|
733
|
-
const lineMark = (type) => LINE_MARK[type] ?? ' ';
|
|
734
|
-
|
|
735
|
-
const formatBodyLine = (line) => {
|
|
736
|
-
const rows = [`${lineMark(line.type)}${line.text}`];
|
|
737
|
-
if (line.noNl) rows.push('\');
|
|
738
|
-
return rows;
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
const formatPatch = (file, hunk, blockId, contextSide) => {
|
|
742
|
-
const lines = flattenBlock(hunk, blockId, contextSide);
|
|
743
|
-
const counts = countSides(lines);
|
|
744
|
-
const header = formatHunkHeader(
|
|
745
|
-
hunk.oldStart,
|
|
746
|
-
counts.oldCount,
|
|
747
|
-
hunk.newStart,
|
|
748
|
-
counts.newCount,
|
|
749
|
-
);
|
|
750
|
-
const parts = [...file.preamble, header];
|
|
751
|
-
for (const line of lines) {
|
|
752
|
-
for (const row of formatBodyLine(line)) parts.push(row);
|
|
753
|
-
}
|
|
754
|
-
return `${parts.join('\n')}\n`;
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
const LAYOUT_ALIGN = {
|
|
758
|
-
mixed: alignPairs,
|
|
759
|
-
side: alignSplit,
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
const windowAroundBlock = (lines, blockId, radius) => {
|
|
763
|
-
let start = -1;
|
|
764
|
-
let end = 0;
|
|
765
|
-
for (let i = 0; i < lines.length; i++) {
|
|
766
|
-
if (lines[i].blockId !== blockId) continue;
|
|
767
|
-
if (start < 0) start = i;
|
|
768
|
-
end = i + 1;
|
|
769
|
-
}
|
|
770
|
-
if (start < 0) return lines;
|
|
771
|
-
const from = Math.max(0, start - radius);
|
|
772
|
-
const to = Math.min(lines.length, end + radius);
|
|
773
|
-
return lines.slice(from, to);
|
|
774
|
-
};
|
|
775
|
-
|
|
776
|
-
const insertAddIndex = (lines) => {
|
|
777
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
778
|
-
if (isCtxType(lines[i].type)) continue;
|
|
779
|
-
return i + 1;
|
|
780
|
-
}
|
|
781
|
-
return lines.length;
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
const taggedAdd = (text, blockId, start, end) => ({
|
|
785
|
-
type: 'add',
|
|
786
|
-
text,
|
|
787
|
-
noNl: false,
|
|
788
|
-
blockId,
|
|
789
|
-
wrap: true,
|
|
790
|
-
editStart: start,
|
|
791
|
-
editEnd: end,
|
|
792
|
-
editLast: true,
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
const replaceAdds = (lines, overlay, blockId) => {
|
|
796
|
-
const text = overlay.text ?? '';
|
|
797
|
-
const keepEmpty = overlay.keepEmpty === true;
|
|
798
|
-
if (text === '' && !keepEmpty) {
|
|
799
|
-
return lines.filter((line) => line.type !== 'add');
|
|
800
|
-
}
|
|
801
|
-
const texts = text.split('\n');
|
|
802
|
-
const tagged = [];
|
|
803
|
-
let offset = 0;
|
|
804
|
-
for (let i = 0; i < texts.length; i++) {
|
|
805
|
-
const row = texts[i];
|
|
806
|
-
const start = offset;
|
|
807
|
-
const end = start + row.length;
|
|
808
|
-
const isLast = i === texts.length - 1;
|
|
809
|
-
tagged.push(taggedAdd(row, blockId, start, end));
|
|
810
|
-
offset = isLast ? end : end + 1;
|
|
811
|
-
}
|
|
812
|
-
const out = [];
|
|
813
|
-
let placed = 0;
|
|
814
|
-
let lastAdd = -1;
|
|
815
|
-
for (const line of lines) {
|
|
816
|
-
if (line.type !== 'add') {
|
|
817
|
-
out.push(line);
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
if (placed >= tagged.length) continue;
|
|
821
|
-
out.push(tagged[placed]);
|
|
822
|
-
lastAdd = out.length - 1;
|
|
823
|
-
placed += 1;
|
|
824
|
-
}
|
|
825
|
-
const extras = tagged.slice(placed);
|
|
826
|
-
if (!extras.length) return out;
|
|
827
|
-
const at = lastAdd >= 0 ? lastAdd + 1 : insertAddIndex(out);
|
|
828
|
-
out.splice(at, 0, ...extras);
|
|
829
|
-
return out;
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
const blockAddText = (hunk, blockId) => {
|
|
833
|
-
if (!hunk) return '';
|
|
834
|
-
const parts = [];
|
|
835
|
-
for (const line of hunk.lines) {
|
|
836
|
-
if (line.type !== 'add') continue;
|
|
837
|
-
if (line.blockId !== blockId) continue;
|
|
838
|
-
parts.push(line.text);
|
|
839
|
-
}
|
|
840
|
-
return parts.join('\n');
|
|
841
|
-
};
|
|
842
|
-
|
|
843
|
-
const displayLines = (hunk, blockId, layout = 'unified', radius, overlay) => {
|
|
844
|
-
const flat = flattenBlock(hunk, blockId, 'new');
|
|
845
|
-
const source = overlay ? replaceAdds(flat, overlay, blockId) : flat;
|
|
846
|
-
const around = radius ?? DISPLAY_CONTEXT;
|
|
847
|
-
const windowed = windowAroundBlock(source, blockId, around);
|
|
848
|
-
const lines = windowed.map((line) => ({ ...line }));
|
|
849
|
-
const painted = attachInline(lines);
|
|
850
|
-
const align = LAYOUT_ALIGN[layout];
|
|
851
|
-
if (align) return align(painted);
|
|
852
|
-
return painted;
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
const splitLines = (content) => {
|
|
856
|
-
if (content === '') return { lines: [''], noNl: true };
|
|
857
|
-
const parts = content.split('\n');
|
|
858
|
-
const noNl = parts.at(-1) !== '';
|
|
859
|
-
if (!noNl) parts.pop();
|
|
860
|
-
return { lines: parts, noNl };
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
const synthesizeNewFile = (relPath, content) => {
|
|
864
|
-
const { lines, noNl } = splitLines(content);
|
|
865
|
-
const body = lines.map((text, index) => ({
|
|
866
|
-
type: 'add',
|
|
867
|
-
text,
|
|
868
|
-
noNl: noNl && index === lines.length - 1,
|
|
869
|
-
blockId: 0,
|
|
870
|
-
}));
|
|
871
|
-
const oldStart = 0;
|
|
872
|
-
const oldCount = 0;
|
|
873
|
-
const newStart = 1;
|
|
874
|
-
const newCount = body.length;
|
|
875
|
-
const header = formatHunkHeader(oldStart, oldCount, newStart, newCount);
|
|
876
|
-
const hunk = { oldStart, oldCount, newStart, newCount, header, lines: body };
|
|
877
|
-
const preamble = [
|
|
878
|
-
`diff --git a/${relPath} b/${relPath}`,
|
|
879
|
-
'new file mode 100644',
|
|
880
|
-
'--- /dev/null',
|
|
881
|
-
`+++ b/${relPath}`,
|
|
882
|
-
];
|
|
883
|
-
return {
|
|
884
|
-
oldPath: relPath,
|
|
885
|
-
newPath: relPath,
|
|
886
|
-
isNew: true,
|
|
887
|
-
isDeleted: false,
|
|
888
|
-
isBinary: false,
|
|
889
|
-
preamble,
|
|
890
|
-
hunks: [hunk],
|
|
891
|
-
};
|
|
892
|
-
};
|
|
893
|
-
|
|
894
|
-
const itemsFromFiles = (files, origin) => {
|
|
895
|
-
const items = [];
|
|
896
|
-
for (const file of files) {
|
|
897
|
-
if (file.isBinary) {
|
|
898
|
-
const hunk = null;
|
|
899
|
-
const blockId = null;
|
|
900
|
-
const patchAdd = '';
|
|
901
|
-
const patchRevert = '';
|
|
902
|
-
items.push({ origin, file, hunk, blockId, patchAdd, patchRevert });
|
|
903
|
-
continue;
|
|
904
|
-
}
|
|
905
|
-
for (const rawHunk of file.hunks) {
|
|
906
|
-
const { hunk, blocks } = splitHunk(rawHunk);
|
|
907
|
-
for (const block of blocks) {
|
|
908
|
-
const blockId = block.id;
|
|
909
|
-
const indexSide = INDEX_SIDE[origin] ?? 'old';
|
|
910
|
-
const patchAdd = formatPatch(file, hunk, blockId, indexSide);
|
|
911
|
-
const patchRevert = formatPatch(file, hunk, blockId, 'new');
|
|
912
|
-
items.push({ origin, file, hunk, blockId, patchAdd, patchRevert });
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
return items;
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
const refreshIndexPatches = (items, item) => {
|
|
920
|
-
if (!item.hunk || !item.file) return;
|
|
921
|
-
const siblings = [];
|
|
922
|
-
for (const other of items) {
|
|
923
|
-
if (other.hunk === item.hunk && other.file === item.file) {
|
|
924
|
-
siblings.push(other);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
const sides = {};
|
|
928
|
-
for (const other of siblings) {
|
|
929
|
-
sides[other.blockId] = INDEX_SIDE[other.origin] ?? 'old';
|
|
930
|
-
}
|
|
931
|
-
for (const other of siblings) {
|
|
932
|
-
other.patchAdd = formatPatch(other.file, other.hunk, other.blockId, sides);
|
|
933
|
-
}
|
|
934
|
-
};
|
|
3
|
+
const { diffChars } = require('./diff/inline.js');
|
|
4
|
+
const { isCtxType, pairIndices, attachInline } = require('./diff/align.js');
|
|
5
|
+
const { parseDiff } = require('./diff/parse.js');
|
|
6
|
+
const patch = require('./diff/patch.js');
|
|
7
|
+
const { splitHunk, flattenBlock, countSides, formatPatch } = patch;
|
|
8
|
+
const display = require('./diff/display.js');
|
|
9
|
+
const { DISPLAY_CONTEXT, displayLines, blockAddText } = display;
|
|
10
|
+
const edit = require('./diff/edit.js');
|
|
11
|
+
const { addBlockRange, replaceBlockAdds } = edit;
|
|
12
|
+
const items = require('./diff/items.js');
|
|
13
|
+
const { synthesizeNewFile, itemsFromFiles, refreshIndexPatches } = items;
|
|
935
14
|
|
|
936
15
|
module.exports = {
|
|
937
16
|
DISPLAY_CONTEXT,
|
|
@@ -946,6 +25,8 @@ module.exports = {
|
|
|
946
25
|
formatPatch,
|
|
947
26
|
displayLines,
|
|
948
27
|
blockAddText,
|
|
28
|
+
addBlockRange,
|
|
29
|
+
replaceBlockAdds,
|
|
949
30
|
synthesizeNewFile,
|
|
950
31
|
itemsFromFiles,
|
|
951
32
|
refreshIndexPatches,
|