reslop 0.2.1 → 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 (81) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +19 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/ansi.js +120 -4
  5. package/lib/api.js +2 -1
  6. package/lib/cli.js +21 -9
  7. package/lib/clipboard.js +6 -3
  8. package/lib/deps-diff.js +61 -115
  9. package/lib/deps.js +57 -54
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +2 -1
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +4 -2
  17. package/lib/editor.js +22 -4
  18. package/lib/files.js +38 -12
  19. package/lib/git-branches.js +150 -165
  20. package/lib/git-deps.js +20 -60
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +2 -1
  24. package/lib/gitlab.js +2 -1
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/css.js +2 -1
  27. package/lib/highlight/csv.js +2 -1
  28. package/lib/highlight/dot.js +2 -1
  29. package/lib/highlight/html.js +2 -1
  30. package/lib/highlight/json.js +2 -1
  31. package/lib/highlight/log.js +2 -1
  32. package/lib/highlight/txt.js +2 -1
  33. package/lib/highlight.js +2 -1
  34. package/lib/keys.js +12 -6
  35. package/lib/manifest.js +4 -2
  36. package/lib/npm-commands.js +592 -0
  37. package/lib/remote.js +20 -13
  38. package/lib/render/branches.js +25 -41
  39. package/lib/render/chrome.js +163 -128
  40. package/lib/render/commits.js +727 -65
  41. package/lib/render/diff.js +130 -47
  42. package/lib/render/files.js +4 -12
  43. package/lib/render/frame.js +41 -13
  44. package/lib/render/notes.js +4 -11
  45. package/lib/render/npm.js +216 -0
  46. package/lib/render/present.js +2 -1
  47. package/lib/render/primitives.js +34 -50
  48. package/lib/render/render.js +2 -1
  49. package/lib/render/unit.js +14 -11
  50. package/lib/report-model.js +129 -0
  51. package/lib/report-parse.js +89 -0
  52. package/lib/report-render.js +104 -0
  53. package/lib/report-run.js +166 -0
  54. package/lib/report-test.js +507 -0
  55. package/lib/report-tools.js +337 -0
  56. package/lib/review-merge.js +2 -1
  57. package/lib/review.js +6 -2
  58. package/lib/session/actions.js +77 -44
  59. package/lib/session/branches.js +13 -49
  60. package/lib/session/change-actions.js +2 -1
  61. package/lib/session/commit.js +113 -53
  62. package/lib/session/compose.js +83 -14
  63. package/lib/session/dispatch.js +42 -22
  64. package/lib/session/files-pane.js +16 -21
  65. package/lib/session/items.js +6 -3
  66. package/lib/session/lifecycle.js +12 -5
  67. package/lib/session/load.js +4 -2
  68. package/lib/session/navigation.js +2 -0
  69. package/lib/session/npm.js +388 -0
  70. package/lib/session/ops.js +15 -1
  71. package/lib/session/pointer.js +30 -3
  72. package/lib/session/review.js +2 -1
  73. package/lib/session/terminal.js +4 -2
  74. package/lib/session/update.js +2 -1
  75. package/lib/session/view.js +41 -4
  76. package/lib/session.js +44 -22
  77. package/lib/source.js +6 -3
  78. package/lib/update.js +5 -9
  79. package/lib/utilities.js +4 -2
  80. package/lib/wrap.js +3 -1
  81. package/package.json +6 -6
@@ -1,10 +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');
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;
8
14
 
9
15
  const COMMIT_STATUS = {
10
16
  commit: 'committed',
@@ -15,8 +21,37 @@ const COMMIT_STATUS = {
15
21
 
16
22
  const isInPlaceCommit = (kind) => kind === 'amend' || kind === 'reword';
17
23
 
24
+ const PENDING_COMMIT = {
25
+ pending: true,
26
+ subject: 'uncommitted changes',
27
+ sha: '',
28
+ shortSha: '',
29
+ author: '',
30
+ email: '',
31
+ date: '',
32
+ when: '',
33
+ refs: '',
34
+ body: '',
35
+ head: false,
36
+ };
37
+
38
+ const listedCommits = (commits) => [PENDING_COMMIT].concat(commits ?? []);
39
+
40
+ const isPending = (entry) => Boolean(entry && entry.pending);
41
+
42
+ const briefCommit = (ui) => ui.commits.commitView !== 'full';
43
+
44
+ const joinMessage = (line, rest) => (rest ? `${line}\n${rest}` : line);
45
+
46
+ const keepBriefBody = (ui, text) => {
47
+ if (!briefCommit(ui)) return text;
48
+ const parts = splitMessage(text);
49
+ ui.composer.state.commitBody = parts.rest;
50
+ return parts.line;
51
+ };
52
+
18
53
  const fixupMessage = (entry) => {
19
- const subject = `${entry && entry.subject ? entry.subject : ''}`.trim();
54
+ const subject = commitLine(entry).trim();
20
55
  if (!subject) return 'fixup!';
21
56
  return `fixup! ${subject}`;
22
57
  };
@@ -47,11 +82,11 @@ const hasStagedChanges = (ui) => {
47
82
 
48
83
  const selectedCommit = (api) => {
49
84
  const cursor = api.ui.nav.commitCursor;
50
- return api.state.commits[cursor] ?? null;
85
+ return listedCommits(api.state.commits)[cursor] ?? null;
51
86
  };
52
87
 
53
88
  const clampCommitCursor = (api) => {
54
- const last = api.state.commits.length - 1;
89
+ const last = listedCommits(api.state.commits).length - 1;
55
90
  const nav = api.ui.nav;
56
91
  if (nav.commitCursor > last) nav.commitCursor = Math.max(0, last);
57
92
  };
@@ -75,7 +110,6 @@ const refreshAfterWrite = (api, extra = {}) => {
75
110
 
76
111
  const showCommits = (api) => {
77
112
  if (api.ui.mode === 'compose') return;
78
- if (!canCommit(api.ui)) return;
79
113
  const repo = api.ui.repo;
80
114
  if (!repo.listCommits) {
81
115
  api.ui.status = 'read only';
@@ -94,11 +128,8 @@ const showCommits = (api) => {
94
128
  };
95
129
 
96
130
  const onCommitMove = (api, delta) => {
97
- const commits = api.state.commits;
98
- if (!commits.length) {
99
- api.ui.status = 'nothing to review';
100
- return;
101
- }
131
+ if (api.ui.mode === 'compose') return;
132
+ const commits = listedCommits(api.state.commits);
102
133
  const next = Math.min(
103
134
  commits.length - 1,
104
135
  Math.max(0, api.ui.nav.commitCursor + delta),
@@ -117,15 +148,13 @@ const startGitCommit = (api, kind) => {
117
148
  return;
118
149
  }
119
150
  let text = '';
120
- if (kind === 'fixup') {
121
- text = fixupMessage(selectedCommit(api));
122
- }
151
+ if (kind === 'fixup') text = fixupMessage(selectedCommit(api));
152
+ if (kind === 'commit') api.ui.nav.commitCursor = 0;
123
153
  if (kind === 'amend') {
124
154
  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
- }
155
+ const at = headAt < 0 ? 0 : headAt;
156
+ api.ui.nav.commitCursor = api.state.commits.length ? at + 1 : 0;
157
+ if (ui.repo.lastMessage) text = ui.repo.lastMessage(ui.top) ?? '';
129
158
  }
130
159
  if (kind === 'reword') {
131
160
  const entry = selectedCommit(api);
@@ -133,19 +162,56 @@ const startGitCommit = (api, kind) => {
133
162
  if (typeof ui.repo.commitMessage === 'function') {
134
163
  text = ui.repo.commitMessage(ui.top, entry.sha) ?? '';
135
164
  } else {
136
- text = `${entry.subject ?? ''}`;
165
+ text = commitText(entry);
137
166
  }
138
167
  }
168
+ ui.composer.state.commitBody = '';
169
+ if (isInPlaceCommit(kind)) text = keepBriefBody(ui, text);
139
170
  ui.composer.state.commitKind = kind;
140
171
  ui.composer.openCompose('commit', text);
172
+ const editor = ui.composer.state.editor;
173
+ if (!editor) return;
174
+ const lineEnd = editor.text.indexOf('\n');
175
+ editor.cursor = lineEnd < 0 ? editor.text.length : lineEnd;
141
176
  };
142
177
 
143
178
  const onCommit = (api) => {
144
179
  if (api.ui.nav.pane !== 'commits') return void showCommits(api);
145
180
  if (!canCommit(api.ui)) return;
181
+ api.ui.nav.commitCursor = 0;
146
182
  startGitCommit(api, 'commit');
147
183
  };
148
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
+
195
+ const showSelectedDiff = (api) => {
196
+ if (api.ui.mode === 'compose') return;
197
+ const entry = selectedCommit(api);
198
+ if (!entry) return;
199
+ const ui = api.ui;
200
+ const viewingCommit = Boolean(ui.rev || ui.revShort);
201
+ if (isPending(entry) && !viewingCommit) return void onCommit(api);
202
+ if (isPending(entry) || !entry.sha) {
203
+ ui.rev = '';
204
+ ui.revShort = '';
205
+ } else {
206
+ ui.rev = entry.sha;
207
+ ui.revShort = entry.shortSha || '';
208
+ }
209
+ ui.refreshFromRepo({
210
+ keepEmpty: true,
211
+ afterLoad: () => openLoadedDiff(ui),
212
+ });
213
+ };
214
+
149
215
  const onAmend = (api) => {
150
216
  if (api.ui.nav.pane !== 'commits') return;
151
217
  if (!canCommit(api.ui)) return;
@@ -167,18 +233,9 @@ const applyFixupNow = (api) => {
167
233
  api.ui.status = 'read only';
168
234
  return;
169
235
  }
170
- const picked = pickRepoWrite(repo, 'applyFixup', api.ui.uiOpen);
171
- if (picked.async) {
172
- const run = (top) => picked.write(top, entry.sha);
173
- const after = () => refreshAfterWrite(api, { doneStatus: done });
174
- return void api.ui.ops.runBusy('applying', done, run, after);
175
- }
176
- const ok = api.ui.ops.runRepo(done, (top) => {
177
- repo.applyFixup(top, entry.sha);
178
- });
179
- if (!ok) return;
180
- refreshAfterWrite(api);
181
- if (!api.ui.status) api.ui.status = done;
236
+ api.ui.ops.runWrite('applyFixup', [entry.sha], 'applying', done, (extra) =>
237
+ refreshAfterWrite(api, extra),
238
+ );
182
239
  };
183
240
 
184
241
  const onApply = (api) => {
@@ -260,7 +317,10 @@ const finishGitCommit = (api) => {
260
317
  const ui = api.ui;
261
318
  const kind = ui.composer.state.commitKind;
262
319
  const editor = ui.composer.state.editor;
263
- const message = editor ? editor.text : '';
320
+ let message = editor ? editor.text : '';
321
+ if (isInPlaceCommit(kind) && briefCommit(ui)) {
322
+ message = joinMessage(message, ui.composer.state.commitBody);
323
+ }
264
324
  if (!message.trim()) {
265
325
  ui.status = 'empty commit message';
266
326
  return;
@@ -298,12 +358,6 @@ const cancelDropCommit = (api) => {
298
358
  api.ui.status = '';
299
359
  };
300
360
 
301
- const dropAsync = (api, sha, done, write) => {
302
- const run = (top) => write(top, sha);
303
- const after = () => refreshAfterWrite(api, { doneStatus: done });
304
- return void api.ui.ops.runBusy('dropping', done, run, after);
305
- };
306
-
307
361
  const confirmDropCommit = (api) => {
308
362
  const sha = api.state.dropSha;
309
363
  const name = api.state.dropName;
@@ -317,23 +371,24 @@ const confirmDropCommit = (api) => {
317
371
  api.ui.status = 'read only';
318
372
  return;
319
373
  }
320
- const uiOpen = api.ui.uiOpen;
321
- const picked = pickRepoWrite(repo, 'dropCommit', uiOpen);
322
- if (picked.async) {
323
- return void dropAsync(api, sha, done, picked.write);
324
- }
325
- const ok = api.ui.ops.runRepo(done, (top) => {
326
- repo.dropCommit(top, sha);
327
- });
328
- if (!ok) return;
329
- refreshAfterWrite(api);
330
- if (!api.ui.status) api.ui.status = done;
374
+ api.ui.ops.runWrite('dropCommit', [sha], 'dropping', done, (extra) =>
375
+ refreshAfterWrite(api, extra),
376
+ );
377
+ };
378
+
379
+ const onView = (api) => {
380
+ if (api.ui.nav.pane !== 'commits') return;
381
+ const next = api.state.commitView === 'full' ? 'brief' : 'full';
382
+ api.state.commitView = next;
383
+ api.ui.nav.listScroll = 0;
384
+ api.ui.status = next;
331
385
  };
332
386
 
333
387
  const reset = (state) => {
334
388
  state.commits = [];
335
389
  state.dropName = '';
336
390
  state.dropSha = '';
391
+ state.commitView = 'brief';
337
392
  };
338
393
 
339
394
  const createCommitController = (ui) => {
@@ -341,6 +396,7 @@ const createCommitController = (ui) => {
341
396
  commits: [],
342
397
  dropName: '',
343
398
  dropSha: '',
399
+ commitView: 'brief',
344
400
  };
345
401
  const api = { ui, state };
346
402
  return stateView(
@@ -354,6 +410,8 @@ const createCommitController = (ui) => {
354
410
  onApply: () => onApply(api),
355
411
  onFixup: () => onFixup(api),
356
412
  onCommitMove: (delta) => onCommitMove(api, delta),
413
+ onShowDiff: () => showSelectedDiff(api),
414
+ onView: () => onView(api),
357
415
  cancelCommit: () => cancelCommit(api),
358
416
  startGitCommit: (kind) => startGitCommit(api, kind),
359
417
  finishGitCommit: () => finishGitCommit(api),
@@ -362,11 +420,13 @@ const createCommitController = (ui) => {
362
420
  cancelDropCommit: () => cancelDropCommit(api),
363
421
  confirmDropCommit: () => confirmDropCommit(api),
364
422
  },
365
- ['commits', 'dropName'],
423
+ ['commits', 'dropName', 'commitView'],
366
424
  );
367
425
  };
368
426
 
369
427
  module.exports = {
370
428
  COMMIT_STATUS,
429
+ hasStagedChanges,
371
430
  createCommitController,
431
+ listedCommits,
372
432
  };
@@ -4,16 +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 { 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;
8
13
  const files = require('../files.js');
9
14
  const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
10
- const { blockAddText } = require('../diff/diff.js');
15
+ const diff = require('../diff/diff.js');
16
+ const { blockAddText } = diff;
11
17
  const items = require('./items.js');
12
18
  const { itemFeedKey, canEditCode } = items;
13
19
  const review = require('../review.js');
14
20
  const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
15
- const { createTemplatePick } = require('./templates.js');
16
- 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;
17
25
  const unit = require('./unit.js');
18
26
 
19
27
  const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
@@ -68,12 +76,21 @@ const KIND = {
68
76
  command: 'branch',
69
77
  view: 'note',
70
78
  },
79
+ npm: {
80
+ enter: 'save',
81
+ tab: 'insert-tab',
82
+ escape: 'cancel',
83
+ move: 'note',
84
+ command: 'npm',
85
+ view: 'note',
86
+ },
71
87
  };
72
88
 
73
89
  const initialState = () => ({
74
90
  editor: null,
75
91
  composeKind: null,
76
92
  commitKind: null,
93
+ commitBody: '',
77
94
  composeTodoId: null,
78
95
  composeBaseline: '',
79
96
  templateIndex: -1,
@@ -101,6 +118,16 @@ const tickBlink = (api) => {
101
118
 
102
119
  const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
103
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
+
104
131
  const followFileEdit = (api) => {
105
132
  if (api.state.composeKind !== 'file') return;
106
133
  const text = api.state.editor ? api.state.editor.text : null;
@@ -140,6 +167,7 @@ const closeCompose = (api) => {
140
167
  state.editor = null;
141
168
  state.composeKind = null;
142
169
  state.commitKind = null;
170
+ state.commitBody = '';
143
171
  state.composeTodoId = null;
144
172
  state.composeBaseline = '';
145
173
  state.fileOpened = '';
@@ -414,7 +442,9 @@ const codeOverlayView = (api) => {
414
442
  api.state.composeKind === 'code' || api.state.composeKind === 'file';
415
443
  const editor = api.state.editor;
416
444
  const cursor = editing && editor ? editor.cursor : null;
417
- 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 };
418
448
  };
419
449
 
420
450
  const idleNoteText = (api) => {
@@ -474,6 +504,22 @@ const moveNoteLine = (api, delta) => {
474
504
  api.state.editor.moveLine(delta, width);
475
505
  };
476
506
 
507
+ const fullCommitEdit = (api) =>
508
+ api.state.composeKind === 'commit' && api.ui.commits.commitView === 'full';
509
+
510
+ const editedCommitEntry = (api) => {
511
+ const kind = api.state.commitKind;
512
+ if (kind !== 'amend' && kind !== 'reword') return null;
513
+ const list = listedCommits(api.ui.commits.commits);
514
+ return list[api.nav.commitCursor] ?? null;
515
+ };
516
+
517
+ const moveCommitLine = (api, delta) => {
518
+ const size = viewSize(api);
519
+ const width = commitMessageWidth(editedCommitEntry(api), size.width);
520
+ api.state.editor.moveLine(delta, width);
521
+ };
522
+
477
523
  const moveComposeLine = (api, delta) => {
478
524
  const spec = KIND[api.state.composeKind];
479
525
  const move = spec ? spec.move : 'note';
@@ -483,10 +529,10 @@ const moveComposeLine = (api, delta) => {
483
529
  return void api.todos.moveTodoCompose(delta, inner);
484
530
  }
485
531
  if (move === 'code') {
486
- const size = viewSize(api);
487
- const inner = codeInnerWidth(size.width, api.ui.layout);
488
- return void api.state.editor.moveLine(delta, inner);
532
+ api.state.editor.moveLine(delta);
533
+ return void syncCodeScroll(api);
489
534
  }
535
+ if (fullCommitEdit(api)) return void moveCommitLine(api, delta);
490
536
  moveNoteLine(api, delta);
491
537
  };
492
538
 
@@ -552,6 +598,7 @@ const onUnitEdit = (api) => {
552
598
  openCompose(api, 'file', at.text);
553
599
  api.state.fileEditRows = at.rows ?? null;
554
600
  api.state.editor.cursor = Math.min(at.cursor, at.text.length);
601
+ syncCodeScroll(api);
555
602
  followFileEdit(api);
556
603
  };
557
604
 
@@ -573,6 +620,22 @@ const onCode = (api) => {
573
620
  : notes.code.get(itemFeedKey(item));
574
621
  const text = prev ? prev.text : original;
575
622
  openCompose(api, 'code', text);
623
+ syncCodeScroll(api);
624
+ };
625
+
626
+ const insertNewline = (api) => {
627
+ api.state.editor.insert('\n');
628
+ resetBlink(api);
629
+ followFileEdit(api);
630
+ return null;
631
+ };
632
+
633
+ const onEmptyLastLine = (editor) => {
634
+ if (!editor) return false;
635
+ const text = editor.text;
636
+ const lastBreak = text.lastIndexOf('\n');
637
+ if (editor.cursor < lastBreak + 1) return false;
638
+ return text.slice(lastBreak + 1).trim() === '';
576
639
  };
577
640
 
578
641
  const enterCompose = (api) => {
@@ -581,11 +644,10 @@ const enterCompose = (api) => {
581
644
  return api.templates.selectTemplate(state.templateIndex);
582
645
  }
583
646
  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;
647
+ if (spec && spec.enter === 'newline') return insertNewline(api);
648
+ if (fullCommitEdit(api)) {
649
+ if (onEmptyLastLine(state.editor)) return saveCompose(api);
650
+ return insertNewline(api);
589
651
  }
590
652
  if (spec && spec.enter === 'edit-next') {
591
653
  return saveCompose(api, { editNext: true });
@@ -624,7 +686,11 @@ const COMPOSE_SPECIAL = {
624
686
  };
625
687
 
626
688
  const handleKey = (api, key) => {
627
- if (key === 'enter') return enterCompose(api);
689
+ if (key === 'enter') {
690
+ const result = enterCompose(api);
691
+ syncCodeScroll(api);
692
+ return result;
693
+ }
628
694
  if (key === 'escape' || key === 'ctrl-s') return leaveCompose(api, key);
629
695
  const special = COMPOSE_SPECIAL[key];
630
696
  if (special) {
@@ -632,6 +698,7 @@ const handleKey = (api, key) => {
632
698
  if (COMPOSE_EDITS.includes(key)) api.templates.clearTemplatePick();
633
699
  resetBlink(api);
634
700
  followFileEdit(api);
701
+ syncCodeScroll(api);
635
702
  return result ?? null;
636
703
  }
637
704
  if (!key.length) return null;
@@ -641,6 +708,7 @@ const handleKey = (api, key) => {
641
708
  api.state.editor.insert(key);
642
709
  resetBlink(api);
643
710
  followFileEdit(api);
711
+ syncCodeScroll(api);
644
712
  }
645
713
  return null;
646
714
  };
@@ -653,6 +721,7 @@ const applyDiskText = (api, text) => {
653
721
  api.state.editor.cursor = Math.min(cursor, text.length);
654
722
  api.state.composeBaseline = text;
655
723
  api.state.fileEditRows = unit.linesFor(api.ui, api.nav.reviewPath, null);
724
+ syncCodeScroll(api);
656
725
  followFileEdit(api);
657
726
  };
658
727
 
@@ -1,14 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  const compose = require('./compose.js');
4
+ const commit = require('./commit.js');
5
+ const { hasStagedChanges, listedCommits } = commit;
4
6
  const { COMPOSE_LEAVE } = compose;
5
7
  const actions = require('./actions.js');
6
- const { CONFIRM_MODE, disabledActions, actionFromKey, applyConfirm } = actions;
7
-
8
- const targetHasStaged = (session) => {
9
- if (session.repo.hasStaged) return session.repo.hasStaged(session.top);
10
- return session.items.some((item) => item.origin === 'staged');
11
- };
8
+ const {
9
+ LIST_PANES,
10
+ CONFIRM_MODE,
11
+ disabledActions,
12
+ actionFromKey,
13
+ applyConfirm,
14
+ } = actions;
12
15
 
13
16
  const currentTarget = (ui) => {
14
17
  if (ui.pane === 'files') return ui.fileCursorEntry();
@@ -16,10 +19,10 @@ const currentTarget = (ui) => {
16
19
  return ui.branches[ui.branchCursor];
17
20
  }
18
21
  if (ui.pane === 'commits') {
19
- const entry = ui.commits.commits[ui.commitCursor];
20
- const staged = targetHasStaged(ui);
21
- if (!entry) return { canCommit: staged };
22
- return { ...entry, canCommit: staged };
22
+ const entry = listedCommits(ui.commits.commits)[ui.commitCursor];
23
+ const staged = hasStagedChanges(ui);
24
+ const unstagedCurrent = !ui.rev && !ui.revShort;
25
+ return { ...entry, canCommit: staged, unstagedCurrent };
23
26
  }
24
27
  return ui.current();
25
28
  };
@@ -43,6 +46,7 @@ const createDispatcher = (ui) => {
43
46
  reword: ui.commits.onReword,
44
47
  apply: ui.commits.onApply,
45
48
  fixup: ui.commits.onFixup,
49
+ view: () => ui.commits.onView(),
46
50
  branch: ui.gitBranches.onBranch,
47
51
  pull: ui.gitBranches.onPull,
48
52
  push: ui.gitBranches.onPush,
@@ -57,8 +61,18 @@ const createDispatcher = (ui) => {
57
61
  open: ui.filesPane.onOpenFile,
58
62
  file: ui.filesPane.onFileScope,
59
63
  diff: ui.filesPane.onDiffScope,
64
+ npm: ui.npm.open,
65
+ npmEdit: ui.npm.edit,
66
+ npmNew: ui.npm.create,
67
+ npmDrop: () => ui.npm.askDrop(),
68
+ npmLogs: () => ui.npm.askLogs(),
69
+ npmStop: () => ui.npm.closeView(),
70
+ npmRerun: () => ui.npm.rerun(),
60
71
  check: () => ui.composer.toggleFocusedTodo(),
61
- removeTodo: ui.composer.removeFocusedTodo,
72
+ removeTodo: () => {
73
+ if (ui.pane === 'npm') return void ui.npm.askDrop();
74
+ ui.composer.removeFocusedTodo();
75
+ },
62
76
  quit: () => ui.onQuit(),
63
77
  scrollUp: () => ui.filesPane.onScroll(-1),
64
78
  scrollDown: () => ui.filesPane.onScroll(1),
@@ -78,17 +92,9 @@ const createDispatcher = (ui) => {
78
92
  if (result && result.kind === 'error') return;
79
93
  }
80
94
  const pane = ui.pane;
81
- if (pane === 'files') {
82
- if (action === 'next') return void ui.filesPane.onFileMove(1);
83
- if (action === 'prev') return void ui.filesPane.onFileMove(-1);
84
- }
85
- if (pane === 'branches') {
86
- if (action === 'next') return void ui.gitBranches.onBranchMove(1);
87
- if (action === 'prev') return void ui.gitBranches.onBranchMove(-1);
88
- }
89
- if (pane === 'commits') {
90
- if (action === 'next') return void ui.commits.onCommitMove(1);
91
- if (action === 'prev') return void ui.commits.onCommitMove(-1);
95
+ if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
96
+ ui.filesPane.onScroll(action === 'next' ? 1 : -1);
97
+ return;
92
98
  }
93
99
  const hidden = disabledActions(pane, currentTarget(ui));
94
100
  if (hidden.includes(action)) return;
@@ -113,12 +119,14 @@ const createInput = (ui) => {
113
119
  },
114
120
  confirmDrop: {
115
121
  confirm: () => {
122
+ if (ui.npm.dropName) return void ui.npm.confirmDrop();
116
123
  if (ui.commits.dropName) {
117
124
  return void ui.commits.confirmDropCommit();
118
125
  }
119
126
  ui.gitBranches.confirmDropBranch();
120
127
  },
121
128
  cancel: () => {
129
+ if (ui.npm.dropName) return void ui.npm.cancelDrop();
122
130
  if (ui.commits.dropName) {
123
131
  return void ui.commits.cancelDropCommit();
124
132
  }
@@ -145,6 +153,18 @@ const createInput = (ui) => {
145
153
  const command = ui.composer.handleKey(key);
146
154
  return void ui.runComposeCommand(command);
147
155
  }
156
+ if (ui.pane === 'npm' && ui.npm.viewing) {
157
+ if (key === 'escape') return void ui.npm.closeView();
158
+ if (key === 'r') return void ui.npm.rerun();
159
+ if (key === 'e' || key === 'n') return;
160
+ }
161
+ if (ui.pane === 'npm' && key === 'ctrl-up') return void ui.npm.reorder(-1);
162
+ if (ui.pane === 'npm' && key === 'ctrl-down') {
163
+ return void ui.npm.reorder(1);
164
+ }
165
+ if (ui.pane === 'npm' && (key === 'up' || key === 'down')) {
166
+ return void ui.npm.move(key === 'up' ? -1 : 1);
167
+ }
148
168
  if (key === 'escape') return void ui.filesPane.onEscape();
149
169
  const toggle = key === 'x' || key === ' ';
150
170
  if (toggle && ui.composer.toggleFocusedTodo()) return;