reslop 0.1.8 → 0.2.0
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/README.md +27 -38
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +6 -2
- package/lib/api.js +2 -2
- package/lib/capabilities.js +10 -14
- package/lib/cli.js +28 -47
- package/lib/clipboard.js +1 -1
- package/lib/deps-diff.js +712 -0
- package/lib/deps.js +556 -32
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +34 -8
- package/lib/{git/branches.js → git-branches.js} +103 -10
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git.js +592 -65
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +22 -9
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +34 -66
- package/lib/render/chrome.js +57 -22
- package/lib/render/commits.js +85 -0
- package/lib/render/diff.js +16 -16
- package/lib/render/files.js +29 -37
- package/lib/render/frame.js +29 -13
- package/lib/render/notes.js +4 -3
- package/lib/render/primitives.js +46 -1
- package/lib/{render.js → render/render.js} +6 -7
- package/lib/render/unit.js +41 -0
- package/lib/review.js +750 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +93 -36
- package/lib/session/branches.js +130 -144
- package/lib/session/change-actions.js +206 -124
- package/lib/session/commit.js +217 -62
- package/lib/session/compose.js +158 -13
- package/lib/session/dispatch.js +109 -55
- package/lib/session/files-pane.js +145 -142
- package/lib/session/items.js +131 -34
- package/lib/session/lifecycle.js +82 -42
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +15 -3
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +58 -54
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +8 -12
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +82 -11
- package/lib/session/watch.js +11 -19
- package/lib/session.js +29 -226
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +2 -2
- package/lib/deps/changes.js +0 -331
- package/lib/deps/items.js +0 -131
- package/lib/deps/manifest.js +0 -145
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -177
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/diff/align.js
CHANGED
|
@@ -189,9 +189,6 @@ const mapHunkBlocks = (lines, onCtx, onChange) => {
|
|
|
189
189
|
return out;
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
-
const alignPairs = (lines) =>
|
|
193
|
-
mapHunkBlocks(lines, (line) => line, alignChangeBlock);
|
|
194
|
-
|
|
195
192
|
const alignSplitBlock = (dels, adds, delToAdd) => {
|
|
196
193
|
const out = [];
|
|
197
194
|
walkAligned(dels, adds, delToAdd, {
|
|
@@ -208,14 +205,14 @@ const alignSplitBlock = (dels, adds, delToAdd) => {
|
|
|
208
205
|
return out;
|
|
209
206
|
};
|
|
210
207
|
|
|
211
|
-
const splitCtxRow = (line) => ({ left: line, right: line });
|
|
212
|
-
|
|
213
|
-
const alignSplit = (lines) =>
|
|
214
|
-
mapHunkBlocks(lines, splitCtxRow, alignSplitBlock);
|
|
215
|
-
|
|
216
208
|
const LAYOUT_ALIGN = {
|
|
217
|
-
mixed:
|
|
218
|
-
side:
|
|
209
|
+
mixed: (lines) => mapHunkBlocks(lines, (line) => line, alignChangeBlock),
|
|
210
|
+
side: (lines) =>
|
|
211
|
+
mapHunkBlocks(
|
|
212
|
+
lines,
|
|
213
|
+
(line) => ({ left: line, right: line }),
|
|
214
|
+
alignSplitBlock,
|
|
215
|
+
),
|
|
219
216
|
};
|
|
220
217
|
|
|
221
218
|
module.exports = {
|
package/lib/diff/diff.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { diffChars } = require('./inline.js');
|
|
4
|
+
const { isCtxType, pairIndices, attachInline } = require('./align.js');
|
|
5
|
+
const { parseDiff } = require('./parse.js');
|
|
6
|
+
const patch = require('./patch.js');
|
|
7
|
+
const { splitHunk, flattenBlock, countSides, formatPatch } = patch;
|
|
8
|
+
const display = require('./display.js');
|
|
9
|
+
const { DISPLAY_CONTEXT, displayLines, blockAddText } = display;
|
|
10
|
+
const edit = require('./edit.js');
|
|
11
|
+
const { addBlockRange, replaceBlockAdds } = edit;
|
|
12
|
+
const items = require('./items.js');
|
|
13
|
+
const { synthesizeNewFile, itemsFromFiles, refreshIndexPatches } = items;
|
|
14
|
+
const unit = require('./unit.js');
|
|
15
|
+
const { unitLines, itemsForPath, blockLineRange, blockLineIndex } = unit;
|
|
16
|
+
const { hunkLineRange, hunkKey, sameHunk, splitEditor } = unit;
|
|
17
|
+
const { fileOffset, fileLines, sameBlock, splitFile } = unit;
|
|
18
|
+
const { fileLineIndex, attachEditSpans, mergeEditRows } = unit;
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
DISPLAY_CONTEXT,
|
|
22
|
+
isCtxType,
|
|
23
|
+
diffChars,
|
|
24
|
+
pairIndices,
|
|
25
|
+
attachInline,
|
|
26
|
+
parseDiff,
|
|
27
|
+
splitHunk,
|
|
28
|
+
flattenBlock,
|
|
29
|
+
countSides,
|
|
30
|
+
formatPatch,
|
|
31
|
+
displayLines,
|
|
32
|
+
blockAddText,
|
|
33
|
+
addBlockRange,
|
|
34
|
+
replaceBlockAdds,
|
|
35
|
+
synthesizeNewFile,
|
|
36
|
+
itemsFromFiles,
|
|
37
|
+
refreshIndexPatches,
|
|
38
|
+
splitFile,
|
|
39
|
+
splitEditor,
|
|
40
|
+
itemsForPath,
|
|
41
|
+
unitLines,
|
|
42
|
+
sameBlock,
|
|
43
|
+
sameHunk,
|
|
44
|
+
hunkKey,
|
|
45
|
+
blockLineRange,
|
|
46
|
+
hunkLineRange,
|
|
47
|
+
blockLineIndex,
|
|
48
|
+
fileOffset,
|
|
49
|
+
fileLines,
|
|
50
|
+
fileLineIndex,
|
|
51
|
+
attachEditSpans,
|
|
52
|
+
mergeEditRows,
|
|
53
|
+
};
|
package/lib/diff/display.js
CHANGED
|
@@ -46,14 +46,15 @@ const replaceAdds = (lines, overlay, blockId) => {
|
|
|
46
46
|
return lines.filter((line) => line.type !== 'add');
|
|
47
47
|
}
|
|
48
48
|
const texts = text.split('\n');
|
|
49
|
-
const
|
|
49
|
+
const { length } = texts;
|
|
50
|
+
const tagged = new Array(length);
|
|
50
51
|
let offset = 0;
|
|
51
|
-
for (let i = 0; i <
|
|
52
|
+
for (let i = 0; i < length; i++) {
|
|
52
53
|
const row = texts[i];
|
|
53
54
|
const start = offset;
|
|
54
55
|
const end = start + row.length;
|
|
55
|
-
const isLast = i ===
|
|
56
|
-
tagged
|
|
56
|
+
const isLast = i === length - 1;
|
|
57
|
+
tagged[i] = taggedAdd(row, blockId, start, end);
|
|
57
58
|
offset = isLast ? end : end + 1;
|
|
58
59
|
}
|
|
59
60
|
const out = [];
|
package/lib/diff/inline.js
CHANGED
|
@@ -72,13 +72,11 @@ const tokenSimilarity = (a, b) => {
|
|
|
72
72
|
return (2 * lcs) / (a.length + b.length);
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
const similarity = (left, right) =>
|
|
76
|
-
tokenSimilarity(graphemes(left), graphemes(right));
|
|
77
|
-
|
|
78
75
|
const fillLcsTable = (a, b) => {
|
|
79
76
|
const n = a.length;
|
|
80
77
|
const m = b.length;
|
|
81
|
-
const dp = Array
|
|
78
|
+
const dp = new Array(n + 1);
|
|
79
|
+
for (let i = 0; i <= n; i++) dp[i] = new Uint32Array(m + 1);
|
|
82
80
|
for (let i = 1; i <= n; i++) {
|
|
83
81
|
const ai = a[i - 1];
|
|
84
82
|
const row = dp[i];
|
|
@@ -92,30 +90,31 @@ const fillLcsTable = (a, b) => {
|
|
|
92
90
|
};
|
|
93
91
|
|
|
94
92
|
const tracebackLcs = (a, b, dp) => {
|
|
95
|
-
const rev = [];
|
|
93
|
+
const rev = new Array(a.length + b.length - dp[a.length][b.length]);
|
|
94
|
+
let n = 0;
|
|
96
95
|
let i = a.length;
|
|
97
96
|
let j = b.length;
|
|
98
97
|
while (i > 0 && j > 0) {
|
|
99
98
|
if (a[i - 1] === b[j - 1]) {
|
|
100
|
-
rev
|
|
99
|
+
rev[n++] = { type: 'eq', text: a[i - 1] };
|
|
101
100
|
i -= 1;
|
|
102
101
|
j -= 1;
|
|
103
102
|
continue;
|
|
104
103
|
}
|
|
105
104
|
if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
106
|
-
rev
|
|
105
|
+
rev[n++] = { type: 'del', text: a[i - 1] };
|
|
107
106
|
i -= 1;
|
|
108
107
|
continue;
|
|
109
108
|
}
|
|
110
|
-
rev
|
|
109
|
+
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
111
110
|
j -= 1;
|
|
112
111
|
}
|
|
113
112
|
while (i > 0) {
|
|
114
|
-
rev
|
|
113
|
+
rev[n++] = { type: 'del', text: a[i - 1] };
|
|
115
114
|
i -= 1;
|
|
116
115
|
}
|
|
117
116
|
while (j > 0) {
|
|
118
|
-
rev
|
|
117
|
+
rev[n++] = { type: 'add', text: b[j - 1] };
|
|
119
118
|
j -= 1;
|
|
120
119
|
}
|
|
121
120
|
rev.reverse();
|
|
@@ -174,13 +173,17 @@ const diffTokenMiddle = (midA, midB) => {
|
|
|
174
173
|
};
|
|
175
174
|
|
|
176
175
|
const joinAffix = (prefix, mid, suffix) => {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
const hasPrefix = prefix.length > 0;
|
|
177
|
+
const hasSuffix = suffix.length > 0;
|
|
178
|
+
const extra = (hasPrefix ? 1 : 0) + (hasSuffix ? 1 : 0);
|
|
179
|
+
const parts = new Array(mid.length + extra);
|
|
180
|
+
const at = hasPrefix ? 1 : 0;
|
|
181
|
+
if (hasPrefix) {
|
|
182
|
+
parts[0] = { text: prefix.join(''), changed: false };
|
|
180
183
|
}
|
|
181
|
-
for (
|
|
182
|
-
if (
|
|
183
|
-
parts.
|
|
184
|
+
for (let i = 0; i < mid.length; i++) parts[at + i] = mid[i];
|
|
185
|
+
if (hasSuffix) {
|
|
186
|
+
parts[at + mid.length] = { text: suffix.join(''), changed: false };
|
|
184
187
|
}
|
|
185
188
|
return collapseSpans(parts);
|
|
186
189
|
};
|
|
@@ -253,7 +256,10 @@ const spansFromWordOps = (ops) => {
|
|
|
253
256
|
i += 1;
|
|
254
257
|
}
|
|
255
258
|
const one = dels.length === 1 && adds.length === 1;
|
|
256
|
-
const close =
|
|
259
|
+
const close =
|
|
260
|
+
one &&
|
|
261
|
+
tokenSimilarity(graphemes(dels[0]), graphemes(adds[0])) >=
|
|
262
|
+
WORD_REFINE_MIN;
|
|
257
263
|
if (close) {
|
|
258
264
|
const inner = diffTokens(graphemes(dels[0]), graphemes(adds[0]));
|
|
259
265
|
for (const span of inner.oldSpans) oldParts.push(span);
|
|
@@ -291,10 +297,9 @@ const diffChars = (oldText, newText) => {
|
|
|
291
297
|
} else {
|
|
292
298
|
mid = spansFromWordOps(lcsOps(midA, midB));
|
|
293
299
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
300
|
+
const oldSpans = joinAffix(prefix, mid.oldSpans, suffix);
|
|
301
|
+
const newSpans = joinAffix(prefix, mid.newSpans, suffix);
|
|
302
|
+
return { oldSpans, newSpans };
|
|
298
303
|
};
|
|
299
304
|
|
|
300
305
|
module.exports = { tokenSimilarity, diffChars };
|
package/lib/diff/items.js
CHANGED
|
@@ -82,14 +82,16 @@ const refreshIndexPatches = (items, item) => {
|
|
|
82
82
|
if (other.hunk !== item.hunk || other.file !== item.file) continue;
|
|
83
83
|
sides[other.blockId] = INDEX_SIDE[other.origin] ?? 'old';
|
|
84
84
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
85
|
+
const { length } = items;
|
|
86
|
+
const next = new Array(length);
|
|
87
|
+
for (let i = 0; i < length; i++) {
|
|
88
|
+
const other = items[i];
|
|
87
89
|
if (other.hunk !== item.hunk || other.file !== item.file) {
|
|
88
|
-
next
|
|
90
|
+
next[i] = other;
|
|
89
91
|
continue;
|
|
90
92
|
}
|
|
91
93
|
const patchAdd = formatPatch(other.file, other.hunk, other.blockId, sides);
|
|
92
|
-
next
|
|
94
|
+
next[i] = { ...other, patchAdd };
|
|
93
95
|
}
|
|
94
96
|
return next;
|
|
95
97
|
};
|
package/lib/diff/patch.js
CHANGED
|
@@ -70,10 +70,8 @@ const countSides = (lines) => {
|
|
|
70
70
|
const formatHunkHeader = (oldStart, oldCount, newStart, newCount) =>
|
|
71
71
|
`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`;
|
|
72
72
|
|
|
73
|
-
const lineMark = (type) => LINE_MARK[type] ?? ' ';
|
|
74
|
-
|
|
75
73
|
const formatBodyLine = (line) => {
|
|
76
|
-
const rows = [`${
|
|
74
|
+
const rows = [`${LINE_MARK[line.type] ?? ' '}${line.text}`];
|
|
77
75
|
if (line.noNl) rows.push('\');
|
|
78
76
|
return rows;
|
|
79
77
|
};
|
package/lib/diff/unit.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { attachInline } = require('./align.js');
|
|
4
|
+
const { listPath } = require('../files.js');
|
|
5
|
+
|
|
6
|
+
const splitEditor = (text) => {
|
|
7
|
+
const lines = `${text}`.split('\n');
|
|
8
|
+
if (!lines.length) return [''];
|
|
9
|
+
return lines;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const splitFile = (text) => {
|
|
13
|
+
if (!text) return [];
|
|
14
|
+
const lines = `${text}`.split('\n');
|
|
15
|
+
if (lines.length && lines[lines.length - 1] === '') lines.pop();
|
|
16
|
+
return lines;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const hunkKey = (item) => {
|
|
20
|
+
if (!item) return '';
|
|
21
|
+
const rel = listPath(item);
|
|
22
|
+
const hunk = item.hunk;
|
|
23
|
+
if (!hunk) return `${rel}\0${item.blockId ?? ''}`;
|
|
24
|
+
return `${rel}\0${hunk.oldStart}\0${hunk.newStart}\0${hunk.header}`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const sameHunk = (left, right) => {
|
|
28
|
+
if (!left || !right) return false;
|
|
29
|
+
if (left.hunk && right.hunk && left.hunk === right.hunk) {
|
|
30
|
+
return listPath(left) === listPath(right);
|
|
31
|
+
}
|
|
32
|
+
const key = hunkKey(left);
|
|
33
|
+
if (!key) return false;
|
|
34
|
+
return key === hunkKey(right);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const emptyMarks = (last) => {
|
|
38
|
+
const size = last + 1;
|
|
39
|
+
const dels = new Array(size);
|
|
40
|
+
const adds = new Array(size);
|
|
41
|
+
for (let i = 0; i < size; i++) {
|
|
42
|
+
dels[i] = [];
|
|
43
|
+
adds[i] = null;
|
|
44
|
+
}
|
|
45
|
+
return { dels, adds };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const clipAt = (n, last) => Math.min(Math.max(1, n), last);
|
|
49
|
+
|
|
50
|
+
const applyHunk = (marks, item) => {
|
|
51
|
+
const hunk = item.hunk;
|
|
52
|
+
if (!hunk || !hunk.lines) return;
|
|
53
|
+
const last = marks.dels.length - 1;
|
|
54
|
+
let newLine = hunk.newStart > 0 ? hunk.newStart : 1;
|
|
55
|
+
for (const line of hunk.lines) {
|
|
56
|
+
const extra = {
|
|
57
|
+
text: line.text ?? '',
|
|
58
|
+
item,
|
|
59
|
+
blockId: line.blockId,
|
|
60
|
+
origin: item.origin,
|
|
61
|
+
};
|
|
62
|
+
if (line.type === 'del') {
|
|
63
|
+
marks.dels[clipAt(newLine, last)].push(extra);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (line.type === 'add') {
|
|
67
|
+
marks.adds[clipAt(newLine, last)] = extra;
|
|
68
|
+
newLine += 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
newLine += 1;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const tagged = (type, text, extra = {}) => ({
|
|
76
|
+
type,
|
|
77
|
+
text,
|
|
78
|
+
noNl: false,
|
|
79
|
+
blockId: extra.blockId ?? null,
|
|
80
|
+
origin: extra.origin ?? '',
|
|
81
|
+
item: extra.item ?? null,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const itemsForPath = (items, rel) => {
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
if (listPath(item) === rel) out.push(item);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const rowsFromAdds = (items) => {
|
|
93
|
+
const rows = [];
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
const lines = item.hunk && item.hunk.lines ? item.hunk.lines : [];
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
if (line.type === 'add') rows.push(line.text ?? '');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return rows;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const unitLines = (text, items) => {
|
|
104
|
+
let rows = splitFile(text);
|
|
105
|
+
if (!rows.length) rows = rowsFromAdds(items);
|
|
106
|
+
const marks = emptyMarks(rows.length + 1);
|
|
107
|
+
for (const item of items) applyHunk(marks, item);
|
|
108
|
+
const out = [];
|
|
109
|
+
for (let i = 0; i < rows.length; i++) {
|
|
110
|
+
const n = i + 1;
|
|
111
|
+
for (const del of marks.dels[n]) out.push(tagged('del', del.text, del));
|
|
112
|
+
const add = marks.adds[n];
|
|
113
|
+
if (add) out.push(tagged('add', rows[i], add));
|
|
114
|
+
else out.push(tagged('ctx', rows[i], {}));
|
|
115
|
+
}
|
|
116
|
+
for (const del of marks.dels[rows.length + 1]) {
|
|
117
|
+
out.push(tagged('del', del.text, del));
|
|
118
|
+
}
|
|
119
|
+
if (!out.length) out.push(tagged('ctx', '', {}));
|
|
120
|
+
return attachInline(out);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const sameBlock = (line, item) => {
|
|
124
|
+
if (!line || !item) return false;
|
|
125
|
+
if (line.item !== item) return false;
|
|
126
|
+
return line.blockId === item.blockId;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const blockLineRange = (lines, item) => {
|
|
130
|
+
let start = 0;
|
|
131
|
+
let end = 0;
|
|
132
|
+
let found = false;
|
|
133
|
+
if (!item) return { start, end };
|
|
134
|
+
for (let i = 0; i < lines.length; i++) {
|
|
135
|
+
const line = lines[i];
|
|
136
|
+
if (!sameBlock(line, item)) continue;
|
|
137
|
+
if (line.type === 'ctx') continue;
|
|
138
|
+
if (!found) {
|
|
139
|
+
start = i;
|
|
140
|
+
found = true;
|
|
141
|
+
}
|
|
142
|
+
end = i;
|
|
143
|
+
}
|
|
144
|
+
return { start, end };
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const blockLineIndex = (lines, item) => blockLineRange(lines, item).start;
|
|
148
|
+
|
|
149
|
+
const hunkLineRange = (lines, item) => {
|
|
150
|
+
let start = 0;
|
|
151
|
+
let end = 0;
|
|
152
|
+
let found = false;
|
|
153
|
+
if (!item) return { start, end };
|
|
154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
155
|
+
const line = lines[i];
|
|
156
|
+
if (!sameHunk(line.item, item)) continue;
|
|
157
|
+
if (line.type === 'ctx') continue;
|
|
158
|
+
if (!found) {
|
|
159
|
+
start = i;
|
|
160
|
+
found = true;
|
|
161
|
+
}
|
|
162
|
+
end = i;
|
|
163
|
+
}
|
|
164
|
+
return { start, end };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const fileOffset = (lines, at) => {
|
|
168
|
+
let offset = 0;
|
|
169
|
+
const target = Math.max(0, at);
|
|
170
|
+
for (let i = 0; i < lines.length; i++) {
|
|
171
|
+
const line = lines[i];
|
|
172
|
+
if (line.type === 'del') continue;
|
|
173
|
+
if (i >= target) return offset;
|
|
174
|
+
offset += (line.text ?? '').length + 1;
|
|
175
|
+
}
|
|
176
|
+
return offset;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const fileLineIndex = (lines, fileLine) => {
|
|
180
|
+
let n = 0;
|
|
181
|
+
let last = 0;
|
|
182
|
+
for (let i = 0; i < lines.length; i++) {
|
|
183
|
+
if (lines[i].type === 'del') continue;
|
|
184
|
+
last = i;
|
|
185
|
+
if (n === fileLine) return i;
|
|
186
|
+
n += 1;
|
|
187
|
+
}
|
|
188
|
+
return last;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const lastEditableIndex = (lines) => {
|
|
192
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
193
|
+
if (lines[i].type !== 'del') return i;
|
|
194
|
+
}
|
|
195
|
+
return -1;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const attachEditSpans = (lines) => {
|
|
199
|
+
let offset = 0;
|
|
200
|
+
const lastAt = lastEditableIndex(lines);
|
|
201
|
+
for (let i = 0; i < lines.length; i++) {
|
|
202
|
+
const line = lines[i];
|
|
203
|
+
if (line.type === 'del') continue;
|
|
204
|
+
const text = line.text ?? '';
|
|
205
|
+
const last = i === lastAt;
|
|
206
|
+
line.wrap = true;
|
|
207
|
+
line.editStart = offset;
|
|
208
|
+
line.editEnd = offset + text.length;
|
|
209
|
+
line.editLast = last;
|
|
210
|
+
offset = last ? offset + text.length : offset + text.length + 1;
|
|
211
|
+
}
|
|
212
|
+
return lines;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const LCS_CELLS = 4000000;
|
|
216
|
+
|
|
217
|
+
const lcsTable = (prev, next) => {
|
|
218
|
+
const n = prev.length;
|
|
219
|
+
const m = next.length;
|
|
220
|
+
const dp = new Array(n + 1);
|
|
221
|
+
for (let i = 0; i <= n; i++) dp[i] = new Array(m + 1).fill(0);
|
|
222
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
223
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
224
|
+
if (prev[i] === next[j]) dp[i][j] = dp[i + 1][j + 1] + 1;
|
|
225
|
+
else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return dp;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const zipOps = (prev, next) => {
|
|
232
|
+
const n = Math.min(prev.length, next.length);
|
|
233
|
+
const ops = new Array(Math.max(prev.length, next.length));
|
|
234
|
+
let at = 0;
|
|
235
|
+
for (let i = 0; i < n; i++) ops[at++] = { kind: 'rep', text: next[i] };
|
|
236
|
+
for (let i = n; i < prev.length; i++) ops[at++] = { kind: 'del' };
|
|
237
|
+
for (let i = n; i < next.length; i++) {
|
|
238
|
+
ops[at++] = { kind: 'ins', text: next[i] };
|
|
239
|
+
}
|
|
240
|
+
return ops;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const walkLcs = (prev, next, dp) => {
|
|
244
|
+
const ops = new Array(prev.length + next.length - dp[0][0]);
|
|
245
|
+
let n = 0;
|
|
246
|
+
let i = 0;
|
|
247
|
+
let j = 0;
|
|
248
|
+
while (i < prev.length && j < next.length) {
|
|
249
|
+
if (prev[i] === next[j]) {
|
|
250
|
+
ops[n++] = { kind: 'eq', text: next[j] };
|
|
251
|
+
i += 1;
|
|
252
|
+
j += 1;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
256
|
+
ops[n++] = { kind: 'del' };
|
|
257
|
+
i += 1;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
ops[n++] = { kind: 'ins', text: next[j] };
|
|
261
|
+
j += 1;
|
|
262
|
+
}
|
|
263
|
+
while (i < prev.length) {
|
|
264
|
+
ops[n++] = { kind: 'del' };
|
|
265
|
+
i += 1;
|
|
266
|
+
}
|
|
267
|
+
while (j < next.length) {
|
|
268
|
+
ops[n++] = { kind: 'ins', text: next[j] };
|
|
269
|
+
j += 1;
|
|
270
|
+
}
|
|
271
|
+
return ops;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const collapseRep = (ops) => {
|
|
275
|
+
const out = [];
|
|
276
|
+
for (let i = 0; i < ops.length; i++) {
|
|
277
|
+
const op = ops[i];
|
|
278
|
+
const next = ops[i + 1];
|
|
279
|
+
if (op.kind === 'del' && next && next.kind === 'ins') {
|
|
280
|
+
out.push({ kind: 'rep', text: next.text });
|
|
281
|
+
i += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (op.kind === 'ins' && next && next.kind === 'del') {
|
|
285
|
+
out.push({ kind: 'rep', text: op.text });
|
|
286
|
+
i += 1;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
out.push(op);
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const lineOps = (prev, next) => {
|
|
295
|
+
if (prev.length === next.length) return zipOps(prev, next);
|
|
296
|
+
if (prev.length * next.length > LCS_CELLS) return zipOps(prev, next);
|
|
297
|
+
return collapseRep(walkLcs(prev, next, lcsTable(prev, next)));
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const copyRow = (row, text) => ({
|
|
301
|
+
type: row.type,
|
|
302
|
+
text,
|
|
303
|
+
noNl: row.noNl === true,
|
|
304
|
+
blockId: row.blockId ?? null,
|
|
305
|
+
origin: row.origin ?? '',
|
|
306
|
+
item: row.item ?? null,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const pushDels = (out, base, at) => {
|
|
310
|
+
let i = at;
|
|
311
|
+
while (i < base.length && base[i].type === 'del') {
|
|
312
|
+
out.push(copyRow(base[i], base[i].text ?? ''));
|
|
313
|
+
i += 1;
|
|
314
|
+
}
|
|
315
|
+
return i;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const mergeEditRows = (baseRows, nextTexts) => {
|
|
319
|
+
const base = baseRows ?? [];
|
|
320
|
+
const texts = nextTexts && nextTexts.length ? nextTexts : [''];
|
|
321
|
+
const prev = [];
|
|
322
|
+
for (const row of base) {
|
|
323
|
+
if (row.type === 'del') continue;
|
|
324
|
+
prev.push(row.text ?? '');
|
|
325
|
+
}
|
|
326
|
+
const ops = lineOps(prev, texts);
|
|
327
|
+
const out = [];
|
|
328
|
+
let at = 0;
|
|
329
|
+
for (const op of ops) {
|
|
330
|
+
at = pushDels(out, base, at);
|
|
331
|
+
if (op.kind === 'ins') {
|
|
332
|
+
out.push(tagged('ctx', op.text, {}));
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (at >= base.length) continue;
|
|
336
|
+
if (op.kind === 'del') {
|
|
337
|
+
at += 1;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
out.push(copyRow(base[at], op.text));
|
|
341
|
+
at += 1;
|
|
342
|
+
}
|
|
343
|
+
pushDels(out, base, at);
|
|
344
|
+
if (!out.length) out.push(tagged('ctx', '', {}));
|
|
345
|
+
return attachEditSpans(attachInline(out));
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const fileLines = (text) => {
|
|
349
|
+
const rows = splitFile(text);
|
|
350
|
+
if (!rows.length) rows.push('');
|
|
351
|
+
const { length } = rows;
|
|
352
|
+
const out = new Array(length);
|
|
353
|
+
let offset = 0;
|
|
354
|
+
for (let i = 0; i < length; i++) {
|
|
355
|
+
const row = rows[i];
|
|
356
|
+
const start = offset;
|
|
357
|
+
const end = start + row.length;
|
|
358
|
+
const last = i === length - 1;
|
|
359
|
+
out[i] = {
|
|
360
|
+
type: 'ctx',
|
|
361
|
+
text: row,
|
|
362
|
+
noNl: false,
|
|
363
|
+
wrap: true,
|
|
364
|
+
editStart: start,
|
|
365
|
+
editEnd: end,
|
|
366
|
+
editLast: last,
|
|
367
|
+
blockId: null,
|
|
368
|
+
origin: '',
|
|
369
|
+
item: null,
|
|
370
|
+
};
|
|
371
|
+
offset = last ? end : end + 1;
|
|
372
|
+
}
|
|
373
|
+
return out;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
module.exports = {
|
|
377
|
+
splitFile,
|
|
378
|
+
splitEditor,
|
|
379
|
+
itemsForPath,
|
|
380
|
+
unitLines,
|
|
381
|
+
sameBlock,
|
|
382
|
+
sameHunk,
|
|
383
|
+
hunkKey,
|
|
384
|
+
blockLineRange,
|
|
385
|
+
hunkLineRange,
|
|
386
|
+
blockLineIndex,
|
|
387
|
+
fileOffset,
|
|
388
|
+
fileLineIndex,
|
|
389
|
+
attachEditSpans,
|
|
390
|
+
mergeEditRows,
|
|
391
|
+
fileLines,
|
|
392
|
+
};
|