reslop 0.1.9 → 0.2.1

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 (96) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +3 -1
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +148 -5
  5. package/lib/api.js +4 -2
  6. package/lib/capabilities.js +13 -14
  7. package/lib/cli.js +38 -49
  8. package/lib/clipboard.js +1 -1
  9. package/lib/{deps/changes.js → deps-diff.js} +309 -19
  10. package/lib/deps.js +498 -29
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +5 -8
  20. package/lib/{git/branches.js → git-branches.js} +191 -11
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git-reword-todo.js +20 -0
  23. package/lib/git.js +606 -68
  24. package/lib/github.js +28 -197
  25. package/lib/gitlab.js +24 -195
  26. package/lib/highlight/bash.js +1 -3
  27. package/lib/highlight/css.js +1 -3
  28. package/lib/highlight/js.js +11 -14
  29. package/lib/highlight.js +6 -9
  30. package/lib/keys.js +14 -8
  31. package/lib/manifest.js +330 -0
  32. package/lib/remote.js +374 -0
  33. package/lib/render/branches.js +18 -100
  34. package/lib/render/chrome.js +71 -14
  35. package/lib/render/commits.js +69 -60
  36. package/lib/render/diff.js +20 -20
  37. package/lib/render/files.js +6 -34
  38. package/lib/render/frame.js +36 -13
  39. package/lib/render/notes.js +26 -8
  40. package/lib/render/primitives.js +99 -11
  41. package/lib/{render.js → render/render.js} +8 -8
  42. package/lib/render/unit.js +41 -0
  43. package/lib/review-merge.js +270 -0
  44. package/lib/review.js +782 -14
  45. package/lib/select.js +5 -10
  46. package/lib/session/accessors.js +9 -1
  47. package/lib/session/actions.js +61 -18
  48. package/lib/session/branches.js +116 -128
  49. package/lib/session/change-actions.js +202 -123
  50. package/lib/session/commit.js +201 -115
  51. package/lib/session/compose.js +181 -15
  52. package/lib/session/dispatch.js +110 -60
  53. package/lib/session/files-pane.js +156 -148
  54. package/lib/session/items.js +22 -30
  55. package/lib/session/lifecycle.js +151 -54
  56. package/lib/session/load.js +19 -33
  57. package/lib/session/navigation.js +12 -1
  58. package/lib/session/ops.js +28 -41
  59. package/lib/session/pointer.js +57 -57
  60. package/lib/session/review.js +23 -37
  61. package/lib/session/terminal.js +6 -12
  62. package/lib/session/todos.js +37 -13
  63. package/lib/session/unit.js +208 -0
  64. package/lib/session/update.js +33 -46
  65. package/lib/session/view.js +78 -9
  66. package/lib/session/watch.js +128 -24
  67. package/lib/session.js +22 -246
  68. package/lib/source.js +3 -3
  69. package/lib/update.js +40 -75
  70. package/lib/{git/proc.js → utilities.js} +75 -11
  71. package/lib/wrap.js +1 -3
  72. package/package.json +2 -2
  73. package/lib/deps/items.js +0 -134
  74. package/lib/deps/manifest.js +0 -152
  75. package/lib/deps/merge.js +0 -120
  76. package/lib/deps/proposals.js +0 -164
  77. package/lib/deps/reports.js +0 -180
  78. package/lib/deps/rows.js +0 -176
  79. package/lib/deps/scan.js +0 -138
  80. package/lib/deps/usage.js +0 -68
  81. package/lib/deps/versions.js +0 -119
  82. package/lib/diff.js +0 -33
  83. package/lib/git/changes.js +0 -164
  84. package/lib/git/read.js +0 -253
  85. package/lib/git/run.js +0 -100
  86. package/lib/npm/commands.js +0 -100
  87. package/lib/remote/changes.js +0 -37
  88. package/lib/remote/headers.js +0 -36
  89. package/lib/remote/http.js +0 -122
  90. package/lib/remote/notes.js +0 -94
  91. package/lib/review/format.js +0 -434
  92. package/lib/review/storage.js +0 -150
  93. package/lib/review/store.js +0 -178
  94. package/lib/review/templates.js +0 -49
  95. package/lib/session/origins.js +0 -7
  96. package/lib/sys.js +0 -84
@@ -2,19 +2,19 @@
2
2
 
3
3
  const wrap = require('../wrap.js');
4
4
  const { cursorInWrap, wrapDoc } = wrap;
5
- const render = require('../render.js');
5
+ const render = require('../render/render.js');
6
6
  const { noteInnerWidth, codeInnerWidth, todoBodyWidth } = render;
7
7
  const { Editor } = require('../editor.js');
8
8
  const files = require('../files.js');
9
- const { itemPath, isTodoItem } = files;
10
- const { blockAddText } = require('../diff.js');
9
+ const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
10
+ const { blockAddText } = require('../diff/diff.js');
11
11
  const items = require('./items.js');
12
12
  const { itemFeedKey, canEditCode } = items;
13
- const { isReadOnlyOrigin } = require('./origins.js');
14
13
  const review = require('../review.js');
15
14
  const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
16
15
  const { createTemplatePick } = require('./templates.js');
17
16
  const { createTodoCompose } = require('./todos.js');
17
+ const unit = require('./unit.js');
18
18
 
19
19
  const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
20
20
  const COMPOSE_LEAVE = ['next', 'prev', 'todo'];
@@ -36,6 +36,14 @@ const KIND = {
36
36
  command: null,
37
37
  view: 'code',
38
38
  },
39
+ file: {
40
+ enter: 'newline',
41
+ tab: 'insert-tab',
42
+ escape: 'save',
43
+ move: 'code',
44
+ command: null,
45
+ view: 'code',
46
+ },
39
47
  todo: {
40
48
  enter: 'edit-next',
41
49
  tab: 'template',
@@ -72,6 +80,8 @@ const initialState = () => ({
72
80
  templateFocus: false,
73
81
  blinkOn: true,
74
82
  fileDirty: false,
83
+ fileEditRows: null,
84
+ fileOpened: '',
75
85
  });
76
86
 
77
87
  const reset = (state) => {
@@ -91,6 +101,18 @@ const tickBlink = (api) => {
91
101
 
92
102
  const viewSize = (api) => api.ui.lastSize ?? api.ui.getSize();
93
103
 
104
+ const followFileEdit = (api) => {
105
+ if (api.state.composeKind !== 'file') return;
106
+ const text = api.state.editor ? api.state.editor.text : null;
107
+ const rows = unit.linesFor(
108
+ api.ui,
109
+ api.nav.reviewPath,
110
+ text,
111
+ api.state.fileEditRows,
112
+ );
113
+ unit.followFileCursor(api.nav, api.state.editor, api.ui.lastFrame, rows);
114
+ };
115
+
94
116
  const afterClose = (api) => {
95
117
  api.ui.clampIndex();
96
118
  api.ui.syncReviewPath();
@@ -103,6 +125,7 @@ const openCompose = (api, kind, text, todoId) => {
103
125
  state.composeKind = kind;
104
126
  state.composeTodoId = todoId ?? null;
105
127
  state.composeBaseline = text;
128
+ state.fileOpened = kind === 'file' ? text : '';
106
129
  state.editor = new Editor(text);
107
130
  state.templateIndex = -1;
108
131
  state.templateFocus = false;
@@ -119,8 +142,10 @@ const closeCompose = (api) => {
119
142
  state.commitKind = null;
120
143
  state.composeTodoId = null;
121
144
  state.composeBaseline = '';
145
+ state.fileOpened = '';
122
146
  state.templateIndex = -1;
123
147
  state.templateFocus = false;
148
+ state.fileEditRows = null;
124
149
  afterClose(api);
125
150
  };
126
151
 
@@ -167,8 +192,7 @@ const saveCodeNote = (api, item, text) => {
167
192
  const key = itemFeedKey(item);
168
193
  const note = itemNote(item, text);
169
194
  if (text === original) {
170
- setCode(notes, key, { ...note, clear: true });
171
- return;
195
+ return void setCode(notes, key, { ...note, clear: true });
172
196
  }
173
197
  setCode(notes, key, note);
174
198
  };
@@ -198,6 +222,91 @@ const commitCode = (api, text) => {
198
222
  return true;
199
223
  };
200
224
 
225
+ const saveUnitFile = (api, text) => {
226
+ const rel = api.nav.reviewPath;
227
+ const repo = api.ui.repo;
228
+ if (!rel || !repo || typeof repo.writeFile !== 'function') return true;
229
+ const caps = api.ui.capabilities;
230
+ if (!caps || caps.changes !== true) {
231
+ api.ui.status = 'read only';
232
+ return false;
233
+ }
234
+ try {
235
+ repo.writeFile(api.ui.top, rel, text);
236
+ } catch (error) {
237
+ api.ui.status = error.message;
238
+ return false;
239
+ }
240
+ if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
241
+ api.state.composeBaseline = text;
242
+ api.state.fileDirty = true;
243
+ return true;
244
+ };
245
+
246
+ const stageEditedFile = (api, rel) => {
247
+ if (!rel) return false;
248
+ const repo = api.ui.repo;
249
+ if (!repo) return false;
250
+ const caps = api.ui.capabilities;
251
+ if (!caps || caps.changes !== true) return false;
252
+ if (typeof repo.stagePath === 'function') {
253
+ try {
254
+ repo.stagePath(api.ui.top, rel);
255
+ } catch (error) {
256
+ api.ui.status = error.message;
257
+ return false;
258
+ }
259
+ if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
260
+ return true;
261
+ }
262
+ if (typeof repo.add !== 'function') return false;
263
+ let staged = 0;
264
+ for (const item of api.ui.items ?? []) {
265
+ if (listPath(item) !== rel) continue;
266
+ if (isReadOnlyOrigin(item.origin)) continue;
267
+ if (item.origin === 'staged') continue;
268
+ try {
269
+ repo.add(api.ui.top, item);
270
+ } catch (error) {
271
+ api.ui.status = error.message;
272
+ return staged > 0;
273
+ }
274
+ if (api.ui.collection && api.ui.collection.keepOrigin) {
275
+ api.ui.collection.keepOrigin(item, 'staged');
276
+ }
277
+ staged += 1;
278
+ }
279
+ if (staged && typeof api.ui.ignoreWatch === 'function') {
280
+ api.ui.ignoreWatch();
281
+ }
282
+ return staged > 0;
283
+ };
284
+
285
+ const saveFileCompose = (api) => {
286
+ const rel = api.nav.reviewPath;
287
+ const opened = api.state.fileOpened;
288
+ const text = api.state.editor ? api.state.editor.text : '';
289
+ if (api.state.editor) {
290
+ api.nav.unitLine = api.state.editor.linePos().line;
291
+ }
292
+ const result = saveUnitFile(api, text);
293
+ if (result === false) return { kind: 'error' };
294
+ const changed = text !== opened;
295
+ const reload = api.state.fileDirty === true;
296
+ if (changed) stageEditedFile(api, rel);
297
+ api.ui.flushReview();
298
+ closeCompose(api);
299
+ const doneStatus = changed ? 'staged' : 'saved';
300
+ if (!reload || typeof api.ui.reloadAfterChange !== 'function') {
301
+ if (!api.ui.status) api.ui.status = doneStatus;
302
+ return { kind: 'saved' };
303
+ }
304
+ api.ui.reloadAfterChange(() => {
305
+ if (!api.ui.status) api.ui.status = doneStatus;
306
+ });
307
+ return { kind: 'saved' };
308
+ };
309
+
201
310
  const commitCompose = (api) => {
202
311
  const { state } = api;
203
312
  if (!state.composeKind || !state.editor) return true;
@@ -211,6 +320,7 @@ const commitCompose = (api) => {
211
320
  return true;
212
321
  }
213
322
  if (state.composeKind === 'code') return commitCode(api, text);
323
+ if (state.composeKind === 'file') return saveUnitFile(api, text);
214
324
  return true;
215
325
  };
216
326
 
@@ -219,6 +329,14 @@ const saveCompose = (api, extra) => {
219
329
  const spec = KIND[api.state.composeKind];
220
330
  if (spec && spec.command) return { kind: spec.command };
221
331
  const kind = api.state.composeKind;
332
+ if (kind === 'file') return saveFileCompose(api);
333
+ const item = api.ui.current ? api.ui.current() : null;
334
+ const shouldStage =
335
+ kind === 'code' &&
336
+ writesCodeFile(api, item) &&
337
+ item &&
338
+ item.origin !== 'staged';
339
+ const rel = itemPath(item);
222
340
  const baseline = api.state.composeBaseline;
223
341
  const result = commitCompose(api);
224
342
  if (result === false) return { kind: 'error' };
@@ -229,12 +347,17 @@ const saveCompose = (api, extra) => {
229
347
  const next = editor ? editor.text : '';
230
348
  if (notes) rememberTemplate(notes, baseline, next);
231
349
  }
350
+ if (shouldStage && reload) stageEditedFile(api, rel);
232
351
  api.ui.flushReview();
233
352
  closeCompose(api);
353
+ const doneStatus = shouldStage && reload ? 'staged' : 'saved';
234
354
  if (reload && typeof api.ui.reloadAfterChange === 'function') {
235
- api.ui.reloadAfterChange();
355
+ api.ui.reloadAfterChange(() => {
356
+ if (!api.ui.status) api.ui.status = doneStatus;
357
+ });
358
+ } else {
359
+ api.ui.status = doneStatus;
236
360
  }
237
- api.ui.status = 'saved';
238
361
  if (opts.editNext === true) api.todos.editNextTodo();
239
362
  return { kind: 'saved' };
240
363
  };
@@ -243,6 +366,10 @@ const autosave = (api) => {
243
366
  const { state } = api;
244
367
  const spec = KIND[state.composeKind];
245
368
  const text = state.editor ? state.editor.text : '';
369
+ if (state.composeKind === 'file' && state.editor) {
370
+ if (text === state.composeBaseline) return;
371
+ return void saveUnitFile(api, text);
372
+ }
246
373
  const editingCode = state.composeKind === 'code';
247
374
  const composing = state.composeKind !== null;
248
375
  const reviewKind = composing && (!spec || !spec.command);
@@ -263,13 +390,14 @@ const composeView = (api) => {
263
390
  const kind = state.composeKind;
264
391
  const text = state.editor.text;
265
392
  const cursor = state.editor.cursor;
266
- return { kind, text, cursor };
393
+ return { kind, text, cursor, commitKind: state.commitKind };
267
394
  };
268
395
 
269
396
  const liveCodeText = (api) => {
397
+ const { state } = api;
398
+ if (state.composeKind === 'file' && state.editor) return state.editor.text;
270
399
  const item = api.ui.current();
271
400
  if (!canEditCode(item)) return null;
272
- const { state } = api;
273
401
  if (state.composeKind === 'code' && state.editor) return state.editor.text;
274
402
  if (writesCodeFile(api, item)) return null;
275
403
  const notes = api.review.store;
@@ -282,7 +410,8 @@ const liveCodeText = (api) => {
282
410
  const codeOverlayView = (api) => {
283
411
  const text = liveCodeText(api);
284
412
  if (text === null) return null;
285
- const editing = api.state.composeKind === 'code';
413
+ const editing =
414
+ api.state.composeKind === 'code' || api.state.composeKind === 'file';
286
415
  const editor = api.state.editor;
287
416
  const cursor = editing && editor ? editor.cursor : null;
288
417
  return { text, keepEmpty: editing, cursor };
@@ -351,14 +480,12 @@ const moveComposeLine = (api, delta) => {
351
480
  if (move === 'todo') {
352
481
  const size = viewSize(api);
353
482
  const inner = todoBodyWidth(size.width);
354
- api.todos.moveTodoCompose(delta, inner);
355
- return;
483
+ return void api.todos.moveTodoCompose(delta, inner);
356
484
  }
357
485
  if (move === 'code') {
358
486
  const size = viewSize(api);
359
487
  const inner = codeInnerWidth(size.width, api.ui.layout);
360
- api.state.editor.moveLine(delta, inner);
361
- return;
488
+ return void api.state.editor.moveLine(delta, inner);
362
489
  }
363
490
  moveNoteLine(api, delta);
364
491
  };
@@ -410,7 +537,26 @@ const onFeedback = (api) => {
410
537
  openCompose(api, 'feedback', text);
411
538
  };
412
539
 
540
+ const onUnitEdit = (api) => {
541
+ const rel = api.nav.reviewPath;
542
+ if (!rel) {
543
+ api.ui.status = 'not a diff block';
544
+ return;
545
+ }
546
+ const caps = api.ui.capabilities;
547
+ if (!caps || caps.changes !== true) {
548
+ api.ui.status = 'read only';
549
+ return;
550
+ }
551
+ const at = unit.editCursor(api.ui);
552
+ openCompose(api, 'file', at.text);
553
+ api.state.fileEditRows = at.rows ?? null;
554
+ api.state.editor.cursor = Math.min(at.cursor, at.text.length);
555
+ followFileEdit(api);
556
+ };
557
+
413
558
  const onCode = (api) => {
559
+ if (api.nav.pane === 'unit') return void onUnitEdit(api);
414
560
  if (api.nav.pane === 'files') {
415
561
  api.ui.status = 'not a diff block';
416
562
  return;
@@ -438,6 +584,7 @@ const enterCompose = (api) => {
438
584
  if (spec && spec.enter === 'newline') {
439
585
  state.editor.insert('\n');
440
586
  resetBlink(api);
587
+ followFileEdit(api);
441
588
  return null;
442
589
  }
443
590
  if (spec && spec.enter === 'edit-next') {
@@ -484,6 +631,7 @@ const handleKey = (api, key) => {
484
631
  const result = special(api);
485
632
  if (COMPOSE_EDITS.includes(key)) api.templates.clearTemplatePick();
486
633
  resetBlink(api);
634
+ followFileEdit(api);
487
635
  return result ?? null;
488
636
  }
489
637
  if (!key.length) return null;
@@ -492,10 +640,22 @@ const handleKey = (api, key) => {
492
640
  api.templates.clearTemplatePick();
493
641
  api.state.editor.insert(key);
494
642
  resetBlink(api);
643
+ followFileEdit(api);
495
644
  }
496
645
  return null;
497
646
  };
498
647
 
648
+ const applyDiskText = (api, text) => {
649
+ if (api.state.composeKind !== 'file' || !api.state.editor) return;
650
+ if (text === api.state.editor.text) return;
651
+ const cursor = api.state.editor.cursor;
652
+ api.state.editor.text = text;
653
+ api.state.editor.cursor = Math.min(cursor, text.length);
654
+ api.state.composeBaseline = text;
655
+ api.state.fileEditRows = unit.linesFor(api.ui, api.nav.reviewPath, null);
656
+ followFileEdit(api);
657
+ };
658
+
499
659
  const composerMethods = (api) => ({
500
660
  reset: () => reset(api.state),
501
661
  openCompose: (kind, text, todoId) => openCompose(api, kind, text, todoId),
@@ -511,6 +671,12 @@ const composerMethods = (api) => ({
511
671
  onFeedback: () => onFeedback(api),
512
672
  onCode: () => onCode(api),
513
673
  handleKey: (key) => handleKey(api, key),
674
+ liveFileText: () => {
675
+ if (api.state.composeKind !== 'file' || !api.state.editor) return null;
676
+ return api.state.editor.text;
677
+ },
678
+ fileEditRows: () => api.state.fileEditRows,
679
+ applyDiskText: (text) => applyDiskText(api, text),
514
680
  });
515
681
 
516
682
  const createComposer = ({ nav, review, ui, restartBlink }) => {
@@ -5,67 +5,92 @@ const { COMPOSE_LEAVE } = compose;
5
5
  const actions = require('./actions.js');
6
6
  const { CONFIRM_MODE, disabledActions, actionFromKey, applyConfirm } = actions;
7
7
 
8
- const createDispatcher = (options) => {
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
+ };
12
+
13
+ const currentTarget = (ui) => {
14
+ if (ui.pane === 'files') return ui.fileCursorEntry();
15
+ if (ui.pane === 'branches') {
16
+ return ui.branches[ui.branchCursor];
17
+ }
18
+ 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 };
23
+ }
24
+ return ui.current();
25
+ };
26
+
27
+ const createDispatcher = (ui) => {
9
28
  const handlers = {
10
- add: options.changeActions.onAdd,
11
- unstage: options.changeActions.onUnstage,
12
- revert: options.changeActions.onRevert,
13
- next: options.onNext,
14
- prev: options.onPrev,
15
- layout: options.onLayout,
16
- files: options.onFiles,
17
- feedback: options.composer.onFeedback,
18
- todo: options.composer.onTodo,
19
- code: options.composer.onCode,
20
- reload: options.onReload,
21
- commit: options.commits.onCommit,
22
- amend: options.commits.onAmend,
23
- fixup: options.commits.onFixup,
24
- branch: options.gitBranches.onBranch,
25
- pull: options.gitBranches.onPull,
26
- push: options.gitBranches.onPush,
27
- newBranch: options.gitBranches.onNewBranch,
28
- rebase: options.gitBranches.onRebaseBranch,
29
+ add: ui.changeActions.onAdd,
30
+ unstage: ui.changeActions.onUnstage,
31
+ revert: ui.changeActions.onRevert,
32
+ next: ui.filesPane.onNext,
33
+ prev: ui.filesPane.onPrev,
34
+ layout: ui.filesPane.onLayout,
35
+ theme: ui.filesPane.onTheme,
36
+ files: ui.filesPane.onFiles,
37
+ feedback: ui.composer.onFeedback,
38
+ todo: ui.composer.onTodo,
39
+ code: ui.composer.onCode,
40
+ reload: ui.lifecycle.onReload,
41
+ commit: ui.commits.onCommit,
42
+ amend: ui.commits.onAmend,
43
+ reword: ui.commits.onReword,
44
+ apply: ui.commits.onApply,
45
+ fixup: ui.commits.onFixup,
46
+ branch: ui.gitBranches.onBranch,
47
+ pull: ui.gitBranches.onPull,
48
+ push: ui.gitBranches.onPush,
49
+ newBranch: ui.gitBranches.onNewBranch,
50
+ rebase: ui.gitBranches.onRebaseBranch,
29
51
  drop: () => {
30
- if (options.getPane() === 'commits') {
31
- return void options.commits.onDropCommit();
52
+ if (ui.pane === 'commits') {
53
+ return void ui.commits.onDropCommit();
32
54
  }
33
- options.gitBranches.onDropBranch();
55
+ ui.gitBranches.onDropBranch();
34
56
  },
35
- open: options.onOpenFile,
36
- removeTodo: options.composer.removeFocusedTodo,
37
- quit: options.onQuit,
38
- scrollUp: () => options.onScroll(-1),
39
- scrollDown: () => options.onScroll(1),
40
- pageUp: () => options.onScroll(options.scrollStep(-1)),
41
- pageDown: () => options.onScroll(options.scrollStep(1)),
42
- halfUp: () => options.onScroll(options.scrollStep(-0.5)),
43
- halfDown: () => options.onScroll(options.scrollStep(0.5)),
44
- home: options.onHome,
45
- end: options.onEnd,
57
+ open: ui.filesPane.onOpenFile,
58
+ file: ui.filesPane.onFileScope,
59
+ diff: ui.filesPane.onDiffScope,
60
+ check: () => ui.composer.toggleFocusedTodo(),
61
+ removeTodo: ui.composer.removeFocusedTodo,
62
+ quit: () => ui.onQuit(),
63
+ scrollUp: () => ui.filesPane.onScroll(-1),
64
+ scrollDown: () => ui.filesPane.onScroll(1),
65
+ pageUp: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(-1)),
66
+ pageDown: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(1)),
67
+ halfUp: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(-0.5)),
68
+ halfDown: () => ui.filesPane.onScroll(ui.filesPane.scrollStep(0.5)),
69
+ home: ui.filesPane.onHome,
70
+ end: ui.filesPane.onEnd,
46
71
  };
47
72
 
48
73
  const dispatch = (action) => {
49
- if (CONFIRM_MODE.includes(options.getMode())) return;
50
- if (options.getMode() === 'compose') {
74
+ if (CONFIRM_MODE.includes(ui.mode)) return;
75
+ if (ui.mode === 'compose') {
51
76
  if (!COMPOSE_LEAVE.includes(action)) return;
52
- const result = options.composer.saveCompose();
77
+ const result = ui.composer.saveCompose();
53
78
  if (result && result.kind === 'error') return;
54
79
  }
55
- const pane = options.getPane();
80
+ const pane = ui.pane;
56
81
  if (pane === 'files') {
57
- if (action === 'next') return void options.onFileMove(1);
58
- if (action === 'prev') return void options.onFileMove(-1);
82
+ if (action === 'next') return void ui.filesPane.onFileMove(1);
83
+ if (action === 'prev') return void ui.filesPane.onFileMove(-1);
59
84
  }
60
85
  if (pane === 'branches') {
61
- if (action === 'next') return void options.onBranchMove(1);
62
- if (action === 'prev') return void options.onBranchMove(-1);
86
+ if (action === 'next') return void ui.gitBranches.onBranchMove(1);
87
+ if (action === 'prev') return void ui.gitBranches.onBranchMove(-1);
63
88
  }
64
89
  if (pane === 'commits') {
65
- if (action === 'next') return void options.onCommitMove(1);
66
- if (action === 'prev') return void options.onCommitMove(-1);
90
+ if (action === 'next') return void ui.commits.onCommitMove(1);
91
+ if (action === 'prev') return void ui.commits.onCommitMove(-1);
67
92
  }
68
- const hidden = disabledActions(pane, options.currentTarget());
93
+ const hidden = disabledActions(pane, currentTarget(ui));
69
94
  if (hidden.includes(action)) return;
70
95
  const handler = handlers[action];
71
96
  if (!handler) return;
@@ -75,38 +100,63 @@ const createDispatcher = (options) => {
75
100
  return { dispatch, handlers };
76
101
  };
77
102
 
78
- const createInput = (options) => {
103
+ const createInput = (ui) => {
79
104
  const confirmations = {
80
- confirmQuit: options.quit,
81
- confirmUpdate: options.update,
82
- confirmDrop: options.drop,
83
- confirmPush: options.push,
105
+ confirmQuit: {
106
+ ready: () => ui.finishQuit('ready'),
107
+ editing: () => ui.finishQuit('editing'),
108
+ cancel: () => ui.cancelQuit(),
109
+ },
110
+ confirmUpdate: {
111
+ accept: ui.updater.accept,
112
+ decline: ui.updater.decline,
113
+ },
114
+ confirmDrop: {
115
+ confirm: () => {
116
+ if (ui.commits.dropName) {
117
+ return void ui.commits.confirmDropCommit();
118
+ }
119
+ ui.gitBranches.confirmDropBranch();
120
+ },
121
+ cancel: () => {
122
+ if (ui.commits.dropName) {
123
+ return void ui.commits.cancelDropCommit();
124
+ }
125
+ ui.gitBranches.cancelDropBranch();
126
+ },
127
+ },
128
+ confirmPush: {
129
+ force: ui.gitBranches.onForcePush,
130
+ cancel: ui.gitBranches.cancelPush,
131
+ },
84
132
  };
85
133
 
86
134
  const confirmChoice = (key) => {
87
- const mode = options.getMode();
135
+ const mode = ui.mode;
88
136
  if (!CONFIRM_MODE.includes(mode)) return;
89
137
  applyConfirm(mode, key, confirmations[mode]);
90
138
  };
91
139
 
92
140
  const handleKey = (key) => {
93
- const mode = options.getMode();
141
+ const mode = ui.mode;
94
142
  if (CONFIRM_MODE.includes(mode)) return void confirmChoice(key);
95
143
  if (mode === 'compose') {
96
- if (key === 'ctrl-c') return void options.onQuit();
97
- const command = options.composer.handleKey(key);
98
- return void options.runComposeCommand(command);
144
+ if (key === 'ctrl-c') return void ui.onQuit();
145
+ const command = ui.composer.handleKey(key);
146
+ return void ui.runComposeCommand(command);
99
147
  }
100
- if (key === 'escape') return void options.onEscape();
101
- if (options.composer.typeIntoTodo(key)) return;
102
- const action = actionFromKey(key, options.getPane());
103
- if (action) options.dispatch(action);
148
+ if (key === 'escape') return void ui.filesPane.onEscape();
149
+ const toggle = key === 'x' || key === ' ';
150
+ if (toggle && ui.composer.toggleFocusedTodo()) return;
151
+ if (ui.composer.typeIntoTodo(key)) return;
152
+ const action = actionFromKey(key, ui.pane, ui.fileScope);
153
+ if (action) ui.dispatch(action);
104
154
  };
105
155
 
106
156
  const handleEvent = (event) => {
107
157
  if (event.type === 'key') return void handleKey(event.key);
108
158
  if (event.type !== 'mouse') return;
109
- options.pointer.handle(event);
159
+ ui.pointer.handle(event);
110
160
  };
111
161
 
112
162
  return { handleEvent, confirmChoice };