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.
@@ -4,6 +4,8 @@ const wrap = require('../wrap.js');
4
4
  const { cursorInWrap, wrapDoc } = wrap;
5
5
  const render = require('../render/render.js');
6
6
  const { noteInnerWidth, codeInnerWidth, todoBodyWidth } = render;
7
+ const { commitMessageWidth } = require('../render/commits.js');
8
+ const { listedCommits } = require('./commit.js');
7
9
  const { Editor } = require('../editor.js');
8
10
  const files = require('../files.js');
9
11
  const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
@@ -68,12 +70,21 @@ const KIND = {
68
70
  command: 'branch',
69
71
  view: 'note',
70
72
  },
73
+ npm: {
74
+ enter: 'save',
75
+ tab: 'insert-tab',
76
+ escape: 'cancel',
77
+ move: 'note',
78
+ command: 'npm',
79
+ view: 'note',
80
+ },
71
81
  };
72
82
 
73
83
  const initialState = () => ({
74
84
  editor: null,
75
85
  composeKind: null,
76
86
  commitKind: null,
87
+ commitBody: '',
77
88
  composeTodoId: null,
78
89
  composeBaseline: '',
79
90
  templateIndex: -1,
@@ -140,6 +151,7 @@ const closeCompose = (api) => {
140
151
  state.editor = null;
141
152
  state.composeKind = null;
142
153
  state.commitKind = null;
154
+ state.commitBody = '';
143
155
  state.composeTodoId = null;
144
156
  state.composeBaseline = '';
145
157
  state.fileOpened = '';
@@ -474,6 +486,22 @@ const moveNoteLine = (api, delta) => {
474
486
  api.state.editor.moveLine(delta, width);
475
487
  };
476
488
 
489
+ const fullCommitEdit = (api) =>
490
+ api.state.composeKind === 'commit' && api.ui.commits.commitView === 'full';
491
+
492
+ const editedCommitEntry = (api) => {
493
+ const kind = api.state.commitKind;
494
+ if (kind !== 'amend' && kind !== 'reword') return null;
495
+ const list = listedCommits(api.ui.commits.commits);
496
+ return list[api.nav.commitCursor] ?? null;
497
+ };
498
+
499
+ const moveCommitLine = (api, delta) => {
500
+ const size = viewSize(api);
501
+ const width = commitMessageWidth(editedCommitEntry(api), size.width);
502
+ api.state.editor.moveLine(delta, width);
503
+ };
504
+
477
505
  const moveComposeLine = (api, delta) => {
478
506
  const spec = KIND[api.state.composeKind];
479
507
  const move = spec ? spec.move : 'note';
@@ -487,6 +515,7 @@ const moveComposeLine = (api, delta) => {
487
515
  const inner = codeInnerWidth(size.width, api.ui.layout);
488
516
  return void api.state.editor.moveLine(delta, inner);
489
517
  }
518
+ if (fullCommitEdit(api)) return void moveCommitLine(api, delta);
490
519
  moveNoteLine(api, delta);
491
520
  };
492
521
 
@@ -575,17 +604,31 @@ const onCode = (api) => {
575
604
  openCompose(api, 'code', text);
576
605
  };
577
606
 
607
+ const insertNewline = (api) => {
608
+ api.state.editor.insert('\n');
609
+ resetBlink(api);
610
+ followFileEdit(api);
611
+ return null;
612
+ };
613
+
614
+ const onEmptyLastLine = (editor) => {
615
+ if (!editor) return false;
616
+ const text = editor.text;
617
+ const lastBreak = text.lastIndexOf('\n');
618
+ if (editor.cursor < lastBreak + 1) return false;
619
+ return text.slice(lastBreak + 1).trim() === '';
620
+ };
621
+
578
622
  const enterCompose = (api) => {
579
623
  const { state } = api;
580
624
  if (state.templateFocus && state.composeKind === 'feedback') {
581
625
  return api.templates.selectTemplate(state.templateIndex);
582
626
  }
583
627
  const spec = KIND[state.composeKind];
584
- if (spec && spec.enter === 'newline') {
585
- state.editor.insert('\n');
586
- resetBlink(api);
587
- followFileEdit(api);
588
- return null;
628
+ if (spec && spec.enter === 'newline') return insertNewline(api);
629
+ if (fullCommitEdit(api)) {
630
+ if (onEmptyLastLine(state.editor)) return saveCompose(api);
631
+ return insertNewline(api);
589
632
  }
590
633
  if (spec && spec.enter === 'edit-next') {
591
634
  return saveCompose(api, { editNext: true });
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const compose = require('./compose.js');
4
+ const { listedCommits } = require('./commit.js');
4
5
  const { COMPOSE_LEAVE } = compose;
5
6
  const actions = require('./actions.js');
6
7
  const { CONFIRM_MODE, disabledActions, actionFromKey, applyConfirm } = actions;
@@ -16,7 +17,7 @@ const currentTarget = (ui) => {
16
17
  return ui.branches[ui.branchCursor];
17
18
  }
18
19
  if (ui.pane === 'commits') {
19
- const entry = ui.commits.commits[ui.commitCursor];
20
+ const entry = listedCommits(ui.commits.commits)[ui.commitCursor];
20
21
  const staged = targetHasStaged(ui);
21
22
  if (!entry) return { canCommit: staged };
22
23
  return { ...entry, canCommit: staged };
@@ -43,6 +44,7 @@ const createDispatcher = (ui) => {
43
44
  reword: ui.commits.onReword,
44
45
  apply: ui.commits.onApply,
45
46
  fixup: ui.commits.onFixup,
47
+ view: () => ui.commits.onView(),
46
48
  branch: ui.gitBranches.onBranch,
47
49
  pull: ui.gitBranches.onPull,
48
50
  push: ui.gitBranches.onPush,
@@ -57,8 +59,16 @@ const createDispatcher = (ui) => {
57
59
  open: ui.filesPane.onOpenFile,
58
60
  file: ui.filesPane.onFileScope,
59
61
  diff: ui.filesPane.onDiffScope,
62
+ npm: ui.npm.open,
63
+ npmEdit: ui.npm.edit,
64
+ npmNew: ui.npm.create,
65
+ npmStop: () => ui.npm.closeView(),
66
+ npmRerun: () => ui.npm.rerun(),
60
67
  check: () => ui.composer.toggleFocusedTodo(),
61
- removeTodo: ui.composer.removeFocusedTodo,
68
+ removeTodo: () => {
69
+ if (ui.pane === 'npm') return void ui.npm.askDrop();
70
+ ui.composer.removeFocusedTodo();
71
+ },
62
72
  quit: () => ui.onQuit(),
63
73
  scrollUp: () => ui.filesPane.onScroll(-1),
64
74
  scrollDown: () => ui.filesPane.onScroll(1),
@@ -90,6 +100,10 @@ const createDispatcher = (ui) => {
90
100
  if (action === 'next') return void ui.commits.onCommitMove(1);
91
101
  if (action === 'prev') return void ui.commits.onCommitMove(-1);
92
102
  }
103
+ if (pane === 'npm') {
104
+ if (action === 'next') return void ui.npm.move(1);
105
+ if (action === 'prev') return void ui.npm.move(-1);
106
+ }
93
107
  const hidden = disabledActions(pane, currentTarget(ui));
94
108
  if (hidden.includes(action)) return;
95
109
  const handler = handlers[action];
@@ -113,12 +127,14 @@ const createInput = (ui) => {
113
127
  },
114
128
  confirmDrop: {
115
129
  confirm: () => {
130
+ if (ui.npm.dropName) return void ui.npm.confirmDrop();
116
131
  if (ui.commits.dropName) {
117
132
  return void ui.commits.confirmDropCommit();
118
133
  }
119
134
  ui.gitBranches.confirmDropBranch();
120
135
  },
121
136
  cancel: () => {
137
+ if (ui.npm.dropName) return void ui.npm.cancelDrop();
122
138
  if (ui.commits.dropName) {
123
139
  return void ui.commits.cancelDropCommit();
124
140
  }
@@ -145,6 +161,18 @@ const createInput = (ui) => {
145
161
  const command = ui.composer.handleKey(key);
146
162
  return void ui.runComposeCommand(command);
147
163
  }
164
+ if (ui.pane === 'npm' && ui.npm.viewing) {
165
+ if (key === 'escape') return void ui.npm.closeView();
166
+ if (key === 'r') return void ui.npm.rerun();
167
+ if (key === 'e' || key === 'n') return;
168
+ }
169
+ if (ui.pane === 'npm' && key === 'ctrl-up') return void ui.npm.reorder(-1);
170
+ if (ui.pane === 'npm' && key === 'ctrl-down') {
171
+ return void ui.npm.reorder(1);
172
+ }
173
+ if (ui.pane === 'npm' && (key === 'up' || key === 'down')) {
174
+ return void ui.npm.move(key === 'up' ? -1 : 1);
175
+ }
148
176
  if (key === 'escape') return void ui.filesPane.onEscape();
149
177
  const toggle = key === 'x' || key === ' ';
150
178
  if (toggle && ui.composer.toggleFocusedTodo()) return;
@@ -4,6 +4,7 @@ const ansi = require('../ansi.js');
4
4
  const files = require('../files.js');
5
5
  const { THEME_NAMES, setTheme, themeName } = ansi;
6
6
  const { isTodosEntry, TODO_FILE } = files;
7
+ const { logViewRows } = require('../render/npm.js');
7
8
  const unit = require('./unit.js');
8
9
  const { setFileScope, openUnitFile, moveUnitBlock } = unit;
9
10
 
@@ -28,7 +29,10 @@ const syncFileCursor = (ui) => {
28
29
  const showFiles = (ui) => {
29
30
  const nav = ui.nav;
30
31
  const held =
31
- nav.todoOpen === true || nav.pane === 'branches' || nav.pane === 'commits';
32
+ nav.todoOpen === true ||
33
+ nav.pane === 'branches' ||
34
+ nav.pane === 'commits' ||
35
+ nav.pane === 'npm';
32
36
  nav.pane = 'files';
33
37
  nav.clearSelection();
34
38
  followReviewPath(ui);
@@ -37,7 +41,11 @@ const showFiles = (ui) => {
37
41
  };
38
42
 
39
43
  const onEscape = (ui) => {
40
- if (ui.nav.pane === 'branches' || ui.nav.pane === 'commits') {
44
+ if (
45
+ ui.nav.pane === 'branches' ||
46
+ ui.nav.pane === 'commits' ||
47
+ ui.nav.pane === 'npm'
48
+ ) {
41
49
  return void showFiles(ui);
42
50
  }
43
51
  if (ui.nav.pane === 'files') return void ui.onQuit();
@@ -65,7 +73,8 @@ const onOpenFile = (ui) => {
65
73
  if (ui.nav.pane === 'branches') {
66
74
  return void ui.gitBranches.onCheckoutBranch();
67
75
  }
68
- if (ui.nav.pane === 'commits') return;
76
+ if (ui.nav.pane === 'commits') return void ui.commits.onShowDiff();
77
+ if (ui.nav.pane === 'npm') return void ui.npm.run();
69
78
  if (ui.nav.pane !== 'files') {
70
79
  if (ui.nav.todoOpen) ui.composer.editFocusedTodo();
71
80
  return;
@@ -152,7 +161,9 @@ const scrollStep = (ui, fraction) => {
152
161
  const rows = size.height ?? 24;
153
162
  const fallback = Math.max(1, rows - 4);
154
163
  const bodyH = frame?.bodyH ?? fallback;
155
- const height = Math.max(1, bodyH);
164
+ const viewing = ui.nav.pane === 'npm' && ui.npm && ui.npm.viewing;
165
+ const viewRows = viewing ? logViewRows(bodyH) : bodyH;
166
+ const height = Math.max(1, viewRows);
156
167
  const span = Math.floor(height * Math.abs(fraction));
157
168
  const n = Math.max(1, span);
158
169
  if (fraction < 0) return -n;
@@ -167,6 +178,7 @@ const onScroll = (ui, delta) => {
167
178
  if (ui.nav.pane === 'commits') {
168
179
  return void ui.commits.onCommitMove(delta);
169
180
  }
181
+ if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
170
182
  if (ui.nav.todoOpen && ui.composer.todoRowCount() > 1) {
171
183
  return void ui.composer.moveTodoFocus(delta);
172
184
  }
@@ -198,6 +210,10 @@ const onJump = (ui, toEnd) => {
198
210
  const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
199
211
  return void ui.commits.onCommitMove(delta);
200
212
  }
213
+ if (ui.nav.pane === 'npm') {
214
+ const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
215
+ return void ui.npm.move(delta);
216
+ }
201
217
  if (ui.nav.todoOpen && ui.composer.todoRowCount() > 0) {
202
218
  const last = ui.composer.todoRowCount() - 1;
203
219
  const target = toEnd ? last : 0;
@@ -485,7 +485,10 @@ const worktreeStamp = (api) => {
485
485
  };
486
486
 
487
487
  const watchesWorktree = (nav) =>
488
- nav.todoOpen === true || nav.pane === 'branches' || nav.pane === 'commits';
488
+ nav.todoOpen === true ||
489
+ nav.pane === 'branches' ||
490
+ nav.pane === 'commits' ||
491
+ nav.pane === 'npm';
489
492
 
490
493
  const currentFileStamp = (api) => {
491
494
  const { nav } = api;
@@ -9,6 +9,7 @@ const initialState = (options = {}) => ({
9
9
  fileCursor: 0,
10
10
  branchCursor: 0,
11
11
  commitCursor: 0,
12
+ npmCursor: 0,
12
13
  scroll: 0,
13
14
  listScroll: 0,
14
15
  reviewPath: null,
@@ -0,0 +1,335 @@
1
+ 'use strict';
2
+
3
+ const commands = require('../npm-commands.js');
4
+ const { logViewRows } = require('../render/npm.js');
5
+
6
+ const { listCommands, reduceOutput, writeLog, parseScriptLine } = commands;
7
+ const { scriptLine, saveScript, removeScript, reorderScript } = commands;
8
+ const { startNpm } = commands;
9
+
10
+ const selected = (api) => {
11
+ const cursor = api.ui.nav.npmCursor;
12
+ return api.state.commands[cursor];
13
+ };
14
+
15
+ const clampCursor = (api) => {
16
+ const last = api.state.commands.length - 1;
17
+ const nav = api.ui.nav;
18
+ if (nav.npmCursor > last) nav.npmCursor = Math.max(0, last);
19
+ if (nav.npmCursor < 0) nav.npmCursor = 0;
20
+ };
21
+
22
+ const refresh = (api) => {
23
+ api.state.commands = listCommands(api.ui.top);
24
+ clampCursor(api);
25
+ };
26
+
27
+ const canEdit = (ui) => {
28
+ if (ui.capabilities && ui.capabilities.changes === false) return false;
29
+ if (ui.readOnly) return false;
30
+ return true;
31
+ };
32
+
33
+ const open = (api) => {
34
+ if (api.ui.mode === 'compose') return;
35
+ refresh(api);
36
+ api.state.viewing = false;
37
+ api.state.output = '';
38
+ api.ui.nav.pane = 'npm';
39
+ api.ui.status = '';
40
+ api.ui.nav.clearSelection();
41
+ };
42
+
43
+ const scrollLog = (api, delta) => {
44
+ const lines = api.state.output.split('\n');
45
+ let count = lines.at(-1) === '' ? lines.length - 1 : lines.length;
46
+ if (api.state.running) count += 1;
47
+ const frame = api.ui.lastFrame;
48
+ const bodyH = frame && frame.bodyH ? frame.bodyH : 1;
49
+ const max = Math.max(0, count - logViewRows(bodyH));
50
+ let start = api.state.followEnd ? max : api.state.logScroll;
51
+ start = Math.min(max, Math.max(0, start + delta));
52
+ api.state.followEnd = start === max;
53
+ api.state.logScroll = start;
54
+ };
55
+
56
+ const move = (api, delta) => {
57
+ if (api.state.viewing) return void scrollLog(api, delta);
58
+ if (!api.state.commands.length) return;
59
+ const nav = api.ui.nav;
60
+ const last = api.state.commands.length - 1;
61
+ const next = Math.min(last, Math.max(0, nav.npmCursor + delta));
62
+ if (next === nav.npmCursor) return;
63
+ nav.npmCursor = next;
64
+ api.ui.status = '';
65
+ };
66
+
67
+ const stopRun = (api) => {
68
+ api.state.running = false;
69
+ api.state.child = null;
70
+ if (api.ui.progress) api.ui.progress.stop('npm');
71
+ };
72
+
73
+ const showOutput = (api, text) => {
74
+ if (!api.state.viewing || !api.state.running) return;
75
+ if (api.state.stopping) return;
76
+ api.state.output = text;
77
+ api.ui.paint();
78
+ };
79
+
80
+ const withNotice = (text, notice) => {
81
+ const body = `${text ?? ''}`;
82
+ if (body.endsWith(`${notice}\n`)) return body;
83
+ if (!body) return `${notice}\n`;
84
+ if (body.endsWith('\n')) return `${body}${notice}\n`;
85
+ return `${body}\n${notice}\n`;
86
+ };
87
+
88
+ const finishRun = (api, entry, result) => {
89
+ const stopped = api.state.stopping === true;
90
+ const reduced = stopped
91
+ ? withNotice(api.state.output, 'terminated')
92
+ : reduceOutput(result.text, api.ui.top, result.status);
93
+ api.state.output = reduced;
94
+ api.state.stopping = false;
95
+ stopRun(api);
96
+ try {
97
+ writeLog(api.ui.top, entry.name, reduced);
98
+ } catch (error) {
99
+ api.ui.status = error.message;
100
+ api.ui.paint();
101
+ return;
102
+ }
103
+ if (stopped && api.state.viewing) api.ui.status = 'terminated';
104
+ api.ui.paint();
105
+ };
106
+
107
+ const startRun = (api, entry) => {
108
+ api.state.output = '';
109
+ api.state.viewing = true;
110
+ api.state.followEnd = true;
111
+ api.state.logScroll = 0;
112
+ api.state.running = true;
113
+ api.state.stopping = false;
114
+ api.ui.status = '';
115
+ if (api.ui.progress) api.ui.progress.start('npm');
116
+ const runner = api.ui.repo && api.ui.repo.runNpmCommand;
117
+ const start = typeof runner === 'function' ? runner : startNpm;
118
+ api.state.child = start(
119
+ api.ui.top,
120
+ entry,
121
+ (text) => showOutput(api, reduceOutput(text, api.ui.top, '')),
122
+ (result) => finishRun(api, entry, result),
123
+ );
124
+ api.ui.paint();
125
+ };
126
+
127
+ const runSelected = (api) => {
128
+ if (api.state.viewing || api.state.child) return;
129
+ const entry = selected(api);
130
+ if (!entry) return;
131
+ startRun(api, entry);
132
+ };
133
+
134
+ const rerun = (api) => {
135
+ if (!api.state.viewing || api.state.running) return;
136
+ const entry = selected(api);
137
+ if (!entry) return;
138
+ startRun(api, entry);
139
+ };
140
+
141
+ const closeView = (api) => {
142
+ if (api.state.running) {
143
+ api.state.stopping = true;
144
+ if (api.state.child) api.state.child.kill();
145
+ if (!api.state.running) return;
146
+ api.state.output = withNotice(api.state.output, 'terminated');
147
+ stopRun(api);
148
+ api.ui.status = 'terminated';
149
+ api.ui.paint();
150
+ return;
151
+ }
152
+ api.state.viewing = false;
153
+ api.ui.status = '';
154
+ api.ui.paint();
155
+ };
156
+
157
+ const editSelected = (api) => {
158
+ if (api.state.viewing) return;
159
+ if (!canEdit(api.ui)) {
160
+ api.ui.status = 'read only';
161
+ return;
162
+ }
163
+ const entry = selected(api);
164
+ if (!entry || entry.kind !== 'script') {
165
+ api.ui.status = 'not a script';
166
+ return;
167
+ }
168
+ api.state.editName = entry.name;
169
+ api.ui.composer.openCompose('npm', scriptLine(entry));
170
+ };
171
+
172
+ const newCommand = (api) => {
173
+ if (api.state.viewing) return;
174
+ if (!canEdit(api.ui)) {
175
+ api.ui.status = 'read only';
176
+ return;
177
+ }
178
+ api.state.editName = '';
179
+ api.ui.composer.openCompose('npm', '');
180
+ };
181
+
182
+ const finishEdit = (api) => {
183
+ const editor = api.ui.composer.state.editor;
184
+ const text = editor ? editor.text : '';
185
+ const parsed = parseScriptLine(text);
186
+ if (!parsed) {
187
+ api.ui.status = 'name: command';
188
+ return;
189
+ }
190
+ try {
191
+ api.ui.ignoreWatch();
192
+ saveScript(api.ui.top, api.state.editName, parsed);
193
+ } catch (error) {
194
+ api.ui.status = error.message;
195
+ return;
196
+ }
197
+ api.ui.composer.closeCompose();
198
+ refresh(api);
199
+ const at = api.state.commands.findIndex(
200
+ (entry) => entry.name === parsed.name,
201
+ );
202
+ if (at >= 0) api.ui.nav.npmCursor = at;
203
+ api.ui.status = 'saved';
204
+ };
205
+
206
+ const askDrop = (api) => {
207
+ if (api.state.viewing) return;
208
+ if (!canEdit(api.ui)) {
209
+ api.ui.status = 'read only';
210
+ return;
211
+ }
212
+ const entry = selected(api);
213
+ if (!entry || entry.kind !== 'script') {
214
+ api.ui.status = 'not a script';
215
+ return;
216
+ }
217
+ api.state.dropName = entry.name;
218
+ api.ui.mode = 'confirmDrop';
219
+ api.ui.status = '';
220
+ };
221
+
222
+ const confirmDrop = (api) => {
223
+ const name = api.state.dropName;
224
+ api.state.dropName = '';
225
+ api.ui.mode = 'review';
226
+ if (!name) return;
227
+ try {
228
+ api.ui.ignoreWatch();
229
+ removeScript(api.ui.top, name);
230
+ } catch (error) {
231
+ api.ui.status = error.message;
232
+ return;
233
+ }
234
+ refresh(api);
235
+ api.ui.status = `dropped ${name}`;
236
+ };
237
+
238
+ const cancelDrop = (api) => {
239
+ api.state.dropName = '';
240
+ api.ui.mode = 'review';
241
+ api.ui.status = '';
242
+ };
243
+
244
+ const reorder = (api, delta) => {
245
+ if (api.state.viewing) return;
246
+ if (!canEdit(api.ui)) {
247
+ api.ui.status = 'read only';
248
+ return;
249
+ }
250
+ const entry = selected(api);
251
+ if (!entry || entry.kind !== 'script') {
252
+ api.ui.status = 'not a script';
253
+ return;
254
+ }
255
+ try {
256
+ api.ui.ignoreWatch();
257
+ if (!reorderScript(api.ui.top, entry.name, delta)) return;
258
+ } catch (error) {
259
+ api.ui.status = error.message;
260
+ return;
261
+ }
262
+ refresh(api);
263
+ const at = api.state.commands.findIndex((item) => item.name === entry.name);
264
+ if (at >= 0) api.ui.nav.npmCursor = at;
265
+ api.ui.status = '';
266
+ };
267
+
268
+ const createNpmController = (ui) => {
269
+ const state = {
270
+ commands: [],
271
+ viewing: false,
272
+ output: '',
273
+ followEnd: true,
274
+ logScroll: 0,
275
+ editName: '',
276
+ dropName: '',
277
+ running: false,
278
+ stopping: false,
279
+ child: null,
280
+ };
281
+ const api = { ui, state };
282
+ return {
283
+ state,
284
+ get commands() {
285
+ return state.commands;
286
+ },
287
+ get viewing() {
288
+ return state.viewing;
289
+ },
290
+ get output() {
291
+ return state.output;
292
+ },
293
+ get dropName() {
294
+ return state.dropName;
295
+ },
296
+ get followEnd() {
297
+ return state.followEnd;
298
+ },
299
+ get logScroll() {
300
+ return state.logScroll;
301
+ },
302
+ get editName() {
303
+ return state.editName;
304
+ },
305
+ get running() {
306
+ return state.running;
307
+ },
308
+ reset() {
309
+ if (state.child) state.child.kill();
310
+ stopRun(api);
311
+ state.commands = [];
312
+ state.viewing = false;
313
+ state.output = '';
314
+ state.followEnd = true;
315
+ state.logScroll = 0;
316
+ state.editName = '';
317
+ state.dropName = '';
318
+ state.stopping = false;
319
+ },
320
+ open: () => open(api),
321
+ move: (delta) => move(api, delta),
322
+ run: () => runSelected(api),
323
+ rerun: () => rerun(api),
324
+ closeView: () => closeView(api),
325
+ edit: () => editSelected(api),
326
+ create: () => newCommand(api),
327
+ finishEdit: () => finishEdit(api),
328
+ askDrop: () => askDrop(api),
329
+ confirmDrop: () => confirmDrop(api),
330
+ cancelDrop: () => cancelDrop(api),
331
+ reorder: (delta) => reorder(api, delta),
332
+ };
333
+ };
334
+
335
+ module.exports = { createNpmController };
@@ -99,9 +99,17 @@ const RELEASE_TARGETS = [
99
99
  match: (ui) => ui.pane === 'commits',
100
100
  hits: 'fileHits',
101
101
  onHit: (ui, hit) => {
102
+ if (ui.mode === 'compose') return;
102
103
  ui.nav.commitCursor = hit.cursor;
103
104
  },
104
105
  },
106
+ {
107
+ match: (ui) => ui.pane === 'npm',
108
+ hits: 'fileHits',
109
+ onHit: (ui, hit) => {
110
+ ui.nav.npmCursor = hit.cursor;
111
+ },
112
+ },
105
113
  {
106
114
  match: (ui) => ui.pane === 'diff',
107
115
  hits: 'todoHits',
@@ -3,6 +3,7 @@
3
3
  const path = require('node:path');
4
4
 
5
5
  const files = require('../files.js');
6
+ const { listedCommits } = require('./commit.js');
6
7
  const { fileEntries, REPO_TODOS_LABEL } = files;
7
8
  const review = require('../review.js');
8
9
  const { noteCounts } = review;
@@ -143,7 +144,7 @@ const unitBodyLines = (api) => {
143
144
  };
144
145
 
145
146
  const view = (api) => {
146
- const { composer, nav, ui, gitBranches, commits, updater } = api;
147
+ const { composer, nav, ui, gitBranches, commits, npm, updater } = api;
147
148
  const templates = composer.shownTemplates();
148
149
  const change = ui.change;
149
150
  const sourceKind = (change && change.source) || '';
@@ -169,9 +170,19 @@ const view = (api) => {
169
170
  branch: ui.branch,
170
171
  branches: gitBranches.branches,
171
172
  branchCursor: nav.branchCursor,
172
- commits: commits.commits,
173
+ commits: listedCommits(commits.commits),
173
174
  commitCursor: nav.commitCursor,
175
+ commitView: commits.commitView,
176
+ npmCommands: npm.commands,
177
+ npmCursor: nav.npmCursor,
178
+ npmView: npm.viewing,
179
+ npmOutput: npm.output,
180
+ npmFollow: npm.followEnd,
181
+ npmScroll: npm.logScroll,
182
+ npmEditName: npm.editName,
183
+ npmRunning: npm.running,
174
184
  repoName: repoNameOf(api),
185
+ rev: ui.rev || '',
175
186
  revShort: ui.revShort || '',
176
187
  sourceLabel: ui.sourceLabel || '',
177
188
  sourceKind,
@@ -186,7 +197,7 @@ const view = (api) => {
186
197
  templateIndex: composer.clampedTemplateIndex(templates),
187
198
  updateFrom: updater.from,
188
199
  updateTo: updater.to,
189
- dropName: gitBranches.dropName || commits.dropName,
200
+ dropName: gitBranches.dropName || commits.dropName || npm.dropName,
190
201
  };
191
202
  };
192
203