reslop 0.1.8 → 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 (95) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +27 -38
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +6 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +10 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/deps-diff.js +712 -0
  10. package/lib/deps.js +556 -32
  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 +34 -8
  20. package/lib/{git/branches.js → git-branches.js} +103 -10
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +592 -65
  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 +22 -9
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +34 -66
  33. package/lib/render/chrome.js +57 -22
  34. package/lib/render/commits.js +85 -0
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +29 -37
  37. package/lib/render/frame.js +29 -13
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +46 -1
  40. package/lib/{render.js → render/render.js} +6 -7
  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 +93 -36
  46. package/lib/session/branches.js +130 -144
  47. package/lib/session/change-actions.js +206 -124
  48. package/lib/session/commit.js +217 -62
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +109 -55
  51. package/lib/session/files-pane.js +145 -142
  52. package/lib/session/items.js +131 -34
  53. package/lib/session/lifecycle.js +82 -42
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +15 -3
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +58 -54
  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 +82 -11
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +29 -226
  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 +2 -2
  71. package/lib/deps/changes.js +0 -331
  72. package/lib/deps/items.js +0 -131
  73. package/lib/deps/manifest.js +0 -145
  74. package/lib/deps/merge.js +0 -120
  75. package/lib/deps/proposals.js +0 -164
  76. package/lib/deps/reports.js +0 -180
  77. package/lib/deps/rows.js +0 -177
  78. package/lib/deps/scan.js +0 -138
  79. package/lib/deps/usage.js +0 -68
  80. package/lib/deps/versions.js +0 -119
  81. package/lib/diff.js +0 -33
  82. package/lib/git/changes.js +0 -164
  83. package/lib/git/read.js +0 -253
  84. package/lib/git/run.js +0 -100
  85. package/lib/npm/commands.js +0 -100
  86. package/lib/remote/changes.js +0 -37
  87. package/lib/remote/headers.js +0 -36
  88. package/lib/remote/http.js +0 -122
  89. package/lib/remote/notes.js +0 -94
  90. package/lib/review/format.js +0 -434
  91. package/lib/review/storage.js +0 -150
  92. package/lib/review/store.js +0 -178
  93. package/lib/review/templates.js +0 -49
  94. package/lib/session/origins.js +0 -7
  95. package/lib/sys.js +0 -84
@@ -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,101 +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() === 'diff',
99
+ match: (ui) => ui.pane === 'commits',
100
+ hits: 'fileHits',
101
+ onHit: (ui, hit) => {
102
+ ui.nav.commitCursor = hit.cursor;
103
+ },
104
+ },
105
+ {
106
+ match: (ui) => ui.pane === 'diff',
102
107
  hits: 'todoHits',
103
- onHit: (options, hit) => options.onTodoHit(hit.cursor),
108
+ onHit: (ui, hit) => ui.composer.onTodoHit(hit.cursor),
104
109
  },
105
110
  {
106
- match: (options) => options.getMode() === 'compose',
111
+ match: (ui) => ui.mode === 'compose',
107
112
  hits: 'templateHits',
108
- onHit: (options, hit) => options.selectTemplate(hit.cursor),
113
+ onHit: (ui, hit) => ui.selectTemplate(hit.cursor),
109
114
  },
110
115
  ];
111
116
 
112
- const onRelease = (options, event) => {
113
- const nav = options.nav;
114
- const cell = mouseCell(options, event);
117
+ const onRelease = (ui, event) => {
118
+ const nav = ui.nav;
119
+ const cell = mouseCell(ui, event);
115
120
  if (nav.mouseAnchor) {
116
121
  nav.selection = { start: nav.mouseAnchor, end: cell };
117
122
  }
118
123
  const clickId = nav.pendingClick;
119
124
  nav.pendingClick = null;
120
125
  nav.mouseAnchor = null;
121
- if (clickButton(options, cell, clickId)) return;
122
- const frame = options.lastFrame();
126
+ if (clickButton(ui, cell, clickId)) return;
127
+ const frame = ui.lastFrame;
123
128
  for (const target of RELEASE_TARGETS) {
124
- if (!target.match(options)) continue;
129
+ if (!target.match(ui)) continue;
125
130
  const hits = frame?.[target.hits];
126
- const opened = clickHit(options, hits, cell, target.onHit);
131
+ const opened = clickHit(ui, hits, cell, target.onHit);
127
132
  if (opened) return;
128
133
  }
129
- copySelection(options);
134
+ copySelection(ui);
130
135
  };
131
136
 
132
137
  const POINTER_HANDLERS = {
133
- wheelUp: (options) => options.onScroll(-1),
134
- wheelDown: (options) => options.onScroll(1),
138
+ wheelUp: (ui) => ui.filesPane.onScroll(-1),
139
+ wheelDown: (ui) => ui.filesPane.onScroll(1),
135
140
  press: onPress,
136
141
  drag: onDrag,
137
142
  release: onRelease,
138
143
  };
139
144
 
140
- const handle = (options, event) => {
145
+ const handle = (ui, event) => {
141
146
  const kind = event.kind;
142
147
  const handler = POINTER_HANDLERS[kind];
143
148
  if (!handler) return;
144
149
  const isWheel = kind === 'wheelUp' || kind === 'wheelDown';
145
150
  if (!isWheel && event.btn !== 0 && event.btn !== undefined) return;
146
- handler(options, event);
151
+ handler(ui, event);
147
152
  };
148
153
 
149
- const createPointer = (options) => ({
150
- handle: (event) => handle(options, event),
151
- mouseCell: (event) => mouseCell(options, event),
154
+ const createPointer = (ui) => ({
155
+ handle: (event) => handle(ui, event),
152
156
  });
153
157
 
154
158
  module.exports = { createPointer };
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- const store = require('../review/store.js');
4
- const { createStore, applyImportedNotes, setStatus } = store;
5
- const storage = require('../review/storage.js');
6
- const { listReviewNames, loadTemplates, resolveReviewPath } = storage;
7
- const { loadReview, flushReview } = storage;
3
+ const { stateView } = require('./accessors.js');
8
4
 
9
- const createReviewController = (options) => {
10
- const io = options.storage;
5
+ const review = require('../review.js');
6
+ const { createStore, applyImportedNotes, setStatus } = review;
7
+ const { listReviewNames, loadTemplates, resolveReviewPath } = review;
8
+ const { loadReview, flushReview } = review;
9
+
10
+ const createReviewController = () => {
11
11
  const state = {
12
12
  store: null,
13
13
  didResume: false,
@@ -21,15 +21,11 @@ const createReviewController = (options) => {
21
21
  };
22
22
 
23
23
  const init = (dir, date, extra = {}) => {
24
- const forceNew = extra.forceNew ?? false;
25
- const names = listReviewNames(dir, io.readdirSync);
26
- const templates = loadTemplates(dir, io.readFileSync);
27
- const resolved = resolveReviewPath(dir, date, names, {
28
- forceNew,
29
- readFileSync: io.readFileSync,
30
- });
24
+ const names = listReviewNames(dir);
25
+ const templates = loadTemplates(dir);
26
+ const resolved = resolveReviewPath(dir, date, names, extra);
31
27
  if (resolved.resume) {
32
- state.store = loadReview(resolved.reviewPath, templates, io.readFileSync);
28
+ state.store = loadReview(resolved.reviewPath, templates);
33
29
  state.didResume = true;
34
30
  return state.store;
35
31
  }
@@ -49,13 +45,7 @@ const createReviewController = (options) => {
49
45
  const flush = (force = false) => {
50
46
  if (!state.store) return { ok: true, wrote: false };
51
47
  try {
52
- const writeFileSync = io.writeFileSync;
53
- const mkdirSync = io.mkdirSync;
54
- const wrote = flushReview(state.store, {
55
- writeFileSync,
56
- mkdirSync,
57
- force,
58
- });
48
+ const wrote = flushReview(state.store, force);
59
49
  return { ok: true, wrote };
60
50
  } catch (error) {
61
51
  return { ok: false, wrote: false, error };
@@ -67,22 +57,18 @@ const createReviewController = (options) => {
67
57
  return flush();
68
58
  };
69
59
 
70
- return {
71
- get store() {
72
- return state.store;
73
- },
74
- get didResume() {
75
- return state.didResume;
60
+ return stateView(
61
+ state,
62
+ {
63
+ reset,
64
+ init,
65
+ applyImported,
66
+ flush,
67
+ finishQuit,
76
68
  },
77
- get importedRemote() {
78
- return state.importedRemote;
79
- },
80
- reset,
81
- init,
82
- applyImported,
83
- flush,
84
- finishQuit,
85
- };
69
+ [],
70
+ ['store', 'didResume', 'importedRemote'],
71
+ );
86
72
  };
87
73
 
88
74
  module.exports = { createReviewController };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { presentRows, presentCursor } = require('../render.js');
4
- const { listen, watchResize } = require('../sys.js');
3
+ const { presentRows, presentCursor } = require('../render/render.js');
4
+ const { listen, watchResize } = require('../utilities.js');
5
5
 
6
6
  const ENTER_TERM = '\x1b[?1049h\x1b[?25l\x1b[?7l\x1b[?1002h\x1b[?1006h';
7
7
  const LEAVE_TERM =
@@ -18,14 +18,16 @@ const hasTimer = (api, name) => api.timers.has(name);
18
18
  const stopTimer = (api, name) => {
19
19
  const id = api.timers.get(name);
20
20
  if (id === undefined) return;
21
- api.clearIntervalFn(id);
21
+ clearInterval(id);
22
22
  api.timers.delete(name);
23
23
  };
24
24
 
25
25
  const startTimer = (api, name, fn, ms) => {
26
26
  if (api.state.disposed) return;
27
27
  if (api.timers.has(name)) return;
28
- api.timers.set(name, api.setIntervalFn(fn, ms));
28
+ const id = setInterval(fn, ms);
29
+ if (id && typeof id.unref === 'function') id.unref();
30
+ api.timers.set(name, id);
29
31
  };
30
32
 
31
33
  const restartTimer = (api, name, fn, ms) => {
@@ -118,8 +120,6 @@ const createTerminal = (options) => {
118
120
  stdin: options.stdin ?? null,
119
121
  stdout: options.stdout,
120
122
  proc: options.proc ?? process,
121
- setIntervalFn: options.setInterval ?? setInterval,
122
- clearIntervalFn: options.clearInterval ?? clearInterval,
123
123
  timers: new Map(),
124
124
  disposers: [],
125
125
  state,
@@ -131,18 +131,12 @@ const createTerminal = (options) => {
131
131
  get lastSize() {
132
132
  return state.lastSize;
133
133
  },
134
- get lastPresentedCursor() {
135
- return state.lastPresentedCursor;
136
- },
137
134
  get carry() {
138
135
  return state.carry;
139
136
  },
140
137
  set carry(value) {
141
138
  state.carry = value;
142
139
  },
143
- get isOpen() {
144
- return state.open;
145
- },
146
140
  get disposed() {
147
141
  return state.disposed;
148
142
  },
@@ -48,17 +48,19 @@ const isDraftCompose = (api) => {
48
48
  const todoTexts = (api) => {
49
49
  const nav = api.nav;
50
50
  if (!nav.todoOpen) return [];
51
- const texts = [];
51
+ const todos = listTodos(api);
52
+ const texts = new Array(todos.length + 1);
52
53
  const { state } = api;
53
54
  const editingTodo = state.composeKind === 'todo';
54
55
  const editingId = editingTodo ? state.composeTodoId : null;
55
- for (const todo of listTodos(api)) {
56
+ for (let i = 0; i < todos.length; i++) {
57
+ const todo = todos[i];
56
58
  const live = editingId === todo.id && state.editor;
57
59
  const text = live ? state.editor.text : todo.text;
58
- texts.push(checkLabel(text, todo.done));
60
+ texts[i] = checkLabel(text, todo.done);
59
61
  }
60
62
  const draft = isDraftCompose(api) && state.editor ? state.editor.text : '';
61
- texts.push(checkLabel(draft, false));
63
+ texts[todos.length] = checkLabel(draft, false);
62
64
  return texts;
63
65
  };
64
66
 
@@ -126,10 +128,7 @@ const editFocusedTodo = (api) => {
126
128
  if (!api.nav.todoOpen) return;
127
129
  const todos = listTodos(api);
128
130
  const todo = todos[clampedTodoFocus(api)];
129
- if (!todo) {
130
- startDraftCompose(api);
131
- return;
132
- }
131
+ if (!todo) return void startDraftCompose(api);
133
132
  api.open('todo', todo.text, todo.id);
134
133
  };
135
134
 
@@ -234,10 +233,7 @@ const commitTodo = (api, text) => {
234
233
  const id = api.state.composeTodoId;
235
234
  const todos = notes.todos;
236
235
  const prev = id === null ? null : todos.find((entry) => entry.id === id);
237
- if (prev) {
238
- setTodoText(notes, id, text);
239
- return;
240
- }
236
+ if (prev) return void setTodoText(notes, id, text);
241
237
  if (!text.trim()) return;
242
238
  const todo = addTodo(notes, TODO_FILE, text);
243
239
  api.state.composeTodoId = todo.id;