reslop 0.2.1 → 0.2.3
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 +11 -1
- package/lib/ansi.js +120 -4
- package/lib/cli.js +3 -0
- package/lib/deps.js +43 -3
- package/lib/git-branches.js +47 -15
- package/lib/keys.js +10 -4
- package/lib/npm-commands.js +513 -0
- package/lib/render/branches.js +21 -28
- package/lib/render/chrome.js +137 -122
- package/lib/render/commits.js +604 -54
- package/lib/render/frame.js +38 -4
- package/lib/render/npm.js +228 -0
- package/lib/render/primitives.js +7 -36
- package/lib/report-model.js +127 -0
- package/lib/report-parse.js +94 -0
- package/lib/report-render.js +87 -0
- package/lib/report-run.js +164 -0
- package/lib/report-test.js +506 -0
- package/lib/report-tools.js +311 -0
- package/lib/session/actions.js +51 -2
- package/lib/session/commit.js +94 -19
- package/lib/session/compose.js +48 -5
- package/lib/session/dispatch.js +30 -2
- package/lib/session/files-pane.js +20 -4
- package/lib/session/lifecycle.js +4 -1
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +335 -0
- package/lib/session/pointer.js +8 -0
- package/lib/session/view.js +14 -3
- package/lib/session.js +10 -2
- package/package.json +6 -6
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseAuditReport, parseOutdatedReport } = require('./deps.js');
|
|
4
|
+
const model = require('./report-model.js');
|
|
5
|
+
|
|
6
|
+
const { summary, problem, report, userTrace } = model;
|
|
7
|
+
|
|
8
|
+
const place = (file, line, column, root) =>
|
|
9
|
+
userTrace(`${file}:${line}:${column}`, root);
|
|
10
|
+
|
|
11
|
+
const parseEslintJson = (data, root) => {
|
|
12
|
+
const rows = Array.isArray(data) ? data : data && data.results;
|
|
13
|
+
if (!Array.isArray(rows) || !rows.length) return null;
|
|
14
|
+
if (!rows[0] || !Array.isArray(rows[0].messages)) return null;
|
|
15
|
+
const stats = summary();
|
|
16
|
+
let errors = 0;
|
|
17
|
+
let warnings = 0;
|
|
18
|
+
const problems = [];
|
|
19
|
+
for (const file of rows) {
|
|
20
|
+
const messages = file.messages || [];
|
|
21
|
+
for (const item of messages) {
|
|
22
|
+
const severity = item.severity === 1 ? 'warning' : 'error';
|
|
23
|
+
if (severity === 'warning') warnings += 1;
|
|
24
|
+
else errors += 1;
|
|
25
|
+
problems.push(
|
|
26
|
+
problem({
|
|
27
|
+
severity,
|
|
28
|
+
message: item.message || '',
|
|
29
|
+
code: item.ruleId || '',
|
|
30
|
+
trace: place(
|
|
31
|
+
file.filePath || '',
|
|
32
|
+
item.line || 0,
|
|
33
|
+
item.column || 0,
|
|
34
|
+
root,
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
stats.errors = errors;
|
|
41
|
+
stats.warnings = warnings;
|
|
42
|
+
return report('eslint', { summary: stats, problems });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const eslintLine = (line) => {
|
|
46
|
+
const match = /^\s*(\d+):(\d+)\s+(error|warning)\s+(.*)$/.exec(line);
|
|
47
|
+
if (!match) return null;
|
|
48
|
+
const rest = match[4];
|
|
49
|
+
const ruleAt = rest.lastIndexOf(' ');
|
|
50
|
+
const message = ruleAt < 0 ? rest : rest.slice(0, ruleAt);
|
|
51
|
+
const code = ruleAt < 0 ? '' : rest.slice(ruleAt + 2).trim();
|
|
52
|
+
return {
|
|
53
|
+
line: match[1],
|
|
54
|
+
column: match[2],
|
|
55
|
+
severity: match[3],
|
|
56
|
+
message: message.trim(),
|
|
57
|
+
code,
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const isPathLine = (line) => {
|
|
62
|
+
const text = line.trim();
|
|
63
|
+
if (!text || text.startsWith('✖')) return false;
|
|
64
|
+
if (eslintLine(text)) return false;
|
|
65
|
+
return text.includes('/') || text.includes('\\') || /\.\w+$/.test(text);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const parseEslintStylish = (text, root) => {
|
|
69
|
+
const lines = `${text ?? ''}`.split(/\r?\n/);
|
|
70
|
+
const problems = [];
|
|
71
|
+
let file = '';
|
|
72
|
+
let errors = 0;
|
|
73
|
+
let warnings = 0;
|
|
74
|
+
let seen = false;
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const item = eslintLine(line);
|
|
77
|
+
if (item) {
|
|
78
|
+
seen = true;
|
|
79
|
+
if (item.severity === 'warning') warnings += 1;
|
|
80
|
+
else errors += 1;
|
|
81
|
+
problems.push(
|
|
82
|
+
problem({
|
|
83
|
+
severity: item.severity,
|
|
84
|
+
message: item.message,
|
|
85
|
+
code: item.code,
|
|
86
|
+
trace: place(file, item.line, item.column, root),
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (/✖\s+\d+\s+problems?\b/.test(line)) seen = true;
|
|
92
|
+
if (isPathLine(line)) file = line.trim();
|
|
93
|
+
}
|
|
94
|
+
if (!seen) return null;
|
|
95
|
+
const stats = summary();
|
|
96
|
+
stats.errors = errors;
|
|
97
|
+
stats.warnings = warnings;
|
|
98
|
+
return report('eslint', { summary: stats, problems });
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const tscLine = (line) => {
|
|
102
|
+
const match = /^(.*)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.*)$/.exec(
|
|
103
|
+
line.trim(),
|
|
104
|
+
);
|
|
105
|
+
if (!match) return null;
|
|
106
|
+
return {
|
|
107
|
+
file: match[1],
|
|
108
|
+
line: match[2],
|
|
109
|
+
column: match[3],
|
|
110
|
+
code: match[4],
|
|
111
|
+
message: match[5],
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const parseTsc = (text, root) => {
|
|
116
|
+
const problems = [];
|
|
117
|
+
for (const line of `${text ?? ''}`.split(/\r?\n/)) {
|
|
118
|
+
const item = tscLine(line);
|
|
119
|
+
if (!item) continue;
|
|
120
|
+
problems.push(
|
|
121
|
+
problem({
|
|
122
|
+
message: item.message,
|
|
123
|
+
code: item.code,
|
|
124
|
+
trace: place(item.file, item.line, item.column, root),
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (!problems.length) return null;
|
|
129
|
+
const stats = summary();
|
|
130
|
+
stats.errors = problems.length;
|
|
131
|
+
return report('tsc', { summary: stats, problems });
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const prettierFile = (line) => {
|
|
135
|
+
const warn = /^\[(?:warn|error)\]\s+(\S+)$/.exec(line.trim());
|
|
136
|
+
if (warn && !warn[1].startsWith('Code')) return warn[1];
|
|
137
|
+
return '';
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const parsePrettier = (text, root) => {
|
|
141
|
+
const lines = `${text ?? ''}`.split(/\r?\n/);
|
|
142
|
+
const problems = [];
|
|
143
|
+
let seen = false;
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
if (line.includes('Code style issues')) seen = true;
|
|
146
|
+
if (line.includes('All matched files use Prettier')) seen = true;
|
|
147
|
+
const file = prettierFile(line);
|
|
148
|
+
if (!file) continue;
|
|
149
|
+
seen = true;
|
|
150
|
+
problems.push(
|
|
151
|
+
problem({
|
|
152
|
+
message: 'format',
|
|
153
|
+
trace: userTrace(file, root),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (!seen) return null;
|
|
158
|
+
const stats = summary();
|
|
159
|
+
stats.errors = problems.length;
|
|
160
|
+
return report('prettier', { summary: stats, problems });
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const auditProblems = (text) => {
|
|
164
|
+
const audit = parseAuditReport(text);
|
|
165
|
+
const problems = [];
|
|
166
|
+
for (const item of audit.values()) {
|
|
167
|
+
const extra = [];
|
|
168
|
+
if (item.range) extra.push(['range', item.range]);
|
|
169
|
+
if (item.fix) extra.push(['fix', item.fix]);
|
|
170
|
+
problems.push(
|
|
171
|
+
problem({
|
|
172
|
+
severity: item.severity || 'error',
|
|
173
|
+
name: item.name,
|
|
174
|
+
message: item.title || '',
|
|
175
|
+
extra,
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return problems;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const parseAudit = (text) => {
|
|
183
|
+
const problems = auditProblems(text);
|
|
184
|
+
if (!problems.length) return null;
|
|
185
|
+
const stats = summary();
|
|
186
|
+
stats.errors = problems.length;
|
|
187
|
+
return report('audit', { summary: stats, problems });
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const behind = (item) => {
|
|
191
|
+
if (!item.current) return true;
|
|
192
|
+
if (item.wanted && item.wanted !== item.current) return true;
|
|
193
|
+
if (item.latest && item.latest !== item.current) return true;
|
|
194
|
+
return false;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const parseOutdated = (text) => {
|
|
198
|
+
const outdated = parseOutdatedReport(text);
|
|
199
|
+
const problems = [];
|
|
200
|
+
for (const [name, item] of outdated) {
|
|
201
|
+
if (!behind(item)) continue;
|
|
202
|
+
problems.push(
|
|
203
|
+
problem({
|
|
204
|
+
severity: 'warning',
|
|
205
|
+
name,
|
|
206
|
+
message: `${item.current} ${item.wanted} ${item.latest}`.trim(),
|
|
207
|
+
extra: [
|
|
208
|
+
['current', item.current],
|
|
209
|
+
['wanted', item.wanted],
|
|
210
|
+
['latest', item.latest],
|
|
211
|
+
],
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (!problems.length) return null;
|
|
216
|
+
const stats = summary();
|
|
217
|
+
stats.warnings = problems.length;
|
|
218
|
+
return report('outdated', { summary: stats, problems });
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const pushLs = (problems, text) => {
|
|
222
|
+
const message = `${text ?? ''}`.trim();
|
|
223
|
+
if (!message) return;
|
|
224
|
+
problems.push(problem({ message }));
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const walkLs = (node, problems) => {
|
|
228
|
+
if (!node || typeof node !== 'object') return;
|
|
229
|
+
if (Array.isArray(node.problems)) {
|
|
230
|
+
for (const item of node.problems) pushLs(problems, item);
|
|
231
|
+
}
|
|
232
|
+
if (node.missing) {
|
|
233
|
+
pushLs(problems, `missing ${node.required || node.version || ''}`);
|
|
234
|
+
}
|
|
235
|
+
if (node.invalid) pushLs(problems, `invalid ${node.version || ''}`);
|
|
236
|
+
if (node.extraneous) pushLs(problems, `extraneous ${node.version || ''}`);
|
|
237
|
+
const deps = node.dependencies;
|
|
238
|
+
if (!deps || typeof deps !== 'object') return;
|
|
239
|
+
for (const name of Object.keys(deps)) walkLs(deps[name], problems);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const parseLs = (data) => {
|
|
243
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return null;
|
|
244
|
+
if (!data.dependencies && !Array.isArray(data.problems)) return null;
|
|
245
|
+
const problems = [];
|
|
246
|
+
walkLs(data, problems);
|
|
247
|
+
if (!problems.length && !Array.isArray(data.problems)) return null;
|
|
248
|
+
const stats = summary();
|
|
249
|
+
stats.errors = problems.length;
|
|
250
|
+
return report('ls', { summary: stats, problems });
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const parseToolJson = (data, root) => {
|
|
254
|
+
const eslint = parseEslintJson(data, root);
|
|
255
|
+
if (eslint) return [eslint];
|
|
256
|
+
const vulns = data && (data.vulnerabilities || data.advisories);
|
|
257
|
+
const audit = vulns ? parseAudit(JSON.stringify(data)) : null;
|
|
258
|
+
if (audit) return [audit];
|
|
259
|
+
const ls = parseLs(data);
|
|
260
|
+
if (ls) return [ls];
|
|
261
|
+
const outdated = parseOutdated(JSON.stringify(data));
|
|
262
|
+
if (outdated) return [outdated];
|
|
263
|
+
return null;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const parseAuditText = (text) => {
|
|
267
|
+
if (`${text ?? ''}`.trim().startsWith('{')) return null;
|
|
268
|
+
const lines = `${text ?? ''}`.split(/\r?\n/);
|
|
269
|
+
const problems = [];
|
|
270
|
+
for (let index = 0; index < lines.length; index++) {
|
|
271
|
+
const match = /^Severity:\s+(\w+)\s*$/.exec(lines[index].trim());
|
|
272
|
+
if (!match) continue;
|
|
273
|
+
const name = (lines[index - 1] || '').trim();
|
|
274
|
+
problems.push(
|
|
275
|
+
problem({
|
|
276
|
+
severity: match[1].toLowerCase(),
|
|
277
|
+
name,
|
|
278
|
+
message: name,
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (!problems.length) return null;
|
|
283
|
+
const stats = summary();
|
|
284
|
+
stats.errors = problems.length;
|
|
285
|
+
return report('audit', { summary: stats, problems });
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const parseToolText = (text, root) => {
|
|
289
|
+
const reports = [];
|
|
290
|
+
const eslint = parseEslintStylish(text, root);
|
|
291
|
+
if (eslint) reports.push(eslint);
|
|
292
|
+
const tsc = parseTsc(text, root);
|
|
293
|
+
if (tsc) reports.push(tsc);
|
|
294
|
+
const prettier = parsePrettier(text, root);
|
|
295
|
+
if (prettier) reports.push(prettier);
|
|
296
|
+
const audit = parseAuditText(text);
|
|
297
|
+
if (audit) reports.push(audit);
|
|
298
|
+
return reports;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
parseEslintJson,
|
|
303
|
+
parseEslintStylish,
|
|
304
|
+
parseTsc,
|
|
305
|
+
parsePrettier,
|
|
306
|
+
parseAudit,
|
|
307
|
+
parseOutdated,
|
|
308
|
+
parseLs,
|
|
309
|
+
parseToolJson,
|
|
310
|
+
parseToolText,
|
|
311
|
+
};
|
package/lib/session/actions.js
CHANGED
|
@@ -33,21 +33,36 @@ const COMMIT_ACTIONS = {
|
|
|
33
33
|
reword: ['r'],
|
|
34
34
|
fixup: ['f'],
|
|
35
35
|
drop: ['d'],
|
|
36
|
+
view: ['v'],
|
|
36
37
|
pull: ['p'],
|
|
37
38
|
push: ['s'],
|
|
38
39
|
};
|
|
39
40
|
|
|
41
|
+
const NPM_ACTIONS = {
|
|
42
|
+
npm: ['n'],
|
|
43
|
+
npmEdit: ['e'],
|
|
44
|
+
npmNew: ['n'],
|
|
45
|
+
npmStop: ['escape'],
|
|
46
|
+
npmRerun: ['r'],
|
|
47
|
+
};
|
|
48
|
+
|
|
40
49
|
const ACTION_HOT = {
|
|
41
50
|
prev: '←',
|
|
42
51
|
next: '→',
|
|
52
|
+
npmStop: 'esc',
|
|
43
53
|
};
|
|
44
54
|
|
|
45
55
|
const ACTION_LABEL = {
|
|
46
56
|
revert: 'drop',
|
|
47
57
|
check: 'x',
|
|
48
58
|
layout: 'mode',
|
|
59
|
+
view: 'view',
|
|
49
60
|
code: 'edit',
|
|
50
61
|
newBranch: 'new',
|
|
62
|
+
npmEdit: 'edit',
|
|
63
|
+
npmNew: 'new',
|
|
64
|
+
npmStop: 'esc',
|
|
65
|
+
npmRerun: 're-run',
|
|
51
66
|
quit: 'q',
|
|
52
67
|
prev: '←',
|
|
53
68
|
next: '→',
|
|
@@ -121,6 +136,24 @@ const COMMITS_DISABLED = [
|
|
|
121
136
|
'prev',
|
|
122
137
|
'next',
|
|
123
138
|
];
|
|
139
|
+
const NPM_DISABLED = [
|
|
140
|
+
'add',
|
|
141
|
+
'unstage',
|
|
142
|
+
'revert',
|
|
143
|
+
'commit',
|
|
144
|
+
'layout',
|
|
145
|
+
'feedback',
|
|
146
|
+
'todo',
|
|
147
|
+
'code',
|
|
148
|
+
'check',
|
|
149
|
+
'branch',
|
|
150
|
+
'file',
|
|
151
|
+
'diff',
|
|
152
|
+
'prev',
|
|
153
|
+
'next',
|
|
154
|
+
'pull',
|
|
155
|
+
'push',
|
|
156
|
+
];
|
|
124
157
|
|
|
125
158
|
const isTodosTarget = (item) =>
|
|
126
159
|
!!(item && (item.kind === 'todos' || item.origin === 'todo'));
|
|
@@ -164,6 +197,7 @@ const disabledActions = (pane, item) => {
|
|
|
164
197
|
if (pane === 'commits') {
|
|
165
198
|
return [...COMMITS_DISABLED, ...commitWriteDisabled(item)];
|
|
166
199
|
}
|
|
200
|
+
if (pane === 'npm') return NPM_DISABLED;
|
|
167
201
|
if (onTodos) return TODO_DISABLED;
|
|
168
202
|
return [...DIFF_DISABLED, ...diffGitDisabled(item)];
|
|
169
203
|
};
|
|
@@ -205,10 +239,16 @@ const COMMIT_KEYS = {};
|
|
|
205
239
|
for (const id of Object.keys(COMMIT_ACTIONS)) {
|
|
206
240
|
for (const key of COMMIT_ACTIONS[id]) COMMIT_KEYS[key] = id;
|
|
207
241
|
}
|
|
242
|
+
const NPM_KEYS = {
|
|
243
|
+
e: 'npmEdit',
|
|
244
|
+
n: 'npmNew',
|
|
245
|
+
};
|
|
246
|
+
FILES_KEYS.n = 'npm';
|
|
208
247
|
const PANE_KEYS = {
|
|
209
248
|
files: FILES_KEYS,
|
|
210
249
|
branches: BRANCH_KEYS,
|
|
211
250
|
commits: COMMIT_KEYS,
|
|
251
|
+
npm: NPM_KEYS,
|
|
212
252
|
};
|
|
213
253
|
|
|
214
254
|
for (const id of ACTION_IDS) {
|
|
@@ -223,7 +263,10 @@ for (const key of Object.keys(HIDDEN_KEYS)) {
|
|
|
223
263
|
|
|
224
264
|
const actionFromKey = (key, pane, fileScope) => {
|
|
225
265
|
const listPane =
|
|
226
|
-
pane === 'files' ||
|
|
266
|
+
pane === 'files' ||
|
|
267
|
+
pane === 'branches' ||
|
|
268
|
+
pane === 'commits' ||
|
|
269
|
+
pane === 'npm';
|
|
227
270
|
if (listPane && (key === 'left' || key === 'right')) return null;
|
|
228
271
|
const paneKeys = PANE_KEYS[pane];
|
|
229
272
|
if (paneKeys && paneKeys[key]) return paneKeys[key];
|
|
@@ -235,7 +278,11 @@ const actionFromKey = (key, pane, fileScope) => {
|
|
|
235
278
|
};
|
|
236
279
|
|
|
237
280
|
const actionKeys = (id) =>
|
|
238
|
-
ACTIONS[id] ??
|
|
281
|
+
ACTIONS[id] ??
|
|
282
|
+
BRANCH_ACTIONS[id] ??
|
|
283
|
+
COMMIT_ACTIONS[id] ??
|
|
284
|
+
NPM_ACTIONS[id] ??
|
|
285
|
+
[];
|
|
239
286
|
|
|
240
287
|
const actionHot = (id) => ACTION_HOT[id] ?? actionKeys(id)[0];
|
|
241
288
|
|
|
@@ -327,6 +374,7 @@ module.exports = {
|
|
|
327
374
|
ACTION_IDS,
|
|
328
375
|
BRANCH_ACTIONS,
|
|
329
376
|
COMMIT_ACTIONS,
|
|
377
|
+
NPM_ACTIONS,
|
|
330
378
|
ACTION_HOT,
|
|
331
379
|
ACTION_LABEL,
|
|
332
380
|
DIFF_DISABLED,
|
|
@@ -338,6 +386,7 @@ module.exports = {
|
|
|
338
386
|
TODO_DISABLED,
|
|
339
387
|
BRANCHES_DISABLED,
|
|
340
388
|
COMMITS_DISABLED,
|
|
389
|
+
NPM_DISABLED,
|
|
341
390
|
SCROLL,
|
|
342
391
|
PANE_ONLY,
|
|
343
392
|
disabledActions,
|
package/lib/session/commit.js
CHANGED
|
@@ -5,6 +5,11 @@ const { stateView } = require('./accessors.js');
|
|
|
5
5
|
const { isReadOnlyOrigin } = require('../files.js');
|
|
6
6
|
const { isFixupCommit } = require('./actions.js');
|
|
7
7
|
const { pickRepoWrite } = require('./ops.js');
|
|
8
|
+
const {
|
|
9
|
+
commitLine,
|
|
10
|
+
commitText,
|
|
11
|
+
splitMessage,
|
|
12
|
+
} = require('../render/commits.js');
|
|
8
13
|
|
|
9
14
|
const COMMIT_STATUS = {
|
|
10
15
|
commit: 'committed',
|
|
@@ -15,8 +20,37 @@ const COMMIT_STATUS = {
|
|
|
15
20
|
|
|
16
21
|
const isInPlaceCommit = (kind) => kind === 'amend' || kind === 'reword';
|
|
17
22
|
|
|
23
|
+
const PENDING_COMMIT = {
|
|
24
|
+
pending: true,
|
|
25
|
+
subject: 'uncommitted changes',
|
|
26
|
+
sha: '',
|
|
27
|
+
shortSha: '',
|
|
28
|
+
author: '',
|
|
29
|
+
email: '',
|
|
30
|
+
date: '',
|
|
31
|
+
when: '',
|
|
32
|
+
refs: '',
|
|
33
|
+
body: '',
|
|
34
|
+
head: false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const listedCommits = (commits) => [PENDING_COMMIT].concat(commits ?? []);
|
|
38
|
+
|
|
39
|
+
const isPending = (entry) => Boolean(entry && entry.pending);
|
|
40
|
+
|
|
41
|
+
const briefCommit = (ui) => ui.commits.commitView !== 'full';
|
|
42
|
+
|
|
43
|
+
const joinMessage = (line, rest) => (rest ? `${line}\n${rest}` : line);
|
|
44
|
+
|
|
45
|
+
const keepBriefBody = (ui, text) => {
|
|
46
|
+
if (!briefCommit(ui)) return text;
|
|
47
|
+
const parts = splitMessage(text);
|
|
48
|
+
ui.composer.state.commitBody = parts.rest;
|
|
49
|
+
return parts.line;
|
|
50
|
+
};
|
|
51
|
+
|
|
18
52
|
const fixupMessage = (entry) => {
|
|
19
|
-
const subject =
|
|
53
|
+
const subject = commitLine(entry).trim();
|
|
20
54
|
if (!subject) return 'fixup!';
|
|
21
55
|
return `fixup! ${subject}`;
|
|
22
56
|
};
|
|
@@ -47,11 +81,11 @@ const hasStagedChanges = (ui) => {
|
|
|
47
81
|
|
|
48
82
|
const selectedCommit = (api) => {
|
|
49
83
|
const cursor = api.ui.nav.commitCursor;
|
|
50
|
-
return api.state.commits[cursor] ?? null;
|
|
84
|
+
return listedCommits(api.state.commits)[cursor] ?? null;
|
|
51
85
|
};
|
|
52
86
|
|
|
53
87
|
const clampCommitCursor = (api) => {
|
|
54
|
-
const last = api.state.commits.length - 1;
|
|
88
|
+
const last = listedCommits(api.state.commits).length - 1;
|
|
55
89
|
const nav = api.ui.nav;
|
|
56
90
|
if (nav.commitCursor > last) nav.commitCursor = Math.max(0, last);
|
|
57
91
|
};
|
|
@@ -75,7 +109,6 @@ const refreshAfterWrite = (api, extra = {}) => {
|
|
|
75
109
|
|
|
76
110
|
const showCommits = (api) => {
|
|
77
111
|
if (api.ui.mode === 'compose') return;
|
|
78
|
-
if (!canCommit(api.ui)) return;
|
|
79
112
|
const repo = api.ui.repo;
|
|
80
113
|
if (!repo.listCommits) {
|
|
81
114
|
api.ui.status = 'read only';
|
|
@@ -94,11 +127,8 @@ const showCommits = (api) => {
|
|
|
94
127
|
};
|
|
95
128
|
|
|
96
129
|
const onCommitMove = (api, delta) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
api.ui.status = 'nothing to review';
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
130
|
+
if (api.ui.mode === 'compose') return;
|
|
131
|
+
const commits = listedCommits(api.state.commits);
|
|
102
132
|
const next = Math.min(
|
|
103
133
|
commits.length - 1,
|
|
104
134
|
Math.max(0, api.ui.nav.commitCursor + delta),
|
|
@@ -117,15 +147,13 @@ const startGitCommit = (api, kind) => {
|
|
|
117
147
|
return;
|
|
118
148
|
}
|
|
119
149
|
let text = '';
|
|
120
|
-
if (kind === 'fixup')
|
|
121
|
-
|
|
122
|
-
}
|
|
150
|
+
if (kind === 'fixup') text = fixupMessage(selectedCommit(api));
|
|
151
|
+
if (kind === 'commit') api.ui.nav.commitCursor = 0;
|
|
123
152
|
if (kind === 'amend') {
|
|
124
153
|
const headAt = api.state.commits.findIndex((entry) => entry.head);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
154
|
+
const at = headAt < 0 ? 0 : headAt;
|
|
155
|
+
api.ui.nav.commitCursor = api.state.commits.length ? at + 1 : 0;
|
|
156
|
+
if (ui.repo.lastMessage) text = ui.repo.lastMessage(ui.top) ?? '';
|
|
129
157
|
}
|
|
130
158
|
if (kind === 'reword') {
|
|
131
159
|
const entry = selectedCommit(api);
|
|
@@ -133,19 +161,50 @@ const startGitCommit = (api, kind) => {
|
|
|
133
161
|
if (typeof ui.repo.commitMessage === 'function') {
|
|
134
162
|
text = ui.repo.commitMessage(ui.top, entry.sha) ?? '';
|
|
135
163
|
} else {
|
|
136
|
-
text =
|
|
164
|
+
text = commitText(entry);
|
|
137
165
|
}
|
|
138
166
|
}
|
|
167
|
+
ui.composer.state.commitBody = '';
|
|
168
|
+
if (isInPlaceCommit(kind)) text = keepBriefBody(ui, text);
|
|
139
169
|
ui.composer.state.commitKind = kind;
|
|
140
170
|
ui.composer.openCompose('commit', text);
|
|
171
|
+
const editor = ui.composer.state.editor;
|
|
172
|
+
if (!editor) return;
|
|
173
|
+
const lineEnd = editor.text.indexOf('\n');
|
|
174
|
+
editor.cursor = lineEnd < 0 ? editor.text.length : lineEnd;
|
|
141
175
|
};
|
|
142
176
|
|
|
143
177
|
const onCommit = (api) => {
|
|
144
178
|
if (api.ui.nav.pane !== 'commits') return void showCommits(api);
|
|
145
179
|
if (!canCommit(api.ui)) return;
|
|
180
|
+
api.ui.nav.commitCursor = 0;
|
|
146
181
|
startGitCommit(api, 'commit');
|
|
147
182
|
};
|
|
148
183
|
|
|
184
|
+
const showSelectedDiff = (api) => {
|
|
185
|
+
if (api.ui.mode === 'compose') return;
|
|
186
|
+
const entry = selectedCommit(api);
|
|
187
|
+
if (!entry) return;
|
|
188
|
+
const ui = api.ui;
|
|
189
|
+
if (isPending(entry) || !entry.sha) {
|
|
190
|
+
ui.rev = '';
|
|
191
|
+
ui.revShort = '';
|
|
192
|
+
} else {
|
|
193
|
+
ui.rev = entry.sha;
|
|
194
|
+
ui.revShort = entry.shortSha || '';
|
|
195
|
+
}
|
|
196
|
+
const openDiff = () => {
|
|
197
|
+
ui.nav.todoOpen = false;
|
|
198
|
+
ui.nav.scroll = 0;
|
|
199
|
+
ui.nav.index = 0;
|
|
200
|
+
ui.nav.pane = ui.items.length ? 'diff' : 'files';
|
|
201
|
+
ui.syncReviewPath();
|
|
202
|
+
ui.syncFileCursor();
|
|
203
|
+
ui.status = '';
|
|
204
|
+
};
|
|
205
|
+
ui.refreshFromRepo({ keepEmpty: true, afterLoad: openDiff });
|
|
206
|
+
};
|
|
207
|
+
|
|
149
208
|
const onAmend = (api) => {
|
|
150
209
|
if (api.ui.nav.pane !== 'commits') return;
|
|
151
210
|
if (!canCommit(api.ui)) return;
|
|
@@ -260,7 +319,10 @@ const finishGitCommit = (api) => {
|
|
|
260
319
|
const ui = api.ui;
|
|
261
320
|
const kind = ui.composer.state.commitKind;
|
|
262
321
|
const editor = ui.composer.state.editor;
|
|
263
|
-
|
|
322
|
+
let message = editor ? editor.text : '';
|
|
323
|
+
if (isInPlaceCommit(kind) && briefCommit(ui)) {
|
|
324
|
+
message = joinMessage(message, ui.composer.state.commitBody);
|
|
325
|
+
}
|
|
264
326
|
if (!message.trim()) {
|
|
265
327
|
ui.status = 'empty commit message';
|
|
266
328
|
return;
|
|
@@ -330,10 +392,19 @@ const confirmDropCommit = (api) => {
|
|
|
330
392
|
if (!api.ui.status) api.ui.status = done;
|
|
331
393
|
};
|
|
332
394
|
|
|
395
|
+
const onView = (api) => {
|
|
396
|
+
if (api.ui.nav.pane !== 'commits') return;
|
|
397
|
+
const next = api.state.commitView === 'full' ? 'brief' : 'full';
|
|
398
|
+
api.state.commitView = next;
|
|
399
|
+
api.ui.nav.listScroll = 0;
|
|
400
|
+
api.ui.status = next;
|
|
401
|
+
};
|
|
402
|
+
|
|
333
403
|
const reset = (state) => {
|
|
334
404
|
state.commits = [];
|
|
335
405
|
state.dropName = '';
|
|
336
406
|
state.dropSha = '';
|
|
407
|
+
state.commitView = 'brief';
|
|
337
408
|
};
|
|
338
409
|
|
|
339
410
|
const createCommitController = (ui) => {
|
|
@@ -341,6 +412,7 @@ const createCommitController = (ui) => {
|
|
|
341
412
|
commits: [],
|
|
342
413
|
dropName: '',
|
|
343
414
|
dropSha: '',
|
|
415
|
+
commitView: 'brief',
|
|
344
416
|
};
|
|
345
417
|
const api = { ui, state };
|
|
346
418
|
return stateView(
|
|
@@ -354,6 +426,8 @@ const createCommitController = (ui) => {
|
|
|
354
426
|
onApply: () => onApply(api),
|
|
355
427
|
onFixup: () => onFixup(api),
|
|
356
428
|
onCommitMove: (delta) => onCommitMove(api, delta),
|
|
429
|
+
onShowDiff: () => showSelectedDiff(api),
|
|
430
|
+
onView: () => onView(api),
|
|
357
431
|
cancelCommit: () => cancelCommit(api),
|
|
358
432
|
startGitCommit: (kind) => startGitCommit(api, kind),
|
|
359
433
|
finishGitCommit: () => finishGitCommit(api),
|
|
@@ -362,11 +436,12 @@ const createCommitController = (ui) => {
|
|
|
362
436
|
cancelDropCommit: () => cancelDropCommit(api),
|
|
363
437
|
confirmDropCommit: () => confirmDropCommit(api),
|
|
364
438
|
},
|
|
365
|
-
['commits', 'dropName'],
|
|
439
|
+
['commits', 'dropName', 'commitView'],
|
|
366
440
|
);
|
|
367
441
|
};
|
|
368
442
|
|
|
369
443
|
module.exports = {
|
|
370
444
|
COMMIT_STATUS,
|
|
371
445
|
createCommitController,
|
|
446
|
+
listedCommits,
|
|
372
447
|
};
|