reslop 0.1.0 → 0.1.2
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 -9
- package/README.md +25 -40
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +0 -1
- package/lib/api.js +5 -0
- package/lib/cli.js +84 -79
- package/lib/clipboard.js +1 -1
- package/lib/deps.js +1440 -0
- package/lib/detect.js +4 -4
- package/lib/diff.js +40 -63
- package/lib/editor.js +1 -1
- package/lib/files.js +23 -4
- package/lib/git.js +367 -18
- package/lib/github.js +625 -0
- package/lib/highlight.js +149 -166
- package/lib/keys.js +34 -41
- package/lib/render.js +218 -143
- package/lib/review.js +52 -45
- package/lib/select.js +21 -31
- package/lib/session.js +298 -235
- package/lib/source.js +30 -0
- package/package.json +37 -18
package/lib/detect.js
CHANGED
|
@@ -46,9 +46,9 @@ const PROSE_BASENAMES = [
|
|
|
46
46
|
|
|
47
47
|
const fileExt = (filePath) => {
|
|
48
48
|
const base = path.basename(filePath || '');
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
return base.slice(
|
|
49
|
+
const dot = base.lastIndexOf('.');
|
|
50
|
+
if (dot <= 0) return '';
|
|
51
|
+
return base.slice(dot + 1).toLowerCase();
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
const mapLang = (name) => {
|
|
@@ -81,4 +81,4 @@ const detectLang = (filePath) => {
|
|
|
81
81
|
return 'dot';
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
module.exports = {
|
|
84
|
+
module.exports = { mapLang, detectLang };
|
package/lib/diff.js
CHANGED
|
@@ -11,22 +11,29 @@ const PAIR_TOKEN_WEIGHT = 0.6;
|
|
|
11
11
|
const WORD_REFINE_MIN = 0.6;
|
|
12
12
|
const TOKEN_RE = /[A-Za-z_$][\w$]*|\d+|[^\s]/g;
|
|
13
13
|
const PIECE_RE = /[A-Za-z_$][\w$]*|\d+|\s+|./g;
|
|
14
|
-
const LINE_MARK =
|
|
14
|
+
const LINE_MARK = {
|
|
15
15
|
add: '+',
|
|
16
16
|
del: '-',
|
|
17
17
|
ctx: ' ',
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
warn: ' ',
|
|
19
|
+
note: ' ',
|
|
20
|
+
noteSep: ' ',
|
|
21
|
+
};
|
|
22
|
+
const CTX_TYPES = ['ctx', 'warn', 'note', 'noteSep'];
|
|
23
|
+
const LINE_TYPE = {
|
|
20
24
|
'+': 'add',
|
|
21
25
|
'-': 'del',
|
|
22
26
|
' ': 'ctx',
|
|
23
|
-
}
|
|
24
|
-
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const isCtxType = (type) => CTX_TYPES.includes(type);
|
|
30
|
+
const INDEX_SIDE = {
|
|
25
31
|
staged: 'new',
|
|
26
32
|
unstaged: 'old',
|
|
27
33
|
untracked: 'old',
|
|
28
34
|
commit: 'old',
|
|
29
|
-
|
|
35
|
+
pr: 'old',
|
|
36
|
+
};
|
|
30
37
|
|
|
31
38
|
const collapseSpans = (parts) => {
|
|
32
39
|
const spans = [];
|
|
@@ -235,27 +242,23 @@ const diffTokenMiddle = (midA, midB) => {
|
|
|
235
242
|
return lcsSpans(midA, midB);
|
|
236
243
|
};
|
|
237
244
|
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
const newParts = [];
|
|
245
|
+
const joinAffix = (prefix, mid, suffix) => {
|
|
246
|
+
const parts = [];
|
|
241
247
|
if (prefix.length) {
|
|
242
|
-
|
|
243
|
-
oldParts.push({ text, changed: false });
|
|
244
|
-
newParts.push({ text, changed: false });
|
|
248
|
+
parts.push({ text: prefix.join(''), changed: false });
|
|
245
249
|
}
|
|
246
|
-
for (const span of mid
|
|
247
|
-
for (const span of mid.newSpans) newParts.push(span);
|
|
250
|
+
for (const span of mid) parts.push(span);
|
|
248
251
|
if (suffix.length) {
|
|
249
|
-
|
|
250
|
-
oldParts.push({ text, changed: false });
|
|
251
|
-
newParts.push({ text, changed: false });
|
|
252
|
+
parts.push({ text: suffix.join(''), changed: false });
|
|
252
253
|
}
|
|
253
|
-
return
|
|
254
|
-
oldSpans: collapseSpans(oldParts),
|
|
255
|
-
newSpans: collapseSpans(newParts),
|
|
256
|
-
};
|
|
254
|
+
return collapseSpans(parts);
|
|
257
255
|
};
|
|
258
256
|
|
|
257
|
+
const joinTrimmedSpans = (prefix, mid, suffix) => ({
|
|
258
|
+
oldSpans: joinAffix(prefix, mid.oldSpans, suffix),
|
|
259
|
+
newSpans: joinAffix(prefix, mid.newSpans, suffix),
|
|
260
|
+
});
|
|
261
|
+
|
|
259
262
|
const diffTokens = (a, b) => {
|
|
260
263
|
if (a.length === 0 && b.length === 0) {
|
|
261
264
|
return { oldSpans: [], newSpans: [] };
|
|
@@ -330,18 +333,6 @@ const spansFromWordOps = (ops) => {
|
|
|
330
333
|
};
|
|
331
334
|
};
|
|
332
335
|
|
|
333
|
-
const joinAffix = (prefix, mid, suffix) => {
|
|
334
|
-
const parts = [];
|
|
335
|
-
if (prefix.length) {
|
|
336
|
-
parts.push({ text: prefix.join(''), changed: false });
|
|
337
|
-
}
|
|
338
|
-
for (const span of mid) parts.push(span);
|
|
339
|
-
if (suffix.length) {
|
|
340
|
-
parts.push({ text: suffix.join(''), changed: false });
|
|
341
|
-
}
|
|
342
|
-
return collapseSpans(parts);
|
|
343
|
-
};
|
|
344
|
-
|
|
345
336
|
const diffChars = (oldText, newText) => {
|
|
346
337
|
const a = splitPieces(oldText);
|
|
347
338
|
const b = splitPieces(newText);
|
|
@@ -349,18 +340,7 @@ const diffChars = (oldText, newText) => {
|
|
|
349
340
|
return { oldSpans: [], newSpans: [] };
|
|
350
341
|
}
|
|
351
342
|
if (a.length === 0 || b.length === 0) return wholeChanged(a, b);
|
|
352
|
-
|
|
353
|
-
while (lo < a.length && lo < b.length && a[lo] === b[lo]) lo += 1;
|
|
354
|
-
let hiA = a.length;
|
|
355
|
-
let hiB = b.length;
|
|
356
|
-
while (hiA > lo && hiB > lo && a[hiA - 1] === b[hiB - 1]) {
|
|
357
|
-
hiA -= 1;
|
|
358
|
-
hiB -= 1;
|
|
359
|
-
}
|
|
360
|
-
const prefix = a.slice(0, lo);
|
|
361
|
-
const suffix = a.slice(hiA);
|
|
362
|
-
const midA = a.slice(lo, hiA);
|
|
363
|
-
const midB = b.slice(lo, hiB);
|
|
343
|
+
const { prefix, suffix, midA, midB } = trimSharedEnds(a, b);
|
|
364
344
|
let mid;
|
|
365
345
|
if (midA.length === 0 && midB.length === 0) {
|
|
366
346
|
mid = { oldSpans: [], newSpans: [] };
|
|
@@ -432,7 +412,7 @@ const attachInline = (lines) => {
|
|
|
432
412
|
);
|
|
433
413
|
applyInlineSpans(dels, adds, delToAdd);
|
|
434
414
|
for (const line of lines) {
|
|
435
|
-
if (line.type
|
|
415
|
+
if (isCtxType(line.type)) {
|
|
436
416
|
line.spans = [{ text: line.text, changed: false }];
|
|
437
417
|
}
|
|
438
418
|
}
|
|
@@ -500,13 +480,13 @@ const mapHunkBlocks = (lines, onCtx, onChange) => {
|
|
|
500
480
|
const out = [];
|
|
501
481
|
let i = 0;
|
|
502
482
|
while (i < lines.length) {
|
|
503
|
-
if (lines[i].type
|
|
483
|
+
if (isCtxType(lines[i].type)) {
|
|
504
484
|
out.push(onCtx(lines[i]));
|
|
505
485
|
i += 1;
|
|
506
486
|
continue;
|
|
507
487
|
}
|
|
508
488
|
const start = i;
|
|
509
|
-
while (i < lines.length && lines[i].type
|
|
489
|
+
while (i < lines.length && !isCtxType(lines[i].type)) i += 1;
|
|
510
490
|
const block = lines.slice(start, i);
|
|
511
491
|
const { dels, adds } = changeSides(block);
|
|
512
492
|
const delToAdd = pairIndices(
|
|
@@ -697,7 +677,7 @@ const splitHunk = (hunk) => {
|
|
|
697
677
|
let current = null;
|
|
698
678
|
let blockId = -1;
|
|
699
679
|
const lines = hunk.lines.map((line) => {
|
|
700
|
-
if (line.type
|
|
680
|
+
if (isCtxType(line.type)) {
|
|
701
681
|
current = null;
|
|
702
682
|
return { ...line, blockId: null };
|
|
703
683
|
}
|
|
@@ -721,7 +701,7 @@ const otherContext = (contextSide, blockId) => {
|
|
|
721
701
|
const flattenBlock = (hunk, blockId, contextSide) => {
|
|
722
702
|
const out = [];
|
|
723
703
|
for (const line of hunk.lines) {
|
|
724
|
-
if (line.blockId === blockId || line.type
|
|
704
|
+
if (line.blockId === blockId || isCtxType(line.type)) {
|
|
725
705
|
out.push(line);
|
|
726
706
|
continue;
|
|
727
707
|
}
|
|
@@ -741,8 +721,8 @@ const countSides = (lines) => {
|
|
|
741
721
|
let oldCount = 0;
|
|
742
722
|
let newCount = 0;
|
|
743
723
|
for (const line of lines) {
|
|
744
|
-
if (line.type
|
|
745
|
-
if (line.type
|
|
724
|
+
if (isCtxType(line.type) || line.type === 'del') oldCount += 1;
|
|
725
|
+
if (isCtxType(line.type) || line.type === 'add') newCount += 1;
|
|
746
726
|
}
|
|
747
727
|
return { oldCount, newCount };
|
|
748
728
|
};
|
|
@@ -774,10 +754,10 @@ const formatPatch = (file, hunk, blockId, contextSide) => {
|
|
|
774
754
|
return `${parts.join('\n')}\n`;
|
|
775
755
|
};
|
|
776
756
|
|
|
777
|
-
const LAYOUT_ALIGN =
|
|
757
|
+
const LAYOUT_ALIGN = {
|
|
778
758
|
mixed: alignPairs,
|
|
779
759
|
side: alignSplit,
|
|
780
|
-
}
|
|
760
|
+
};
|
|
781
761
|
|
|
782
762
|
const windowAroundBlock = (lines, blockId, radius) => {
|
|
783
763
|
let start = -1;
|
|
@@ -793,9 +773,10 @@ const windowAroundBlock = (lines, blockId, radius) => {
|
|
|
793
773
|
return lines.slice(from, to);
|
|
794
774
|
};
|
|
795
775
|
|
|
796
|
-
const displayLines = (hunk, blockId, layout = 'unified') => {
|
|
776
|
+
const displayLines = (hunk, blockId, layout = 'unified', radius) => {
|
|
797
777
|
const flat = flattenBlock(hunk, blockId, 'new');
|
|
798
|
-
const
|
|
778
|
+
const around = radius ?? DISPLAY_CONTEXT;
|
|
779
|
+
const windowed = windowAroundBlock(flat, blockId, around);
|
|
799
780
|
const lines = windowed.map((line) => ({ ...line }));
|
|
800
781
|
const painted = attachInline(lines);
|
|
801
782
|
const align = LAYOUT_ALIGN[layout];
|
|
@@ -875,7 +856,7 @@ const refreshIndexPatches = (items, item) => {
|
|
|
875
856
|
siblings.push(other);
|
|
876
857
|
}
|
|
877
858
|
}
|
|
878
|
-
const sides =
|
|
859
|
+
const sides = {};
|
|
879
860
|
for (const other of siblings) {
|
|
880
861
|
sides[other.blockId] = INDEX_SIDE[other.origin] ?? 'old';
|
|
881
862
|
}
|
|
@@ -885,22 +866,18 @@ const refreshIndexPatches = (items, item) => {
|
|
|
885
866
|
};
|
|
886
867
|
|
|
887
868
|
module.exports = {
|
|
888
|
-
LCS_CELL_LIMIT,
|
|
889
869
|
DISPLAY_CONTEXT,
|
|
890
|
-
|
|
870
|
+
isCtxType,
|
|
891
871
|
diffChars,
|
|
892
|
-
similarity,
|
|
893
|
-
pairScore,
|
|
894
872
|
pairIndices,
|
|
895
873
|
attachInline,
|
|
896
874
|
parseDiff,
|
|
897
875
|
splitHunk,
|
|
898
876
|
flattenBlock,
|
|
877
|
+
countSides,
|
|
899
878
|
formatPatch,
|
|
900
879
|
displayLines,
|
|
901
|
-
alignSplit,
|
|
902
880
|
synthesizeNewFile,
|
|
903
881
|
itemsFromFiles,
|
|
904
882
|
refreshIndexPatches,
|
|
905
|
-
countSides,
|
|
906
883
|
};
|
package/lib/editor.js
CHANGED
package/lib/files.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const REVIEW_DIR = '.review';
|
|
4
|
+
const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
|
|
5
|
+
|
|
6
|
+
const normalizePathSpec = (spec) =>
|
|
7
|
+
`${spec ?? ''}`.replaceAll('\\', '/').replace(/\/+$/, '');
|
|
8
|
+
|
|
9
|
+
const isPathInScope = (rel, paths) => {
|
|
10
|
+
if (!paths.length) return true;
|
|
11
|
+
for (const spec of paths) {
|
|
12
|
+
const norm = normalizePathSpec(spec);
|
|
13
|
+
if (!norm || norm === '.') return true;
|
|
14
|
+
if (rel === norm || rel.startsWith(`${norm}/`)) return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
4
18
|
|
|
5
19
|
const itemPath = (item) => {
|
|
6
20
|
if (!item || !item.file) return '';
|
|
@@ -19,10 +33,9 @@ const fileStatus = (origins) => {
|
|
|
19
33
|
return key;
|
|
20
34
|
};
|
|
21
35
|
|
|
22
|
-
const fileEntries = (items
|
|
36
|
+
const fileEntries = (items) => {
|
|
23
37
|
const groups = new Map();
|
|
24
38
|
for (let i = 0; i < items.length; i++) {
|
|
25
|
-
if (!kept.has(i)) continue;
|
|
26
39
|
const item = items[i];
|
|
27
40
|
const rel = itemPath(item);
|
|
28
41
|
let group = groups.get(rel);
|
|
@@ -44,4 +57,10 @@ const fileEntries = (items, kept) => {
|
|
|
44
57
|
return entries;
|
|
45
58
|
};
|
|
46
59
|
|
|
47
|
-
module.exports = {
|
|
60
|
+
module.exports = {
|
|
61
|
+
REVIEW_DIR,
|
|
62
|
+
isPathInScope,
|
|
63
|
+
itemPath,
|
|
64
|
+
fileStatus,
|
|
65
|
+
fileEntries,
|
|
66
|
+
};
|