reslop 0.1.9 → 0.2.0

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 (94) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +2 -1
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +4 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +7 -14
  7. package/lib/cli.js +28 -47
  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} +6 -5
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +589 -68
  23. package/lib/github.js +28 -197
  24. package/lib/gitlab.js +24 -195
  25. package/lib/highlight/bash.js +1 -3
  26. package/lib/highlight/css.js +1 -3
  27. package/lib/highlight/js.js +11 -14
  28. package/lib/highlight.js +6 -9
  29. package/lib/keys.js +14 -8
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +14 -68
  33. package/lib/render/chrome.js +24 -9
  34. package/lib/render/commits.js +6 -59
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +6 -34
  37. package/lib/render/frame.js +23 -12
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +45 -0
  40. package/lib/{render.js → render/render.js} +5 -5
  41. package/lib/render/unit.js +41 -0
  42. package/lib/review.js +750 -14
  43. package/lib/select.js +5 -10
  44. package/lib/session/accessors.js +9 -1
  45. package/lib/session/actions.js +32 -16
  46. package/lib/session/branches.js +116 -128
  47. package/lib/session/change-actions.js +202 -123
  48. package/lib/session/commit.js +105 -112
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +104 -60
  51. package/lib/session/files-pane.js +142 -148
  52. package/lib/session/items.js +22 -30
  53. package/lib/session/lifecycle.js +44 -44
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +12 -1
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +54 -57
  58. package/lib/session/review.js +23 -37
  59. package/lib/session/terminal.js +6 -12
  60. package/lib/session/todos.js +8 -12
  61. package/lib/session/unit.js +208 -0
  62. package/lib/session/update.js +33 -46
  63. package/lib/session/view.js +78 -9
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +21 -246
  66. package/lib/source.js +3 -3
  67. package/lib/update.js +40 -75
  68. package/lib/{git/proc.js → utilities.js} +75 -11
  69. package/lib/wrap.js +1 -3
  70. package/package.json +1 -1
  71. package/lib/deps/items.js +0 -134
  72. package/lib/deps/manifest.js +0 -152
  73. package/lib/deps/merge.js +0 -120
  74. package/lib/deps/proposals.js +0 -164
  75. package/lib/deps/reports.js +0 -180
  76. package/lib/deps/rows.js +0 -176
  77. package/lib/deps/scan.js +0 -138
  78. package/lib/deps/usage.js +0 -68
  79. package/lib/deps/versions.js +0 -119
  80. package/lib/diff.js +0 -33
  81. package/lib/git/changes.js +0 -164
  82. package/lib/git/read.js +0 -253
  83. package/lib/git/run.js +0 -100
  84. package/lib/npm/commands.js +0 -100
  85. package/lib/remote/changes.js +0 -37
  86. package/lib/remote/headers.js +0 -36
  87. package/lib/remote/http.js +0 -122
  88. package/lib/remote/notes.js +0 -94
  89. package/lib/review/format.js +0 -434
  90. package/lib/review/storage.js +0 -150
  91. package/lib/review/store.js +0 -178
  92. package/lib/review/templates.js +0 -49
  93. package/lib/session/origins.js +0 -7
  94. package/lib/sys.js +0 -84
@@ -4,10 +4,10 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
6
  const { isAbortError } = require('./load.js');
7
- const { createDiskWatcher, POLL_MS } = require('./watch.js');
7
+ const { createDiskWatcher, POLL_MS, DEBOUNCE_MS } = require('./watch.js');
8
8
  const files = require('../files.js');
9
9
  const { itemPath, relativeAge } = files;
10
- const { MANIFEST, LOCKFILE } = require('../deps/manifest.js');
10
+ const { MANIFEST, LOCKFILE } = require('../deps.js');
11
11
  const review = require('../review.js');
12
12
  const { hasNotes } = review;
13
13
 
@@ -30,8 +30,6 @@ const shouldDeferExtras = (api) => {
30
30
  return audit && (waitingOutdated || waitingAudit);
31
31
  };
32
32
 
33
- const watchOpts = (api) => api.watch ?? {};
34
-
35
33
  const endReadLoop = (api) => {
36
34
  const finish = api.ui.finishReadLoop;
37
35
  if (finish) finish();
@@ -50,8 +48,6 @@ const kindFromExtra = (extra = {}) => {
50
48
  return 'reload';
51
49
  };
52
50
 
53
- const shouldResetNav = (kind) => kind !== 'open' && kind !== 'watch';
54
-
55
51
  const finishIfEmpty = (api, kind) => {
56
52
  const { collection, review: notes, ui, nav } = api;
57
53
  if (collection.items.length) return;
@@ -125,9 +121,25 @@ const stampItemDates = (items, dates) => {
125
121
  }
126
122
  };
127
123
 
124
+ const syncUnitEditor = (api) => {
125
+ const composer = api.ui.composer;
126
+ if (!composer || typeof composer.applyDiskText !== 'function') return;
127
+ if (api.nav.pane !== 'unit') return;
128
+ const rel = api.nav.reviewPath;
129
+ if (!rel) return;
130
+ const repo = api.ui.repo;
131
+ if (!repo || typeof repo.fileText !== 'function') return;
132
+ const text = repo.fileText(api.ui.top || api.loadConfig.cwd, rel, api.ui.rev);
133
+ composer.applyDiskText(text);
134
+ };
135
+
128
136
  const syncItemWatches = (api) => {
129
137
  if (!api.diskWatcher) return;
130
- api.diskWatcher.watchFiles(itemRels(api.collection));
138
+ const rels = itemRels(api.collection);
139
+ if (api.nav.pane === 'unit' && api.nav.reviewPath) {
140
+ if (!rels.includes(api.nav.reviewPath)) rels.push(api.nav.reviewPath);
141
+ }
142
+ api.diskWatcher.watchFiles(rels);
131
143
  };
132
144
 
133
145
  const applyLoaded = (api, loaded) => {
@@ -160,6 +172,7 @@ const applyLoaded = (api, loaded) => {
160
172
  markLoaded(api);
161
173
  loader.pendingExtras = loaded.pending === true;
162
174
  syncItemWatches(api);
175
+ syncUnitEditor(api);
163
176
  return true;
164
177
  };
165
178
 
@@ -173,10 +186,10 @@ const load = (api) => {
173
186
  applyLoaded(api, loaded);
174
187
  };
175
188
 
176
- const fileStamp = (top, rel, statSync) => {
189
+ const fileStamp = (top, rel) => {
177
190
  const full = path.join(top, rel);
178
191
  try {
179
- const stat = statSync(full);
192
+ const stat = fs.statSync(full);
180
193
  return `${stat.mtimeMs}:${stat.size}`;
181
194
  } catch {
182
195
  return 'missing';
@@ -184,11 +197,9 @@ const fileStamp = (top, rel, statSync) => {
184
197
  };
185
198
 
186
199
  const npmStamp = (api) => {
187
- const extra = watchOpts(api);
188
- const statSync = extra.statSync ?? fs.statSync;
189
200
  const top = api.ui.top || api.loadConfig.cwd;
190
- const pkg = fileStamp(top, MANIFEST, statSync);
191
- const lock = fileStamp(top, LOCKFILE, statSync);
201
+ const pkg = fileStamp(top, MANIFEST);
202
+ const lock = fileStamp(top, LOCKFILE);
192
203
  return `${pkg}|${lock}`;
193
204
  };
194
205
 
@@ -216,8 +227,7 @@ const followExtras = async (api, loaded, gen, kind) => {
216
227
  next = await api.loader.fetchExtras(api.loadConfig.repo, loaded, extra);
217
228
  } catch (error) {
218
229
  if (!api.loader.isCurrent(gen)) return;
219
- failOpen(api, error);
220
- return;
230
+ return void failOpen(api, error);
221
231
  }
222
232
  if (!api.loader.isCurrent(gen)) return;
223
233
  if (api.ui.done) {
@@ -267,9 +277,8 @@ const resetNavAfterLoad = (api, extra = {}) => {
267
277
  };
268
278
 
269
279
  const afterSnapshot = (api, kind, extra = {}) => {
270
- if (shouldResetNav(kind)) {
271
- resetNavAfterLoad(api, extra);
272
- return;
280
+ if (kind !== 'open' && kind !== 'watch') {
281
+ return void resetNavAfterLoad(api, extra);
273
282
  }
274
283
  if (extra.afterLoad) extra.afterLoad();
275
284
  };
@@ -296,8 +305,7 @@ const runSnapshot = async (api, gen, kind, extra = {}) => {
296
305
  );
297
306
  } catch (error) {
298
307
  if (!api.loader.isCurrent(gen)) return;
299
- failOpen(api, error);
300
- return;
308
+ return void failOpen(api, error);
301
309
  }
302
310
  if (!api.loader.isCurrent(gen)) return;
303
311
  if (api.ui.done) {
@@ -354,8 +362,7 @@ const refreshFromRepo = (api, extra = {}) => {
354
362
  afterSnapshot(api, kind, extra);
355
363
  if (loaded.pending) {
356
364
  api.ops.setBusy('checking npm');
357
- followExtras(api, loaded, gen, kind);
358
- return;
365
+ return void followExtras(api, loaded, gen, kind);
359
366
  }
360
367
  if (kind === 'keep') return;
361
368
  finishIfEmpty(api, kind);
@@ -377,8 +384,7 @@ const reloadAfterChange = (api, afterLoad) => {
377
384
  if (afterLoad) afterLoad();
378
385
  };
379
386
  if (api.ui.uiOpen && api.loadConfig.repo.loadAsync) {
380
- refreshFromRepo(api, { afterLoad: restore });
381
- return;
387
+ return void refreshFromRepo(api, { afterLoad: restore });
382
388
  }
383
389
  refreshFromRepo(api);
384
390
  restore();
@@ -392,9 +398,7 @@ const onReload = (api) => {
392
398
 
393
399
  const clearWatchRetry = (api) => {
394
400
  if (api.watchRetry === null) return;
395
- const extra = watchOpts(api);
396
- const clearTimeoutFn = extra.clearTimeout ?? clearTimeout;
397
- clearTimeoutFn(api.watchRetry);
401
+ clearTimeout(api.watchRetry);
398
402
  api.watchRetry = null;
399
403
  };
400
404
 
@@ -404,6 +408,10 @@ const shouldDeferWatch = (api) => {
404
408
  if (!ui.uiOpen) return true;
405
409
  if (!api.loader.didLoad) return true;
406
410
  if (ops.gitBusy) return true;
411
+ if (ui.mode === 'compose') {
412
+ const kind = ui.composer && ui.composer.state.composeKind;
413
+ if (kind === 'file') return false;
414
+ }
407
415
  if (ui.mode !== 'review') return true;
408
416
  return false;
409
417
  };
@@ -411,38 +419,32 @@ const shouldDeferWatch = (api) => {
411
419
  const WATCH_IGNORE_MS = 500;
412
420
 
413
421
  const ignoreWatch = (api, ms = WATCH_IGNORE_MS) => {
414
- const extra = watchOpts(api);
415
- const now = extra.now ?? Date.now;
416
- api.ignoreWatchUntil = now() + ms;
422
+ api.ignoreWatchUntil = Date.now() + ms;
417
423
  };
418
424
 
419
425
  const onDiskChange = (api) => {
420
- const extra = watchOpts(api);
421
- const now = extra.now ?? Date.now;
422
- if (now() < (api.ignoreWatchUntil ?? 0)) return;
426
+ if (Date.now() < (api.ignoreWatchUntil ?? 0)) return;
423
427
  if (shouldDeferWatch(api)) {
424
428
  if (api.watchRetry !== null) return;
425
- const setTimeoutFn = extra.setTimeout ?? setTimeout;
426
- const delay = extra.debounceMs ?? 200;
427
- api.watchRetry = setTimeoutFn(() => {
429
+ api.watchRetry = setTimeout(() => {
428
430
  api.watchRetry = null;
429
431
  onDiskChange(api);
430
- }, delay);
432
+ }, DEBOUNCE_MS);
433
+ if (typeof api.watchRetry.unref === 'function') api.watchRetry.unref();
431
434
  return;
432
435
  }
433
436
  refreshFromRepo(api, { keepView: true, quiet: true });
434
437
  };
435
438
 
436
439
  const currentFileStamp = (api) => {
437
- if (api.nav.pane !== 'diff') return '';
438
- const rel = itemPath(api.ui.current());
440
+ const pane = api.nav.pane;
441
+ if (pane !== 'diff' && pane !== 'unit') return '';
442
+ const rel = pane === 'unit' ? api.nav.reviewPath : itemPath(api.ui.current());
439
443
  if (!rel) return '';
440
- const extra = watchOpts(api);
441
- const statSync = extra.statSync ?? fs.statSync;
442
444
  const full = path.join(api.ui.top || api.loadConfig.cwd, rel);
443
445
  let stat;
444
446
  try {
445
- stat = statSync(full);
447
+ stat = fs.statSync(full);
446
448
  } catch {
447
449
  return `${rel}:missing`;
448
450
  }
@@ -478,12 +480,10 @@ const startWatch = (api) => {
478
480
  if (!isWorktree(api.ui)) return;
479
481
  const root = api.ui.top || api.loadConfig.cwd;
480
482
  if (!root) return;
481
- const extra = watchOpts(api);
482
483
  api.diskWatcher = createDiskWatcher({
483
484
  root,
484
485
  paths: api.loadConfig.paths,
485
486
  onChange: () => onDiskChange(api),
486
- ...extra,
487
487
  });
488
488
  syncItemWatches(api);
489
489
  if (!api.ui.term) return;
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { stateView } = require('./accessors.js');
4
+
3
5
  const isAbortError = (error) => !!(error && error.code === 'ABORT');
4
6
 
5
7
  const createLoadCoordinator = () => {
@@ -46,40 +48,24 @@ const createLoadCoordinator = () => {
46
48
  return repo.loadExtras(loaded, extra);
47
49
  };
48
50
 
49
- return {
50
- get gen() {
51
- return state.gen;
52
- },
53
- get promise() {
54
- return state.promise;
55
- },
56
- set promise(value) {
57
- state.promise = value;
58
- },
59
- get pendingExtras() {
60
- return state.pendingExtras;
61
- },
62
- set pendingExtras(value) {
63
- state.pendingExtras = value;
51
+ return stateView(
52
+ state,
53
+ {
54
+ get signal() {
55
+ return state.abort?.signal;
56
+ },
57
+ bump,
58
+ isCurrent,
59
+ accept,
60
+ abort,
61
+ reset,
62
+ tag,
63
+ fetchSnapshot,
64
+ fetchExtras,
64
65
  },
65
- get didLoad() {
66
- return state.didLoad;
67
- },
68
- set didLoad(value) {
69
- state.didLoad = value;
70
- },
71
- get signal() {
72
- return state.abort?.signal;
73
- },
74
- bump,
75
- isCurrent,
76
- accept,
77
- abort,
78
- reset,
79
- tag,
80
- fetchSnapshot,
81
- fetchExtras,
82
- };
66
+ ['promise', 'pendingExtras', 'didLoad'],
67
+ ['gen'],
68
+ );
83
69
  };
84
70
 
85
71
  module.exports = { isAbortError, createLoadCoordinator };
@@ -12,6 +12,8 @@ const initialState = (options = {}) => ({
12
12
  scroll: 0,
13
13
  listScroll: 0,
14
14
  reviewPath: null,
15
+ fileScope: 'diff',
16
+ unitLine: 0,
15
17
  selection: null,
16
18
  mouseAnchor: null,
17
19
  pendingClick: null,
@@ -30,11 +32,20 @@ const clampIndex = (nav, length) => {
30
32
 
31
33
  const current = (nav, items, todoItem) => {
32
34
  if (nav.todoOpen) return todoItem;
35
+ if (nav.pane === 'unit' && nav.reviewPath) {
36
+ const item = items[nav.index];
37
+ if (item && listPath(item) === nav.reviewPath) return item;
38
+ for (const entry of items) {
39
+ if (listPath(entry) === nav.reviewPath) return entry;
40
+ }
41
+ return null;
42
+ }
33
43
  return items[nav.index] ?? null;
34
44
  };
35
45
 
36
46
  const syncReviewPath = (nav, item) => {
37
- if (nav.pane !== 'diff') return;
47
+ if (nav.pane !== 'diff' && nav.pane !== 'unit') return;
48
+ if (nav.pane === 'unit' && nav.reviewPath) return;
38
49
  nav.reviewPath = item ? listPath(item) : null;
39
50
  };
40
51
 
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { stateView } = require('./accessors.js');
4
+
3
5
  const ASYNC_WITHOUT_UI = ['pull', 'push'];
4
6
 
5
7
  const pickRepoWrite = (repo, name, uiOpen) => {
@@ -18,7 +20,7 @@ const pickRepoWrite = (repo, name, uiOpen) => {
18
20
  };
19
21
  };
20
22
 
21
- const createOpsRunner = (options) => {
23
+ const createOpsRunner = (ui) => {
22
24
  const state = {
23
25
  gitBusy: false,
24
26
  busy: '',
@@ -31,53 +33,46 @@ const createOpsRunner = (options) => {
31
33
 
32
34
  const setBusy = (kind) => {
33
35
  state.busy = kind;
34
- options.startProgress();
36
+ ui.startProgress();
35
37
  };
36
38
 
37
39
  const clearBusy = () => {
38
40
  state.busy = '';
39
- options.stopProgress();
41
+ ui.stopProgress();
40
42
  };
41
43
 
42
44
  const runRepo = (label, write) => {
43
45
  try {
44
- write(options.top());
45
- options.setStatus(label);
46
+ write(ui.top);
47
+ ui.status = label;
46
48
  return true;
47
49
  } catch (error) {
48
- options.setStatus(error.message);
50
+ ui.status = error.message;
49
51
  return false;
50
52
  }
51
53
  };
52
54
 
53
55
  const finishBusy = (done, after) => {
54
- if (options.isDone()) {
55
- options.paint();
56
- return;
57
- }
58
- if (options.getMode() === 'confirmPush') {
59
- options.paint();
60
- return;
61
- }
62
- if (!options.getStatus() && after) after();
63
- if (!options.getStatus() && !state.busy) options.setStatus(done);
64
- options.paint();
56
+ if (ui.done || ui.mode === 'confirmPush') return void ui.paint();
57
+ if (!ui.status && after) after();
58
+ if (!ui.status && !state.busy) ui.status = done;
59
+ ui.paint();
65
60
  };
66
61
 
67
62
  const runBusy = async (kind, done, write, after) => {
68
63
  if (state.gitBusy) return;
69
64
  state.gitBusy = true;
70
- options.setStatus('');
71
- options.resetProgressFrame();
65
+ ui.status = '';
66
+ ui.progressFrame = 0;
72
67
  setBusy(kind);
73
- options.paint();
68
+ ui.paint();
74
69
  try {
75
- await write(options.top());
70
+ await write(ui.top);
76
71
  } catch (error) {
77
72
  if (error.rejected && kind !== 'force pushing') {
78
- options.setMode('confirmPush');
73
+ ui.mode = 'confirmPush';
79
74
  } else {
80
- options.setStatus(error.message);
75
+ ui.status = error.message;
81
76
  }
82
77
  } finally {
83
78
  clearBusy();
@@ -86,25 +81,17 @@ const createOpsRunner = (options) => {
86
81
  finishBusy(done, after);
87
82
  };
88
83
 
89
- return {
90
- get gitBusy() {
91
- return state.gitBusy;
84
+ return stateView(
85
+ state,
86
+ {
87
+ reset,
88
+ setBusy,
89
+ clearBusy,
90
+ runRepo,
91
+ runBusy,
92
92
  },
93
- set gitBusy(value) {
94
- state.gitBusy = value;
95
- },
96
- get busy() {
97
- return state.busy;
98
- },
99
- set busy(value) {
100
- state.busy = value;
101
- },
102
- reset,
103
- setBusy,
104
- clearBusy,
105
- runRepo,
106
- runBusy,
107
- };
93
+ ['gitBusy', 'busy'],
94
+ );
108
95
  };
109
96
 
110
97
  module.exports = { ASYNC_WITHOUT_UI, pickRepoWrite, createOpsRunner };
@@ -2,50 +2,51 @@
2
2
 
3
3
  const { extractText, isRange } = require('../select.js');
4
4
  const { hitAction } = require('../keys.js');
5
+ const { copyText } = require('../clipboard.js');
5
6
 
6
- const mouseCell = (options, event) => {
7
- const size = options.getSize();
7
+ const mouseCell = (ui, event) => {
8
+ const size = ui.lastSize ?? ui.getSize();
8
9
  const x = Math.min(Math.max(1, event.x), size.width);
9
10
  const y = Math.min(Math.max(1, event.y), size.height);
10
11
  return { x, y };
11
12
  };
12
13
 
13
- const onPress = (options, event) => {
14
- const nav = options.nav;
15
- const cell = mouseCell(options, event);
14
+ const onPress = (ui, event) => {
15
+ const nav = ui.nav;
16
+ const cell = mouseCell(ui, event);
16
17
  nav.mouseAnchor = cell;
17
18
  nav.pendingClick = null;
18
19
  nav.selection = null;
19
- const frame = options.lastFrame();
20
+ const frame = ui.lastFrame;
20
21
  if (frame && cell.y === frame.height) {
21
22
  nav.pendingClick = hitAction(frame.buttons, cell.x - 1);
22
23
  }
23
24
  };
24
25
 
25
- const onDrag = (options, event) => {
26
- const nav = options.nav;
26
+ const onDrag = (ui, event) => {
27
+ const nav = ui.nav;
27
28
  if (!nav.mouseAnchor) return;
28
29
  nav.pendingClick = null;
29
30
  nav.selection = {
30
31
  start: nav.mouseAnchor,
31
- end: mouseCell(options, event),
32
+ end: mouseCell(ui, event),
32
33
  };
33
34
  };
34
35
 
35
- const clickButton = (options, cell, clickId) => {
36
- const nav = options.nav;
37
- const frame = options.lastFrame();
36
+ const clickButton = (ui, cell, clickId) => {
37
+ const nav = ui.nav;
38
+ const frame = ui.lastFrame;
38
39
  if (!clickId || !frame || isRange(nav.selection)) return false;
39
40
  const id = hitAction(frame.buttons, cell.x - 1);
40
41
  if (id === clickId && cell.y === frame.height) {
41
- options.dispatch(id);
42
+ ui.dispatch(id);
42
43
  }
43
44
  nav.selection = null;
44
45
  return true;
45
46
  };
46
47
 
47
- const clickHit = (options, hits, cell, onHit) => {
48
- const nav = options.nav;
48
+ const clickHit = (ui, hits, cell, onHit) => {
49
+ const nav = ui.nav;
49
50
  if (!hits || isRange(nav.selection)) return false;
50
51
  const x = cell.x - 1;
51
52
  const hit = hits.find((row) => {
@@ -54,108 +55,104 @@ const clickHit = (options, hits, cell, onHit) => {
54
55
  return x >= row.x0 && x < row.x1;
55
56
  });
56
57
  if (!hit) return false;
57
- onHit(options, hit);
58
+ onHit(ui, hit);
58
59
  nav.selection = null;
59
60
  return true;
60
61
  };
61
62
 
62
- const copySelection = (options) => {
63
- const nav = options.nav;
64
- const frame = options.lastFrame();
63
+ const copySelection = (ui) => {
64
+ const nav = ui.nav;
65
+ const frame = ui.lastFrame;
65
66
  if (!isRange(nav.selection) || !frame) return;
66
67
  const text = extractText(frame.rows, nav.selection);
67
68
  if (!text) return;
68
- const ok = options.copyText(text, options.stdout());
69
- options.setStatus(ok ? 'copied' : 'copy failed');
69
+ const ok = copyText(text, ui.stdout);
70
+ ui.status = ok ? 'copied' : 'copy failed';
70
71
  };
71
72
 
72
73
  const RELEASE_TARGETS = [
73
74
  {
74
75
  match: () => true,
75
76
  hits: 'statusHits',
76
- onHit: (options, hit) => {
77
- if (hit.id === 'branch') {
78
- options.onBranch();
79
- return;
80
- }
81
- options.confirmChoice(hit.id);
77
+ onHit: (ui, hit) => {
78
+ if (hit.id === 'branch') return void ui.gitBranches.onBranch();
79
+ ui.input.confirmChoice(hit.id);
82
80
  },
83
81
  },
84
82
  {
85
- match: (options) => options.getPane() === 'files',
83
+ match: (ui) => ui.pane === 'files',
86
84
  hits: 'fileHits',
87
- onHit: (options, hit) => {
88
- options.nav.fileCursor = hit.cursor;
89
- options.onOpenFile();
85
+ onHit: (ui, hit) => {
86
+ ui.nav.fileCursor = hit.cursor;
87
+ ui.filesPane.onOpenFile();
90
88
  },
91
89
  },
92
90
  {
93
- match: (options) => options.getPane() === 'branches',
91
+ match: (ui) => ui.pane === 'branches',
94
92
  hits: 'fileHits',
95
- onHit: (options, hit) => {
96
- options.nav.branchCursor = hit.cursor;
97
- options.onCheckoutBranch();
93
+ onHit: (ui, hit) => {
94
+ ui.nav.branchCursor = hit.cursor;
95
+ ui.gitBranches.onCheckoutBranch();
98
96
  },
99
97
  },
100
98
  {
101
- match: (options) => options.getPane() === 'commits',
99
+ match: (ui) => ui.pane === 'commits',
102
100
  hits: 'fileHits',
103
- onHit: (options, hit) => {
104
- options.nav.commitCursor = hit.cursor;
101
+ onHit: (ui, hit) => {
102
+ ui.nav.commitCursor = hit.cursor;
105
103
  },
106
104
  },
107
105
  {
108
- match: (options) => options.getPane() === 'diff',
106
+ match: (ui) => ui.pane === 'diff',
109
107
  hits: 'todoHits',
110
- onHit: (options, hit) => options.onTodoHit(hit.cursor),
108
+ onHit: (ui, hit) => ui.composer.onTodoHit(hit.cursor),
111
109
  },
112
110
  {
113
- match: (options) => options.getMode() === 'compose',
111
+ match: (ui) => ui.mode === 'compose',
114
112
  hits: 'templateHits',
115
- onHit: (options, hit) => options.selectTemplate(hit.cursor),
113
+ onHit: (ui, hit) => ui.selectTemplate(hit.cursor),
116
114
  },
117
115
  ];
118
116
 
119
- const onRelease = (options, event) => {
120
- const nav = options.nav;
121
- const cell = mouseCell(options, event);
117
+ const onRelease = (ui, event) => {
118
+ const nav = ui.nav;
119
+ const cell = mouseCell(ui, event);
122
120
  if (nav.mouseAnchor) {
123
121
  nav.selection = { start: nav.mouseAnchor, end: cell };
124
122
  }
125
123
  const clickId = nav.pendingClick;
126
124
  nav.pendingClick = null;
127
125
  nav.mouseAnchor = null;
128
- if (clickButton(options, cell, clickId)) return;
129
- const frame = options.lastFrame();
126
+ if (clickButton(ui, cell, clickId)) return;
127
+ const frame = ui.lastFrame;
130
128
  for (const target of RELEASE_TARGETS) {
131
- if (!target.match(options)) continue;
129
+ if (!target.match(ui)) continue;
132
130
  const hits = frame?.[target.hits];
133
- const opened = clickHit(options, hits, cell, target.onHit);
131
+ const opened = clickHit(ui, hits, cell, target.onHit);
134
132
  if (opened) return;
135
133
  }
136
- copySelection(options);
134
+ copySelection(ui);
137
135
  };
138
136
 
139
137
  const POINTER_HANDLERS = {
140
- wheelUp: (options) => options.onScroll(-1),
141
- wheelDown: (options) => options.onScroll(1),
138
+ wheelUp: (ui) => ui.filesPane.onScroll(-1),
139
+ wheelDown: (ui) => ui.filesPane.onScroll(1),
142
140
  press: onPress,
143
141
  drag: onDrag,
144
142
  release: onRelease,
145
143
  };
146
144
 
147
- const handle = (options, event) => {
145
+ const handle = (ui, event) => {
148
146
  const kind = event.kind;
149
147
  const handler = POINTER_HANDLERS[kind];
150
148
  if (!handler) return;
151
149
  const isWheel = kind === 'wheelUp' || kind === 'wheelDown';
152
150
  if (!isWheel && event.btn !== 0 && event.btn !== undefined) return;
153
- handler(options, event);
151
+ handler(ui, event);
154
152
  };
155
153
 
156
- const createPointer = (options) => ({
157
- handle: (event) => handle(options, event),
158
- mouseCell: (event) => mouseCell(options, event),
154
+ const createPointer = (ui) => ({
155
+ handle: (event) => handle(ui, event),
159
156
  });
160
157
 
161
158
  module.exports = { createPointer };