reslop 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +8 -3
  3. package/bin/reslop.js +4 -2
  4. package/lib/api.js +2 -1
  5. package/lib/cli.js +19 -10
  6. package/lib/clipboard.js +6 -3
  7. package/lib/deps-diff.js +61 -115
  8. package/lib/deps.js +24 -61
  9. package/lib/detect.js +4 -2
  10. package/lib/diff/align.js +2 -1
  11. package/lib/diff/diff.js +6 -3
  12. package/lib/diff/display.js +2 -1
  13. package/lib/diff/inline.js +2 -1
  14. package/lib/diff/patch.js +2 -1
  15. package/lib/diff/unit.js +4 -2
  16. package/lib/editor.js +22 -4
  17. package/lib/files.js +38 -12
  18. package/lib/git-branches.js +107 -154
  19. package/lib/git-deps.js +20 -60
  20. package/lib/git-worktree.js +508 -0
  21. package/lib/git.js +23 -580
  22. package/lib/github.js +2 -1
  23. package/lib/gitlab.js +2 -1
  24. package/lib/highlight/bash.js +2 -1
  25. package/lib/highlight/css.js +2 -1
  26. package/lib/highlight/csv.js +2 -1
  27. package/lib/highlight/dot.js +2 -1
  28. package/lib/highlight/html.js +2 -1
  29. package/lib/highlight/json.js +2 -1
  30. package/lib/highlight/log.js +2 -1
  31. package/lib/highlight/txt.js +2 -1
  32. package/lib/highlight.js +2 -1
  33. package/lib/keys.js +2 -2
  34. package/lib/manifest.js +4 -2
  35. package/lib/npm-commands.js +83 -4
  36. package/lib/remote.js +20 -13
  37. package/lib/render/branches.js +6 -15
  38. package/lib/render/chrome.js +27 -7
  39. package/lib/render/commits.js +158 -46
  40. package/lib/render/diff.js +130 -47
  41. package/lib/render/files.js +4 -12
  42. package/lib/render/frame.js +3 -9
  43. package/lib/render/notes.js +4 -11
  44. package/lib/render/npm.js +1 -13
  45. package/lib/render/present.js +2 -1
  46. package/lib/render/primitives.js +28 -15
  47. package/lib/render/render.js +2 -1
  48. package/lib/render/unit.js +14 -11
  49. package/lib/report-model.js +6 -4
  50. package/lib/report-parse.js +8 -13
  51. package/lib/report-render.js +38 -21
  52. package/lib/report-run.js +12 -10
  53. package/lib/report-test.js +5 -4
  54. package/lib/report-tools.js +44 -18
  55. package/lib/review-merge.js +2 -1
  56. package/lib/review.js +6 -2
  57. package/lib/session/actions.js +49 -65
  58. package/lib/session/branches.js +13 -49
  59. package/lib/session/change-actions.js +2 -1
  60. package/lib/session/commit.js +34 -49
  61. package/lib/session/compose.js +37 -11
  62. package/lib/session/dispatch.js +17 -25
  63. package/lib/session/files-pane.js +6 -27
  64. package/lib/session/items.js +6 -3
  65. package/lib/session/lifecycle.js +8 -4
  66. package/lib/session/load.js +4 -2
  67. package/lib/session/navigation.js +1 -0
  68. package/lib/session/npm.js +54 -1
  69. package/lib/session/ops.js +15 -1
  70. package/lib/session/pointer.js +22 -3
  71. package/lib/session/review.js +2 -1
  72. package/lib/session/terminal.js +4 -2
  73. package/lib/session/update.js +2 -1
  74. package/lib/session/view.js +29 -3
  75. package/lib/session.js +37 -23
  76. package/lib/source.js +6 -3
  77. package/lib/update.js +5 -9
  78. package/lib/utilities.js +4 -2
  79. package/lib/wrap.js +3 -1
  80. package/package.json +1 -1
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const LIST_PANES = ['files', 'branches', 'commits', 'npm'];
4
+
3
5
  const ACTIONS = {
4
6
  add: ['a'],
5
7
  unstage: ['u'],
@@ -42,6 +44,8 @@ const NPM_ACTIONS = {
42
44
  npm: ['n'],
43
45
  npmEdit: ['e'],
44
46
  npmNew: ['n'],
47
+ npmDrop: ['d'],
48
+ npmLogs: ['c'],
45
49
  npmStop: ['escape'],
46
50
  npmRerun: ['r'],
47
51
  };
@@ -61,6 +65,8 @@ const ACTION_LABEL = {
61
65
  newBranch: 'new',
62
66
  npmEdit: 'edit',
63
67
  npmNew: 'new',
68
+ npmDrop: 'delete',
69
+ npmLogs: 'cleanup',
64
70
  npmStop: 'esc',
65
71
  npmRerun: 're-run',
66
72
  quit: 'q',
@@ -121,39 +127,8 @@ const BRANCHES_DISABLED = [
121
127
  'prev',
122
128
  'next',
123
129
  ];
124
- const COMMITS_DISABLED = [
125
- 'add',
126
- 'unstage',
127
- 'revert',
128
- 'layout',
129
- 'feedback',
130
- 'todo',
131
- 'code',
132
- 'check',
133
- 'branch',
134
- 'file',
135
- 'diff',
136
- 'prev',
137
- 'next',
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
- ];
130
+ const COMMITS_DISABLED = BRANCHES_DISABLED.filter((id) => id !== 'commit');
131
+ const NPM_DISABLED = [...BRANCHES_DISABLED, 'pull', 'push'];
157
132
 
158
133
  const isTodosTarget = (item) =>
159
134
  !!(item && (item.kind === 'todos' || item.origin === 'todo'));
@@ -176,8 +151,16 @@ const isFixupCommit = (item) =>
176
151
  `${item && item.subject ? item.subject : ''}`.startsWith('fixup!');
177
152
 
178
153
  const commitWriteDisabled = (item) => {
179
- if (!item || !item.sha) return ['amend', 'apply', 'reword', 'fixup', 'drop'];
180
- return [];
154
+ const pending = Boolean(item && item.pending);
155
+ const unstaged = Boolean(item && item.unstagedCurrent);
156
+ if (!item || !item.sha) {
157
+ const off = ['reword', 'fixup', 'drop'];
158
+ if (pending) return off;
159
+ return ['amend', 'apply', ...off];
160
+ }
161
+ if (unstaged) return [];
162
+ if (isFixupCommit(item)) return ['amend'];
163
+ return ['amend', 'apply'];
181
164
  };
182
165
 
183
166
  const commitGitDisabled = (item) => {
@@ -224,26 +207,29 @@ const SCROLL = {
224
207
  delete: 'removeTodo',
225
208
  };
226
209
 
227
- const KEY_TO_ACTION = {};
210
+ const keysForActions = (actions) => {
211
+ const keys = {};
212
+ for (const [id, shortcuts] of Object.entries(actions)) {
213
+ for (const key of shortcuts) keys[key] = id;
214
+ }
215
+ return keys;
216
+ };
217
+
228
218
  const PANE_ONLY = ['branch', 'file', 'diff'];
229
- const FILES_KEYS = {};
230
- for (const key of ACTIONS.branch) FILES_KEYS[key] = 'branch';
231
- for (const id of BRANCH_REMOTE) {
232
- for (const key of BRANCH_ACTIONS[id]) FILES_KEYS[key] = id;
233
- }
234
- const BRANCH_KEYS = {};
235
- for (const id of Object.keys(BRANCH_ACTIONS)) {
236
- for (const key of BRANCH_ACTIONS[id]) BRANCH_KEYS[key] = id;
237
- }
238
- const COMMIT_KEYS = {};
239
- for (const id of Object.keys(COMMIT_ACTIONS)) {
240
- for (const key of COMMIT_ACTIONS[id]) COMMIT_KEYS[key] = id;
241
- }
219
+ const FILES_KEYS = keysForActions({
220
+ branch: ACTIONS.branch,
221
+ pull: BRANCH_ACTIONS.pull,
222
+ push: BRANCH_ACTIONS.push,
223
+ npm: NPM_ACTIONS.npm,
224
+ });
225
+ const BRANCH_KEYS = keysForActions(BRANCH_ACTIONS);
226
+ const COMMIT_KEYS = keysForActions(COMMIT_ACTIONS);
242
227
  const NPM_KEYS = {
243
228
  e: 'npmEdit',
244
229
  n: 'npmNew',
230
+ d: 'npmDrop',
231
+ c: 'npmLogs',
245
232
  };
246
- FILES_KEYS.n = 'npm';
247
233
  const PANE_KEYS = {
248
234
  files: FILES_KEYS,
249
235
  branches: BRANCH_KEYS,
@@ -251,23 +237,20 @@ const PANE_KEYS = {
251
237
  npm: NPM_KEYS,
252
238
  };
253
239
 
254
- for (const id of ACTION_IDS) {
255
- if (PANE_ONLY.includes(id)) continue;
256
- for (const key of ACTIONS[id]) KEY_TO_ACTION[key] = id;
257
- }
258
-
259
- for (const key of Object.keys(SCROLL)) KEY_TO_ACTION[key] = SCROLL[key];
260
- for (const key of Object.keys(HIDDEN_KEYS)) {
261
- KEY_TO_ACTION[key] = HIDDEN_KEYS[key];
262
- }
240
+ const KEY_TO_ACTION = {
241
+ ...keysForActions(
242
+ Object.fromEntries(
243
+ Object.entries(ACTIONS).filter(([id]) => !PANE_ONLY.includes(id)),
244
+ ),
245
+ ),
246
+ ...SCROLL,
247
+ ...HIDDEN_KEYS,
248
+ };
263
249
 
264
250
  const actionFromKey = (key, pane, fileScope) => {
265
- const listPane =
266
- pane === 'files' ||
267
- pane === 'branches' ||
268
- pane === 'commits' ||
269
- pane === 'npm';
270
- if (listPane && (key === 'left' || key === 'right')) return null;
251
+ if (LIST_PANES.includes(pane) && (key === 'left' || key === 'right')) {
252
+ return null;
253
+ }
271
254
  const paneKeys = PANE_KEYS[pane];
272
255
  if (paneKeys && paneKeys[key]) return paneKeys[key];
273
256
  if (pane === 'files') {
@@ -370,6 +353,7 @@ const promptFromChoices = (choices) =>
370
353
  choices.map((choice) => `${choice.lead}${choice.label}`).join('');
371
354
 
372
355
  module.exports = {
356
+ LIST_PANES,
373
357
  ACTIONS,
374
358
  ACTION_IDS,
375
359
  BRANCH_ACTIONS,
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { stateView } = require('./accessors.js');
3
+ const accessors = require('./accessors.js');
4
+ const { stateView } = accessors;
4
5
 
5
- const { pickRepoWrite } = require('./ops.js');
6
+ const ops = require('./ops.js');
7
+ const { pickRepoWrite } = ops;
6
8
 
7
9
  const selectedBranch = (api) => {
8
10
  const cursor = api.ui.nav.branchCursor;
@@ -95,12 +97,6 @@ const onCheckoutBranch = (api) => {
95
97
  if (!api.ui.status) api.ui.status = done;
96
98
  };
97
99
 
98
- const rebaseAsync = (api, onto, done, write) => {
99
- const run = (top) => write(top, onto);
100
- const after = () => refreshAfterWrite(api, { doneStatus: done });
101
- return void api.ui.ops.runBusy('rebasing', done, run, after);
102
- };
103
-
104
100
  const onRebaseBranch = (api) => {
105
101
  if (api.ui.nav.pane !== 'branches') return;
106
102
  if (!api.ui.commits.canCommit()) return;
@@ -108,17 +104,9 @@ const onRebaseBranch = (api) => {
108
104
  if (!entry || entry.current) return;
109
105
  const onto = entry.name;
110
106
  const done = `rebased onto ${onto}`;
111
- const uiOpen = api.ui.uiOpen;
112
- const picked = pickRepoWrite(api.ui.repo, 'rebase', uiOpen);
113
- if (picked.async) {
114
- return void rebaseAsync(api, onto, done, picked.write);
115
- }
116
- const ok = api.ui.ops.runRepo(done, (top) => {
117
- api.ui.repo.rebase(top, onto);
118
- });
119
- if (!ok) return;
120
- refreshAfterWrite(api);
121
- if (!api.ui.status) api.ui.status = done;
107
+ api.ui.ops.runWrite('rebase', [onto], 'rebasing', done, (extra) =>
108
+ refreshAfterWrite(api, extra),
109
+ );
122
110
  };
123
111
 
124
112
  const onDropBranch = (api) => {
@@ -137,29 +125,15 @@ const cancelDropBranch = (api) => {
137
125
  api.ui.status = '';
138
126
  };
139
127
 
140
- const dropAsync = (api, name, done, write) => {
141
- const run = (top) => write(top, name);
142
- const after = () => refreshAfterWrite(api, { doneStatus: done });
143
- return void api.ui.ops.runBusy('dropping', done, run, after);
144
- };
145
-
146
128
  const confirmDropBranch = (api) => {
147
129
  const name = api.state.dropName;
148
130
  api.ui.mode = 'review';
149
131
  api.state.dropName = '';
150
132
  if (!name) return;
151
133
  const done = `dropped ${name}`;
152
- const uiOpen = api.ui.uiOpen;
153
- const picked = pickRepoWrite(api.ui.repo, 'drop', uiOpen);
154
- if (picked.async) {
155
- return void dropAsync(api, name, done, picked.write);
156
- }
157
- const ok = api.ui.ops.runRepo(done, (top) => {
158
- api.ui.repo.drop(top, name);
159
- });
160
- if (!ok) return;
161
- refreshAfterWrite(api);
162
- if (!api.ui.status) api.ui.status = done;
134
+ api.ui.ops.runWrite('drop', [name], 'dropping', done, (extra) =>
135
+ refreshAfterWrite(api, extra),
136
+ );
163
137
  };
164
138
 
165
139
  const onNewBranch = (api) => {
@@ -201,19 +175,9 @@ const finishCreateBranch = (api) => {
201
175
 
202
176
  const onPull = (api) => {
203
177
  if (!api.ui.commits.canCommit()) return;
204
- const repo = api.ui.repo;
205
- const picked = pickRepoWrite(repo, 'pull', api.ui.uiOpen);
206
- if (picked.async) {
207
- const after = () => refreshAfterWrite(api);
208
- const write = (top) => picked.write(top);
209
- return void api.ui.ops.runBusy('pulling', 'pulled', write, after);
210
- }
211
- const ok = api.ui.ops.runRepo('pulled', (top) => {
212
- repo.pull(top);
213
- });
214
- if (!ok) return;
215
- refreshAfterWrite(api);
216
- if (!api.ui.status) api.ui.status = 'pulled';
178
+ api.ui.ops.runWrite('pull', [], 'pulling', 'pulled', (extra) =>
179
+ refreshAfterWrite(api, extra),
180
+ );
217
181
  };
218
182
 
219
183
  const startPush = (api, force) => {
@@ -4,7 +4,8 @@ const files = require('../files.js');
4
4
  const { listPath, isTodoItem, isTodosEntry, isReadOnlyOrigin } = files;
5
5
  const items = require('./items.js');
6
6
  const { needsReload, npmBusyKind, sameBlock } = items;
7
- const { hunkKey } = require('../diff/diff.js');
7
+ const diff = require('../diff/diff.js');
8
+ const { hunkKey } = diff;
8
9
 
9
10
  const nextUnstageOrigin = (item) => {
10
11
  if (item.file.isNew === true) return 'untracked';
@@ -1,15 +1,16 @@
1
1
  'use strict';
2
2
 
3
- const { stateView } = require('./accessors.js');
4
-
5
- const { isReadOnlyOrigin } = require('../files.js');
6
- const { isFixupCommit } = require('./actions.js');
7
- const { pickRepoWrite } = require('./ops.js');
8
- const {
9
- commitLine,
10
- commitText,
11
- splitMessage,
12
- } = require('../render/commits.js');
3
+ const accessors = require('./accessors.js');
4
+ const { stateView } = accessors;
5
+
6
+ const files = require('../files.js');
7
+ const { isReadOnlyOrigin } = files;
8
+ const actions = require('./actions.js');
9
+ const { isFixupCommit } = actions;
10
+ const ops = require('./ops.js');
11
+ const { pickRepoWrite } = ops;
12
+ const commits = require('../render/commits.js');
13
+ const { commitLine, commitText, splitMessage } = commits;
13
14
 
14
15
  const COMMIT_STATUS = {
15
16
  commit: 'committed',
@@ -181,11 +182,23 @@ const onCommit = (api) => {
181
182
  startGitCommit(api, 'commit');
182
183
  };
183
184
 
185
+ const openLoadedDiff = (ui) => {
186
+ ui.nav.todoOpen = false;
187
+ ui.nav.scroll = 0;
188
+ ui.nav.index = 0;
189
+ ui.nav.pane = ui.items.length ? 'diff' : 'files';
190
+ ui.syncReviewPath();
191
+ ui.syncFileCursor();
192
+ ui.status = '';
193
+ };
194
+
184
195
  const showSelectedDiff = (api) => {
185
196
  if (api.ui.mode === 'compose') return;
186
197
  const entry = selectedCommit(api);
187
198
  if (!entry) return;
188
199
  const ui = api.ui;
200
+ const viewingCommit = Boolean(ui.rev || ui.revShort);
201
+ if (isPending(entry) && !viewingCommit) return void onCommit(api);
189
202
  if (isPending(entry) || !entry.sha) {
190
203
  ui.rev = '';
191
204
  ui.revShort = '';
@@ -193,16 +206,10 @@ const showSelectedDiff = (api) => {
193
206
  ui.rev = entry.sha;
194
207
  ui.revShort = entry.shortSha || '';
195
208
  }
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 });
209
+ ui.refreshFromRepo({
210
+ keepEmpty: true,
211
+ afterLoad: () => openLoadedDiff(ui),
212
+ });
206
213
  };
207
214
 
208
215
  const onAmend = (api) => {
@@ -226,18 +233,9 @@ const applyFixupNow = (api) => {
226
233
  api.ui.status = 'read only';
227
234
  return;
228
235
  }
229
- const picked = pickRepoWrite(repo, 'applyFixup', api.ui.uiOpen);
230
- if (picked.async) {
231
- const run = (top) => picked.write(top, entry.sha);
232
- const after = () => refreshAfterWrite(api, { doneStatus: done });
233
- return void api.ui.ops.runBusy('applying', done, run, after);
234
- }
235
- const ok = api.ui.ops.runRepo(done, (top) => {
236
- repo.applyFixup(top, entry.sha);
237
- });
238
- if (!ok) return;
239
- refreshAfterWrite(api);
240
- if (!api.ui.status) api.ui.status = done;
236
+ api.ui.ops.runWrite('applyFixup', [entry.sha], 'applying', done, (extra) =>
237
+ refreshAfterWrite(api, extra),
238
+ );
241
239
  };
242
240
 
243
241
  const onApply = (api) => {
@@ -360,12 +358,6 @@ const cancelDropCommit = (api) => {
360
358
  api.ui.status = '';
361
359
  };
362
360
 
363
- const dropAsync = (api, sha, done, write) => {
364
- const run = (top) => write(top, sha);
365
- const after = () => refreshAfterWrite(api, { doneStatus: done });
366
- return void api.ui.ops.runBusy('dropping', done, run, after);
367
- };
368
-
369
361
  const confirmDropCommit = (api) => {
370
362
  const sha = api.state.dropSha;
371
363
  const name = api.state.dropName;
@@ -379,17 +371,9 @@ const confirmDropCommit = (api) => {
379
371
  api.ui.status = 'read only';
380
372
  return;
381
373
  }
382
- const uiOpen = api.ui.uiOpen;
383
- const picked = pickRepoWrite(repo, 'dropCommit', uiOpen);
384
- if (picked.async) {
385
- return void dropAsync(api, sha, done, picked.write);
386
- }
387
- const ok = api.ui.ops.runRepo(done, (top) => {
388
- repo.dropCommit(top, sha);
389
- });
390
- if (!ok) return;
391
- refreshAfterWrite(api);
392
- if (!api.ui.status) api.ui.status = done;
374
+ api.ui.ops.runWrite('dropCommit', [sha], 'dropping', done, (extra) =>
375
+ refreshAfterWrite(api, extra),
376
+ );
393
377
  };
394
378
 
395
379
  const onView = (api) => {
@@ -442,6 +426,7 @@ const createCommitController = (ui) => {
442
426
 
443
427
  module.exports = {
444
428
  COMMIT_STATUS,
429
+ hasStagedChanges,
445
430
  createCommitController,
446
431
  listedCommits,
447
432
  };
@@ -4,18 +4,24 @@ 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');
9
- const { Editor } = require('../editor.js');
7
+ const commits = require('../render/commits.js');
8
+ const { commitMessageWidth } = commits;
9
+ const commit = require('./commit.js');
10
+ const { listedCommits } = commit;
11
+ const editor = require('../editor.js');
12
+ const { Editor } = editor;
10
13
  const files = require('../files.js');
11
14
  const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
12
- const { blockAddText } = require('../diff/diff.js');
15
+ const diff = require('../diff/diff.js');
16
+ const { blockAddText } = diff;
13
17
  const items = require('./items.js');
14
18
  const { itemFeedKey, canEditCode } = items;
15
19
  const review = require('../review.js');
16
20
  const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
17
- const { createTemplatePick } = require('./templates.js');
18
- const { createTodoCompose } = require('./todos.js');
21
+ const templates = require('./templates.js');
22
+ const { createTemplatePick } = templates;
23
+ const todos = require('./todos.js');
24
+ const { createTodoCompose } = todos;
19
25
  const unit = require('./unit.js');
20
26
 
21
27
  const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
@@ -112,6 +118,16 @@ const tickBlink = (api) => {
112
118
 
113
119
  const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
114
120
 
121
+ const syncCodeScroll = (api) => {
122
+ const kind = api.state.composeKind;
123
+ if (kind !== 'code' && kind !== 'file') return;
124
+ const ed = api.state.editor;
125
+ if (!ed) return;
126
+ const size = viewSize(api);
127
+ const inner = codeInnerWidth(size.width, api.ui.layout);
128
+ ed.reveal(inner);
129
+ };
130
+
115
131
  const followFileEdit = (api) => {
116
132
  if (api.state.composeKind !== 'file') return;
117
133
  const text = api.state.editor ? api.state.editor.text : null;
@@ -426,7 +442,9 @@ const codeOverlayView = (api) => {
426
442
  api.state.composeKind === 'code' || api.state.composeKind === 'file';
427
443
  const editor = api.state.editor;
428
444
  const cursor = editing && editor ? editor.cursor : null;
429
- return { text, keepEmpty: editing, cursor };
445
+ const picked = editing && editor ? editor.scrollCol : 0;
446
+ const scrollCol = picked ?? 0;
447
+ return { text, keepEmpty: editing, cursor, scrollCol };
430
448
  };
431
449
 
432
450
  const idleNoteText = (api) => {
@@ -511,9 +529,8 @@ const moveComposeLine = (api, delta) => {
511
529
  return void api.todos.moveTodoCompose(delta, inner);
512
530
  }
513
531
  if (move === 'code') {
514
- const size = viewSize(api);
515
- const inner = codeInnerWidth(size.width, api.ui.layout);
516
- return void api.state.editor.moveLine(delta, inner);
532
+ api.state.editor.moveLine(delta);
533
+ return void syncCodeScroll(api);
517
534
  }
518
535
  if (fullCommitEdit(api)) return void moveCommitLine(api, delta);
519
536
  moveNoteLine(api, delta);
@@ -581,6 +598,7 @@ const onUnitEdit = (api) => {
581
598
  openCompose(api, 'file', at.text);
582
599
  api.state.fileEditRows = at.rows ?? null;
583
600
  api.state.editor.cursor = Math.min(at.cursor, at.text.length);
601
+ syncCodeScroll(api);
584
602
  followFileEdit(api);
585
603
  };
586
604
 
@@ -602,6 +620,7 @@ const onCode = (api) => {
602
620
  : notes.code.get(itemFeedKey(item));
603
621
  const text = prev ? prev.text : original;
604
622
  openCompose(api, 'code', text);
623
+ syncCodeScroll(api);
605
624
  };
606
625
 
607
626
  const insertNewline = (api) => {
@@ -667,7 +686,11 @@ const COMPOSE_SPECIAL = {
667
686
  };
668
687
 
669
688
  const handleKey = (api, key) => {
670
- if (key === 'enter') return enterCompose(api);
689
+ if (key === 'enter') {
690
+ const result = enterCompose(api);
691
+ syncCodeScroll(api);
692
+ return result;
693
+ }
671
694
  if (key === 'escape' || key === 'ctrl-s') return leaveCompose(api, key);
672
695
  const special = COMPOSE_SPECIAL[key];
673
696
  if (special) {
@@ -675,6 +698,7 @@ const handleKey = (api, key) => {
675
698
  if (COMPOSE_EDITS.includes(key)) api.templates.clearTemplatePick();
676
699
  resetBlink(api);
677
700
  followFileEdit(api);
701
+ syncCodeScroll(api);
678
702
  return result ?? null;
679
703
  }
680
704
  if (!key.length) return null;
@@ -684,6 +708,7 @@ const handleKey = (api, key) => {
684
708
  api.state.editor.insert(key);
685
709
  resetBlink(api);
686
710
  followFileEdit(api);
711
+ syncCodeScroll(api);
687
712
  }
688
713
  return null;
689
714
  };
@@ -696,6 +721,7 @@ const applyDiskText = (api, text) => {
696
721
  api.state.editor.cursor = Math.min(cursor, text.length);
697
722
  api.state.composeBaseline = text;
698
723
  api.state.fileEditRows = unit.linesFor(api.ui, api.nav.reviewPath, null);
724
+ syncCodeScroll(api);
699
725
  followFileEdit(api);
700
726
  };
701
727
 
@@ -1,15 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  const compose = require('./compose.js');
4
- const { listedCommits } = require('./commit.js');
4
+ const commit = require('./commit.js');
5
+ const { hasStagedChanges, listedCommits } = commit;
5
6
  const { COMPOSE_LEAVE } = compose;
6
7
  const actions = require('./actions.js');
7
- const { CONFIRM_MODE, disabledActions, actionFromKey, applyConfirm } = actions;
8
-
9
- const targetHasStaged = (session) => {
10
- if (session.repo.hasStaged) return session.repo.hasStaged(session.top);
11
- return session.items.some((item) => item.origin === 'staged');
12
- };
8
+ const {
9
+ LIST_PANES,
10
+ CONFIRM_MODE,
11
+ disabledActions,
12
+ actionFromKey,
13
+ applyConfirm,
14
+ } = actions;
13
15
 
14
16
  const currentTarget = (ui) => {
15
17
  if (ui.pane === 'files') return ui.fileCursorEntry();
@@ -18,9 +20,9 @@ const currentTarget = (ui) => {
18
20
  }
19
21
  if (ui.pane === 'commits') {
20
22
  const entry = listedCommits(ui.commits.commits)[ui.commitCursor];
21
- const staged = targetHasStaged(ui);
22
- if (!entry) return { canCommit: staged };
23
- return { ...entry, canCommit: staged };
23
+ const staged = hasStagedChanges(ui);
24
+ const unstagedCurrent = !ui.rev && !ui.revShort;
25
+ return { ...entry, canCommit: staged, unstagedCurrent };
24
26
  }
25
27
  return ui.current();
26
28
  };
@@ -62,6 +64,8 @@ const createDispatcher = (ui) => {
62
64
  npm: ui.npm.open,
63
65
  npmEdit: ui.npm.edit,
64
66
  npmNew: ui.npm.create,
67
+ npmDrop: () => ui.npm.askDrop(),
68
+ npmLogs: () => ui.npm.askLogs(),
65
69
  npmStop: () => ui.npm.closeView(),
66
70
  npmRerun: () => ui.npm.rerun(),
67
71
  check: () => ui.composer.toggleFocusedTodo(),
@@ -88,21 +92,9 @@ const createDispatcher = (ui) => {
88
92
  if (result && result.kind === 'error') return;
89
93
  }
90
94
  const pane = ui.pane;
91
- if (pane === 'files') {
92
- if (action === 'next') return void ui.filesPane.onFileMove(1);
93
- if (action === 'prev') return void ui.filesPane.onFileMove(-1);
94
- }
95
- if (pane === 'branches') {
96
- if (action === 'next') return void ui.gitBranches.onBranchMove(1);
97
- if (action === 'prev') return void ui.gitBranches.onBranchMove(-1);
98
- }
99
- if (pane === 'commits') {
100
- if (action === 'next') return void ui.commits.onCommitMove(1);
101
- if (action === 'prev') return void ui.commits.onCommitMove(-1);
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);
95
+ if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
96
+ ui.filesPane.onScroll(action === 'next' ? 1 : -1);
97
+ return;
106
98
  }
107
99
  const hidden = disabledActions(pane, currentTarget(ui));
108
100
  if (hidden.includes(action)) return;
@@ -4,8 +4,11 @@ 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
+ const npm = require('../render/npm.js');
8
+ const { logViewRows } = npm;
8
9
  const unit = require('./unit.js');
10
+ const actions = require('./actions.js');
11
+ const { LIST_PANES } = actions;
9
12
  const { setFileScope, openUnitFile, moveUnitBlock } = unit;
10
13
 
11
14
  const LAYOUTS = ['unified', 'mixed', 'side'];
@@ -41,13 +44,6 @@ const showFiles = (ui) => {
41
44
  };
42
45
 
43
46
  const onEscape = (ui) => {
44
- if (
45
- ui.nav.pane === 'branches' ||
46
- ui.nav.pane === 'commits' ||
47
- ui.nav.pane === 'npm'
48
- ) {
49
- return void showFiles(ui);
50
- }
51
47
  if (ui.nav.pane === 'files') return void ui.onQuit();
52
48
  showFiles(ui);
53
49
  };
@@ -193,26 +189,9 @@ const onScroll = (ui, delta) => {
193
189
  };
194
190
 
195
191
  const onJump = (ui, toEnd) => {
196
- if (ui.nav.pane === 'files') {
197
- const list = ui.fileList();
198
- if (!list.length) {
199
- ui.status = 'nothing to review';
200
- return;
201
- }
202
- const target = toEnd ? list.length - 1 : 0;
203
- return void onFileMove(ui, target - ui.nav.fileCursor);
204
- }
205
- if (ui.nav.pane === 'branches') {
206
- const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
207
- return void ui.gitBranches.onBranchMove(delta);
208
- }
209
- if (ui.nav.pane === 'commits') {
210
- const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
211
- return void ui.commits.onCommitMove(delta);
212
- }
213
- if (ui.nav.pane === 'npm') {
192
+ if (LIST_PANES.includes(ui.nav.pane)) {
214
193
  const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
215
- return void ui.npm.move(delta);
194
+ return void onScroll(ui, delta);
216
195
  }
217
196
  if (ui.nav.todoOpen && ui.composer.todoRowCount() > 0) {
218
197
  const last = ui.composer.todoRowCount() - 1;
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const { stateView } = require('./accessors.js');
3
+ const accessors = require('./accessors.js');
4
+ const { stateView } = accessors;
4
5
 
5
6
  const files = require('../files.js');
6
7
  const { itemPath, TODO_FILE, isTodoItem } = files;
7
- const { feedbackKey } = require('../review.js');
8
- const { refreshIndexPatches } = require('../diff/diff.js');
8
+ const review = require('../review.js');
9
+ const { feedbackKey } = review;
10
+ const diff = require('../diff/diff.js');
11
+ const { refreshIndexPatches } = diff;
9
12
 
10
13
  const depChangeKey = (item) => {
11
14
  const change = item.dep && item.dep.change;