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
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const files = require('../files.js');
4
- const { listPath, isTodoItem, isTodosEntry } = files;
4
+ const { listPath, isTodoItem, isTodosEntry, isReadOnlyOrigin } = files;
5
5
  const items = require('./items.js');
6
6
  const { needsReload, npmBusyKind, sameBlock } = items;
7
- const { isReadOnlyOrigin } = require('./origins.js');
7
+ const { hunkKey } = require('../diff/diff.js');
8
8
 
9
9
  const nextUnstageOrigin = (item) => {
10
10
  if (item.file.isNew === true) return 'untracked';
@@ -25,27 +25,27 @@ const COMMANDS = {
25
25
  },
26
26
  };
27
27
 
28
- const keepOrigin = (options, item, origin) => {
29
- options.collection.keepOrigin(item, origin);
30
- const idx = options.collection.findRestoredIndex({ ...item, origin });
31
- if (idx >= 0) options.nav.index = idx;
32
- if (options.ignoreWatch) options.ignoreWatch();
33
- options.nav.clearSelection();
34
- options.syncReviewPath();
35
- options.syncFileCursor();
28
+ const keepOrigin = (ui, item, origin) => {
29
+ ui.collection.keepOrigin(item, origin);
30
+ const idx = ui.collection.findRestoredIndex({ ...item, origin });
31
+ if (idx >= 0) ui.nav.index = idx;
32
+ if (ui.ignoreWatch) ui.ignoreWatch();
33
+ ui.nav.clearSelection();
34
+ ui.syncReviewPath();
35
+ ui.syncFileCursor();
36
36
  };
37
37
 
38
- const fileCursorEntry = (options) => {
39
- const list = options.fileList();
40
- return list[options.nav.fileCursor] ?? null;
38
+ const fileCursorEntry = (ui) => {
39
+ const list = ui.fileList();
40
+ return list[ui.nav.fileCursor] ?? null;
41
41
  };
42
42
 
43
- const fileActionItems = (options) => {
44
- const entry = fileCursorEntry(options);
43
+ const fileActionItems = (ui) => {
44
+ const entry = fileCursorEntry(ui);
45
45
  if (!entry || isTodosEntry(entry)) return [];
46
46
  const rel = entry.path;
47
47
  const selected = [];
48
- for (const item of options.getItems()) {
48
+ for (const item of ui.items) {
49
49
  if (isTodoItem(item)) continue;
50
50
  if (listPath(item) !== rel) continue;
51
51
  selected.push(item);
@@ -53,29 +53,29 @@ const fileActionItems = (options) => {
53
53
  return selected;
54
54
  };
55
55
 
56
- const activeDiffItem = (options) => {
57
- const item = options.currentItem();
56
+ const activeDiffItem = (ui) => {
57
+ const item = ui.current();
58
58
  if (!item) return null;
59
59
  if (isTodoItem(item)) {
60
- options.setStatus('not a diff block');
60
+ ui.status = 'not a diff block';
61
61
  return null;
62
62
  }
63
63
  return item;
64
64
  };
65
65
 
66
- const guardWritable = (options, item) => {
66
+ const guardWritable = (ui, item) => {
67
67
  if (!item) return false;
68
- const caps = options.capabilities();
68
+ const caps = ui.capabilities;
69
69
  if (!caps.changes || isReadOnlyOrigin(item.origin)) {
70
- options.setStatus('read only');
70
+ ui.status = 'read only';
71
71
  return false;
72
72
  }
73
73
  return true;
74
74
  };
75
75
 
76
- const selectNextFileAfter = (options, rel) => {
77
- const list = options.fileList();
78
- const nav = options.nav;
76
+ const selectNextFileAfter = (ui, rel) => {
77
+ const list = ui.fileList();
78
+ const nav = ui.nav;
79
79
  if (!list.length) {
80
80
  nav.fileCursor = 0;
81
81
  nav.reviewPath = null;
@@ -90,96 +90,96 @@ const selectNextFileAfter = (options, rel) => {
90
90
  nav.index = entry.firstIndex;
91
91
  };
92
92
 
93
- const liveItem = (options, item) => {
94
- for (const entry of options.getItems()) {
93
+ const liveItem = (ui, item) => {
94
+ for (const entry of ui.items) {
95
95
  if (sameBlock(entry, item)) return entry;
96
96
  }
97
97
  return item;
98
98
  };
99
99
 
100
- const applyGit = (options, item, label, write, after) => {
100
+ const applyGit = (ui, item, label, write, after) => {
101
101
  try {
102
- write(options.top(), item);
103
- options.setStatus(label);
102
+ write(ui.top, item);
103
+ ui.status = label;
104
104
  after();
105
105
  } catch (error) {
106
- options.setStatus(error.message);
106
+ ui.status = error.message;
107
107
  }
108
108
  };
109
109
 
110
- const applyFileGit = (options, targets, label, write, afterItem) => {
111
- const completed = [];
110
+ const applyFileGit = (ui, targets, label, write, afterItem) => {
111
+ const { length } = targets;
112
+ const completed = new Array(length);
113
+ let n = 0;
112
114
  try {
113
- for (const item of targets) {
114
- const current = liveItem(options, item);
115
- write(options.top(), current);
115
+ for (let i = 0; i < length; i++) {
116
+ const current = liveItem(ui, targets[i]);
117
+ write(ui.top, current);
116
118
  afterItem(current);
117
- completed.push(current);
119
+ completed[n++] = current;
118
120
  }
119
- options.setStatus(label);
121
+ completed.length = n;
122
+ ui.status = label;
120
123
  return { ok: true, completed };
121
124
  } catch (error) {
122
- options.setStatus(error.message);
125
+ completed.length = n;
126
+ ui.status = error.message;
123
127
  return { ok: false, completed };
124
128
  }
125
129
  };
126
130
 
127
- const reconcilePartial = (options, result) => {
131
+ const reconcilePartial = (ui, result) => {
128
132
  if (result.ok) return false;
129
- if (result.completed.length) options.reloadAfterChange();
133
+ if (result.completed.length) ui.reloadAfterChange();
130
134
  return true;
131
135
  };
132
136
 
133
- const applyBlockAdd = (options, item) => {
134
- if (needsReload(item)) {
135
- options.reloadAfterChange();
136
- return;
137
- }
138
- keepOrigin(options, item, 'staged');
137
+ const applyBlockAdd = (ui, item) => {
138
+ if (needsReload(item)) return void ui.reloadAfterChange();
139
+ keepOrigin(ui, item, 'staged');
139
140
  };
140
141
 
141
- const onBlockAdd = (options, item) => {
142
+ const onBlockAdd = (ui, item) => {
142
143
  const busy = npmBusyKind(item);
143
- const repo = options.repo();
144
- if (options.uiOpen() && busy && repo.addAsync) {
145
- const after = () => applyBlockAdd(options, item);
144
+ const repo = ui.repo;
145
+ if (ui.uiOpen && busy && repo.addAsync) {
146
+ const after = () => applyBlockAdd(ui, item);
146
147
  const write = (top) => repo.addAsync(top, item);
147
- options.runBusy(busy, 'staged', write, after);
148
- return;
148
+ return void ui.ops.runBusy(busy, 'staged', write, after);
149
149
  }
150
150
  applyGit(
151
- options,
151
+ ui,
152
152
  item,
153
153
  'staged',
154
154
  (top, current) => repo.add(top, current),
155
155
  () => {
156
- applyBlockAdd(options, item);
156
+ applyBlockAdd(ui, item);
157
157
  },
158
158
  );
159
159
  };
160
160
 
161
- const onBlockUnstage = (options, item) => {
161
+ const onBlockUnstage = (ui, item) => {
162
162
  const origin = nextUnstageOrigin(item);
163
- const repo = options.repo();
163
+ const repo = ui.repo;
164
164
  applyGit(
165
- options,
165
+ ui,
166
166
  item,
167
167
  'unstaged',
168
168
  (top, current) => repo.unstage(top, current),
169
- () => keepOrigin(options, item, origin),
169
+ () => keepOrigin(ui, item, origin),
170
170
  );
171
171
  };
172
172
 
173
- const onBlockRevert = (options, item) => {
174
- const repo = options.repo();
173
+ const onBlockRevert = (ui, item) => {
174
+ const repo = ui.repo;
175
175
  applyGit(
176
- options,
176
+ ui,
177
177
  item,
178
178
  'dropped',
179
179
  (top, current) => repo.revert(top, current),
180
180
  () => {
181
- if (needsReload(item)) options.collection.dismiss(item);
182
- options.reloadAfterChange();
181
+ if (needsReload(item)) ui.collection.dismiss(item);
182
+ ui.reloadAfterChange();
183
183
  },
184
184
  );
185
185
  };
@@ -190,116 +190,114 @@ const BLOCK = {
190
190
  revert: onBlockRevert,
191
191
  };
192
192
 
193
- const onBlockCommand = (options, name) => {
193
+ const onBlockCommand = (ui, name) => {
194
194
  const command = COMMANDS[name];
195
- const item = activeDiffItem(options);
196
- if (!guardWritable(options, item)) return;
195
+ const item = activeDiffItem(ui);
196
+ if (!guardWritable(ui, item)) return;
197
197
  if (!command.match(item)) {
198
- options.setStatus(command.skipStatus);
198
+ ui.status = command.skipStatus;
199
199
  return;
200
200
  }
201
- BLOCK[name](options, item);
201
+ BLOCK[name](ui, item);
202
202
  };
203
203
 
204
- const writeFileAddAsync = (options, targets) => async (top) => {
205
- const repo = options.repo();
204
+ const writeFileAddAsync = (ui, targets) => async (top) => {
205
+ const repo = ui.repo;
206
206
  for (const item of targets) {
207
- const current = liveItem(options, item);
207
+ const current = liveItem(ui, item);
208
208
  await repo.addAsync(top, current);
209
- keepOrigin(options, current, 'staged');
209
+ keepOrigin(ui, current, 'staged');
210
210
  }
211
211
  };
212
212
 
213
- const afterFileAdd = (options, rel, proposed) => () => {
213
+ const afterFileAdd = (ui, rel, proposed) => () => {
214
214
  if (proposed) {
215
- options.reloadAfterChange(() => selectNextFileAfter(options, rel));
216
- return;
215
+ return void ui.reloadAfterChange(() => selectNextFileAfter(ui, rel));
217
216
  }
218
- selectNextFileAfter(options, rel);
217
+ selectNextFileAfter(ui, rel);
219
218
  };
220
219
 
221
- const onFileAdd = (options) => {
222
- const selected = fileActionItems(options);
220
+ const onFileAdd = (ui) => {
221
+ const selected = fileActionItems(ui);
223
222
  if (!selected.length) {
224
- options.setStatus('not a diff block');
223
+ ui.status = 'not a diff block';
225
224
  return;
226
225
  }
227
- if (!guardWritable(options, selected[0])) return;
226
+ if (!guardWritable(ui, selected[0])) return;
228
227
  const rel = listPath(selected[0]);
229
228
  const targets = selected.filter(COMMANDS.add.match);
230
229
  if (!targets.length) {
231
- options.setStatus('already staged');
232
- selectNextFileAfter(options, rel);
230
+ ui.status = 'already staged';
231
+ selectNextFileAfter(ui, rel);
233
232
  return;
234
233
  }
235
234
  const proposed = targets.some(needsReload);
236
235
  const busy = targets.map(npmBusyKind).find((kind) => kind);
237
- const repo = options.repo();
238
- if (options.uiOpen() && busy && repo.addAsync) {
239
- const write = writeFileAddAsync(options, targets);
240
- const after = afterFileAdd(options, rel, proposed);
241
- options.runBusy(busy, 'staged', write, after);
242
- return;
236
+ const repo = ui.repo;
237
+ if (ui.uiOpen && busy && repo.addAsync) {
238
+ const write = writeFileAddAsync(ui, targets);
239
+ const after = afterFileAdd(ui, rel, proposed);
240
+ return void ui.ops.runBusy(busy, 'staged', write, after);
243
241
  }
244
242
  const result = applyFileGit(
245
- options,
243
+ ui,
246
244
  targets,
247
245
  'staged',
248
246
  (top, current) => repo.add(top, current),
249
- (item) => keepOrigin(options, item, 'staged'),
247
+ (item) => keepOrigin(ui, item, 'staged'),
250
248
  );
251
- if (reconcilePartial(options, result)) return;
252
- if (proposed) options.reloadAfterChange();
253
- selectNextFileAfter(options, rel);
249
+ if (reconcilePartial(ui, result)) return;
250
+ if (proposed) ui.reloadAfterChange();
251
+ selectNextFileAfter(ui, rel);
254
252
  };
255
253
 
256
- const onFileUnstage = (options) => {
257
- const selected = fileActionItems(options);
254
+ const onFileUnstage = (ui) => {
255
+ const selected = fileActionItems(ui);
258
256
  if (!selected.length) {
259
- options.setStatus('not a diff block');
257
+ ui.status = 'not a diff block';
260
258
  return;
261
259
  }
262
- if (!guardWritable(options, selected[0])) return;
260
+ if (!guardWritable(ui, selected[0])) return;
263
261
  const rel = listPath(selected[0]);
264
262
  const targets = selected.filter(COMMANDS.unstage.match);
265
263
  if (!targets.length) {
266
- options.setStatus('not staged');
267
- selectNextFileAfter(options, rel);
264
+ ui.status = 'not staged';
265
+ selectNextFileAfter(ui, rel);
268
266
  return;
269
267
  }
270
- const repo = options.repo();
268
+ const repo = ui.repo;
271
269
  const result = applyFileGit(
272
- options,
270
+ ui,
273
271
  targets,
274
272
  'unstaged',
275
273
  (top, current) => repo.unstage(top, current),
276
- (item) => keepOrigin(options, item, nextUnstageOrigin(item)),
274
+ (item) => keepOrigin(ui, item, nextUnstageOrigin(item)),
277
275
  );
278
- if (reconcilePartial(options, result)) return;
279
- selectNextFileAfter(options, rel);
276
+ if (reconcilePartial(ui, result)) return;
277
+ selectNextFileAfter(ui, rel);
280
278
  };
281
279
 
282
- const onFileRevert = (options) => {
283
- const selected = fileActionItems(options);
280
+ const onFileRevert = (ui) => {
281
+ const selected = fileActionItems(ui);
284
282
  if (!selected.length) {
285
- options.setStatus('not a diff block');
283
+ ui.status = 'not a diff block';
286
284
  return;
287
285
  }
288
- if (!guardWritable(options, selected[0])) return;
286
+ if (!guardWritable(ui, selected[0])) return;
289
287
  const rel = listPath(selected[0]);
290
288
  for (const item of selected) {
291
- if (needsReload(item)) options.collection.dismiss(item);
289
+ if (needsReload(item)) ui.collection.dismiss(item);
292
290
  }
293
291
  let ok = false;
294
292
  try {
295
- options.repo().revertFile(options.top(), rel, selected);
296
- options.setStatus('dropped');
293
+ ui.repo.revertFile(ui.top, rel, selected);
294
+ ui.status = 'dropped';
297
295
  ok = true;
298
296
  } catch (error) {
299
- options.setStatus(error.message);
297
+ ui.status = error.message;
300
298
  }
301
- const after = ok ? () => selectNextFileAfter(options, rel) : null;
302
- options.reloadAfterChange(after);
299
+ const after = ok ? () => selectNextFileAfter(ui, rel) : null;
300
+ ui.reloadAfterChange(after);
303
301
  };
304
302
 
305
303
  const FILE = {
@@ -308,16 +306,97 @@ const FILE = {
308
306
  revert: onFileRevert,
309
307
  };
310
308
 
311
- const onCommand = (options, name) => {
312
- if (options.nav.pane === 'files') return void FILE[name](options);
313
- onBlockCommand(options, name);
309
+ const hunkActionItems = (ui, item) => {
310
+ const key = hunkKey(item);
311
+ const selected = [];
312
+ if (!key) return selected;
313
+ for (const entry of ui.items) {
314
+ if (hunkKey(entry) !== key) continue;
315
+ selected.push(entry);
316
+ }
317
+ return selected;
318
+ };
319
+
320
+ const onUnitAdd = (ui, targets) => {
321
+ const proposed = targets.some(needsReload);
322
+ const busy = targets.map(npmBusyKind).find((kind) => kind);
323
+ const repo = ui.repo;
324
+ if (ui.uiOpen && busy && repo.addAsync) {
325
+ const write = writeFileAddAsync(ui, targets);
326
+ const after = () => {
327
+ if (proposed) ui.reloadAfterChange();
328
+ };
329
+ return void ui.ops.runBusy(busy, 'staged', write, after);
330
+ }
331
+ const result = applyFileGit(
332
+ ui,
333
+ targets,
334
+ 'staged',
335
+ (top, current) => repo.add(top, current),
336
+ (entry) => keepOrigin(ui, entry, 'staged'),
337
+ );
338
+ if (reconcilePartial(ui, result)) return;
339
+ if (proposed) ui.reloadAfterChange();
340
+ };
341
+
342
+ const onUnitUnstage = (ui, targets) => {
343
+ const repo = ui.repo;
344
+ const result = applyFileGit(
345
+ ui,
346
+ targets,
347
+ 'unstaged',
348
+ (top, current) => repo.unstage(top, current),
349
+ (entry) => keepOrigin(ui, entry, nextUnstageOrigin(entry)),
350
+ );
351
+ reconcilePartial(ui, result);
352
+ };
353
+
354
+ const onUnitRevert = (ui, targets) => {
355
+ for (const item of targets) {
356
+ if (needsReload(item)) ui.collection.dismiss(item);
357
+ }
358
+ const repo = ui.repo;
359
+ const result = applyFileGit(
360
+ ui,
361
+ targets,
362
+ 'dropped',
363
+ (top, current) => repo.revert(top, current),
364
+ (entry) => entry,
365
+ );
366
+ if (!result.ok && !result.completed.length) return;
367
+ ui.reloadAfterChange();
368
+ };
369
+
370
+ const UNIT = {
371
+ add: onUnitAdd,
372
+ unstage: onUnitUnstage,
373
+ revert: onUnitRevert,
374
+ };
375
+
376
+ const onUnitCommand = (ui, name) => {
377
+ const command = COMMANDS[name];
378
+ const item = activeDiffItem(ui);
379
+ if (!guardWritable(ui, item)) return;
380
+ const selected = hunkActionItems(ui, item);
381
+ const targets = name === 'revert' ? selected : selected.filter(command.match);
382
+ if (!targets.length) {
383
+ ui.status = command.skipStatus ?? 'not a diff block';
384
+ return;
385
+ }
386
+ UNIT[name](ui, targets);
387
+ };
388
+
389
+ const onCommand = (ui, name) => {
390
+ if (ui.nav.pane === 'files') return void FILE[name](ui);
391
+ if (ui.nav.pane === 'unit') return void onUnitCommand(ui, name);
392
+ onBlockCommand(ui, name);
314
393
  };
315
394
 
316
- const createChangeActions = (options) => ({
317
- fileCursorEntry: () => fileCursorEntry(options),
318
- onAdd: () => onCommand(options, 'add'),
319
- onUnstage: () => onCommand(options, 'unstage'),
320
- onRevert: () => onCommand(options, 'revert'),
395
+ const createChangeActions = (ui) => ({
396
+ fileCursorEntry: () => fileCursorEntry(ui),
397
+ onAdd: () => onCommand(ui, 'add'),
398
+ onUnstage: () => onCommand(ui, 'unstage'),
399
+ onRevert: () => onCommand(ui, 'revert'),
321
400
  });
322
401
 
323
402
  module.exports = { createChangeActions };