reslop 0.2.5 → 0.2.6
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 +12 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +39 -47
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +197 -402
- package/lib/deps.js +113 -240
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +76 -144
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +71 -89
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +147 -272
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +155 -141
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +14 -14
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +49 -82
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +220 -333
- package/lib/render/commits.js +160 -361
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -258
- package/lib/render/files.js +40 -85
- package/lib/render/frame.js +78 -117
- package/lib/render/notes.js +139 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +72 -174
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +68 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +61 -43
- package/lib/session/branches.js +166 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +274 -355
- package/lib/session/compose.js +556 -714
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +162 -170
- package/lib/session/files-pane.js +76 -44
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +393 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +2 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/pointer.js +16 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +276 -235
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +47 -81
- package/lib/session/watch.js +196 -212
- package/lib/session.js +81 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/diff/inline.js
CHANGED
|
@@ -36,19 +36,24 @@ const wholeChanged = (a, b) => ({
|
|
|
36
36
|
newSpans: b.length ? [{ text: b.join(''), changed: true }] : [],
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
const sharedEnds = (a, b) => {
|
|
40
|
+
let lo = 0;
|
|
41
|
+
while (lo < a.length && lo < b.length && a[lo] === b[lo]) lo += 1;
|
|
42
|
+
let hiA = a.length;
|
|
43
|
+
let hiB = b.length;
|
|
44
|
+
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
45
|
+
hiA -= 1;
|
|
46
|
+
hiB -= 1;
|
|
47
|
+
}
|
|
48
|
+
return { lo, hiA, hiB };
|
|
49
|
+
};
|
|
50
|
+
|
|
39
51
|
const lcsLength = (a, b) => {
|
|
40
52
|
const n = a.length;
|
|
41
53
|
const m = b.length;
|
|
42
54
|
if (n === 0 || m === 0) return 0;
|
|
43
55
|
if (n * m > LCS_CELL_LIMIT) {
|
|
44
|
-
|
|
45
|
-
while (lo < n && lo < m && a[lo] === b[lo]) lo += 1;
|
|
46
|
-
let hiA = n;
|
|
47
|
-
let hiB = m;
|
|
48
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
49
|
-
hiA -= 1;
|
|
50
|
-
hiB -= 1;
|
|
51
|
-
}
|
|
56
|
+
const { lo, hiA } = sharedEnds(a, b);
|
|
52
57
|
return lo + (n - hiA);
|
|
53
58
|
}
|
|
54
59
|
let prev = new Uint32Array(m + 1);
|
|
@@ -74,7 +79,7 @@ const tokenSimilarity = (a, b) => {
|
|
|
74
79
|
return (2 * lcs) / (a.length + b.length);
|
|
75
80
|
};
|
|
76
81
|
|
|
77
|
-
const
|
|
82
|
+
const lcsTable = (a, b) => {
|
|
78
83
|
const n = a.length;
|
|
79
84
|
const m = b.length;
|
|
80
85
|
const dp = new Array(n + 1);
|
|
@@ -91,7 +96,8 @@ const fillLcsTable = (a, b) => {
|
|
|
91
96
|
return dp;
|
|
92
97
|
};
|
|
93
98
|
|
|
94
|
-
const
|
|
99
|
+
const lcsOps = (a, b) => {
|
|
100
|
+
const dp = lcsTable(a, b);
|
|
95
101
|
const rev = new Array(a.length + b.length - dp[a.length][b.length]);
|
|
96
102
|
let n = 0;
|
|
97
103
|
let i = a.length;
|
|
@@ -101,44 +107,29 @@ const tracebackLcs = (a, b, dp) => {
|
|
|
101
107
|
rev[n++] = { type: 'eq', text: a[i - 1] };
|
|
102
108
|
i -= 1;
|
|
103
109
|
j -= 1;
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
110
|
+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
107
111
|
rev[n++] = { type: 'del', text: a[i - 1] };
|
|
108
112
|
i -= 1;
|
|
109
|
-
|
|
113
|
+
} else {
|
|
114
|
+
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
115
|
+
j -= 1;
|
|
110
116
|
}
|
|
111
|
-
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
112
|
-
j -= 1;
|
|
113
117
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
while (j > 0) {
|
|
119
|
-
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
120
|
-
j -= 1;
|
|
121
|
-
}
|
|
122
|
-
rev.reverse();
|
|
123
|
-
return rev;
|
|
118
|
+
for (; i > 0; i--) rev[n++] = { type: 'del', text: a[i - 1] };
|
|
119
|
+
for (; j > 0; j--) rev[n++] = { type: 'add', text: b[j - 1] };
|
|
120
|
+
return rev.reverse();
|
|
124
121
|
};
|
|
125
122
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
const lcsSpans = (a, b) => {
|
|
123
|
+
const opsSpans = (ops) => {
|
|
129
124
|
const oldParts = [];
|
|
130
125
|
const newParts = [];
|
|
131
|
-
for (const op of
|
|
132
|
-
if (op.type
|
|
133
|
-
oldParts.push({ text: op.text, changed:
|
|
134
|
-
newParts.push({ text: op.text, changed: false });
|
|
135
|
-
continue;
|
|
126
|
+
for (const op of ops) {
|
|
127
|
+
if (op.type !== 'add') {
|
|
128
|
+
oldParts.push({ text: op.text, changed: op.type === 'del' });
|
|
136
129
|
}
|
|
137
|
-
if (op.type
|
|
138
|
-
|
|
139
|
-
continue;
|
|
130
|
+
if (op.type !== 'del') {
|
|
131
|
+
newParts.push({ text: op.text, changed: op.type === 'add' });
|
|
140
132
|
}
|
|
141
|
-
newParts.push({ text: op.text, changed: true });
|
|
142
133
|
}
|
|
143
134
|
return {
|
|
144
135
|
oldSpans: collapseSpans(oldParts),
|
|
@@ -146,67 +137,35 @@ const lcsSpans = (a, b) => {
|
|
|
146
137
|
};
|
|
147
138
|
};
|
|
148
139
|
|
|
149
|
-
const trimSharedEnds = (a, b) => {
|
|
150
|
-
let lo = 0;
|
|
151
|
-
while (lo < a.length && lo < b.length && a[lo] === b[lo]) lo += 1;
|
|
152
|
-
let hiA = a.length;
|
|
153
|
-
let hiB = b.length;
|
|
154
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
155
|
-
hiA -= 1;
|
|
156
|
-
hiB -= 1;
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
prefix: a.slice(0, lo),
|
|
160
|
-
suffix: a.slice(hiA),
|
|
161
|
-
midA: a.slice(lo, hiA),
|
|
162
|
-
midB: b.slice(lo, hiB),
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const diffTokenMiddle = (midA, midB) => {
|
|
167
|
-
if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
168
|
-
return wholeChanged(midA, midB);
|
|
169
|
-
}
|
|
170
|
-
if (midA.length === 0 && midB.length === 0) {
|
|
171
|
-
return { oldSpans: [], newSpans: [] };
|
|
172
|
-
}
|
|
173
|
-
if (midA.length === 0 || midB.length === 0) return wholeChanged(midA, midB);
|
|
174
|
-
return lcsSpans(midA, midB);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
140
|
const joinAffix = (prefix, mid, suffix) => {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const at = hasPrefix ? 1 : 0;
|
|
183
|
-
if (hasPrefix) {
|
|
184
|
-
parts[0] = { text: prefix.join(''), changed: false };
|
|
185
|
-
}
|
|
186
|
-
for (let i = 0; i < mid.length; i++) parts[at + i] = mid[i];
|
|
187
|
-
if (hasSuffix) {
|
|
188
|
-
parts[at + mid.length] = { text: suffix.join(''), changed: false };
|
|
189
|
-
}
|
|
141
|
+
const parts = [];
|
|
142
|
+
if (prefix.length) parts.push({ text: prefix.join(''), changed: false });
|
|
143
|
+
for (const span of mid) parts.push(span);
|
|
144
|
+
if (suffix.length) parts.push({ text: suffix.join(''), changed: false });
|
|
190
145
|
return collapseSpans(parts);
|
|
191
146
|
};
|
|
192
147
|
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
148
|
+
const diffTrimmed = (a, b, spansOfOps) => {
|
|
149
|
+
const { lo, hiA, hiB } = sharedEnds(a, b);
|
|
150
|
+
const midA = a.slice(lo, hiA);
|
|
151
|
+
const midB = b.slice(lo, hiB);
|
|
152
|
+
const tooBig = midA.length * midB.length > LCS_CELL_LIMIT;
|
|
153
|
+
const whole = !midA.length || !midB.length || tooBig;
|
|
154
|
+
const mid = whole ? wholeChanged(midA, midB) : spansOfOps(lcsOps(midA, midB));
|
|
155
|
+
const prefix = a.slice(0, lo);
|
|
156
|
+
const suffix = a.slice(hiA);
|
|
157
|
+
return {
|
|
158
|
+
oldSpans: joinAffix(prefix, mid.oldSpans, suffix),
|
|
159
|
+
newSpans: joinAffix(prefix, mid.newSpans, suffix),
|
|
160
|
+
};
|
|
161
|
+
};
|
|
197
162
|
|
|
198
163
|
const diffTokens = (a, b) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
if (a.length === 0 || b.length === 0) return wholeChanged(a, b);
|
|
203
|
-
const trimmed = trimSharedEnds(a, b);
|
|
204
|
-
const mid = diffTokenMiddle(trimmed.midA, trimmed.midB);
|
|
205
|
-
const joined = joinTrimmedSpans(trimmed.prefix, mid, trimmed.suffix);
|
|
206
|
-
if (isNoisy(joined.oldSpans) || isNoisy(joined.newSpans)) {
|
|
164
|
+
const spans = diffTrimmed(a, b, opsSpans);
|
|
165
|
+
if (isNoisy(spans.oldSpans) || isNoisy(spans.newSpans)) {
|
|
207
166
|
return wholeChanged(a, b);
|
|
208
167
|
}
|
|
209
|
-
return
|
|
168
|
+
return spans;
|
|
210
169
|
};
|
|
211
170
|
|
|
212
171
|
const splitCamel = (part) => {
|
|
@@ -214,31 +173,23 @@ const splitCamel = (part) => {
|
|
|
214
173
|
return part.match(CAMEL_RE) ?? [part];
|
|
215
174
|
};
|
|
216
175
|
|
|
217
|
-
const splitIdentWords = (part) => {
|
|
218
|
-
const words = [];
|
|
219
|
-
for (const chunk of part.split(/(_+)/)) {
|
|
220
|
-
if (chunk === '') continue;
|
|
221
|
-
if (chunk.includes('_')) {
|
|
222
|
-
words.push(chunk);
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
for (const word of splitCamel(chunk)) words.push(word);
|
|
226
|
-
}
|
|
227
|
-
return words;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
176
|
const splitPieces = (text) => {
|
|
231
|
-
if (text === '') return [];
|
|
232
177
|
PIECE_RE.lastIndex = 0;
|
|
233
|
-
const coarse = text.match(PIECE_RE) ?? [];
|
|
234
178
|
const pieces = [];
|
|
235
|
-
for (const part of
|
|
236
|
-
for (const
|
|
179
|
+
for (const part of text.match(PIECE_RE) ?? []) {
|
|
180
|
+
for (const chunk of part.split(/(_+)/)) {
|
|
181
|
+
if (chunk === '') continue;
|
|
182
|
+
if (chunk.includes('_')) {
|
|
183
|
+
pieces.push(chunk);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
for (const word of splitCamel(chunk)) pieces.push(word);
|
|
187
|
+
}
|
|
237
188
|
}
|
|
238
189
|
return pieces;
|
|
239
190
|
};
|
|
240
191
|
|
|
241
|
-
const
|
|
192
|
+
const refineWordOps = (ops) => {
|
|
242
193
|
const oldParts = [];
|
|
243
194
|
const newParts = [];
|
|
244
195
|
let i = 0;
|
|
@@ -257,23 +208,18 @@ const spansFromWordOps = (ops) => {
|
|
|
257
208
|
else adds.push(ops[i].text);
|
|
258
209
|
i += 1;
|
|
259
210
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
tokenSimilarity(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
if (dels.length) {
|
|
272
|
-
oldParts.push({ text: dels.join(''), changed: true });
|
|
273
|
-
}
|
|
274
|
-
if (adds.length) {
|
|
275
|
-
newParts.push({ text: adds.join(''), changed: true });
|
|
211
|
+
if (dels.length === 1 && adds.length === 1) {
|
|
212
|
+
const delChars = graphemes(dels[0]);
|
|
213
|
+
const addChars = graphemes(adds[0]);
|
|
214
|
+
if (tokenSimilarity(delChars, addChars) >= WORD_REFINE_MIN) {
|
|
215
|
+
const inner = diffTokens(delChars, addChars);
|
|
216
|
+
for (const span of inner.oldSpans) oldParts.push(span);
|
|
217
|
+
for (const span of inner.newSpans) newParts.push(span);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
276
220
|
}
|
|
221
|
+
if (dels.length) oldParts.push({ text: dels.join(''), changed: true });
|
|
222
|
+
if (adds.length) newParts.push({ text: adds.join(''), changed: true });
|
|
277
223
|
}
|
|
278
224
|
return {
|
|
279
225
|
oldSpans: collapseSpans(oldParts),
|
|
@@ -302,7 +248,9 @@ const editSize = (run) => Math.max(textSize(run.del), textSize(run.add));
|
|
|
302
248
|
|
|
303
249
|
const absorbTinyEquals = (runs) => {
|
|
304
250
|
for (let k = 1; k < runs.length - 1; k++) {
|
|
305
|
-
const
|
|
251
|
+
const before = runs[k - 1];
|
|
252
|
+
const eq = runs[k];
|
|
253
|
+
const after = runs[k + 1];
|
|
306
254
|
if (!eq.eq) continue;
|
|
307
255
|
const size = textSize(eq.del);
|
|
308
256
|
if (size > editSize(before) || size > editSize(after)) continue;
|
|
@@ -327,7 +275,7 @@ const runOps = (runs) => {
|
|
|
327
275
|
return ops;
|
|
328
276
|
};
|
|
329
277
|
|
|
330
|
-
const
|
|
278
|
+
const wordSpans = (ops) => refineWordOps(runOps(absorbTinyEquals(opRuns(ops))));
|
|
331
279
|
|
|
332
280
|
const sharedShare = (spans) => {
|
|
333
281
|
let shared = 0;
|
|
@@ -343,23 +291,7 @@ const sharedShare = (spans) => {
|
|
|
343
291
|
const diffChars = (oldText, newText) => {
|
|
344
292
|
const a = splitPieces(oldText);
|
|
345
293
|
const b = splitPieces(newText);
|
|
346
|
-
|
|
347
|
-
return { oldSpans: [], newSpans: [] };
|
|
348
|
-
}
|
|
349
|
-
if (a.length === 0 || b.length === 0) return wholeChanged(a, b);
|
|
350
|
-
const { prefix, suffix, midA, midB } = trimSharedEnds(a, b);
|
|
351
|
-
let mid;
|
|
352
|
-
if (midA.length === 0 && midB.length === 0) {
|
|
353
|
-
mid = { oldSpans: [], newSpans: [] };
|
|
354
|
-
} else if (midA.length === 0 || midB.length === 0) {
|
|
355
|
-
mid = wholeChanged(midA, midB);
|
|
356
|
-
} else if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
357
|
-
mid = wholeChanged(midA, midB);
|
|
358
|
-
} else {
|
|
359
|
-
mid = spansFromWordOps(cleanOps(lcsOps(midA, midB)));
|
|
360
|
-
}
|
|
361
|
-
const oldSpans = joinAffix(prefix, mid.oldSpans, suffix);
|
|
362
|
-
const newSpans = joinAffix(prefix, mid.newSpans, suffix);
|
|
294
|
+
const { oldSpans, newSpans } = diffTrimmed(a, b, wordSpans);
|
|
363
295
|
const oldShare = sharedShare(oldSpans);
|
|
364
296
|
const newShare = sharedShare(newSpans);
|
|
365
297
|
if (oldShare < WHOLE_MIN_SHARED && newShare < WHOLE_MIN_SHARED) {
|
package/lib/diff/items.js
CHANGED
|
@@ -3,36 +3,34 @@
|
|
|
3
3
|
const patch = require('./patch.js');
|
|
4
4
|
const { splitHunk, formatHunkHeader, formatPatch } = patch;
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
pr: 'old',
|
|
6
|
+
const BINARY_ITEM = {
|
|
7
|
+
hunk: null,
|
|
8
|
+
blockId: null,
|
|
9
|
+
patchAdd: '',
|
|
10
|
+
patchRevert: '',
|
|
12
11
|
};
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
if (content === '') return { lines: [''], noNl: true };
|
|
16
|
-
const parts = content.split('\n');
|
|
17
|
-
const noNl = parts.at(-1) !== '';
|
|
18
|
-
if (!noNl) parts.pop();
|
|
19
|
-
return { lines: parts, noNl };
|
|
20
|
-
};
|
|
13
|
+
const indexSide = (origin) => (origin === 'staged' ? 'new' : 'old');
|
|
21
14
|
|
|
22
15
|
const synthesizeNewFile = (relPath, content) => {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
16
|
+
const noNl = content === '' || !content.endsWith('\n');
|
|
17
|
+
const texts = noNl ? content.split('\n') : content.slice(0, -1).split('\n');
|
|
18
|
+
const lines = texts.map((text, index) => ({
|
|
25
19
|
type: 'add',
|
|
26
20
|
text,
|
|
27
|
-
noNl: noNl && index ===
|
|
21
|
+
noNl: noNl && index === texts.length - 1,
|
|
28
22
|
blockId: 0,
|
|
29
23
|
}));
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
const newCount = lines.length;
|
|
25
|
+
const header = formatHunkHeader(0, 0, 1, newCount);
|
|
26
|
+
const hunk = {
|
|
27
|
+
oldStart: 0,
|
|
28
|
+
oldCount: 0,
|
|
29
|
+
newStart: 1,
|
|
30
|
+
newCount,
|
|
31
|
+
header,
|
|
32
|
+
lines,
|
|
33
|
+
};
|
|
36
34
|
const preamble = [
|
|
37
35
|
`diff --git a/${relPath} b/${relPath}`,
|
|
38
36
|
'new file mode 100644',
|
|
@@ -52,21 +50,16 @@ const synthesizeNewFile = (relPath, content) => {
|
|
|
52
50
|
|
|
53
51
|
const itemsFromFiles = (files, origin) => {
|
|
54
52
|
const items = [];
|
|
53
|
+
const side = indexSide(origin);
|
|
55
54
|
for (const file of files) {
|
|
56
55
|
if (file.isBinary) {
|
|
57
|
-
|
|
58
|
-
const blockId = null;
|
|
59
|
-
const patchAdd = '';
|
|
60
|
-
const patchRevert = '';
|
|
61
|
-
items.push({ origin, file, hunk, blockId, patchAdd, patchRevert });
|
|
56
|
+
items.push({ origin, file, ...BINARY_ITEM });
|
|
62
57
|
continue;
|
|
63
58
|
}
|
|
64
59
|
for (const rawHunk of file.hunks) {
|
|
65
60
|
const { hunk, blocks } = splitHunk(rawHunk);
|
|
66
|
-
for (const
|
|
67
|
-
const
|
|
68
|
-
const indexSide = INDEX_SIDE[origin] ?? 'old';
|
|
69
|
-
const patchAdd = formatPatch(file, hunk, blockId, indexSide);
|
|
61
|
+
for (const { id: blockId } of blocks) {
|
|
62
|
+
const patchAdd = formatPatch(file, hunk, blockId, side);
|
|
70
63
|
const patchRevert = formatPatch(file, hunk, blockId, 'new');
|
|
71
64
|
items.push({ origin, file, hunk, blockId, patchAdd, patchRevert });
|
|
72
65
|
}
|
|
@@ -76,24 +69,17 @@ const itemsFromFiles = (files, origin) => {
|
|
|
76
69
|
};
|
|
77
70
|
|
|
78
71
|
const refreshIndexPatches = (items, item) => {
|
|
79
|
-
if (!item.hunk
|
|
72
|
+
if (!item.hunk) return items;
|
|
73
|
+
const isPeer = (other) =>
|
|
74
|
+
other.hunk === item.hunk && other.file === item.file;
|
|
75
|
+
const peers = items.filter(isPeer);
|
|
80
76
|
const sides = {};
|
|
81
|
-
for (const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
const { length } = items;
|
|
86
|
-
const next = new Array(length);
|
|
87
|
-
for (let i = 0; i < length; i++) {
|
|
88
|
-
const other = items[i];
|
|
89
|
-
if (other.hunk !== item.hunk || other.file !== item.file) {
|
|
90
|
-
next[i] = other;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
77
|
+
for (const peer of peers) sides[peer.blockId] = indexSide(peer.origin);
|
|
78
|
+
return items.map((other) => {
|
|
79
|
+
if (!isPeer(other)) return other;
|
|
93
80
|
const patchAdd = formatPatch(other.file, other.hunk, other.blockId, sides);
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return next;
|
|
81
|
+
return { ...other, patchAdd };
|
|
82
|
+
});
|
|
97
83
|
};
|
|
98
84
|
|
|
99
85
|
module.exports = {
|
package/lib/diff/parse.js
CHANGED
|
@@ -1,57 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const LINE_TYPE = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
' ': 'ctx',
|
|
7
|
-
};
|
|
3
|
+
const LINE_TYPE = { '+': 'add', '-': 'del', ' ': 'ctx' };
|
|
4
|
+
|
|
5
|
+
const HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/;
|
|
8
6
|
|
|
9
7
|
const parseGitPaths = (line) => {
|
|
10
8
|
const rest = line.slice('diff --git '.length);
|
|
11
9
|
const mid = rest.lastIndexOf(' b/');
|
|
12
10
|
if (mid < 0 || !rest.startsWith('a/')) return { oldPath: '', newPath: '' };
|
|
13
|
-
|
|
14
|
-
const newPath = rest.slice(mid + 3);
|
|
15
|
-
return { oldPath, newPath };
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const stripAb = (raw) => {
|
|
19
|
-
if (raw === '/dev/null') return raw;
|
|
20
|
-
if (raw.startsWith('a/') || raw.startsWith('b/')) return raw.slice(2);
|
|
21
|
-
return raw;
|
|
11
|
+
return { oldPath: rest.slice(2, mid), newPath: rest.slice(mid + 3) };
|
|
22
12
|
};
|
|
23
13
|
|
|
24
14
|
const parsePathLine = (line) => {
|
|
25
15
|
const space = line.indexOf(' ');
|
|
26
16
|
if (space < 0) return '';
|
|
27
17
|
let rest = line.slice(space + 1);
|
|
28
|
-
if (rest.startsWith('"') && rest.endsWith('"'))
|
|
29
|
-
rest = rest.slice(1, -1);
|
|
30
|
-
}
|
|
18
|
+
if (rest.startsWith('"') && rest.endsWith('"')) rest = rest.slice(1, -1);
|
|
31
19
|
const tab = rest.indexOf('\t');
|
|
32
20
|
if (tab >= 0) rest = rest.slice(0, tab);
|
|
33
|
-
|
|
21
|
+
if (rest.startsWith('a/') || rest.startsWith('b/')) return rest.slice(2);
|
|
22
|
+
return rest;
|
|
34
23
|
};
|
|
35
24
|
|
|
36
|
-
const
|
|
37
|
-
const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/.exec(line);
|
|
38
|
-
if (!match) return null;
|
|
39
|
-
const oldStart = parseInt(match[1], 10);
|
|
40
|
-
const oldCount = match[2] === undefined ? 1 : parseInt(match[2], 10);
|
|
41
|
-
const newStart = parseInt(match[3], 10);
|
|
42
|
-
const newCount = match[4] === undefined ? 1 : parseInt(match[4], 10);
|
|
43
|
-
return { oldStart, oldCount, newStart, newCount, header: line };
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const closeFile = (file, files) => {
|
|
47
|
-
if (file) files.push(file);
|
|
48
|
-
};
|
|
25
|
+
const parseCount = (text) => (text === undefined ? 1 : parseInt(text, 10));
|
|
49
26
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
27
|
+
const startGitFile = (line, ctx) => {
|
|
28
|
+
if (ctx.file) ctx.files.push(ctx.file);
|
|
29
|
+
ctx.hunk = null;
|
|
30
|
+
ctx.file = {
|
|
31
|
+
...parseGitPaths(line),
|
|
55
32
|
isNew: false,
|
|
56
33
|
isDeleted: false,
|
|
57
34
|
isBinary: false,
|
|
@@ -60,12 +37,6 @@ const createFile = (line) => {
|
|
|
60
37
|
};
|
|
61
38
|
};
|
|
62
39
|
|
|
63
|
-
const startGitFile = (line, ctx) => {
|
|
64
|
-
ctx.hunk = null;
|
|
65
|
-
closeFile(ctx.file, ctx.files);
|
|
66
|
-
ctx.file = createFile(line);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
40
|
const markBinary = (line, ctx) => {
|
|
70
41
|
ctx.file.isBinary = true;
|
|
71
42
|
ctx.file.preamble.push(line);
|
|
@@ -97,9 +68,16 @@ const setNewPath = (line, ctx) => {
|
|
|
97
68
|
};
|
|
98
69
|
|
|
99
70
|
const startHunk = (line, ctx) => {
|
|
100
|
-
const
|
|
101
|
-
if (!
|
|
102
|
-
ctx.hunk = {
|
|
71
|
+
const match = HUNK_RE.exec(line);
|
|
72
|
+
if (!match) return;
|
|
73
|
+
ctx.hunk = {
|
|
74
|
+
oldStart: parseInt(match[1], 10),
|
|
75
|
+
oldCount: parseCount(match[2]),
|
|
76
|
+
newStart: parseInt(match[3], 10),
|
|
77
|
+
newCount: parseCount(match[4]),
|
|
78
|
+
header: line,
|
|
79
|
+
lines: [],
|
|
80
|
+
};
|
|
103
81
|
ctx.file.hunks.push(ctx.hunk);
|
|
104
82
|
};
|
|
105
83
|
|
|
@@ -121,11 +99,8 @@ const takeHunkLine = (line, hunk) => {
|
|
|
121
99
|
return;
|
|
122
100
|
}
|
|
123
101
|
const type = LINE_TYPE[line[0]];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
hunk.lines.push({ type: 'ctx', text: line, noNl: false });
|
|
102
|
+
const text = type ? line.slice(1) : line;
|
|
103
|
+
hunk.lines.push({ type: type ?? 'ctx', text, noNl: false });
|
|
129
104
|
};
|
|
130
105
|
|
|
131
106
|
const parseDiff = (text) => {
|
|
@@ -143,13 +118,10 @@ const parseDiff = (text) => {
|
|
|
143
118
|
continue;
|
|
144
119
|
}
|
|
145
120
|
if (!ctx.file) continue;
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
takeHunkLine(line, ctx.hunk);
|
|
121
|
+
if (ctx.hunk) takeHunkLine(line, ctx.hunk);
|
|
122
|
+
else ctx.file.preamble.push(line);
|
|
151
123
|
}
|
|
152
|
-
|
|
124
|
+
if (ctx.file) files.push(ctx.file);
|
|
153
125
|
return files;
|
|
154
126
|
};
|
|
155
127
|
|
package/lib/diff/patch.js
CHANGED
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
const align = require('./align.js');
|
|
4
4
|
const { isCtxType } = align;
|
|
5
5
|
|
|
6
|
-
const LINE_MARK = {
|
|
7
|
-
add: '+',
|
|
8
|
-
del: '-',
|
|
9
|
-
ctx: ' ',
|
|
10
|
-
warn: ' ',
|
|
11
|
-
note: ' ',
|
|
12
|
-
noteSep: ' ',
|
|
13
|
-
};
|
|
6
|
+
const LINE_MARK = { add: '+', del: '-' };
|
|
14
7
|
|
|
15
8
|
const splitHunk = (hunk) => {
|
|
16
9
|
const { oldStart, oldCount, newStart, newCount, header } = hunk;
|
|
@@ -33,9 +26,8 @@ const splitHunk = (hunk) => {
|
|
|
33
26
|
return { hunk: nextHunk, blocks };
|
|
34
27
|
};
|
|
35
28
|
|
|
36
|
-
const
|
|
37
|
-
if (contextSide === '
|
|
38
|
-
if (contextSide === 'old') return 'old';
|
|
29
|
+
const sideOf = (contextSide, blockId) => {
|
|
30
|
+
if (typeof contextSide === 'string') return contextSide;
|
|
39
31
|
return contextSide[blockId] ?? 'old';
|
|
40
32
|
};
|
|
41
33
|
|
|
@@ -46,12 +38,8 @@ const flattenBlock = (hunk, blockId, contextSide) => {
|
|
|
46
38
|
out.push(line);
|
|
47
39
|
continue;
|
|
48
40
|
}
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
out.push({ type: 'ctx', text: line.text, noNl: line.noNl });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (side === 'new' && line.type === 'add') {
|
|
41
|
+
const kept = sideOf(contextSide, line.blockId) === 'old' ? 'del' : 'add';
|
|
42
|
+
if (line.type === kept) {
|
|
55
43
|
out.push({ type: 'ctx', text: line.text, noNl: line.noNl });
|
|
56
44
|
}
|
|
57
45
|
}
|
|
@@ -62,8 +50,8 @@ const countSides = (lines) => {
|
|
|
62
50
|
let oldCount = 0;
|
|
63
51
|
let newCount = 0;
|
|
64
52
|
for (const line of lines) {
|
|
65
|
-
if (
|
|
66
|
-
if (
|
|
53
|
+
if (line.type !== 'add') oldCount += 1;
|
|
54
|
+
if (line.type !== 'del') newCount += 1;
|
|
67
55
|
}
|
|
68
56
|
return { oldCount, newCount };
|
|
69
57
|
};
|
|
@@ -71,24 +59,15 @@ const countSides = (lines) => {
|
|
|
71
59
|
const formatHunkHeader = (oldStart, oldCount, newStart, newCount) =>
|
|
72
60
|
`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`;
|
|
73
61
|
|
|
74
|
-
const formatBodyLine = (line) => {
|
|
75
|
-
const rows = [`${LINE_MARK[line.type] ?? ' '}${line.text}`];
|
|
76
|
-
if (line.noNl) rows.push('\');
|
|
77
|
-
return rows;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
62
|
const formatPatch = (file, hunk, blockId, contextSide) => {
|
|
81
63
|
const lines = flattenBlock(hunk, blockId, contextSide);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
counts.oldCount,
|
|
86
|
-
hunk.newStart,
|
|
87
|
-
counts.newCount,
|
|
88
|
-
);
|
|
64
|
+
const { oldCount, newCount } = countSides(lines);
|
|
65
|
+
const { oldStart, newStart } = hunk;
|
|
66
|
+
const header = formatHunkHeader(oldStart, oldCount, newStart, newCount);
|
|
89
67
|
const parts = [...file.preamble, header];
|
|
90
68
|
for (const line of lines) {
|
|
91
|
-
|
|
69
|
+
parts.push(`${LINE_MARK[line.type] ?? ' '}${line.text}`);
|
|
70
|
+
if (line.noNl) parts.push('\');
|
|
92
71
|
}
|
|
93
72
|
return `${parts.join('\n')}\n`;
|
|
94
73
|
};
|