reslop 0.2.4 → 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 +25 -1
- package/LICENSE +1 -1
- package/README.md +12 -13
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -12
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +48 -18
- 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 +196 -404
- package/lib/deps.js +240 -399
- 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 +138 -141
- 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 +90 -206
- package/lib/editor.js +98 -55
- package/lib/files.js +86 -103
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +182 -344
- 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 +157 -132
- 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 +16 -26
- 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 +22 -35
- package/lib/highlight/txt.js +9 -22
- package/lib/highlight.js +17 -46
- package/lib/keys.js +62 -85
- package/lib/manifest.js +67 -130
- package/lib/npm-commands.js +119 -189
- package/lib/remote.js +141 -197
- package/lib/render/branches.js +66 -73
- package/lib/render/chrome.js +264 -323
- package/lib/render/commits.js +187 -500
- 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 -228
- package/lib/render/files.js +45 -99
- package/lib/render/frame.js +129 -183
- package/lib/render/notes.js +144 -160
- package/lib/render/npm.js +234 -112
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +131 -170
- 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 -28
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -38
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -78
- package/lib/report-test.js +256 -141
- package/lib/report-tools.js +96 -161
- package/lib/review-merge.js +114 -152
- package/lib/review.js +115 -189
- package/lib/runs.js +225 -0
- package/lib/select.js +89 -75
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +82 -63
- package/lib/session/branches.js +193 -247
- package/lib/session/change-actions.js +171 -214
- package/lib/session/commit.js +280 -342
- package/lib/session/compose.js +559 -674
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +164 -157
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +85 -53
- package/lib/session/items.js +73 -104
- package/lib/session/lifecycle.js +403 -541
- package/lib/session/load.js +48 -68
- package/lib/session/navigation.js +96 -87
- package/lib/session/npm.js +409 -345
- package/lib/session/ops.js +196 -63
- package/lib/session/pointer.js +23 -15
- package/lib/session/progress.js +33 -21
- package/lib/session/review.js +33 -54
- package/lib/session/tasks.js +333 -0
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -136
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -97
- package/lib/session/view.js +52 -83
- package/lib/session/watch.js +208 -204
- package/lib/session.js +112 -237
- package/lib/update.js +45 -116
- package/lib/utilities.js +67 -89
- package/lib/wrap.js +33 -44
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/diff/inline.js
CHANGED
|
@@ -6,6 +6,7 @@ const { graphemes } = ansi;
|
|
|
6
6
|
const LCS_CELL_LIMIT = 250000;
|
|
7
7
|
const NOISY_TINY_SPANS = 6;
|
|
8
8
|
const WORD_REFINE_MIN = 0.6;
|
|
9
|
+
const WHOLE_MIN_SHARED = 0.25;
|
|
9
10
|
const PIECE_RE = /[A-Za-z_$][\w$]*|\d+|\s+|./g;
|
|
10
11
|
const CAMEL_RE = /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+|./g;
|
|
11
12
|
|
|
@@ -35,19 +36,24 @@ const wholeChanged = (a, b) => ({
|
|
|
35
36
|
newSpans: b.length ? [{ text: b.join(''), changed: true }] : [],
|
|
36
37
|
});
|
|
37
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
|
+
|
|
38
51
|
const lcsLength = (a, b) => {
|
|
39
52
|
const n = a.length;
|
|
40
53
|
const m = b.length;
|
|
41
54
|
if (n === 0 || m === 0) return 0;
|
|
42
55
|
if (n * m > LCS_CELL_LIMIT) {
|
|
43
|
-
|
|
44
|
-
while (lo < n && lo < m && a[lo] === b[lo]) lo += 1;
|
|
45
|
-
let hiA = n;
|
|
46
|
-
let hiB = m;
|
|
47
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
48
|
-
hiA -= 1;
|
|
49
|
-
hiB -= 1;
|
|
50
|
-
}
|
|
56
|
+
const { lo, hiA } = sharedEnds(a, b);
|
|
51
57
|
return lo + (n - hiA);
|
|
52
58
|
}
|
|
53
59
|
let prev = new Uint32Array(m + 1);
|
|
@@ -73,7 +79,7 @@ const tokenSimilarity = (a, b) => {
|
|
|
73
79
|
return (2 * lcs) / (a.length + b.length);
|
|
74
80
|
};
|
|
75
81
|
|
|
76
|
-
const
|
|
82
|
+
const lcsTable = (a, b) => {
|
|
77
83
|
const n = a.length;
|
|
78
84
|
const m = b.length;
|
|
79
85
|
const dp = new Array(n + 1);
|
|
@@ -90,7 +96,8 @@ const fillLcsTable = (a, b) => {
|
|
|
90
96
|
return dp;
|
|
91
97
|
};
|
|
92
98
|
|
|
93
|
-
const
|
|
99
|
+
const lcsOps = (a, b) => {
|
|
100
|
+
const dp = lcsTable(a, b);
|
|
94
101
|
const rev = new Array(a.length + b.length - dp[a.length][b.length]);
|
|
95
102
|
let n = 0;
|
|
96
103
|
let i = a.length;
|
|
@@ -100,44 +107,29 @@ const tracebackLcs = (a, b, dp) => {
|
|
|
100
107
|
rev[n++] = { type: 'eq', text: a[i - 1] };
|
|
101
108
|
i -= 1;
|
|
102
109
|
j -= 1;
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
110
|
+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
106
111
|
rev[n++] = { type: 'del', text: a[i - 1] };
|
|
107
112
|
i -= 1;
|
|
108
|
-
|
|
113
|
+
} else {
|
|
114
|
+
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
115
|
+
j -= 1;
|
|
109
116
|
}
|
|
110
|
-
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
111
|
-
j -= 1;
|
|
112
117
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
while (j > 0) {
|
|
118
|
-
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
119
|
-
j -= 1;
|
|
120
|
-
}
|
|
121
|
-
rev.reverse();
|
|
122
|
-
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();
|
|
123
121
|
};
|
|
124
122
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const lcsSpans = (a, b) => {
|
|
123
|
+
const opsSpans = (ops) => {
|
|
128
124
|
const oldParts = [];
|
|
129
125
|
const newParts = [];
|
|
130
|
-
for (const op of
|
|
131
|
-
if (op.type
|
|
132
|
-
oldParts.push({ text: op.text, changed:
|
|
133
|
-
newParts.push({ text: op.text, changed: false });
|
|
134
|
-
continue;
|
|
126
|
+
for (const op of ops) {
|
|
127
|
+
if (op.type !== 'add') {
|
|
128
|
+
oldParts.push({ text: op.text, changed: op.type === 'del' });
|
|
135
129
|
}
|
|
136
|
-
if (op.type
|
|
137
|
-
|
|
138
|
-
continue;
|
|
130
|
+
if (op.type !== 'del') {
|
|
131
|
+
newParts.push({ text: op.text, changed: op.type === 'add' });
|
|
139
132
|
}
|
|
140
|
-
newParts.push({ text: op.text, changed: true });
|
|
141
133
|
}
|
|
142
134
|
return {
|
|
143
135
|
oldSpans: collapseSpans(oldParts),
|
|
@@ -145,67 +137,35 @@ const lcsSpans = (a, b) => {
|
|
|
145
137
|
};
|
|
146
138
|
};
|
|
147
139
|
|
|
148
|
-
const trimSharedEnds = (a, b) => {
|
|
149
|
-
let lo = 0;
|
|
150
|
-
while (lo < a.length && lo < b.length && a[lo] === b[lo]) lo += 1;
|
|
151
|
-
let hiA = a.length;
|
|
152
|
-
let hiB = b.length;
|
|
153
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
154
|
-
hiA -= 1;
|
|
155
|
-
hiB -= 1;
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
prefix: a.slice(0, lo),
|
|
159
|
-
suffix: a.slice(hiA),
|
|
160
|
-
midA: a.slice(lo, hiA),
|
|
161
|
-
midB: b.slice(lo, hiB),
|
|
162
|
-
};
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const diffTokenMiddle = (midA, midB) => {
|
|
166
|
-
if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
167
|
-
return wholeChanged(midA, midB);
|
|
168
|
-
}
|
|
169
|
-
if (midA.length === 0 && midB.length === 0) {
|
|
170
|
-
return { oldSpans: [], newSpans: [] };
|
|
171
|
-
}
|
|
172
|
-
if (midA.length === 0 || midB.length === 0) return wholeChanged(midA, midB);
|
|
173
|
-
return lcsSpans(midA, midB);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
140
|
const joinAffix = (prefix, mid, suffix) => {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const at = hasPrefix ? 1 : 0;
|
|
182
|
-
if (hasPrefix) {
|
|
183
|
-
parts[0] = { text: prefix.join(''), changed: false };
|
|
184
|
-
}
|
|
185
|
-
for (let i = 0; i < mid.length; i++) parts[at + i] = mid[i];
|
|
186
|
-
if (hasSuffix) {
|
|
187
|
-
parts[at + mid.length] = { text: suffix.join(''), changed: false };
|
|
188
|
-
}
|
|
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 });
|
|
189
145
|
return collapseSpans(parts);
|
|
190
146
|
};
|
|
191
147
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
};
|
|
196
162
|
|
|
197
163
|
const diffTokens = (a, b) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
if (a.length === 0 || b.length === 0) return wholeChanged(a, b);
|
|
202
|
-
const trimmed = trimSharedEnds(a, b);
|
|
203
|
-
const mid = diffTokenMiddle(trimmed.midA, trimmed.midB);
|
|
204
|
-
const joined = joinTrimmedSpans(trimmed.prefix, mid, trimmed.suffix);
|
|
205
|
-
if (isNoisy(joined.oldSpans) || isNoisy(joined.newSpans)) {
|
|
164
|
+
const spans = diffTrimmed(a, b, opsSpans);
|
|
165
|
+
if (isNoisy(spans.oldSpans) || isNoisy(spans.newSpans)) {
|
|
206
166
|
return wholeChanged(a, b);
|
|
207
167
|
}
|
|
208
|
-
return
|
|
168
|
+
return spans;
|
|
209
169
|
};
|
|
210
170
|
|
|
211
171
|
const splitCamel = (part) => {
|
|
@@ -213,31 +173,23 @@ const splitCamel = (part) => {
|
|
|
213
173
|
return part.match(CAMEL_RE) ?? [part];
|
|
214
174
|
};
|
|
215
175
|
|
|
216
|
-
const splitIdentWords = (part) => {
|
|
217
|
-
const words = [];
|
|
218
|
-
for (const chunk of part.split(/(_+)/)) {
|
|
219
|
-
if (chunk === '') continue;
|
|
220
|
-
if (chunk.includes('_')) {
|
|
221
|
-
words.push(chunk);
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
for (const word of splitCamel(chunk)) words.push(word);
|
|
225
|
-
}
|
|
226
|
-
return words;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
176
|
const splitPieces = (text) => {
|
|
230
|
-
if (text === '') return [];
|
|
231
177
|
PIECE_RE.lastIndex = 0;
|
|
232
|
-
const coarse = text.match(PIECE_RE) ?? [];
|
|
233
178
|
const pieces = [];
|
|
234
|
-
for (const part of
|
|
235
|
-
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
|
+
}
|
|
236
188
|
}
|
|
237
189
|
return pieces;
|
|
238
190
|
};
|
|
239
191
|
|
|
240
|
-
const
|
|
192
|
+
const refineWordOps = (ops) => {
|
|
241
193
|
const oldParts = [];
|
|
242
194
|
const newParts = [];
|
|
243
195
|
let i = 0;
|
|
@@ -256,23 +208,18 @@ const spansFromWordOps = (ops) => {
|
|
|
256
208
|
else adds.push(ops[i].text);
|
|
257
209
|
i += 1;
|
|
258
210
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
tokenSimilarity(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (dels.length) {
|
|
271
|
-
oldParts.push({ text: dels.join(''), changed: true });
|
|
272
|
-
}
|
|
273
|
-
if (adds.length) {
|
|
274
|
-
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
|
+
}
|
|
275
220
|
}
|
|
221
|
+
if (dels.length) oldParts.push({ text: dels.join(''), changed: true });
|
|
222
|
+
if (adds.length) newParts.push({ text: adds.join(''), changed: true });
|
|
276
223
|
}
|
|
277
224
|
return {
|
|
278
225
|
oldSpans: collapseSpans(oldParts),
|
|
@@ -280,26 +227,76 @@ const spansFromWordOps = (ops) => {
|
|
|
280
227
|
};
|
|
281
228
|
};
|
|
282
229
|
|
|
230
|
+
const opRuns = (ops) => {
|
|
231
|
+
const runs = [];
|
|
232
|
+
for (const op of ops) {
|
|
233
|
+
const eq = op.type === 'eq';
|
|
234
|
+
let run = runs[runs.length - 1];
|
|
235
|
+
if (!run || run.eq !== eq) {
|
|
236
|
+
run = { eq, del: [], add: [] };
|
|
237
|
+
runs.push(run);
|
|
238
|
+
}
|
|
239
|
+
if (op.type !== 'add') run.del.push(op.text);
|
|
240
|
+
if (op.type !== 'del') run.add.push(op.text);
|
|
241
|
+
}
|
|
242
|
+
return runs;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const textSize = (parts) => parts.join('').length;
|
|
246
|
+
|
|
247
|
+
const editSize = (run) => Math.max(textSize(run.del), textSize(run.add));
|
|
248
|
+
|
|
249
|
+
const absorbTinyEquals = (runs) => {
|
|
250
|
+
for (let k = 1; k < runs.length - 1; k++) {
|
|
251
|
+
const before = runs[k - 1];
|
|
252
|
+
const eq = runs[k];
|
|
253
|
+
const after = runs[k + 1];
|
|
254
|
+
if (!eq.eq) continue;
|
|
255
|
+
const size = textSize(eq.del);
|
|
256
|
+
if (size > editSize(before) || size > editSize(after)) continue;
|
|
257
|
+
const del = [...before.del, ...eq.del, ...after.del];
|
|
258
|
+
const add = [...before.add, ...eq.add, ...after.add];
|
|
259
|
+
runs.splice(k - 1, 3, { eq: false, del, add });
|
|
260
|
+
k = 0;
|
|
261
|
+
}
|
|
262
|
+
return runs;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const runOps = (runs) => {
|
|
266
|
+
const ops = [];
|
|
267
|
+
for (const run of runs) {
|
|
268
|
+
if (run.eq) {
|
|
269
|
+
for (const text of run.del) ops.push({ type: 'eq', text });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
for (const text of run.del) ops.push({ type: 'del', text });
|
|
273
|
+
for (const text of run.add) ops.push({ type: 'add', text });
|
|
274
|
+
}
|
|
275
|
+
return ops;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const wordSpans = (ops) => refineWordOps(runOps(absorbTinyEquals(opRuns(ops))));
|
|
279
|
+
|
|
280
|
+
const sharedShare = (spans) => {
|
|
281
|
+
let shared = 0;
|
|
282
|
+
let total = 0;
|
|
283
|
+
for (const span of spans) {
|
|
284
|
+
const size = span.text.replace(/\s/g, '').length;
|
|
285
|
+
total += size;
|
|
286
|
+
if (!span.changed) shared += size;
|
|
287
|
+
}
|
|
288
|
+
return total === 0 ? 1 : shared / total;
|
|
289
|
+
};
|
|
290
|
+
|
|
283
291
|
const diffChars = (oldText, newText) => {
|
|
284
292
|
const a = splitPieces(oldText);
|
|
285
293
|
const b = splitPieces(newText);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
let mid;
|
|
292
|
-
if (midA.length === 0 && midB.length === 0) {
|
|
293
|
-
mid = { oldSpans: [], newSpans: [] };
|
|
294
|
-
} else if (midA.length === 0 || midB.length === 0) {
|
|
295
|
-
mid = wholeChanged(midA, midB);
|
|
296
|
-
} else if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
297
|
-
mid = wholeChanged(midA, midB);
|
|
298
|
-
} else {
|
|
299
|
-
mid = spansFromWordOps(lcsOps(midA, midB));
|
|
294
|
+
const { oldSpans, newSpans } = diffTrimmed(a, b, wordSpans);
|
|
295
|
+
const oldShare = sharedShare(oldSpans);
|
|
296
|
+
const newShare = sharedShare(newSpans);
|
|
297
|
+
if (oldShare < WHOLE_MIN_SHARED && newShare < WHOLE_MIN_SHARED) {
|
|
298
|
+
return wholeChanged(a, b);
|
|
300
299
|
}
|
|
301
|
-
const oldSpans = joinAffix(prefix, mid.oldSpans, suffix);
|
|
302
|
-
const newSpans = joinAffix(prefix, mid.newSpans, suffix);
|
|
303
300
|
return { oldSpans, newSpans };
|
|
304
301
|
};
|
|
305
302
|
|
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
|
|