reslop 0.2.3 → 0.2.4
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 +14 -1
- package/README.md +8 -3
- package/bin/reslop.js +4 -2
- package/lib/api.js +2 -1
- package/lib/cli.js +19 -10
- package/lib/clipboard.js +6 -3
- package/lib/deps-diff.js +61 -115
- package/lib/deps.js +24 -61
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +2 -1
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +4 -2
- package/lib/editor.js +22 -4
- package/lib/files.js +38 -12
- package/lib/git-branches.js +107 -154
- package/lib/git-deps.js +20 -60
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +2 -1
- package/lib/gitlab.js +2 -1
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +2 -1
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +2 -1
- package/lib/highlight/txt.js +2 -1
- package/lib/highlight.js +2 -1
- package/lib/keys.js +2 -2
- package/lib/manifest.js +4 -2
- package/lib/npm-commands.js +83 -4
- package/lib/remote.js +20 -13
- package/lib/render/branches.js +6 -15
- package/lib/render/chrome.js +27 -7
- package/lib/render/commits.js +158 -46
- package/lib/render/diff.js +130 -47
- package/lib/render/files.js +4 -12
- package/lib/render/frame.js +3 -9
- package/lib/render/notes.js +4 -11
- package/lib/render/npm.js +1 -13
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +28 -15
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +14 -11
- package/lib/report-model.js +6 -4
- package/lib/report-parse.js +8 -13
- package/lib/report-render.js +38 -21
- package/lib/report-run.js +12 -10
- package/lib/report-test.js +5 -4
- package/lib/report-tools.js +44 -18
- package/lib/review-merge.js +2 -1
- package/lib/review.js +6 -2
- package/lib/session/actions.js +49 -65
- package/lib/session/branches.js +13 -49
- package/lib/session/change-actions.js +2 -1
- package/lib/session/commit.js +34 -49
- package/lib/session/compose.js +37 -11
- package/lib/session/dispatch.js +17 -25
- package/lib/session/files-pane.js +6 -27
- package/lib/session/items.js +6 -3
- package/lib/session/lifecycle.js +8 -4
- package/lib/session/load.js +4 -2
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +54 -1
- package/lib/session/ops.js +15 -1
- package/lib/session/pointer.js +22 -3
- package/lib/session/review.js +2 -1
- package/lib/session/terminal.js +4 -2
- package/lib/session/update.js +2 -1
- package/lib/session/view.js +29 -3
- package/lib/session.js +37 -23
- package/lib/source.js +6 -3
- package/lib/update.js +5 -9
- package/lib/utilities.js +4 -2
- package/lib/wrap.js +3 -1
- package/package.json +1 -1
package/lib/render/commits.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
+
const files = require('../files.js');
|
|
4
5
|
const primitives = require('./primitives.js');
|
|
5
|
-
const
|
|
6
|
+
const wrap = require('../wrap.js');
|
|
7
|
+
const { shortAge } = files;
|
|
8
|
+
const { wrapMultiline, cursorInWrap } = wrap;
|
|
6
9
|
const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
|
|
10
|
+
const { padVisible } = primitives;
|
|
7
11
|
|
|
8
12
|
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
9
13
|
const { FILE_MARK, paneResult, paintCursorList } = primitives;
|
|
@@ -12,12 +16,16 @@ const { paintBodyFill, listWindowStart } = primitives;
|
|
|
12
16
|
const COMMIT_COL_ALIGN = {
|
|
13
17
|
sha: 'end',
|
|
14
18
|
refs: 'end',
|
|
19
|
+
add: 'start',
|
|
20
|
+
del: 'start',
|
|
15
21
|
date: 'start',
|
|
16
22
|
};
|
|
17
23
|
|
|
18
24
|
const COMMIT_COL_FG = {
|
|
19
25
|
sha: THEME.shaFg,
|
|
20
26
|
refs: THEME.warnFg,
|
|
27
|
+
add: THEME.addLineFg,
|
|
28
|
+
del: THEME.delLineFg,
|
|
21
29
|
date: THEME.mutedFg,
|
|
22
30
|
};
|
|
23
31
|
|
|
@@ -29,11 +37,65 @@ const COMMIT_CURRENT_COL_FG = {
|
|
|
29
37
|
const REFS_MAX = 28;
|
|
30
38
|
const LIST_PAD = 1;
|
|
31
39
|
const COMMIT_TAIL = 1;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
40
|
+
const lineCounts = (entry) => {
|
|
41
|
+
const added = entry.added ?? 0;
|
|
42
|
+
const removed = entry.removed ?? 0;
|
|
43
|
+
if (!added && !removed) return null;
|
|
44
|
+
return { added, removed };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const countLabel = (entry) => {
|
|
48
|
+
if (entry && entry.pending) {
|
|
49
|
+
const stagedAdd = entry.stagedAdded ?? 0;
|
|
50
|
+
const unstagedAdd = entry.unstagedAdded ?? 0;
|
|
51
|
+
const stagedDel = entry.stagedRemoved ?? 0;
|
|
52
|
+
const unstagedDel = entry.unstagedRemoved ?? 0;
|
|
53
|
+
const any = stagedAdd || unstagedAdd || stagedDel || unstagedDel;
|
|
54
|
+
if (!any) return null;
|
|
55
|
+
return {
|
|
56
|
+
add: `+${stagedAdd}/${unstagedAdd}`,
|
|
57
|
+
del: `-${stagedDel}/${unstagedDel}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const counts = lineCounts(entry);
|
|
61
|
+
if (!counts) return null;
|
|
62
|
+
return { add: `+${counts.added}`, del: `-${counts.removed}` };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const DIFF_LABEL = 'Diffs:';
|
|
66
|
+
const DIFF_GAP = ' ';
|
|
67
|
+
|
|
68
|
+
const entryCountWidth = (entry) => {
|
|
69
|
+
const label = countLabel(entry);
|
|
70
|
+
if (!label) return { add: 0, del: 0 };
|
|
71
|
+
return {
|
|
72
|
+
add: visibleWidth(label.add),
|
|
73
|
+
del: visibleWidth(label.del),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const listCountWidth = (commits) => {
|
|
78
|
+
let add = 0;
|
|
79
|
+
let del = 0;
|
|
80
|
+
for (const entry of commits) {
|
|
81
|
+
const width = entryCountWidth(entry);
|
|
82
|
+
add = Math.max(add, width.add);
|
|
83
|
+
del = Math.max(del, width.del);
|
|
84
|
+
}
|
|
85
|
+
return { add, del };
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const commitRowFields = (entry) => {
|
|
89
|
+
const refs = `${entry.refs ?? ''}`.trim();
|
|
90
|
+
const label = countLabel(entry);
|
|
91
|
+
return {
|
|
92
|
+
sha: entry.shortSha ?? '',
|
|
93
|
+
refs: refs ? truncateVisible(refs, REFS_MAX) : '',
|
|
94
|
+
add: label ? label.add : '',
|
|
95
|
+
del: label ? label.del : '',
|
|
96
|
+
date: shortAge(entry.date ?? ''),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
37
99
|
|
|
38
100
|
const contentWidth = (width) => Math.max(1, width - COMMIT_TAIL);
|
|
39
101
|
|
|
@@ -268,19 +330,45 @@ const partsWidth = (parts) => {
|
|
|
268
330
|
|
|
269
331
|
const timeNeed = (entry) => {
|
|
270
332
|
const when = `${entry.when ?? ''}`.trim();
|
|
271
|
-
const ago =
|
|
333
|
+
const ago = shortAge(entry.date ?? '');
|
|
272
334
|
if (when && ago) return visibleWidth(when) + COL_GAP + visibleWidth(ago);
|
|
273
335
|
return visibleWidth(when || ago);
|
|
274
336
|
};
|
|
275
337
|
|
|
276
338
|
const refsText = (entry) => `${entry.refs ?? ''}`.trim();
|
|
277
339
|
|
|
278
|
-
const
|
|
279
|
-
|
|
340
|
+
const diffLine = (entry, widths) => {
|
|
341
|
+
const label = countLabel(entry);
|
|
342
|
+
if (!label) return null;
|
|
343
|
+
const add = padVisible(label.add, widths.add, 'start');
|
|
344
|
+
const del = padVisible(label.del, widths.del, 'start');
|
|
345
|
+
const text = `${DIFF_LABEL}${DIFF_GAP}${add}${DIFF_GAP}${del}`;
|
|
346
|
+
return { add, del, text };
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const diffCountParts = (entry, widths, rightW) => {
|
|
350
|
+
const line = diffLine(entry, widths);
|
|
351
|
+
if (!line || !rightW) return [];
|
|
352
|
+
const pad = Math.max(0, rightW - visibleWidth(line.text));
|
|
353
|
+
return [
|
|
354
|
+
{ text: ' '.repeat(pad), kind: 'meta' },
|
|
355
|
+
{ text: DIFF_LABEL, kind: 'meta' },
|
|
356
|
+
{ text: DIFF_GAP, kind: 'meta' },
|
|
357
|
+
{ text: line.add, kind: 'add' },
|
|
358
|
+
{ text: DIFF_GAP, kind: 'meta' },
|
|
359
|
+
{ text: line.del, kind: 'del' },
|
|
360
|
+
];
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const rightNeed = (entry, widths = entryCountWidth(entry)) => {
|
|
364
|
+
const line = diffLine(entry, widths);
|
|
365
|
+
return Math.max(
|
|
280
366
|
partsWidth(authorParts(entry)),
|
|
281
367
|
timeNeed(entry),
|
|
282
368
|
visibleWidth(refsText(entry)),
|
|
369
|
+
line ? visibleWidth(line.text) : 0,
|
|
283
370
|
);
|
|
371
|
+
};
|
|
284
372
|
|
|
285
373
|
const spreadTime = (when, ago, width) => {
|
|
286
374
|
const agoW = visibleWidth(ago);
|
|
@@ -309,7 +397,7 @@ const alignEnd = (text, width, kind) => {
|
|
|
309
397
|
|
|
310
398
|
const timeParts = (entry, width) => {
|
|
311
399
|
const when = `${entry.when ?? ''}`.trim();
|
|
312
|
-
const ago =
|
|
400
|
+
const ago = shortAge(entry.date ?? '');
|
|
313
401
|
if (!width) return [];
|
|
314
402
|
if (when && ago) return spreadTime(when, ago, width);
|
|
315
403
|
if (ago) return alignEnd(ago, width, 'meta');
|
|
@@ -317,10 +405,16 @@ const timeParts = (entry, width) => {
|
|
|
317
405
|
return [];
|
|
318
406
|
};
|
|
319
407
|
|
|
320
|
-
const rightRows = (entry, rightW) => {
|
|
321
|
-
const rows = [
|
|
408
|
+
const rightRows = (entry, rightW, widths) => {
|
|
409
|
+
const rows = [];
|
|
410
|
+
const author = authorParts(entry);
|
|
411
|
+
if (author.length) rows.push(author);
|
|
412
|
+
const time = timeParts(entry, rightW);
|
|
413
|
+
if (time.length) rows.push(time);
|
|
322
414
|
const refs = refsText(entry);
|
|
323
415
|
if (refs) rows.push([{ text: refs, kind: 'refs' }]);
|
|
416
|
+
const diff = diffCountParts(entry, widths, rightW);
|
|
417
|
+
if (diff.length) rows.push(diff);
|
|
324
418
|
return rows;
|
|
325
419
|
};
|
|
326
420
|
|
|
@@ -342,7 +436,8 @@ const panelLeft = (entry, leftW, edit) => {
|
|
|
342
436
|
const parts = edit
|
|
343
437
|
? messagePartsFrom(edit.text ?? '', leftW)
|
|
344
438
|
: messageParts(entry, leftW);
|
|
345
|
-
|
|
439
|
+
if (!entry.sha) return parts;
|
|
440
|
+
return [{ text: entry.sha, kind: 'hash' }, ...parts];
|
|
346
441
|
};
|
|
347
442
|
|
|
348
443
|
const commitMessageWidth = (entry, screenWidth) => {
|
|
@@ -368,6 +463,8 @@ const messageCaret = (text, cursor, width, row, first, selected) => {
|
|
|
368
463
|
};
|
|
369
464
|
|
|
370
465
|
const pieceFg = (kind, tone) => {
|
|
466
|
+
if (kind === 'add') return THEME.addLineFg;
|
|
467
|
+
if (kind === 'del') return THEME.delLineFg;
|
|
371
468
|
if (tone.current) {
|
|
372
469
|
if (kind === 'hash') return THEME.shaDarkFg;
|
|
373
470
|
if (kind === 'refs') return THEME.warnFg;
|
|
@@ -431,11 +528,21 @@ const lineRecord = (commit, row, hit, cursor = null) => ({
|
|
|
431
528
|
cursor,
|
|
432
529
|
});
|
|
433
530
|
|
|
434
|
-
const commitLines = (
|
|
531
|
+
const commitLines = (
|
|
532
|
+
entry,
|
|
533
|
+
index,
|
|
534
|
+
width,
|
|
535
|
+
color,
|
|
536
|
+
selected,
|
|
537
|
+
edit,
|
|
538
|
+
current,
|
|
539
|
+
diffWidths,
|
|
540
|
+
) => {
|
|
435
541
|
const inner = contentWidth(width);
|
|
436
542
|
const hashW = visibleWidth(entry.sha ?? '');
|
|
437
|
-
const
|
|
438
|
-
const
|
|
543
|
+
const widths = diffWidths ?? entryCountWidth(entry);
|
|
544
|
+
const cols = panelWidths(rightNeed(entry, widths), hashW, inner);
|
|
545
|
+
const rights = rightRows(entry, cols.rightW, widths);
|
|
439
546
|
const left = panelLeft(entry, cols.leftW, edit);
|
|
440
547
|
const count = Math.max(left.length, rights.length);
|
|
441
548
|
const lines = [];
|
|
@@ -455,13 +562,14 @@ const commitLines = (entry, index, width, color, selected, edit, current) => {
|
|
|
455
562
|
leftKind: line.kind,
|
|
456
563
|
rightKind: 'meta',
|
|
457
564
|
});
|
|
565
|
+
const messageRow = entry.sha ? row - 1 : row;
|
|
458
566
|
const caret = edit
|
|
459
567
|
? messageCaret(
|
|
460
568
|
edit.text,
|
|
461
569
|
edit.cursor,
|
|
462
570
|
cols.leftW,
|
|
463
|
-
|
|
464
|
-
|
|
571
|
+
messageRow,
|
|
572
|
+
!entry.sha && row === 0,
|
|
465
573
|
selected,
|
|
466
574
|
)
|
|
467
575
|
: null;
|
|
@@ -516,6 +624,7 @@ const gapLine = (width, color) =>
|
|
|
516
624
|
|
|
517
625
|
const flattenCommits = (commits, cursor, width, color, compose, rev, short) => {
|
|
518
626
|
const lines = [];
|
|
627
|
+
const diffWidths = listCountWidth(commits);
|
|
519
628
|
const editing = compose && compose.kind === 'commit';
|
|
520
629
|
const pushGap = () => {
|
|
521
630
|
if (lines.length) lines.push(gapLine(width, color));
|
|
@@ -544,6 +653,7 @@ const flattenCommits = (commits, cursor, width, color, compose, rev, short) => {
|
|
|
544
653
|
selected,
|
|
545
654
|
edit,
|
|
546
655
|
viewedCommit(commits[index], rev, short),
|
|
656
|
+
diffWidths,
|
|
547
657
|
);
|
|
548
658
|
for (const line of block) lines.push(line);
|
|
549
659
|
}
|
|
@@ -592,7 +702,12 @@ const paintLineWindow = (
|
|
|
592
702
|
fileHits.push({ y, cursor: line.commit });
|
|
593
703
|
}
|
|
594
704
|
if (pad) body.push(blank);
|
|
595
|
-
return {
|
|
705
|
+
return paneResult({
|
|
706
|
+
body,
|
|
707
|
+
fileHits,
|
|
708
|
+
cursor: textCursor,
|
|
709
|
+
listScroll: start,
|
|
710
|
+
});
|
|
596
711
|
};
|
|
597
712
|
|
|
598
713
|
const paintFullCommits = (view, width, color, bodyH, headerLines) => {
|
|
@@ -656,36 +771,33 @@ const paintBriefCommits = (
|
|
|
656
771
|
};
|
|
657
772
|
|
|
658
773
|
const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
|
|
774
|
+
if (view.commitView === 'full') {
|
|
775
|
+
return paintFullCommits(view, width, color, bodyH, headerLines);
|
|
776
|
+
}
|
|
659
777
|
const commits = view.commits ?? [];
|
|
660
778
|
const cols = measureColumns(commits, commitRowFields, COMMIT_COL_ALIGN);
|
|
661
779
|
const cursor = view.commitCursor ?? 0;
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
fileHits: painted.fileHits,
|
|
684
|
-
todoOwners: [],
|
|
685
|
-
cursor: painted.cursor,
|
|
686
|
-
splitBody: false,
|
|
687
|
-
listScroll: painted.offset,
|
|
688
|
-
});
|
|
780
|
+
const extra = commitEditRow(
|
|
781
|
+
view.compose,
|
|
782
|
+
commits,
|
|
783
|
+
cursor,
|
|
784
|
+
width,
|
|
785
|
+
color,
|
|
786
|
+
cols,
|
|
787
|
+
);
|
|
788
|
+
return paintBriefCommits(
|
|
789
|
+
commits,
|
|
790
|
+
cursor,
|
|
791
|
+
width,
|
|
792
|
+
color,
|
|
793
|
+
bodyH,
|
|
794
|
+
headerLines,
|
|
795
|
+
extra,
|
|
796
|
+
view.listScroll,
|
|
797
|
+
cols,
|
|
798
|
+
view.rev,
|
|
799
|
+
view.revShort,
|
|
800
|
+
);
|
|
689
801
|
};
|
|
690
802
|
|
|
691
803
|
module.exports = {
|
package/lib/render/diff.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
-
const
|
|
4
|
+
const diff = require('../diff/diff.js');
|
|
5
|
+
const { displayLines } = diff;
|
|
5
6
|
const highlight = require('../highlight.js');
|
|
6
|
-
const
|
|
7
|
+
const detect = require('../detect.js');
|
|
8
|
+
const { detectLang } = detect;
|
|
7
9
|
const files = require('../files.js');
|
|
8
10
|
const wrap = require('../wrap.js');
|
|
9
11
|
const primitives = require('./primitives.js');
|
|
@@ -11,7 +13,8 @@ const primitives = require('./primitives.js');
|
|
|
11
13
|
const { itemPath } = files;
|
|
12
14
|
const { tokenize, overlayTokens } = highlight;
|
|
13
15
|
const { wrapPlain } = wrap;
|
|
14
|
-
const { THEME, CODE_FG, codeFg, paint, visibleWidth
|
|
16
|
+
const { THEME, CODE_FG, codeFg, paint, visibleWidth } = ansi;
|
|
17
|
+
const { truncateVisible, graphemes, graphemeWidth } = ansi;
|
|
15
18
|
const { GUTTER_W, fill, splitWidths, paintSplitRule, paneResult } = primitives;
|
|
16
19
|
|
|
17
20
|
const STYLES = {
|
|
@@ -77,10 +80,43 @@ const STAGED_STYLES = {
|
|
|
77
80
|
ctx: STYLES.ctx,
|
|
78
81
|
};
|
|
79
82
|
|
|
80
|
-
const SOFT_WRAP = ['warn', 'note'];
|
|
81
83
|
const delCharBg = () => ansi.bg(THEME.delCharBg);
|
|
82
84
|
const addCharBg = () => ansi.bg(THEME.addCharBg);
|
|
83
85
|
|
|
86
|
+
const skipText = (text, skip) => {
|
|
87
|
+
if (skip <= 0) return text ?? '';
|
|
88
|
+
const parts = graphemes(text ?? '');
|
|
89
|
+
let used = 0;
|
|
90
|
+
let i = 0;
|
|
91
|
+
while (i < parts.length) {
|
|
92
|
+
const w = graphemeWidth(parts[i], used);
|
|
93
|
+
if (used + w > skip) break;
|
|
94
|
+
used += w;
|
|
95
|
+
i += 1;
|
|
96
|
+
}
|
|
97
|
+
let out = '';
|
|
98
|
+
for (; i < parts.length; i++) out += parts[i];
|
|
99
|
+
return out;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const clipPieces = (pieces, skip) => {
|
|
103
|
+
if (skip <= 0) return pieces;
|
|
104
|
+
const out = [];
|
|
105
|
+
let drop = skip;
|
|
106
|
+
for (const piece of pieces) {
|
|
107
|
+
const text = piece.text ?? '';
|
|
108
|
+
const width = visibleWidth(text);
|
|
109
|
+
if (drop >= width) {
|
|
110
|
+
drop -= width;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const shown = drop > 0 ? skipText(text, drop) : text;
|
|
114
|
+
drop = 0;
|
|
115
|
+
if (shown) out.push({ ...piece, text: shown });
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
};
|
|
119
|
+
|
|
84
120
|
const paintSpans = (pieces, lineStyle, budget, color) => {
|
|
85
121
|
let out = '';
|
|
86
122
|
let used = 0;
|
|
@@ -124,14 +160,14 @@ const paintAuditRule = (width, color) => {
|
|
|
124
160
|
return paint(bar, style.baseFg, style.baseBg, color);
|
|
125
161
|
};
|
|
126
162
|
|
|
127
|
-
const paintDiffLine = (line, width, color, origin, lang) => {
|
|
163
|
+
const paintDiffLine = (line, width, color, origin, lang, scrollCol = 0) => {
|
|
128
164
|
if (line.type === 'noteSep') return paintAuditRule(width, color);
|
|
129
165
|
if (line.type === 'note') return paintAuditNote(line, width, color);
|
|
130
166
|
const styles = origin === 'staged' ? STAGED_STYLES : STYLES;
|
|
131
167
|
const style = styles[line.type] ?? styles.ctx;
|
|
132
168
|
const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
|
|
133
169
|
const tokens = tokenize(lang, line.text ?? '');
|
|
134
|
-
const pieces = overlayTokens(tokens, diffSpans);
|
|
170
|
+
const pieces = clipPieces(overlayTokens(tokens, diffSpans), scrollCol);
|
|
135
171
|
const gutterW = Math.min(GUTTER_W, Math.max(0, width));
|
|
136
172
|
const label = `${line.mark ?? style.mark} `.slice(0, gutterW);
|
|
137
173
|
const gutter = paint(label, CODE_FG.punct, style.baseBg, color);
|
|
@@ -140,17 +176,24 @@ const paintDiffLine = (line, width, color, origin, lang) => {
|
|
|
140
176
|
return gutter + rest;
|
|
141
177
|
};
|
|
142
178
|
|
|
143
|
-
const paintSplitHalf = (line, width, color, origin, lang) => {
|
|
179
|
+
const paintSplitHalf = (line, width, color, origin, lang, scrollCol) => {
|
|
144
180
|
if (!line || width <= 0) {
|
|
145
181
|
return fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
146
182
|
}
|
|
147
|
-
return paintDiffLine(line, width, color, origin, lang);
|
|
183
|
+
return paintDiffLine(line, width, color, origin, lang, scrollCol);
|
|
148
184
|
};
|
|
149
185
|
|
|
150
|
-
const paintSplitRow = (row, width, color, origin, lang) => {
|
|
186
|
+
const paintSplitRow = (row, width, color, origin, lang, scrollCol) => {
|
|
151
187
|
const { leftW, rightW } = splitWidths(width);
|
|
152
|
-
const left = paintSplitHalf(row.left, leftW, color, origin, lang);
|
|
153
|
-
const right = paintSplitHalf(
|
|
188
|
+
const left = paintSplitHalf(row.left, leftW, color, origin, lang, scrollCol);
|
|
189
|
+
const right = paintSplitHalf(
|
|
190
|
+
row.right,
|
|
191
|
+
rightW,
|
|
192
|
+
color,
|
|
193
|
+
origin,
|
|
194
|
+
lang,
|
|
195
|
+
scrollCol,
|
|
196
|
+
);
|
|
154
197
|
return `${left}${paintSplitRule(color)}${right}`;
|
|
155
198
|
};
|
|
156
199
|
|
|
@@ -183,6 +226,34 @@ const dropLeadingEmpty = (lines, split) => {
|
|
|
183
226
|
return i ? lines.slice(i) : lines;
|
|
184
227
|
};
|
|
185
228
|
|
|
229
|
+
const cutSpans = (spans, count) => {
|
|
230
|
+
const taken = [];
|
|
231
|
+
const rest = [];
|
|
232
|
+
let left = count;
|
|
233
|
+
let i = 0;
|
|
234
|
+
while (i < spans.length) {
|
|
235
|
+
const span = spans[i];
|
|
236
|
+
const text = span.text ?? '';
|
|
237
|
+
if (left <= 0) {
|
|
238
|
+
rest.push(span);
|
|
239
|
+
i += 1;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (text.length <= left) {
|
|
243
|
+
taken.push(span);
|
|
244
|
+
left -= text.length;
|
|
245
|
+
i += 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const changed = span.changed === true;
|
|
249
|
+
taken.push({ text: text.slice(0, left), changed });
|
|
250
|
+
rest.push({ text: text.slice(left), changed });
|
|
251
|
+
left = 0;
|
|
252
|
+
i += 1;
|
|
253
|
+
}
|
|
254
|
+
return { taken, rest };
|
|
255
|
+
};
|
|
256
|
+
|
|
186
257
|
const wrapSoftLine = (line, width) => {
|
|
187
258
|
const gutterW = Math.min(GUTTER_W, Math.max(0, width));
|
|
188
259
|
const budget = Math.max(1, width - gutterW);
|
|
@@ -191,11 +262,14 @@ const wrapSoftLine = (line, width) => {
|
|
|
191
262
|
const { length } = rows;
|
|
192
263
|
const pieces = new Array(length);
|
|
193
264
|
let used = 0;
|
|
265
|
+
let spans = line.spans ?? [{ text: line.text ?? '', changed }];
|
|
194
266
|
for (let i = 0; i < length; i++) {
|
|
195
267
|
const text = rows[i];
|
|
196
268
|
const last = i === length - 1;
|
|
197
|
-
const
|
|
198
|
-
|
|
269
|
+
const cut = cutSpans(spans, text.length);
|
|
270
|
+
spans = cut.rest;
|
|
271
|
+
const nextSpans = cut.taken.length ? cut.taken : [{ text, changed }];
|
|
272
|
+
const next = { ...line, text, spans: nextSpans, editLast: last };
|
|
199
273
|
if (line.editStart !== undefined) {
|
|
200
274
|
next.editStart = line.editStart + used;
|
|
201
275
|
if (last) next.editEnd = line.editEnd;
|
|
@@ -207,40 +281,30 @@ const wrapSoftLine = (line, width) => {
|
|
|
207
281
|
return pieces;
|
|
208
282
|
};
|
|
209
283
|
|
|
210
|
-
const
|
|
211
|
-
if (!line) return false;
|
|
212
|
-
if (SOFT_WRAP.includes(line.type)) return true;
|
|
213
|
-
return line.wrap === true;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const expandSoftRows = (lines, width, split) => {
|
|
284
|
+
const expandSoftRows = (lines, width, split, wrapView) => {
|
|
217
285
|
const { leftW, rightW } = splitWidths(width);
|
|
218
286
|
const out = [];
|
|
287
|
+
const partsOf = (line, lineW) => {
|
|
288
|
+
if (!wrapView || !line || line.type === 'noteSep') return [line];
|
|
289
|
+
const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
|
|
290
|
+
const budget = Math.max(1, lineW - gutterW);
|
|
291
|
+
if (visibleWidth(line.text ?? '') <= budget) return [line];
|
|
292
|
+
return wrapSoftLine(line, lineW);
|
|
293
|
+
};
|
|
219
294
|
for (const row of lines) {
|
|
220
295
|
if (!split) {
|
|
221
|
-
|
|
222
|
-
out.push(row);
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
for (const part of wrapSoftLine(row, width)) out.push(part);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const right = row.right;
|
|
229
|
-
if (shouldWrap(right)) {
|
|
230
|
-
const parts = wrapSoftLine(right, rightW);
|
|
231
|
-
out.push({ left: row.left, right: parts[0] });
|
|
232
|
-
for (let i = 1; i < parts.length; i++) {
|
|
233
|
-
out.push({ left: null, right: parts[i] });
|
|
234
|
-
}
|
|
296
|
+
for (const part of partsOf(row, width)) out.push(part);
|
|
235
297
|
continue;
|
|
236
298
|
}
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
299
|
+
const leftParts = partsOf(row.left, leftW);
|
|
300
|
+
const rightParts = partsOf(row.right, rightW);
|
|
301
|
+
const n = Math.max(leftParts.length, rightParts.length);
|
|
302
|
+
for (let i = 0; i < n; i++) {
|
|
303
|
+
out.push({
|
|
304
|
+
left: leftParts[i] ?? null,
|
|
305
|
+
right: rightParts[i] ?? null,
|
|
306
|
+
});
|
|
242
307
|
}
|
|
243
|
-
out.push(row);
|
|
244
308
|
}
|
|
245
309
|
return out;
|
|
246
310
|
};
|
|
@@ -251,7 +315,7 @@ const onCodePiece = (line, cursor) => {
|
|
|
251
315
|
return cursor <= line.editEnd;
|
|
252
316
|
};
|
|
253
317
|
|
|
254
|
-
const codeCursorInRows = (rows, split, width, cursor) => {
|
|
318
|
+
const codeCursorInRows = (rows, split, width, cursor, scrollCol = 0) => {
|
|
255
319
|
if (cursor === undefined || cursor === null) return null;
|
|
256
320
|
const { leftW, rightW } = splitWidths(width);
|
|
257
321
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -261,8 +325,9 @@ const codeCursorInRows = (rows, split, width, cursor) => {
|
|
|
261
325
|
const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
|
|
262
326
|
const take = Math.min(cursor - line.editStart, line.text.length);
|
|
263
327
|
const col = visibleWidth(line.text.slice(0, take));
|
|
328
|
+
const shown = Math.max(0, col - scrollCol);
|
|
264
329
|
const x0 = split ? leftW + 1 : 0;
|
|
265
|
-
return { x: x0 + gutterW +
|
|
330
|
+
return { x: x0 + gutterW + shown + 1, row: i };
|
|
266
331
|
}
|
|
267
332
|
return null;
|
|
268
333
|
};
|
|
@@ -279,22 +344,39 @@ const paintBodyDiff = (view, width, color) => {
|
|
|
279
344
|
const overlay = view.codeOverlay;
|
|
280
345
|
const lang = detectLang(itemPath(item));
|
|
281
346
|
const splitBody = layout === 'side' && !item.file.isBinary;
|
|
347
|
+
const editing = Boolean(overlay && typeof overlay.cursor === 'number');
|
|
348
|
+
const picked = editing ? overlay.scrollCol : 0;
|
|
349
|
+
const scrollCol = picked ?? 0;
|
|
282
350
|
const raw = dropLeadingEmpty(bodyLines(item, layout, overlay), splitBody);
|
|
283
|
-
const lines = expandSoftRows(raw, width, splitBody);
|
|
351
|
+
const lines = expandSoftRows(raw, width, splitBody, !editing);
|
|
284
352
|
const { length } = lines;
|
|
285
353
|
const body = new Array(length);
|
|
286
354
|
if (splitBody) {
|
|
287
355
|
for (let i = 0; i < length; i++) {
|
|
288
|
-
body[i] = paintSplitRow(
|
|
356
|
+
body[i] = paintSplitRow(
|
|
357
|
+
lines[i],
|
|
358
|
+
width,
|
|
359
|
+
color,
|
|
360
|
+
item.origin,
|
|
361
|
+
lang,
|
|
362
|
+
scrollCol,
|
|
363
|
+
);
|
|
289
364
|
}
|
|
290
365
|
} else {
|
|
291
366
|
for (let i = 0; i < length; i++) {
|
|
292
|
-
body[i] = paintDiffLine(
|
|
367
|
+
body[i] = paintDiffLine(
|
|
368
|
+
lines[i],
|
|
369
|
+
width,
|
|
370
|
+
color,
|
|
371
|
+
item.origin,
|
|
372
|
+
lang,
|
|
373
|
+
scrollCol,
|
|
374
|
+
);
|
|
293
375
|
}
|
|
294
376
|
}
|
|
295
377
|
const edit = overlay ? overlay.cursor : null;
|
|
296
|
-
const cursor = codeCursorInRows(lines, splitBody, width, edit);
|
|
297
|
-
return paneResult({ body,
|
|
378
|
+
const cursor = codeCursorInRows(lines, splitBody, width, edit, scrollCol);
|
|
379
|
+
return paneResult({ body, cursor, splitBody });
|
|
298
380
|
};
|
|
299
381
|
|
|
300
382
|
module.exports = {
|
|
@@ -304,4 +386,5 @@ module.exports = {
|
|
|
304
386
|
codeInnerWidth,
|
|
305
387
|
paintBodyDiff,
|
|
306
388
|
codeCursorInRows,
|
|
389
|
+
expandSoftRows,
|
|
307
390
|
};
|
package/lib/render/files.js
CHANGED
|
@@ -5,9 +5,9 @@ const files = require('../files.js');
|
|
|
5
5
|
const primitives = require('./primitives.js');
|
|
6
6
|
const { measureColumns, padColumns } = primitives;
|
|
7
7
|
|
|
8
|
-
const { isTodosEntry } = files;
|
|
8
|
+
const { isTodosEntry, shortAge } = files;
|
|
9
9
|
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
10
|
-
const { fill, FILE_MARK,
|
|
10
|
+
const { fill, FILE_MARK, paintCursorList } = primitives;
|
|
11
11
|
|
|
12
12
|
const FILE_STATUS_FG = {
|
|
13
13
|
unstaged: THEME.delLineFg,
|
|
@@ -41,7 +41,7 @@ const fileRowFields = (entry) => {
|
|
|
41
41
|
const status = entry.status ?? '';
|
|
42
42
|
const n = `${staged ?? 0}`;
|
|
43
43
|
const m = `${remaining ?? 0}`;
|
|
44
|
-
const date = entry.date ?? '';
|
|
44
|
+
const date = shortAge(entry.date ?? '');
|
|
45
45
|
return { add, del, status, n, m, date };
|
|
46
46
|
};
|
|
47
47
|
|
|
@@ -134,7 +134,7 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
|
|
|
134
134
|
const cursor = view.fileCursor ?? 0;
|
|
135
135
|
const paintRow = (entry, rowWidth, rowColor, selected) =>
|
|
136
136
|
paintFileRow(entry, rowWidth, rowColor, selected, cols);
|
|
137
|
-
|
|
137
|
+
return paintCursorList(
|
|
138
138
|
files,
|
|
139
139
|
cursor,
|
|
140
140
|
width,
|
|
@@ -145,14 +145,6 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
|
|
|
145
145
|
null,
|
|
146
146
|
view.listScroll,
|
|
147
147
|
);
|
|
148
|
-
return paneResult({
|
|
149
|
-
body: painted.body,
|
|
150
|
-
fileHits: painted.fileHits,
|
|
151
|
-
todoOwners: [],
|
|
152
|
-
cursor: painted.cursor,
|
|
153
|
-
splitBody: false,
|
|
154
|
-
listScroll: painted.offset,
|
|
155
|
-
});
|
|
156
148
|
};
|
|
157
149
|
|
|
158
150
|
module.exports = { paintBodyFiles };
|
package/lib/render/frame.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
-
const
|
|
4
|
+
const select = require('../select.js');
|
|
5
|
+
const { overlayRows } = select;
|
|
5
6
|
const primitives = require('./primitives.js');
|
|
6
7
|
const chrome = require('./chrome.js');
|
|
7
8
|
const files = require('./files.js');
|
|
@@ -37,14 +38,7 @@ const bodyKind = (view) => {
|
|
|
37
38
|
return 'diff';
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
const paintBodyEmpty = () =>
|
|
41
|
-
paneResult({
|
|
42
|
-
body: [],
|
|
43
|
-
fileHits: [],
|
|
44
|
-
todoOwners: [],
|
|
45
|
-
cursor: null,
|
|
46
|
-
splitBody: false,
|
|
47
|
-
});
|
|
41
|
+
const paintBodyEmpty = () => paneResult();
|
|
48
42
|
|
|
49
43
|
const BODY_PAINT = {
|
|
50
44
|
files: paintBodyFiles,
|