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.
@@ -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
+ };
@@ -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' || pane === 'branches' || pane === 'commits';
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] ?? BRANCH_ACTIONS[id] ?? COMMIT_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,
@@ -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 = `${entry && entry.subject ? entry.subject : ''}`.trim();
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
- const commits = api.state.commits;
98
- if (!commits.length) {
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
- text = fixupMessage(selectedCommit(api));
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
- api.ui.nav.commitCursor = headAt < 0 ? 0 : headAt;
126
- if (ui.repo.lastMessage) {
127
- text = ui.repo.lastMessage(ui.top) ?? '';
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 = `${entry.subject ?? ''}`;
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
- const message = editor ? editor.text : '';
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
  };