reslop 0.2.3 → 0.2.5

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 (89) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +8 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/api.js +2 -1
  5. package/lib/capabilities.js +0 -4
  6. package/lib/cli.js +19 -10
  7. package/lib/clipboard.js +45 -4
  8. package/lib/deps-diff.js +61 -118
  9. package/lib/deps.js +166 -235
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +68 -2
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +15 -30
  17. package/lib/editor.js +91 -13
  18. package/lib/files.js +54 -27
  19. package/lib/git-branches.js +107 -154
  20. package/lib/git-deps.js +78 -155
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +9 -8
  24. package/lib/gitlab.js +5 -4
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/core.js +11 -0
  27. package/lib/highlight/css.js +2 -1
  28. package/lib/highlight/csv.js +2 -1
  29. package/lib/highlight/dot.js +3 -12
  30. package/lib/highlight/html.js +2 -1
  31. package/lib/highlight/json.js +2 -1
  32. package/lib/highlight/log.js +5 -11
  33. package/lib/highlight/txt.js +3 -12
  34. package/lib/highlight.js +2 -1
  35. package/lib/keys.js +22 -12
  36. package/lib/manifest.js +4 -6
  37. package/lib/npm-commands.js +82 -24
  38. package/lib/remote.js +21 -20
  39. package/lib/render/branches.js +9 -27
  40. package/lib/render/chrome.js +109 -35
  41. package/lib/render/commits.js +216 -216
  42. package/lib/render/diff.js +160 -47
  43. package/lib/render/files.js +9 -26
  44. package/lib/render/frame.js +97 -118
  45. package/lib/render/notes.js +74 -26
  46. package/lib/render/npm.js +104 -30
  47. package/lib/render/present.js +2 -1
  48. package/lib/render/primitives.js +128 -52
  49. package/lib/render/render.js +2 -1
  50. package/lib/render/unit.js +17 -11
  51. package/lib/report-model.js +6 -4
  52. package/lib/report-parse.js +8 -14
  53. package/lib/report-render.js +38 -21
  54. package/lib/report-run.js +12 -11
  55. package/lib/report-test.js +45 -51
  56. package/lib/report-tools.js +44 -25
  57. package/lib/review-merge.js +102 -131
  58. package/lib/review.js +69 -78
  59. package/lib/select.js +52 -0
  60. package/lib/session/actions.js +72 -87
  61. package/lib/session/branches.js +18 -52
  62. package/lib/session/change-actions.js +175 -147
  63. package/lib/session/commit.js +65 -61
  64. package/lib/session/compose.js +152 -83
  65. package/lib/session/dispatch.js +53 -46
  66. package/lib/session/edit-click.js +57 -0
  67. package/lib/session/files-pane.js +30 -51
  68. package/lib/session/items.js +73 -90
  69. package/lib/session/lifecycle.js +37 -98
  70. package/lib/session/load.js +52 -57
  71. package/lib/session/navigation.js +105 -95
  72. package/lib/session/npm.js +205 -71
  73. package/lib/session/ops.js +200 -45
  74. package/lib/session/pointer.js +33 -8
  75. package/lib/session/progress.js +9 -6
  76. package/lib/session/review.js +35 -53
  77. package/lib/session/tasks.js +292 -0
  78. package/lib/session/terminal.js +17 -19
  79. package/lib/session/unit.js +3 -4
  80. package/lib/session/update.js +76 -96
  81. package/lib/session/view.js +45 -16
  82. package/lib/session/watch.js +47 -27
  83. package/lib/session.js +66 -97
  84. package/lib/source.js +6 -3
  85. package/lib/update.js +5 -13
  86. package/lib/utilities.js +25 -3
  87. package/lib/wrap.js +18 -1
  88. package/package.json +1 -1
  89. package/lib/session/todos.js +0 -293
@@ -3,8 +3,9 @@
3
3
  const path = require('node:path');
4
4
 
5
5
  const files = require('../files.js');
6
- const { listedCommits } = require('./commit.js');
7
- const { fileEntries, REPO_TODOS_LABEL } = files;
6
+ const commit = require('./commit.js');
7
+ const { listedCommits } = commit;
8
+ const { fileEntries, fileTotals, REPO_TASKS_LABEL } = files;
8
9
  const review = require('../review.js');
9
10
  const { noteCounts } = review;
10
11
  const diff = require('../diff/diff.js');
@@ -24,12 +25,12 @@ const repoNameOf = (api) => {
24
25
  return path.basename(ui.top || ui.cwd || '');
25
26
  };
26
27
 
27
- const todosEntry = (counts) => ({
28
- path: REPO_TODOS_LABEL,
29
- kind: 'todos',
30
- status: 'todos',
31
- remaining: counts.todo,
32
- staged: counts.todoDone,
28
+ const tasksEntry = (counts) => ({
29
+ path: REPO_TASKS_LABEL,
30
+ kind: 'tasks',
31
+ status: 'tasks',
32
+ remaining: counts.tasks,
33
+ staged: counts.tasksDone,
33
34
  firstIndex: 0,
34
35
  openIndex: 0,
35
36
  added: 0,
@@ -93,7 +94,7 @@ const fileList = (api, notes = noteCounts(api.review.store)) => {
93
94
  const names = listedNames(api);
94
95
  const scoped =
95
96
  api.nav.fileScope === 'file' ? mergeUnitFiles(labeled, names) : labeled;
96
- scoped.unshift(todosEntry(notes));
97
+ scoped.unshift(tasksEntry(notes));
97
98
  return scoped;
98
99
  };
99
100
 
@@ -104,7 +105,7 @@ const counts = (api, notes = noteCounts(api.review.store)) => {
104
105
  untracked: 0,
105
106
  commit: 0,
106
107
  pr: 0,
107
- todo: 0,
108
+ tasks: 0,
108
109
  feedback: 0,
109
110
  code: 0,
110
111
  };
@@ -113,7 +114,7 @@ const counts = (api, notes = noteCounts(api.review.store)) => {
113
114
  next[item.origin] += 1;
114
115
  }
115
116
  next.feedback = notes.feedback;
116
- next.todo = notes.todo;
117
+ next.tasks = notes.tasks;
117
118
  next.code = notes.code;
118
119
  return next;
119
120
  };
@@ -143,6 +144,30 @@ const unitBodyLines = (api) => {
143
144
  return unitLines(unitFileText(api), group);
144
145
  };
145
146
 
147
+ const pendingLines = (items, rev, revShort) => {
148
+ if (rev || revShort) return null;
149
+ for (const item of items) {
150
+ if (item.origin === 'commit' || item.origin === 'pr') return null;
151
+ }
152
+ return fileTotals(fileEntries(items));
153
+ };
154
+
155
+ const commitRows = (api) => {
156
+ const rows = listedCommits(api.commits.commits);
157
+ const lines = pendingLines(api.collection.items, api.ui.rev, api.ui.revShort);
158
+ if (!lines || !rows[0]) return rows;
159
+ rows[0] = {
160
+ ...rows[0],
161
+ added: lines.added,
162
+ removed: lines.removed,
163
+ stagedAdded: lines.stagedAdded,
164
+ unstagedAdded: lines.unstagedAdded,
165
+ stagedRemoved: lines.stagedRemoved,
166
+ unstagedRemoved: lines.unstagedRemoved,
167
+ };
168
+ return rows;
169
+ };
170
+
146
171
  const view = (api) => {
147
172
  const { composer, nav, ui, gitBranches, commits, npm, updater } = api;
148
173
  const templates = composer.shownTemplates();
@@ -170,7 +195,7 @@ const view = (api) => {
170
195
  branch: ui.branch,
171
196
  branches: gitBranches.branches,
172
197
  branchCursor: nav.branchCursor,
173
- commits: listedCommits(commits.commits),
198
+ commits: commitRows(api),
174
199
  commitCursor: nav.commitCursor,
175
200
  commitView: commits.commitView,
176
201
  npmCommands: npm.commands,
@@ -180,7 +205,11 @@ const view = (api) => {
180
205
  npmFollow: npm.followEnd,
181
206
  npmScroll: npm.logScroll,
182
207
  npmEditName: npm.editName,
208
+ npmEditField: npm.editField,
209
+ npmDraftName: npm.draftName,
210
+ npmDraftCommand: npm.draftCommand,
183
211
  npmRunning: npm.running,
212
+ npmLogs: nav.pane === 'npm' ? npm.logLabel : '',
184
213
  repoName: repoNameOf(api),
185
214
  rev: ui.rev || '',
186
215
  revShort: ui.revShort || '',
@@ -189,9 +218,9 @@ const view = (api) => {
189
218
  mode: ui.mode,
190
219
  compose: composer.composeView(),
191
220
  noteText: composer.idleNoteText(),
192
- todos: composer.todoTexts(),
193
- todoFocus: composer.clampedTodoFocus(),
194
- todoEdit: composer.todoEditView(),
221
+ tasks: composer.taskTexts(),
222
+ tasksFocus: composer.clampedTaskFocus(),
223
+ taskEdit: composer.taskEditView(),
195
224
  codeOverlay: composer.codeOverlayView(),
196
225
  templates,
197
226
  templateIndex: composer.clampedTemplateIndex(templates),
@@ -208,4 +237,4 @@ const createView = (api) => ({
208
237
  view: () => view(api),
209
238
  });
210
239
 
211
- module.exports = { relabelStatus, createView };
240
+ module.exports = { createView };
@@ -4,7 +4,6 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
6
  const DEBOUNCE_MS = 200;
7
- const POLL_MS = 250;
8
7
 
9
8
  const SKIP_DIRS = ['node_modules', '.review', 'coverage', 'dist', '.cache'];
10
9
 
@@ -45,14 +44,17 @@ const ignoredName = (name) => {
45
44
  };
46
45
 
47
46
  const ignoredGitRel = (parts) => {
47
+ if (parts[1] === 'logs' && parts[2] === 'HEAD') return false;
48
48
  const name = parts[1] ?? '';
49
49
  if (!name) return false;
50
50
  if (GIT_WATCH_NAMES.includes(name)) return false;
51
51
  return true;
52
52
  };
53
53
 
54
+ const slashRel = (rel) => `${rel}`.replaceAll('\\', '/');
55
+
54
56
  const ignoredRel = (rel) => {
55
- const norm = `${rel}`.replaceAll('\\', '/');
57
+ const norm = slashRel(rel);
56
58
  if (!norm || norm === '.') return false;
57
59
  const parts = norm.split('/');
58
60
  for (const part of parts) {
@@ -95,7 +97,7 @@ const hasFile = (full) => {
95
97
  };
96
98
 
97
99
  const isBareReviewName = (root, dir, rel) => {
98
- if (!rel || rel.includes('/') || !rel.endsWith('.md')) return false;
100
+ if (!rel || rel.includes('/')) return false;
99
101
  const base = path.basename(rel);
100
102
  if (hasFile(path.join(dir, base))) return false;
101
103
  return hasFile(path.join(root, '.review', base));
@@ -133,6 +135,15 @@ const isOwnDirEvent = (dir, name) => {
133
135
  }
134
136
  };
135
137
 
138
+ const unchangedSince = (full, since) => {
139
+ try {
140
+ const stat = fs.statSync(full);
141
+ return Math.max(stat.mtimeMs, stat.ctimeMs) < since;
142
+ } catch {
143
+ return false;
144
+ }
145
+ };
146
+
136
147
  const schedule = (state, timerKey, fire) => {
137
148
  if (state.closed || !fire) return;
138
149
  if (state[timerKey] !== null) clearTimeout(state[timerKey]);
@@ -154,9 +165,10 @@ const reviewBurst = (state) => {
154
165
  return Date.now() - seen < hold;
155
166
  };
156
167
 
157
- const scheduleChange = (state) => {
168
+ const scheduleChange = (state, loose = false) => {
169
+ state.looseChange = loose && (state.timer === null || state.looseChange);
158
170
  schedule(state, 'timer', () => {
159
- if (reviewBurst(state)) return;
171
+ if (state.looseChange && reviewBurst(state)) return;
160
172
  if (state.onChange) state.onChange();
161
173
  });
162
174
  };
@@ -164,34 +176,39 @@ const scheduleChange = (state) => {
164
176
  const scheduleUnnamed = (state) => {
165
177
  if (reviewBurst(state)) return;
166
178
  schedule(state, 'looseTimer', () => {
167
- if (reviewBurst(state)) return;
168
- scheduleChange(state);
179
+ if (!reviewBurst(state)) scheduleChange(state, true);
169
180
  });
170
181
  };
171
182
 
172
- const dropChange = (state) => {
173
- if (state.timer !== null) clearTimeout(state.timer);
174
- if (state.looseTimer !== null) clearTimeout(state.looseTimer);
175
- state.timer = null;
176
- state.looseTimer = null;
183
+ const clearTimer = (state, timerKey) => {
184
+ if (state[timerKey] !== null) clearTimeout(state[timerKey]);
185
+ state[timerKey] = null;
186
+ };
187
+
188
+ const dropLooseChange = (state) => {
189
+ clearTimer(state, 'looseTimer');
190
+ if (state.looseChange) clearTimer(state, 'timer');
177
191
  };
178
192
 
179
193
  const handleWatchEvent = (state, dir, eventType, filename) => {
180
194
  if (state.closed) return;
181
195
  const name = filename ? `${filename}` : '';
182
- const rel = name ? path.relative(state.root, path.join(dir, name)) : '';
196
+ const rel = name
197
+ ? slashRel(path.relative(state.root, path.join(dir, name)))
198
+ : '';
199
+ const wanted = state.files.has(rel);
183
200
  if (isReviewEvent(state.root, dir, name)) {
184
201
  state.reviewSeenAt = Date.now();
185
- dropChange(state);
186
- return void schedule(state, 'reviewTimer', state.onReview);
202
+ dropLooseChange(state);
203
+ schedule(state, 'reviewTimer', state.onReview);
204
+ if (!wanted) return;
187
205
  }
188
- if (rel && ignoredRel(rel)) return;
189
- if (reviewBurst(state)) return;
190
206
  if (!name) return void scheduleUnnamed(state);
207
+ if (!wanted && ignoredRel(rel)) return;
191
208
  if (isOwnDirEvent(dir, name)) return;
192
- if (eventType === 'rename' && name) {
193
- state.watchNewDir(path.join(dir, name));
194
- }
209
+ const full = path.join(dir, name);
210
+ if (unchangedSince(full, state.startedAt)) return;
211
+ if (eventType === 'rename') state.watchNewDir(full);
195
212
  scheduleChange(state);
196
213
  };
197
214
 
@@ -263,24 +280,24 @@ const watchGit = (state) => {
263
280
  const gitDir = path.resolve(state.root, '.git');
264
281
  watchOne(state, gitDir);
265
282
  watchOne(state, path.resolve(gitDir, 'refs'));
283
+ watchOne(state, path.resolve(gitDir, 'logs'));
266
284
  };
267
285
 
268
286
  const stopState = (state) => {
269
287
  state.closed = true;
270
- if (state.timer !== null) clearTimeout(state.timer);
271
- if (state.reviewTimer !== null) clearTimeout(state.reviewTimer);
272
- if (state.looseTimer !== null) clearTimeout(state.looseTimer);
273
- state.timer = null;
274
- state.reviewTimer = null;
275
- state.looseTimer = null;
288
+ clearTimer(state, 'timer');
289
+ clearTimer(state, 'reviewTimer');
290
+ clearTimer(state, 'looseTimer');
276
291
  while (state.watchers.length) closeWatcher(state.watchers.pop());
277
292
  state.watched.clear();
278
293
  };
279
294
 
280
295
  const watchFiles = (state, rels) => {
296
+ state.files = new Set();
281
297
  for (const rel of rels) {
282
298
  if (!rel) continue;
283
299
  const full = path.resolve(state.root, rel);
300
+ state.files.add(slashRel(path.relative(state.root, full)));
284
301
  watchOne(state, path.dirname(full));
285
302
  }
286
303
  };
@@ -295,11 +312,14 @@ const createDiskWatcher = (options) => {
295
312
  dirWatchOpts: recursive ? { recursive: true } : {},
296
313
  watchers: [],
297
314
  watched: new Set(),
315
+ files: new Set(),
298
316
  onReview: options.onReview,
299
317
  timer: null,
300
318
  reviewTimer: null,
301
319
  looseTimer: null,
320
+ looseChange: false,
302
321
  reviewSeenAt: 0,
322
+ startedAt: Date.now(),
303
323
  closed: false,
304
324
  watchNewDir: (full) => maybeWatchNewDir(state, full),
305
325
  };
@@ -315,9 +335,9 @@ const createDiskWatcher = (options) => {
315
335
 
316
336
  module.exports = {
317
337
  DEBOUNCE_MS,
318
- POLL_MS,
319
338
  canWatchRecursive,
320
339
  ignoredRel,
321
340
  isOwnDirEvent,
341
+ unchangedSince,
322
342
  createDiskWatcher,
323
343
  };
package/lib/session.js CHANGED
@@ -4,35 +4,53 @@ const render = require('./render/render.js');
4
4
  const keys = require('./keys.js');
5
5
  const review = require('./review.js');
6
6
  const files = require('./files.js');
7
- const { makeTodoItem } = files;
7
+ const { makeTaskItem } = files;
8
8
  const { renderFrame } = render;
9
9
  const { decodeChunk } = keys;
10
10
  const { AUTOSAVE_MS, hasNotes } = review;
11
11
  const capabilities = require('./capabilities.js');
12
+ const actions = require('./session/actions.js');
13
+ const { LIST_PANES } = actions;
12
14
  const { sessionCapabilities } = capabilities;
13
- const { createTerminal, LEAVE_TERM } = require('./session/terminal.js');
14
- const { createLoadCoordinator } = require('./session/load.js');
15
+ const terminal = require('./session/terminal.js');
16
+ const { createTerminal, LEAVE_TERM } = terminal;
17
+ const load = require('./session/load.js');
18
+ const { LoadCoordinator } = load;
15
19
  const items = require('./session/items.js');
16
- const { createItemCollection } = items;
17
- const { createNavigation } = require('./session/navigation.js');
18
- const { createReviewController } = require('./session/review.js');
19
- const { createUpdateCoordinator } = require('./session/update.js');
20
- const { createProgress } = require('./session/progress.js');
21
- const { createComposer } = require('./session/compose.js');
22
- const { createOpsRunner } = require('./session/ops.js');
23
- const { createChangeActions } = require('./session/change-actions.js');
24
- const { createCommitController } = require('./session/commit.js');
25
- const { createBranchController } = require('./session/branches.js');
26
- const { createNpmController } = require('./session/npm.js');
20
+ const { ItemCollection } = items;
21
+ const navigation = require('./session/navigation.js');
22
+ const { Navigation } = navigation;
23
+ const sessionReview = require('./session/review.js');
24
+ const { ReviewController } = sessionReview;
25
+ const update = require('./session/update.js');
26
+ const { UpdateCoordinator } = update;
27
+ const progress = require('./session/progress.js');
28
+ const { createProgress } = progress;
29
+ const compose = require('./session/compose.js');
30
+ const { createComposer } = compose;
31
+ const ops = require('./session/ops.js');
32
+ const { OpsRunner } = ops;
33
+ const changeActions = require('./session/change-actions.js');
34
+ const { createChangeActions } = changeActions;
35
+ const commit = require('./session/commit.js');
36
+ const { createCommitController } = commit;
37
+ const branches = require('./session/branches.js');
38
+ const { createBranchController } = branches;
39
+ const npm = require('./session/npm.js');
40
+ const { createNpmController } = npm;
27
41
  const accessors = require('./session/accessors.js');
28
42
  const { bindAccessors, bindGetters, bindAlias } = accessors;
29
- const { createView } = require('./session/view.js');
30
- const { createFilesPane } = require('./session/files-pane.js');
31
- const { createLifecycle } = require('./session/lifecycle.js');
32
- const { createPointer } = require('./session/pointer.js');
33
- const { createDispatcher, createInput } = require('./session/dispatch.js');
43
+ const view = require('./session/view.js');
44
+ const { createView } = view;
45
+ const filesPane = require('./session/files-pane.js');
46
+ const { createFilesPane } = filesPane;
47
+ const lifecycle = require('./session/lifecycle.js');
48
+ const { createLifecycle } = lifecycle;
49
+ const pointer = require('./session/pointer.js');
50
+ const { createPointer } = pointer;
51
+ const dispatch = require('./session/dispatch.js');
52
+ const { createDispatcher, createInput } = dispatch;
34
53
 
35
- const CURSOR_BLINK_MS = 500;
36
54
  const PROGRESS_MS = 160;
37
55
 
38
56
  const NAV_FIELDS = [
@@ -50,19 +68,18 @@ const NAV_FIELDS = [
50
68
  'selection',
51
69
  'mouseAnchor',
52
70
  'pendingClick',
53
- 'todoOpen',
54
- 'todoFocus',
71
+ 'tasksOpen',
72
+ 'tasksFocus',
55
73
  ];
56
74
 
57
75
  const COMPOSER_FIELDS = [
58
76
  'editor',
59
77
  'composeKind',
60
78
  'commitKind',
61
- 'composeTodoId',
79
+ 'composeTaskId',
62
80
  'composeBaseline',
63
81
  'templateIndex',
64
82
  'templateFocus',
65
- 'blinkOn',
66
83
  ];
67
84
 
68
85
  const assignOptions = (session, options) => {
@@ -95,12 +112,10 @@ const assignOptions = (session, options) => {
95
112
  stdout: session.stdout,
96
113
  proc: session.proc,
97
114
  });
98
- session.loader = createLoadCoordinator();
99
- session.collection = createItemCollection();
100
- session.nav = createNavigation({
101
- startPane: options.startPane ?? 'files',
102
- });
103
- session.review = createReviewController();
115
+ session.loader = new LoadCoordinator();
116
+ session.collection = new ItemCollection();
117
+ session.nav = new Navigation({ startPane: options.startPane ?? 'files' });
118
+ session.review = new ReviewController();
104
119
  session.progress = createProgress(
105
120
  session.term,
106
121
  () => session.tickProgress(),
@@ -113,19 +128,11 @@ const assignComposer = (session) => {
113
128
  nav: session.nav,
114
129
  review: session.review,
115
130
  ui: session,
116
- restartBlink: () => {
117
- if (!session.term.hasTimer('blink')) return;
118
- session.term.restartTimer(
119
- 'blink',
120
- () => session.tickBlink(),
121
- CURSOR_BLINK_MS,
122
- );
123
- },
124
131
  });
125
132
  };
126
133
 
127
134
  const assignControllers = (session) => {
128
- session.ops = createOpsRunner(session);
135
+ session.ops = new OpsRunner(session);
129
136
  session.changeActions = createChangeActions(session);
130
137
  session.commits = createCommitController(session);
131
138
  session.gitBranches = createBranchController(session);
@@ -136,48 +143,14 @@ const bindSessionAccessors = (session) => {
136
143
  bindAccessors(session, session.nav, NAV_FIELDS);
137
144
  bindAccessors(session, session.composer.state, COMPOSER_FIELDS);
138
145
  bindAccessors(session, session.loader, ['pendingExtras']);
139
- bindAlias(
140
- session,
141
- 'loadPromise',
142
- () => session.loader.promise,
143
- (value) => {
144
- session.loader.promise = value;
145
- },
146
- );
146
+ bindGetters(session, session.loader, ['didLoad']);
147
147
  bindAccessors(session, session.collection, ['items']);
148
148
  bindGetters(session, session.collection, ['dismissed']);
149
- bindAccessors(session, session.updater, ['installPromise']);
149
+ bindGetters(session, session.updater, ['installPromise']);
150
150
  bindAccessors(session, session.ops, ['gitBusy', 'busy']);
151
151
  bindAccessors(session, session.gitBranches, ['branches', 'dropName']);
152
152
  bindGetters(session, session.term, ['lastFrame', 'lastSize']);
153
- bindAlias(session, 'loadGen', () => session.loader.gen);
154
- bindAlias(
155
- session,
156
- 'didLoad',
157
- () => session.loader.didLoad,
158
- (value) => {
159
- session.loader.didLoad = value;
160
- if (value) session.updater.maybePrompt();
161
- },
162
- );
163
153
  bindAlias(session, 'notes', () => session.review.store);
164
- bindAlias(session, 'didResumeReview', () => session.review.didResume);
165
- bindAlias(
166
- session,
167
- 'importedRemoteNotes',
168
- () => session.review.importedRemote,
169
- );
170
- bindAlias(
171
- session,
172
- 'updateOffer',
173
- () => session.updater.offer,
174
- (value) => {
175
- session.updater.offer = value;
176
- },
177
- );
178
- bindAlias(session, 'updateFrom', () => session.updater.from);
179
- bindAlias(session, 'updateTo', () => session.updater.to);
180
- bindAlias(session, 'updatePromise', () => session.updater.checkPromise);
181
154
  };
182
155
 
183
156
  const bindViewer = (session) => {
@@ -222,7 +195,7 @@ const bindDispatch = (session) => {
222
195
 
223
196
  const composeSession = (session, options) => {
224
197
  assignOptions(session, options);
225
- session.updater = createUpdateCoordinator(session);
198
+ session.updater = new UpdateCoordinator(session);
226
199
  assignComposer(session);
227
200
  assignControllers(session);
228
201
  bindSessionAccessors(session);
@@ -254,7 +227,7 @@ class Session {
254
227
  this.done = false;
255
228
  this.exitCode = 0;
256
229
  this.mode = 'review';
257
- this.todoItem = makeTodoItem();
230
+ this.tasksItem = makeTaskItem();
258
231
  this.progressFrame = 0;
259
232
  this.emptyReview = false;
260
233
  this.loadError = null;
@@ -297,10 +270,6 @@ class Session {
297
270
  this.lifecycle.ignoreWatch(ms);
298
271
  }
299
272
 
300
- applyLoaded(loaded) {
301
- return this.lifecycle.applyLoaded(loaded);
302
- }
303
-
304
273
  paint() {
305
274
  if (!this.uiOpen) return;
306
275
  this.draw();
@@ -314,18 +283,24 @@ class Session {
314
283
  height: size.height,
315
284
  color: this.color,
316
285
  });
317
- if (
318
- this.pane === 'files' ||
319
- this.pane === 'branches' ||
320
- this.pane === 'commits' ||
321
- this.pane === 'npm'
322
- ) {
286
+ if (LIST_PANES.includes(this.pane)) {
323
287
  this.nav.listScroll = frame.listScroll;
324
288
  }
325
- if (this.nav.todoOpen) this.nav.scroll = frame.scroll;
289
+ if (this.nav.tasksOpen) this.nav.scroll = frame.scroll;
290
+ const editScroll = frame.cursor && frame.cursor.scroll;
291
+ if (this.editor && typeof editScroll === 'number') {
292
+ this.editor.scrollCol = editScroll;
293
+ }
326
294
  const composing = this.mode === 'compose';
327
- const cursor = composing && this.blinkOn ? frame.cursor : null;
328
- this.term.paint(frame, size, cursor);
295
+ this.term.paint(frame, size, composing ? frame.cursor : null);
296
+ this.scheduleAutosave();
297
+ }
298
+
299
+ scheduleAutosave() {
300
+ if (!this.finishReadLoop) return;
301
+ const dirty = this.notes && this.notes.dirty;
302
+ if (!dirty && this.composeKind === null) return;
303
+ this.term.later('save', () => this.autosave(), AUTOSAVE_MS);
329
304
  }
330
305
 
331
306
  view() {
@@ -345,7 +320,7 @@ class Session {
345
320
  }
346
321
 
347
322
  current() {
348
- return this.nav.current(this.items, this.todoItem);
323
+ return this.nav.current(this.items, this.tasksItem);
349
324
  }
350
325
 
351
326
  fileCursorEntry() {
@@ -428,10 +403,6 @@ class Session {
428
403
  this.composer.startDraftCompose();
429
404
  }
430
405
 
431
- tickBlink() {
432
- this.composer.tickBlink();
433
- }
434
-
435
406
  autosave() {
436
407
  this.composer.autosave();
437
408
  }
@@ -536,8 +507,6 @@ class Session {
536
507
  onData,
537
508
  onResize: () => this.draw(),
538
509
  });
539
- this.term.startTimer('save', () => this.autosave(), AUTOSAVE_MS);
540
- this.term.startTimer('blink', () => this.tickBlink(), CURSOR_BLINK_MS);
541
510
  this.lifecycle.startWatch();
542
511
  });
543
512
  }
package/lib/source.js CHANGED
@@ -1,8 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const { parseGithubPrUrl } = require('./github.js');
4
- const { parseGitlabMrUrl } = require('./gitlab.js');
5
- const { capabilitiesFor, attachCapabilities } = require('./capabilities.js');
3
+ const github = require('./github.js');
4
+ const { parseGithubPrUrl } = github;
5
+ const gitlab = require('./gitlab.js');
6
+ const { parseGitlabMrUrl } = gitlab;
7
+ const capabilities = require('./capabilities.js');
8
+ const { capabilitiesFor, attachCapabilities } = capabilities;
6
9
 
7
10
  const selectChangeSource = (paths) => {
8
11
  if (!paths.length) return { kind: 'local', paths };
package/lib/update.js CHANGED
@@ -4,15 +4,11 @@ const fs = require('node:fs');
4
4
  const os = require('node:os');
5
5
  const path = require('node:path');
6
6
 
7
- const { jsonParse, isHashObject } = require('metautil');
8
- const {
9
- parseVersion,
10
- cmpVersion,
11
- npmBin,
12
- npmOpts,
13
- runProc,
14
- oneLine,
15
- } = require('./utilities.js');
7
+ const metautil = require('metautil');
8
+ const { jsonParse, isHashObject } = metautil;
9
+ const utilities = require('./utilities.js');
10
+ const { parseVersion, cmpVersion, npmBin } = utilities;
11
+ const { npmOpts, runProc, oneLine, asText } = utilities;
16
12
 
17
13
  const PKG = require('../package.json');
18
14
 
@@ -32,8 +28,6 @@ const emptyCache = () => ({
32
28
  skipped: '',
33
29
  });
34
30
 
35
- const asText = (value) => (typeof value === 'string' ? value : '');
36
-
37
31
  const toInt = (text) => parseInt(text, 10);
38
32
 
39
33
  const isNpmInstall = (pkgDir) => {
@@ -251,8 +245,6 @@ const checkUpdate = async (options = {}) => {
251
245
 
252
246
  module.exports = {
253
247
  CHECK_INTERVAL_MS,
254
- PKG_NAME,
255
- PKG_VERSION,
256
248
  parseVersion,
257
249
  cmpVersion,
258
250
  isNpmInstall,
package/lib/utilities.js CHANGED
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { spawn, spawnSync } = require('node:child_process');
4
- const { split } = require('metautil');
3
+ const childProcess = require('node:child_process');
4
+ const { spawn, spawnSync } = childProcess;
5
+ const metautil = require('metautil');
6
+ const { split } = metautil;
5
7
 
6
8
  const IS_WIN = process.platform === 'win32';
7
9
  const DEFAULT_ENCODING = 'utf8';
@@ -28,6 +30,23 @@ const cmpVersion = (left, right) =>
28
30
  const oneLine = (text, fallback = '') =>
29
31
  split(`${text ?? ''}`.trim(), '\n')[0] || fallback;
30
32
 
33
+ const asText = (value) => (typeof value === 'string' ? value : '');
34
+
35
+ const unquote = (value) => {
36
+ const text = `${value ?? ''}`;
37
+ if (text.length < 2) return text;
38
+ const open = text[0];
39
+ const quoted = open === text.at(-1) && (open === '"' || open === '\x27');
40
+ return quoted ? text.slice(1, -1) : text;
41
+ };
42
+
43
+ const pad2 = (n) => `${n}`.padStart(2, '0');
44
+
45
+ const dateStamp = (date) => {
46
+ const month = pad2(date.getMonth() + 1);
47
+ return `${date.getFullYear()}-${month}-${pad2(date.getDate())}`;
48
+ };
49
+
31
50
  const npmBin = (platform = process.platform) =>
32
51
  platform === 'win32' ? 'npm.cmd' : 'npm';
33
52
 
@@ -212,6 +231,10 @@ module.exports = {
212
231
  parseVersion,
213
232
  cmpVersion,
214
233
  oneLine,
234
+ asText,
235
+ unquote,
236
+ pad2,
237
+ dateStamp,
215
238
  npmBin,
216
239
  spawnBase,
217
240
  npmOpts,
@@ -220,7 +243,6 @@ module.exports = {
220
243
  watchResize,
221
244
  abortError,
222
245
  isAbort,
223
- procResult,
224
246
  runProcSync,
225
247
  runProc,
226
248
  };