reslop 0.1.8 → 0.1.9
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 +26 -38
- package/lib/ansi.js +2 -0
- package/lib/capabilities.js +3 -0
- package/lib/deps/changes.js +92 -1
- package/lib/deps/items.js +5 -2
- package/lib/deps/manifest.js +7 -0
- package/lib/deps/rows.js +0 -1
- package/lib/deps.js +65 -10
- package/lib/files.js +30 -1
- package/lib/git/branches.js +97 -5
- package/lib/git.js +6 -0
- package/lib/keys.js +9 -2
- package/lib/render/branches.js +39 -17
- package/lib/render/chrome.js +35 -15
- package/lib/render/commits.js +138 -0
- package/lib/render/files.js +30 -10
- package/lib/render/frame.js +6 -1
- package/lib/render/primitives.js +1 -1
- package/lib/render.js +1 -2
- package/lib/session/actions.js +65 -24
- package/lib/session/branches.js +16 -18
- package/lib/session/change-actions.js +8 -5
- package/lib/session/commit.js +191 -29
- package/lib/session/dispatch.js +12 -2
- package/lib/session/files-pane.js +10 -1
- package/lib/session/items.js +112 -7
- package/lib/session/lifecycle.js +44 -4
- package/lib/session/navigation.js +3 -2
- package/lib/session/pointer.js +7 -0
- package/lib/session/view.js +4 -2
- package/lib/session.js +37 -9
- package/package.json +2 -2
package/lib/git.js
CHANGED
|
@@ -73,6 +73,7 @@ const createGitRepo = () =>
|
|
|
73
73
|
hasStaged: branches.hasStaged,
|
|
74
74
|
lastMessage: branches.lastMessage,
|
|
75
75
|
listBranches: branches.listBranches,
|
|
76
|
+
listCommits: branches.listCommits,
|
|
76
77
|
currentBranch: read.currentBranch,
|
|
77
78
|
checkout: branches.checkoutBranch,
|
|
78
79
|
checkoutAsync: branches.checkoutBranchAsync,
|
|
@@ -82,6 +83,8 @@ const createGitRepo = () =>
|
|
|
82
83
|
rebaseAsync: branches.rebaseBranchAsync,
|
|
83
84
|
drop: branches.dropBranch,
|
|
84
85
|
dropAsync: branches.dropBranchAsync,
|
|
86
|
+
dropCommit: branches.dropCommit,
|
|
87
|
+
dropCommitAsync: branches.dropCommitAsync,
|
|
85
88
|
pull: branches.pullChanges,
|
|
86
89
|
pullAsync: branches.pullChangesAsync,
|
|
87
90
|
push: branches.pushChanges,
|
|
@@ -104,6 +107,7 @@ module.exports = {
|
|
|
104
107
|
currentBranch: read.currentBranch,
|
|
105
108
|
lastMessage: branches.lastMessage,
|
|
106
109
|
listBranches: branches.listBranches,
|
|
110
|
+
listCommits: branches.listCommits,
|
|
107
111
|
checkoutBranch: branches.checkoutBranch,
|
|
108
112
|
checkoutBranchAsync: branches.checkoutBranchAsync,
|
|
109
113
|
createBranch: branches.createBranch,
|
|
@@ -112,6 +116,8 @@ module.exports = {
|
|
|
112
116
|
rebaseBranchAsync: branches.rebaseBranchAsync,
|
|
113
117
|
dropBranch: branches.dropBranch,
|
|
114
118
|
dropBranchAsync: branches.dropBranchAsync,
|
|
119
|
+
dropCommit: branches.dropCommit,
|
|
120
|
+
dropCommitAsync: branches.dropCommitAsync,
|
|
115
121
|
pullChanges: branches.pullChanges,
|
|
116
122
|
pullChangesAsync: branches.pullChangesAsync,
|
|
117
123
|
pushChanges: branches.pushChanges,
|
package/lib/keys.js
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const actions = require('./session/actions.js');
|
|
4
4
|
|
|
5
|
-
const { ACTIONS, ACTION_IDS, BRANCH_ACTIONS } = actions;
|
|
5
|
+
const { ACTIONS, ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS } = actions;
|
|
6
6
|
const { DIFF_DISABLED, FILES_DISABLED, FILES_GIT_DISABLED } = actions;
|
|
7
|
-
const { FILES_TODO_DISABLED, TODO_DISABLED, BRANCHES_DISABLED } =
|
|
7
|
+
const { FILES_HIDDEN, FILES_TODO_DISABLED, TODO_DISABLED, BRANCHES_DISABLED } =
|
|
8
|
+
actions;
|
|
9
|
+
const { COMMITS_DISABLED } = actions;
|
|
8
10
|
const { disabledActions, diffGitDisabled, branchGitDisabled } = actions;
|
|
11
|
+
const { commitGitDisabled } = actions;
|
|
9
12
|
const { actionFromKey, actionHot, actionLetter, buttonWord } = actions;
|
|
10
13
|
const { CONFIRM, CONFIRM_MODE, confirmKey, applyConfirm } = actions;
|
|
11
14
|
const { confirmPrefix, promptFromChoices } = actions;
|
|
@@ -176,15 +179,19 @@ module.exports = {
|
|
|
176
179
|
ACTIONS,
|
|
177
180
|
ACTION_IDS,
|
|
178
181
|
BRANCH_ACTIONS,
|
|
182
|
+
COMMIT_ACTIONS,
|
|
179
183
|
DIFF_DISABLED,
|
|
180
184
|
FILES_DISABLED,
|
|
185
|
+
FILES_HIDDEN,
|
|
181
186
|
FILES_GIT_DISABLED,
|
|
182
187
|
FILES_TODO_DISABLED,
|
|
183
188
|
TODO_DISABLED,
|
|
184
189
|
BRANCHES_DISABLED,
|
|
190
|
+
COMMITS_DISABLED,
|
|
185
191
|
disabledActions,
|
|
186
192
|
diffGitDisabled,
|
|
187
193
|
branchGitDisabled,
|
|
194
|
+
commitGitDisabled,
|
|
188
195
|
decodeChunk,
|
|
189
196
|
actionFromKey,
|
|
190
197
|
actionHot,
|
package/lib/render/branches.js
CHANGED
|
@@ -14,7 +14,7 @@ const BRANCH_COL_ALIGN = {
|
|
|
14
14
|
ahead: 'start',
|
|
15
15
|
behind: 'start',
|
|
16
16
|
gone: 'end',
|
|
17
|
-
date: '
|
|
17
|
+
date: 'start',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
const BRANCH_COL_FG = {
|
|
@@ -25,14 +25,25 @@ const BRANCH_COL_FG = {
|
|
|
25
25
|
date: THEME.mutedFg,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
const BRANCH_CURRENT_COL_FG = {
|
|
29
|
+
sha: THEME.shaDarkFg,
|
|
30
|
+
date: THEME.headerFg,
|
|
31
|
+
};
|
|
32
|
+
|
|
28
33
|
const BRANCH_NAME_MAX = 24;
|
|
34
|
+
const BRANCH_EDGE = 2;
|
|
35
|
+
const BRANCH_TAIL = 1;
|
|
36
|
+
const BRANCH_GAP = ' ';
|
|
37
|
+
const BRANCH_DATE_GAP = ' ';
|
|
38
|
+
|
|
39
|
+
const colGap = (key) => (key === 'date' ? BRANCH_DATE_GAP : BRANCH_GAP);
|
|
29
40
|
|
|
30
41
|
const branchNameChip = (entry, rowBg) => {
|
|
31
42
|
if (entry.current) {
|
|
32
43
|
return {
|
|
33
|
-
nameFg: THEME.
|
|
44
|
+
nameFg: THEME.mutedFg,
|
|
34
45
|
wrapFg: THEME.mutedFg,
|
|
35
|
-
nameBg: THEME.
|
|
46
|
+
nameBg: THEME.currentBg,
|
|
36
47
|
nameBold: false,
|
|
37
48
|
};
|
|
38
49
|
}
|
|
@@ -97,26 +108,33 @@ const joinBranchSuffix = (parts, cols) => {
|
|
|
97
108
|
const bits = [];
|
|
98
109
|
for (const key of BRANCH_COL_KEYS) {
|
|
99
110
|
if (!cols[key]) continue;
|
|
100
|
-
bits.push(parts[key]);
|
|
111
|
+
bits.push(colGap(key) + parts[key]);
|
|
101
112
|
}
|
|
102
113
|
if (!bits.length) return '';
|
|
103
|
-
return
|
|
114
|
+
return bits.join('');
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const branchColFg = (key, current) => {
|
|
118
|
+
if (current && Object.hasOwn(BRANCH_CURRENT_COL_FG, key)) {
|
|
119
|
+
return BRANCH_CURRENT_COL_FG[key];
|
|
120
|
+
}
|
|
121
|
+
return BRANCH_COL_FG[key];
|
|
104
122
|
};
|
|
105
123
|
|
|
106
|
-
const paintBranchSuffix = (parts, cols, fgRgb, bgRgb, color) => {
|
|
107
|
-
const gap = paint(' ', fgRgb, bgRgb, color);
|
|
124
|
+
const paintBranchSuffix = (parts, cols, fgRgb, bgRgb, color, current) => {
|
|
108
125
|
const bits = [];
|
|
109
126
|
for (const key of BRANCH_COL_KEYS) {
|
|
110
127
|
if (!cols[key]) continue;
|
|
111
|
-
const tone =
|
|
112
|
-
|
|
128
|
+
const tone = branchColFg(key, current) ?? fgRgb;
|
|
129
|
+
const gap = paint(colGap(key), fgRgb, bgRgb, color);
|
|
130
|
+
bits.push(gap + paint(parts[key], tone, bgRgb, color));
|
|
113
131
|
}
|
|
114
132
|
if (!bits.length) return '';
|
|
115
|
-
return
|
|
133
|
+
return bits.join('');
|
|
116
134
|
};
|
|
117
135
|
|
|
118
136
|
const paintBranchRow = (entry, width, color, selected, cols) => {
|
|
119
|
-
const inner = Math.max(1, width -
|
|
137
|
+
const inner = Math.max(1, width - BRANCH_EDGE - BRANCH_TAIL);
|
|
120
138
|
const mark = selected ? FILE_MARK : ' ';
|
|
121
139
|
const prefix = ` ${mark} `;
|
|
122
140
|
const parts = branchRowParts(entry, cols);
|
|
@@ -136,8 +154,11 @@ const paintBranchRow = (entry, width, color, selected, cols) => {
|
|
|
136
154
|
const fgRgb = branchRowFg(entry, selected);
|
|
137
155
|
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
138
156
|
const chip = branchNameChip(entry, bgRgb);
|
|
139
|
-
const
|
|
140
|
-
|
|
157
|
+
const markFg = entry.current ? THEME.headerFg : fgRgb;
|
|
158
|
+
const markBg = entry.current ? THEME.currentBg : bgRgb;
|
|
159
|
+
const tail = ' '.repeat(BRANCH_TAIL);
|
|
160
|
+
const edge = fill(BRANCH_EDGE, fgRgb, THEME.ctxBg, color);
|
|
161
|
+
if (!color) return `${left}${suffix}${tail}${edge}`;
|
|
141
162
|
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
142
163
|
out += paint(prefix, fgRgb, bgRgb, color);
|
|
143
164
|
out += paintBracketName(
|
|
@@ -148,13 +169,14 @@ const paintBranchRow = (entry, width, color, selected, cols) => {
|
|
|
148
169
|
color,
|
|
149
170
|
chip.nameBold,
|
|
150
171
|
);
|
|
151
|
-
out += paint(after,
|
|
152
|
-
out += paintBranchSuffix(parts, cols,
|
|
172
|
+
out += paint(after, markFg, markBg, color);
|
|
173
|
+
out += paintBranchSuffix(parts, cols, markFg, markBg, color, entry.current);
|
|
174
|
+
out += paint(tail, markFg, markBg, color);
|
|
153
175
|
return out + edge;
|
|
154
176
|
};
|
|
155
177
|
|
|
156
178
|
const paintBranchEdit = (compose, width, color) => {
|
|
157
|
-
const inner = Math.max(1, width -
|
|
179
|
+
const inner = Math.max(1, width - BRANCH_EDGE);
|
|
158
180
|
const prefix = ` ${FILE_MARK} `;
|
|
159
181
|
const prefixW = visibleWidth(prefix);
|
|
160
182
|
const rest = Math.max(1, inner - prefixW);
|
|
@@ -165,7 +187,7 @@ const paintBranchEdit = (compose, width, color) => {
|
|
|
165
187
|
const left = `${prefix}${clipped}${' '.repeat(pad)}`;
|
|
166
188
|
const fgRgb = THEME.chromeFg;
|
|
167
189
|
const bgRgb = THEME.buttonBg;
|
|
168
|
-
const edge = fill(
|
|
190
|
+
const edge = fill(BRANCH_EDGE, fgRgb, THEME.ctxBg, color);
|
|
169
191
|
let row = `${left}${edge}`;
|
|
170
192
|
if (color) {
|
|
171
193
|
const gap = ' '.repeat(pad);
|
package/lib/render/chrome.js
CHANGED
|
@@ -6,10 +6,12 @@ const files = require('../files.js');
|
|
|
6
6
|
const primitives = require('./primitives.js');
|
|
7
7
|
|
|
8
8
|
const { itemPath, TODO_FILE, isTodosEntry, fileTotals } = files;
|
|
9
|
-
const { ACTION_IDS, BRANCH_ACTIONS,
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
9
|
+
const { ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS, actionLetter } = keys;
|
|
10
|
+
const { buttonWord } = keys;
|
|
11
|
+
const { FILES_HIDDEN, FILES_GIT_DISABLED, BRANCHES_DISABLED } = keys;
|
|
12
|
+
const { COMMITS_DISABLED, TODO_DISABLED, DIFF_DISABLED } = keys;
|
|
13
|
+
const { diffGitDisabled, branchGitDisabled, commitGitDisabled } = keys;
|
|
14
|
+
const { CONFIRM, confirmPrefix, promptFromChoices } = keys;
|
|
13
15
|
const { THEME, paint, visibleWidth } = ansi;
|
|
14
16
|
const { truncateVisible, fg, RESET, EL, seq } = ansi;
|
|
15
17
|
const { fill, branchLabel, paintBracketName } = primitives;
|
|
@@ -91,7 +93,6 @@ const INFO_STATUS = [
|
|
|
91
93
|
const INFO_PREFIX = ['checked out ', 'created ', 'rebased onto ', 'dropped '];
|
|
92
94
|
|
|
93
95
|
const QUIT_PROMPT = promptFromChoices(CONFIRM.confirmQuit.choices);
|
|
94
|
-
const COMMIT_PROMPT = promptFromChoices(CONFIRM.confirmCommit.choices);
|
|
95
96
|
const updatePrompt = (from, to) => ` update reslop ${from} → ${to}? y/n`;
|
|
96
97
|
const dropPrompt = (name) => ` drop ${name}? y/n`;
|
|
97
98
|
const PUSH_REJECT_PREFIX = CONFIRM.confirmPush.prefix;
|
|
@@ -160,6 +161,16 @@ const headerTarget = (view) => {
|
|
|
160
161
|
const origin = entry.current ? 'current' : 'branch';
|
|
161
162
|
return { path: entry.name, origin, n, total };
|
|
162
163
|
}
|
|
164
|
+
if (pane === 'commits') {
|
|
165
|
+
const commits = view.commits ?? [];
|
|
166
|
+
const cursor = view.commitCursor ?? 0;
|
|
167
|
+
const entry = commits[cursor];
|
|
168
|
+
const total = commits.length;
|
|
169
|
+
const n = total ? cursor + 1 : 0;
|
|
170
|
+
if (!entry) return { path: '', origin: 'commit', n, total };
|
|
171
|
+
const origin = entry.head ? 'HEAD' : 'commit';
|
|
172
|
+
return { path: entry.subject ?? '', origin, n, total };
|
|
173
|
+
}
|
|
163
174
|
if (!view.item) return { path: '', origin: '', n: 0, total: view.total ?? 0 };
|
|
164
175
|
if (view.item.origin === 'todo') {
|
|
165
176
|
return { path: TODO_FILE, origin: 'todo', n: 1, total: 1 };
|
|
@@ -276,7 +287,7 @@ const layoutButtons = (width, compact, hidden, extra, disabled) => {
|
|
|
276
287
|
for (const id of ACTION_IDS) {
|
|
277
288
|
if (id === 'quit') {
|
|
278
289
|
for (const add of extraIds) {
|
|
279
|
-
if (!
|
|
290
|
+
if (!ids.includes(add)) ids.push(add);
|
|
280
291
|
}
|
|
281
292
|
}
|
|
282
293
|
if (hiddenIds.includes(id)) continue;
|
|
@@ -380,8 +391,8 @@ const statusDelta = (total) => {
|
|
|
380
391
|
const hasDelta =
|
|
381
392
|
stagedAdd || unstagedAdd || stagedDel || unstagedDel || total.added;
|
|
382
393
|
if (!hasDelta && !total.removed) return '';
|
|
383
|
-
const plus = `+${stagedAdd}/${unstagedAdd}`;
|
|
384
|
-
const minus = `-${stagedDel}/${unstagedDel}`;
|
|
394
|
+
const plus = `+${stagedAdd}/${stagedAdd + unstagedAdd}`;
|
|
395
|
+
const minus = `-${stagedDel}/${stagedDel + unstagedDel}`;
|
|
385
396
|
return joinStatus([plus, minus]);
|
|
386
397
|
};
|
|
387
398
|
|
|
@@ -525,10 +536,15 @@ const statusBranchHit = (view, width, y) => {
|
|
|
525
536
|
return { id: 'branch', y, x0: open, x1: close + 1 };
|
|
526
537
|
};
|
|
527
538
|
|
|
539
|
+
const selectedBranch = (view) => (view.branches ?? [])[view.branchCursor ?? 0];
|
|
540
|
+
|
|
541
|
+
const selectedCommit = (view) => (view.commits ?? [])[view.commitCursor ?? 0];
|
|
542
|
+
|
|
528
543
|
const hiddenActions = (view) => {
|
|
529
544
|
const pane = view.pane ?? 'diff';
|
|
530
|
-
if (pane === 'files') return
|
|
545
|
+
if (pane === 'files') return FILES_HIDDEN;
|
|
531
546
|
if (pane === 'branches') return BRANCHES_DISABLED;
|
|
547
|
+
if (pane === 'commits') return COMMITS_DISABLED;
|
|
532
548
|
if (view.item && view.item.origin === 'todo') return TODO_DISABLED;
|
|
533
549
|
return DIFF_DISABLED;
|
|
534
550
|
};
|
|
@@ -542,22 +558,26 @@ const dimFooterActions = (view) => {
|
|
|
542
558
|
return FILES_GIT_DISABLED;
|
|
543
559
|
}
|
|
544
560
|
if (pane === 'diff') return diffGitDisabled(view.item);
|
|
545
|
-
if (pane === 'branches')
|
|
546
|
-
|
|
547
|
-
const entry =
|
|
548
|
-
|
|
561
|
+
if (pane === 'branches') return branchGitDisabled(selectedBranch(view));
|
|
562
|
+
if (pane === 'commits') {
|
|
563
|
+
const entry = selectedCommit(view);
|
|
564
|
+
const staged = (view.counts?.staged ?? 0) > 0;
|
|
565
|
+
if (!entry) return commitGitDisabled({ canCommit: staged });
|
|
566
|
+
return commitGitDisabled({ ...entry, canCommit: staged });
|
|
549
567
|
}
|
|
550
568
|
return [];
|
|
551
569
|
};
|
|
552
570
|
|
|
553
571
|
const extraFooterActions = (view) => {
|
|
554
|
-
|
|
572
|
+
const pane = view.pane ?? 'diff';
|
|
573
|
+
if (pane === 'files') return ['commit', 'pull', 'push'];
|
|
574
|
+
if (pane === 'branches') return Object.keys(BRANCH_ACTIONS);
|
|
575
|
+
if (pane === 'commits') return Object.keys(COMMIT_ACTIONS);
|
|
555
576
|
return [];
|
|
556
577
|
};
|
|
557
578
|
|
|
558
579
|
module.exports = {
|
|
559
580
|
QUIT_PROMPT,
|
|
560
|
-
COMMIT_PROMPT,
|
|
561
581
|
PUSH_PROMPT,
|
|
562
582
|
updatePrompt,
|
|
563
583
|
dropPrompt,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const primitives = require('./primitives.js');
|
|
5
|
+
|
|
6
|
+
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
7
|
+
const { fill, padVisible, FILE_MARK, paneResult, paintCursorList } = primitives;
|
|
8
|
+
|
|
9
|
+
const COMMIT_COL_KEYS = ['sha', 'author', 'date'];
|
|
10
|
+
|
|
11
|
+
const COMMIT_COL_ALIGN = {
|
|
12
|
+
sha: 'end',
|
|
13
|
+
author: 'start',
|
|
14
|
+
date: 'start',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const COMMIT_COL_FG = {
|
|
18
|
+
sha: THEME.shaFg,
|
|
19
|
+
author: THEME.mutedFg,
|
|
20
|
+
date: THEME.mutedFg,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const COMMIT_EDGE = 2;
|
|
24
|
+
const COMMIT_TAIL = 1;
|
|
25
|
+
const COMMIT_GAP = ' ';
|
|
26
|
+
const COMMIT_DATE_GAP = ' ';
|
|
27
|
+
|
|
28
|
+
const colGap = (key) => (key === 'date' ? COMMIT_DATE_GAP : COMMIT_GAP);
|
|
29
|
+
|
|
30
|
+
const commitRowFields = (entry) => ({
|
|
31
|
+
sha: entry.shortSha ?? '',
|
|
32
|
+
author: entry.author ?? '',
|
|
33
|
+
date: entry.date ?? '',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const commitColumns = (commits) => {
|
|
37
|
+
const widths = {};
|
|
38
|
+
for (const key of COMMIT_COL_KEYS) widths[key] = 0;
|
|
39
|
+
for (const entry of commits) {
|
|
40
|
+
const fields = commitRowFields(entry);
|
|
41
|
+
for (const key of COMMIT_COL_KEYS) {
|
|
42
|
+
const w = visibleWidth(fields[key]);
|
|
43
|
+
if (w > widths[key]) widths[key] = w;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return widths;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const commitRowParts = (entry, cols) => {
|
|
50
|
+
const fields = commitRowFields(entry);
|
|
51
|
+
const parts = {};
|
|
52
|
+
for (const key of COMMIT_COL_KEYS) {
|
|
53
|
+
const width = cols?.[key] ?? visibleWidth(fields[key]);
|
|
54
|
+
parts[key] = padVisible(fields[key], width, COMMIT_COL_ALIGN[key]);
|
|
55
|
+
}
|
|
56
|
+
return parts;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const joinCommitSuffix = (parts, cols) => {
|
|
60
|
+
const bits = [];
|
|
61
|
+
for (const key of COMMIT_COL_KEYS) {
|
|
62
|
+
if (!cols[key]) continue;
|
|
63
|
+
bits.push(colGap(key) + parts[key]);
|
|
64
|
+
}
|
|
65
|
+
if (!bits.length) return '';
|
|
66
|
+
return bits.join('');
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const commitColFg = (key) => COMMIT_COL_FG[key];
|
|
70
|
+
|
|
71
|
+
const paintCommitSuffix = (parts, cols, fgRgb, bgRgb, color) => {
|
|
72
|
+
const bits = [];
|
|
73
|
+
for (const key of COMMIT_COL_KEYS) {
|
|
74
|
+
if (!cols[key]) continue;
|
|
75
|
+
const tone = commitColFg(key) ?? fgRgb;
|
|
76
|
+
const gap = paint(colGap(key), fgRgb, bgRgb, color);
|
|
77
|
+
bits.push(gap + paint(parts[key], tone, bgRgb, color));
|
|
78
|
+
}
|
|
79
|
+
if (!bits.length) return '';
|
|
80
|
+
return bits.join('');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const paintCommitRow = (entry, width, color, selected, cols) => {
|
|
84
|
+
const inner = Math.max(1, width - COMMIT_EDGE - COMMIT_TAIL);
|
|
85
|
+
const mark = selected ? FILE_MARK : ' ';
|
|
86
|
+
const prefix = ` ${mark} `;
|
|
87
|
+
const lead = ' ';
|
|
88
|
+
const parts = commitRowParts(entry, cols);
|
|
89
|
+
const suffix = joinCommitSuffix(parts, cols);
|
|
90
|
+
const rest = Math.max(1, inner - visibleWidth(prefix + lead + suffix));
|
|
91
|
+
const subject = truncateVisible(entry.subject ?? '', rest);
|
|
92
|
+
const pad = Math.max(0, rest - visibleWidth(subject));
|
|
93
|
+
const after = `${' '.repeat(pad)}`;
|
|
94
|
+
const left = `${prefix}${lead}${subject}${after}`;
|
|
95
|
+
const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
96
|
+
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
97
|
+
const nameFg = selected ? THEME.buttonHotFg : THEME.chromeFg;
|
|
98
|
+
const tail = ' '.repeat(COMMIT_TAIL);
|
|
99
|
+
const edge = fill(COMMIT_EDGE, fgRgb, THEME.ctxBg, color);
|
|
100
|
+
if (!color) return `${left}${suffix}${tail}${edge}`;
|
|
101
|
+
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
102
|
+
out += paint(prefix, fgRgb, bgRgb, color);
|
|
103
|
+
out += paint(lead, nameFg, bgRgb, color);
|
|
104
|
+
out += paint(subject, nameFg, bgRgb, color);
|
|
105
|
+
out += paint(after, fgRgb, bgRgb, color);
|
|
106
|
+
out += paintCommitSuffix(parts, cols, fgRgb, bgRgb, color);
|
|
107
|
+
out += paint(tail, fgRgb, bgRgb, color);
|
|
108
|
+
return out + edge;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
|
|
112
|
+
const commits = view.commits ?? [];
|
|
113
|
+
const cols = commitColumns(commits);
|
|
114
|
+
const cursor = view.commitCursor ?? 0;
|
|
115
|
+
const paintRow = (entry, rowWidth, rowColor, selected) =>
|
|
116
|
+
paintCommitRow(entry, rowWidth, rowColor, selected, cols);
|
|
117
|
+
const painted = paintCursorList(
|
|
118
|
+
commits,
|
|
119
|
+
cursor,
|
|
120
|
+
width,
|
|
121
|
+
color,
|
|
122
|
+
bodyH,
|
|
123
|
+
headerLines,
|
|
124
|
+
paintRow,
|
|
125
|
+
null,
|
|
126
|
+
view.listScroll,
|
|
127
|
+
);
|
|
128
|
+
return paneResult({
|
|
129
|
+
body: painted.body,
|
|
130
|
+
fileHits: painted.fileHits,
|
|
131
|
+
todoOwners: [],
|
|
132
|
+
cursor: painted.cursor,
|
|
133
|
+
splitBody: false,
|
|
134
|
+
listScroll: painted.offset,
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
module.exports = { paintBodyCommits };
|
package/lib/render/files.js
CHANGED
|
@@ -15,7 +15,7 @@ const FILE_STATUS_FG = {
|
|
|
15
15
|
partial: THEME.warnFg,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const FILE_COL_KEYS = ['add', 'del', 'status', 'n', 'm'];
|
|
18
|
+
const FILE_COL_KEYS = ['add', 'del', 'status', 'n', 'm', 'date'];
|
|
19
19
|
|
|
20
20
|
const FILE_COL_ALIGN = {
|
|
21
21
|
add: 'start',
|
|
@@ -23,15 +23,18 @@ const FILE_COL_ALIGN = {
|
|
|
23
23
|
status: 'start',
|
|
24
24
|
n: 'start',
|
|
25
25
|
m: 'end',
|
|
26
|
+
date: 'start',
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
const FILE_DATE_GAP = ' ';
|
|
30
|
+
|
|
28
31
|
const signedCount = (sign, n) => `${sign}${n ?? 0}`;
|
|
29
32
|
|
|
30
33
|
const fileRowFields = (entry) => {
|
|
31
34
|
if (isTodosEntry(entry)) {
|
|
32
35
|
const n = `${entry.staged ?? 0}`;
|
|
33
36
|
const m = `${entry.remaining ?? 0}`;
|
|
34
|
-
return { add: '', del: '', status: '', n, m };
|
|
37
|
+
return { add: '', del: '', status: '', n, m, date: '' };
|
|
35
38
|
}
|
|
36
39
|
const { added, removed, staged, remaining } = entry;
|
|
37
40
|
const add = signedCount('+', added);
|
|
@@ -39,7 +42,8 @@ const fileRowFields = (entry) => {
|
|
|
39
42
|
const status = entry.status ?? '';
|
|
40
43
|
const n = `${staged ?? 0}`;
|
|
41
44
|
const m = `${remaining ?? 0}`;
|
|
42
|
-
|
|
45
|
+
const date = entry.date ?? '';
|
|
46
|
+
return { add, del, status, n, m, date };
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
const fileColumns = (files) => {
|
|
@@ -73,12 +77,20 @@ const fileRowParts = (entry, cols) => {
|
|
|
73
77
|
return parts;
|
|
74
78
|
};
|
|
75
79
|
|
|
76
|
-
const
|
|
80
|
+
const joinFileDate = (parts, cols) => {
|
|
81
|
+
if (!cols?.date) return '';
|
|
82
|
+
return `${FILE_DATE_GAP}${parts.date}`;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const joinFileMeta = (parts) => {
|
|
77
86
|
const { add, del, status, n, m } = parts;
|
|
78
87
|
return ` ${add} ${del} ${status} ${n}/${m}`;
|
|
79
88
|
};
|
|
80
89
|
|
|
81
|
-
const
|
|
90
|
+
const joinFileSuffix = (parts, cols) =>
|
|
91
|
+
`${joinFileMeta(parts)}${joinFileDate(parts, cols)}`;
|
|
92
|
+
|
|
93
|
+
const paintFileSuffix = (parts, cols, fgRgb, bgRgb, color) => {
|
|
82
94
|
const { add, del, status, n, m } = parts;
|
|
83
95
|
const gap = paint(' ', fgRgb, bgRgb, color);
|
|
84
96
|
const addText = paint(add, THEME.addLineFg, bgRgb, color);
|
|
@@ -89,7 +101,10 @@ const paintFileSuffix = (parts, fgRgb, bgRgb, color) => {
|
|
|
89
101
|
const mText = paint(m, fgRgb, bgRgb, color);
|
|
90
102
|
const counts = addText + gap + delText;
|
|
91
103
|
const meta = statusText + gap + nText + slash + mText;
|
|
92
|
-
return gap + counts + gap + meta;
|
|
104
|
+
if (!cols?.date) return gap + counts + gap + meta;
|
|
105
|
+
const dateGap = paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
|
|
106
|
+
const age = paint(parts.date, THEME.mutedFg, bgRgb, color);
|
|
107
|
+
return gap + counts + gap + meta + dateGap + age;
|
|
93
108
|
};
|
|
94
109
|
|
|
95
110
|
const paintTodosRow = (entry, width, color, selected, cols) => {
|
|
@@ -97,7 +112,9 @@ const paintTodosRow = (entry, width, color, selected, cols) => {
|
|
|
97
112
|
const mark = selected ? FILE_MARK : ' ';
|
|
98
113
|
const prefix = ` ${mark} `;
|
|
99
114
|
const parts = fileRowParts(entry, cols);
|
|
100
|
-
const
|
|
115
|
+
const meta = joinFileMeta(parts);
|
|
116
|
+
const dated = joinFileDate(parts, cols);
|
|
117
|
+
const suffix = `${meta}${dated}`;
|
|
101
118
|
const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
|
|
102
119
|
const clipped = truncateVisible(entry.path, pathW);
|
|
103
120
|
const pad = Math.max(0, pathW - visibleWidth(clipped));
|
|
@@ -108,7 +125,10 @@ const paintTodosRow = (entry, width, color, selected, cols) => {
|
|
|
108
125
|
if (!color) return `${left}${suffix}${edge}`;
|
|
109
126
|
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
110
127
|
out += paint(left, fgRgb, bgRgb, color, true);
|
|
111
|
-
out += paint(
|
|
128
|
+
out += paint(meta, fgRgb, bgRgb, color, true);
|
|
129
|
+
if (!dated) return out + edge;
|
|
130
|
+
out += paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
|
|
131
|
+
out += paint(parts.date, THEME.mutedFg, bgRgb, color);
|
|
112
132
|
return out + edge;
|
|
113
133
|
};
|
|
114
134
|
|
|
@@ -120,7 +140,7 @@ const paintFileRow = (entry, width, color, selected, cols) => {
|
|
|
120
140
|
const mark = selected ? FILE_MARK : ' ';
|
|
121
141
|
const prefix = ` ${mark} `;
|
|
122
142
|
const parts = fileRowParts(entry, cols);
|
|
123
|
-
const suffix = joinFileSuffix(parts);
|
|
143
|
+
const suffix = joinFileSuffix(parts, cols);
|
|
124
144
|
const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
|
|
125
145
|
const { path, status } = entry;
|
|
126
146
|
const clipped = truncateVisible(path, pathW);
|
|
@@ -132,7 +152,7 @@ const paintFileRow = (entry, width, color, selected, cols) => {
|
|
|
132
152
|
if (!color) return `${left}${suffix}${edge}`;
|
|
133
153
|
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
134
154
|
out += paint(left, fgRgb, bgRgb, color);
|
|
135
|
-
out += paintFileSuffix(parts, fgRgb, bgRgb, color);
|
|
155
|
+
out += paintFileSuffix(parts, cols, fgRgb, bgRgb, color);
|
|
136
156
|
return out + edge;
|
|
137
157
|
};
|
|
138
158
|
|
package/lib/render/frame.js
CHANGED
|
@@ -6,6 +6,7 @@ const primitives = require('./primitives.js');
|
|
|
6
6
|
const chrome = require('./chrome.js');
|
|
7
7
|
const files = require('./files.js');
|
|
8
8
|
const branches = require('./branches.js');
|
|
9
|
+
const commits = require('./commits.js');
|
|
9
10
|
const notes = require('./notes.js');
|
|
10
11
|
const diff = require('./diff.js');
|
|
11
12
|
|
|
@@ -17,11 +18,14 @@ const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
|
|
|
17
18
|
const { paintNotePanel, paintBodyTodo } = notes;
|
|
18
19
|
const { paintBodyFiles } = files;
|
|
19
20
|
const { paintBodyBranches } = branches;
|
|
21
|
+
const { paintBodyCommits } = commits;
|
|
20
22
|
const { paintBodyDiff } = diff;
|
|
21
23
|
|
|
22
24
|
const bodyKind = (view) => {
|
|
23
25
|
const pane = view.pane ?? 'diff';
|
|
24
|
-
if (pane === 'files' || pane === 'branches'
|
|
26
|
+
if (pane === 'files' || pane === 'branches' || pane === 'commits') {
|
|
27
|
+
return pane;
|
|
28
|
+
}
|
|
25
29
|
if (!view.item) return 'empty';
|
|
26
30
|
if (view.item.origin === 'todo') return 'todo';
|
|
27
31
|
return 'diff';
|
|
@@ -39,6 +43,7 @@ const paintBodyEmpty = () =>
|
|
|
39
43
|
const BODY_PAINT = {
|
|
40
44
|
files: paintBodyFiles,
|
|
41
45
|
branches: paintBodyBranches,
|
|
46
|
+
commits: paintBodyCommits,
|
|
42
47
|
empty: paintBodyEmpty,
|
|
43
48
|
todo: paintBodyTodo,
|
|
44
49
|
diff: paintBodyDiff,
|
package/lib/render/primitives.js
CHANGED
|
@@ -8,7 +8,7 @@ const GUTTER_W = 2;
|
|
|
8
8
|
const FILE_MARK = '▶';
|
|
9
9
|
const FILE_LIST_PAD = 1;
|
|
10
10
|
const CHECK_DONE = 'xX';
|
|
11
|
-
const LIST_PANES = ['files', 'branches'];
|
|
11
|
+
const LIST_PANES = ['files', 'branches', 'commits'];
|
|
12
12
|
const CHECK_TOKEN = /\[([ xX])\]/;
|
|
13
13
|
const CHECK_LEAD = /^\[[ xX]\] /;
|
|
14
14
|
|
package/lib/render.js
CHANGED
|
@@ -6,7 +6,7 @@ const diff = require('./render/diff.js');
|
|
|
6
6
|
const present = require('./render/present.js');
|
|
7
7
|
const { renderFrame } = require('./render/frame.js');
|
|
8
8
|
|
|
9
|
-
const { QUIT_PROMPT,
|
|
9
|
+
const { QUIT_PROMPT, PUSH_PROMPT } = chrome;
|
|
10
10
|
const { updatePrompt, dropPrompt } = chrome;
|
|
11
11
|
const { formatBusyStatus, headerText, layoutButtons } = chrome;
|
|
12
12
|
const { DEL_CHAR_BG, ADD_CHAR_BG, paintDiffLine, codeInnerWidth } = diff;
|
|
@@ -15,7 +15,6 @@ const { presentCursor, presentRows } = present;
|
|
|
15
15
|
|
|
16
16
|
module.exports = {
|
|
17
17
|
QUIT_PROMPT,
|
|
18
|
-
COMMIT_PROMPT,
|
|
19
18
|
PUSH_PROMPT,
|
|
20
19
|
updatePrompt,
|
|
21
20
|
dropPrompt,
|