reslop 0.2.4 → 0.2.6

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 (109) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/LICENSE +1 -1
  3. package/README.md +12 -13
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -12
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +48 -18
  9. package/lib/dashboard/git.js +128 -0
  10. package/lib/dashboard/model.js +332 -0
  11. package/lib/dashboard/npm.js +193 -0
  12. package/lib/dashboard/tree.js +278 -0
  13. package/lib/deps-diff.js +196 -404
  14. package/lib/deps.js +240 -399
  15. package/lib/detect.js +8 -18
  16. package/lib/diff/align.js +41 -113
  17. package/lib/diff/diff.js +12 -43
  18. package/lib/diff/display.js +25 -36
  19. package/lib/diff/edit.js +6 -20
  20. package/lib/diff/inline.js +138 -141
  21. package/lib/diff/items.js +33 -47
  22. package/lib/diff/parse.js +28 -56
  23. package/lib/diff/patch.js +12 -33
  24. package/lib/diff/unit.js +90 -206
  25. package/lib/editor.js +98 -55
  26. package/lib/files.js +86 -103
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +182 -344
  29. package/lib/git-reword-todo.js +5 -13
  30. package/lib/git-worktree.js +141 -212
  31. package/lib/git.js +21 -49
  32. package/lib/github.js +67 -181
  33. package/lib/gitlab.js +40 -90
  34. package/lib/highlight/bash.js +55 -177
  35. package/lib/highlight/core.js +157 -132
  36. package/lib/highlight/css.js +25 -58
  37. package/lib/highlight/csv.js +19 -37
  38. package/lib/highlight/dart.js +72 -213
  39. package/lib/highlight/dot.js +16 -26
  40. package/lib/highlight/html.js +42 -63
  41. package/lib/highlight/js.js +177 -506
  42. package/lib/highlight/json.js +17 -37
  43. package/lib/highlight/log.js +22 -35
  44. package/lib/highlight/txt.js +9 -22
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +62 -85
  47. package/lib/manifest.js +67 -130
  48. package/lib/npm-commands.js +119 -189
  49. package/lib/remote.js +141 -197
  50. package/lib/render/branches.js +66 -73
  51. package/lib/render/chrome.js +264 -323
  52. package/lib/render/commits.js +187 -500
  53. package/lib/render/dash-activity.js +212 -0
  54. package/lib/render/dash-blocks.js +245 -0
  55. package/lib/render/dash-table.js +290 -0
  56. package/lib/render/dashboard.js +62 -0
  57. package/lib/render/diff.js +123 -228
  58. package/lib/render/files.js +45 -99
  59. package/lib/render/frame.js +129 -183
  60. package/lib/render/notes.js +144 -160
  61. package/lib/render/npm.js +234 -112
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +131 -170
  64. package/lib/render/render.js +5 -14
  65. package/lib/render/report.js +43 -0
  66. package/lib/render/tiles.js +260 -0
  67. package/lib/render/unit.js +15 -28
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -38
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -78
  72. package/lib/report-test.js +256 -141
  73. package/lib/report-tools.js +96 -161
  74. package/lib/review-merge.js +114 -152
  75. package/lib/review.js +115 -189
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +89 -75
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +82 -63
  80. package/lib/session/branches.js +193 -247
  81. package/lib/session/change-actions.js +171 -214
  82. package/lib/session/commit.js +280 -342
  83. package/lib/session/compose.js +559 -674
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +164 -157
  86. package/lib/session/edit-click.js +57 -0
  87. package/lib/session/files-pane.js +85 -53
  88. package/lib/session/items.js +73 -104
  89. package/lib/session/lifecycle.js +403 -541
  90. package/lib/session/load.js +48 -68
  91. package/lib/session/navigation.js +96 -87
  92. package/lib/session/npm.js +409 -345
  93. package/lib/session/ops.js +196 -63
  94. package/lib/session/pointer.js +23 -15
  95. package/lib/session/progress.js +33 -21
  96. package/lib/session/review.js +33 -54
  97. package/lib/session/tasks.js +333 -0
  98. package/lib/session/templates.js +51 -59
  99. package/lib/session/terminal.js +85 -136
  100. package/lib/session/unit.js +3 -4
  101. package/lib/session/update.js +76 -97
  102. package/lib/session/view.js +52 -83
  103. package/lib/session/watch.js +208 -204
  104. package/lib/session.js +112 -237
  105. package/lib/update.js +45 -116
  106. package/lib/utilities.js +67 -89
  107. package/lib/wrap.js +33 -44
  108. package/package.json +1 -1
  109. package/lib/session/todos.js +0 -293
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const files = require('../files.js');
4
- const { listPath, isTodosEntry } = files;
4
+ const { listPath, isTasksEntry } = files;
5
5
  const diff = require('../diff/diff.js');
6
6
  const { unitLines, itemsForPath, blockLineIndex } = diff;
7
7
  const { hunkLineRange, sameHunk, hunkKey } = diff;
@@ -150,10 +150,10 @@ const moveUnitBlock = (ui, step) => {
150
150
  };
151
151
 
152
152
  const openUnitFile = (ui, entry) => {
153
- if (!entry || isTodosEntry(entry)) return;
153
+ if (!entry || isTasksEntry(entry)) return;
154
154
  const nav = ui.nav;
155
155
  nav.reviewPath = entry.path;
156
- nav.todoOpen = false;
156
+ nav.tasksOpen = false;
157
157
  nav.pane = 'unit';
158
158
  nav.scroll = 0;
159
159
  nav.unitLine = 0;
@@ -203,6 +203,5 @@ module.exports = {
203
203
  openUnitFile,
204
204
  setFileScope,
205
205
  editCursor,
206
- ensureVisible,
207
206
  followFileCursor,
208
207
  };
@@ -1,113 +1,92 @@
1
1
  'use strict';
2
2
 
3
- const accessors = require('./accessors.js');
4
- const { stateView } = accessors;
5
-
6
3
  const update = require('../update.js');
7
4
  const { checkUpdate, installUpdate, markSkipped } = update;
8
5
 
9
- const optsOf = (api) => {
10
- const value = api.ui.updateOpts;
11
- if (!value || value === true) return {};
12
- return value;
13
- };
14
-
15
- const reset = (state) => {
16
- state.offer = false;
17
- state.from = '';
18
- state.to = '';
19
- state.checkPromise = null;
20
- state.installPromise = null;
21
- };
6
+ class UpdateCoordinator {
7
+ constructor(ui) {
8
+ this.ui = ui;
9
+ this.reset();
10
+ }
22
11
 
23
- const maybePrompt = (api) => {
24
- if (!api.state.offer) return;
25
- if (!api.ui.loader.didLoad) return;
26
- if (api.ui.mode !== 'review') return;
27
- api.ui.mode = 'confirmUpdate';
28
- };
12
+ get opts() {
13
+ const value = this.ui.updateOpts;
14
+ if (!value || value === true) return {};
15
+ return value;
16
+ }
29
17
 
30
- const runInstall = async (api, version) => {
31
- api.ui.status = 'updating reslop';
32
- api.ui.progressFrame = 0;
33
- api.ui.progress.start('install');
34
- api.ui.paint();
35
- let ok = false;
36
- try {
37
- await installUpdate(version, optsOf(api));
38
- ok = true;
39
- } catch {
40
- // ignore install errors
18
+ reset() {
19
+ this.offer = false;
20
+ this.from = '';
21
+ this.to = '';
22
+ this.checkPromise = null;
23
+ this.installPromise = null;
41
24
  }
42
- api.ui.progress.stop('install');
43
- if (api.ui.done || !api.ui.uiOpen) return;
44
- api.ui.status = ok ? 'updated' : 'update failed';
45
- api.ui.paint();
46
- };
47
25
 
48
- const acceptUpdate = (api) => {
49
- api.ui.mode = 'review';
50
- api.state.offer = false;
51
- const version = api.state.to;
52
- api.state.installPromise = runInstall(api, version);
53
- };
26
+ maybePrompt() {
27
+ const { ui } = this;
28
+ if (!this.offer || !ui.loader.didLoad) return;
29
+ if (ui.mode === 'review') ui.mode = 'confirmUpdate';
30
+ }
54
31
 
55
- const declineUpdate = (api) => {
56
- api.ui.mode = 'review';
57
- api.state.offer = false;
58
- api.ui.status = '';
59
- markSkipped(api.state.to, optsOf(api));
60
- };
32
+ async install(version) {
33
+ const { ui } = this;
34
+ ui.status = 'updating reslop';
35
+ ui.progressFrame = 0;
36
+ ui.progress.start('install');
37
+ ui.paint();
38
+ let ok = false;
39
+ try {
40
+ await installUpdate(version, this.opts);
41
+ ok = true;
42
+ } catch {
43
+ // ignore install errors
44
+ }
45
+ ui.progress.stop('install');
46
+ if (ui.done || !ui.uiOpen) return;
47
+ ui.status = ok ? 'updated' : 'update failed';
48
+ ui.paint();
49
+ }
61
50
 
62
- const runOpenUpdate = async (api) => {
63
- let plan;
64
- try {
65
- plan = await checkUpdate(optsOf(api));
66
- } catch {
67
- return;
51
+ accept() {
52
+ this.ui.mode = 'review';
53
+ this.offer = false;
54
+ this.installPromise = this.install(this.to);
68
55
  }
69
- if (api.ui.done) return;
70
- if (plan.action === 'install') {
71
- api.state.installPromise = runInstall(api, plan.latest);
72
- await api.state.installPromise;
73
- return;
56
+
57
+ decline() {
58
+ this.ui.mode = 'review';
59
+ this.offer = false;
60
+ this.ui.status = '';
61
+ markSkipped(this.to, this.opts);
74
62
  }
75
- if (plan.action !== 'confirm') return;
76
- api.state.from = plan.current;
77
- api.state.to = plan.latest;
78
- api.state.offer = true;
79
- maybePrompt(api);
80
- api.ui.paint();
81
- };
82
63
 
83
- const openUpdate = (api) => {
84
- if (api.ui.updateOpts === false) return Promise.resolve();
85
- if (api.state.checkPromise) return api.state.checkPromise;
86
- api.state.checkPromise = runOpenUpdate(api);
87
- return api.state.checkPromise;
88
- };
64
+ async check() {
65
+ let plan;
66
+ try {
67
+ plan = await checkUpdate(this.opts);
68
+ } catch {
69
+ return;
70
+ }
71
+ if (this.ui.done) return;
72
+ if (plan.action === 'install') {
73
+ this.installPromise = this.install(plan.latest);
74
+ await this.installPromise;
75
+ return;
76
+ }
77
+ if (plan.action !== 'confirm') return;
78
+ this.from = plan.current;
79
+ this.to = plan.latest;
80
+ this.offer = true;
81
+ this.maybePrompt();
82
+ this.ui.paint();
83
+ }
89
84
 
90
- const createUpdateCoordinator = (ui) => {
91
- const state = {
92
- offer: false,
93
- from: '',
94
- to: '',
95
- checkPromise: null,
96
- installPromise: null,
97
- };
98
- const api = { ui, state };
99
- return stateView(
100
- state,
101
- {
102
- reset: () => reset(state),
103
- maybePrompt: () => maybePrompt(api),
104
- accept: () => acceptUpdate(api),
105
- decline: () => declineUpdate(api),
106
- open: () => openUpdate(api),
107
- },
108
- ['offer', 'installPromise'],
109
- ['from', 'to', 'checkPromise'],
110
- );
111
- };
85
+ open() {
86
+ if (this.ui.updateOpts === false) return Promise.resolve();
87
+ if (!this.checkPromise) this.checkPromise = this.check();
88
+ return this.checkPromise;
89
+ }
90
+ }
112
91
 
113
- module.exports = { createUpdateCoordinator };
92
+ module.exports = { UpdateCoordinator };
@@ -5,11 +5,12 @@ const path = require('node:path');
5
5
  const files = require('../files.js');
6
6
  const commit = require('./commit.js');
7
7
  const { listedCommits } = commit;
8
- const { fileEntries, fileTotals, REPO_TODOS_LABEL } = files;
8
+ const { fileEntries, fileTotals } = files;
9
9
  const review = require('../review.js');
10
10
  const { noteCounts } = review;
11
11
  const diff = require('../diff/diff.js');
12
12
  const { unitLines, itemsForPath, splitEditor, mergeEditRows } = diff;
13
+ const unit = require('./unit.js');
13
14
 
14
15
  const relabelStatus = (entries, from, to) => {
15
16
  if (!to) return entries;
@@ -19,29 +20,11 @@ const relabelStatus = (entries, from, to) => {
19
20
  });
20
21
  };
21
22
 
22
- const repoNameOf = (api) => {
23
- const { ui } = api;
23
+ const repoNameOf = (ui) => {
24
24
  if (ui.repoName) return ui.repoName;
25
25
  return path.basename(ui.top || ui.cwd || '');
26
26
  };
27
27
 
28
- const todosEntry = (counts) => ({
29
- path: REPO_TODOS_LABEL,
30
- kind: 'todos',
31
- status: 'todos',
32
- remaining: counts.todo,
33
- staged: counts.todoDone,
34
- firstIndex: 0,
35
- openIndex: 0,
36
- added: 0,
37
- removed: 0,
38
- stagedAdded: 0,
39
- stagedRemoved: 0,
40
- unstagedAdded: 0,
41
- unstagedRemoved: 0,
42
- unstaged: 0,
43
- });
44
-
45
28
  const blankEntry = (rel) => ({
46
29
  path: rel,
47
30
  origins: [],
@@ -60,12 +43,12 @@ const blankEntry = (rel) => ({
60
43
  status: '',
61
44
  });
62
45
 
63
- const listedNames = (api) => {
64
- const repo = api.ui.repo;
65
- if (!repo || typeof repo.listFiles !== 'function') return [];
46
+ const listedNames = (ui) => {
47
+ const repo = ui.repo;
48
+ if (!repo.listFiles) return [];
66
49
  try {
67
- return repo.listFiles(api.ui.top || api.ui.cwd, api.ui.paths ?? [], {
68
- commit: api.ui.rev,
50
+ return repo.listFiles(ui.top || ui.cwd, ui.paths ?? [], {
51
+ commit: ui.rev,
69
52
  });
70
53
  } catch {
71
54
  return [];
@@ -85,63 +68,51 @@ const mergeUnitFiles = (entries, names) => {
85
68
  return merged;
86
69
  };
87
70
 
88
- const fileList = (api, notes = noteCounts(api.review.store)) => {
89
- const entries = fileEntries(api.collection.items);
90
- const sourceLabel = api.ui.sourceLabel;
71
+ const fileList = (ui) => {
72
+ const entries = fileEntries(ui.collection.items);
73
+ const sourceLabel = ui.sourceLabel;
91
74
  const from = sourceLabel ? 'pr' : 'commit';
92
- const to = sourceLabel || api.ui.revShort;
75
+ const to = sourceLabel || ui.revShort;
93
76
  const labeled = relabelStatus(entries, from, to);
94
- const names = listedNames(api);
95
- const scoped =
96
- api.nav.fileScope === 'file' ? mergeUnitFiles(labeled, names) : labeled;
97
- scoped.unshift(todosEntry(notes));
98
- return scoped;
77
+ const names = listedNames(ui);
78
+ if (ui.nav.fileScope === 'file') return mergeUnitFiles(labeled, names);
79
+ return labeled;
99
80
  };
100
81
 
101
- const counts = (api, notes = noteCounts(api.review.store)) => {
82
+ const counts = (ui, notes = noteCounts(ui.review.store)) => {
102
83
  const next = {
103
84
  staged: 0,
104
85
  unstaged: 0,
105
86
  untracked: 0,
106
87
  commit: 0,
107
88
  pr: 0,
108
- todo: 0,
89
+ tasks: 0,
109
90
  feedback: 0,
110
91
  code: 0,
111
92
  };
112
- for (const item of api.collection.items) {
93
+ for (const item of ui.collection.items) {
113
94
  if (!Object.hasOwn(next, item.origin)) continue;
114
95
  next[item.origin] += 1;
115
96
  }
116
97
  next.feedback = notes.feedback;
117
- next.todo = notes.todo;
98
+ next.tasks = notes.tasks;
118
99
  next.code = notes.code;
119
100
  return next;
120
101
  };
121
102
 
122
- const viewStatus = (api) => api.ui.status || api.ui.busy;
123
-
124
- const unitFileText = (api) => {
125
- const rel = api.nav.reviewPath;
126
- if (!rel) return '';
127
- const repo = api.ui.repo;
128
- if (repo && typeof repo.fileText === 'function') {
129
- return repo.fileText(api.ui.top, rel, api.ui.rev);
130
- }
131
- return '';
132
- };
103
+ const viewStatus = (ui) => ui.status || ui.busy;
133
104
 
134
- const unitBodyLines = (api) => {
135
- if (api.nav.pane !== 'unit') return [];
136
- const rel = api.nav.reviewPath;
137
- const group = itemsForPath(api.collection.items, rel);
138
- const live = api.composer.liveFileText();
105
+ const unitBodyLines = (ui) => {
106
+ if (ui.nav.pane !== 'unit') return [];
107
+ const rel = ui.nav.reviewPath;
108
+ const group = itemsForPath(ui.collection.items, rel);
109
+ const live = ui.composer.liveFileText();
139
110
  if (live !== null) {
140
- const base = api.composer.fileEditRows();
111
+ const base = ui.composer.fileEditRows;
141
112
  if (base && base.length) return mergeEditRows(base, splitEditor(live));
142
113
  return mergeEditRows(unitLines(live, group), splitEditor(live));
143
114
  }
144
- return unitLines(unitFileText(api), group);
115
+ return unitLines(unit.fileText(ui, rel), group);
145
116
  };
146
117
 
147
118
  const pendingLines = (items, rev, revShort) => {
@@ -152,10 +123,10 @@ const pendingLines = (items, rev, revShort) => {
152
123
  return fileTotals(fileEntries(items));
153
124
  };
154
125
 
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;
126
+ const commitRows = (ui) => {
127
+ const rows = listedCommits(ui.commits.commits);
128
+ const lines = pendingLines(ui.collection.items, ui.rev, ui.revShort);
129
+ if (!lines) return rows;
159
130
  rows[0] = {
160
131
  ...rows[0],
161
132
  added: lines.added,
@@ -168,34 +139,34 @@ const commitRows = (api) => {
168
139
  return rows;
169
140
  };
170
141
 
171
- const view = (api) => {
172
- const { composer, nav, ui, gitBranches, commits, npm, updater } = api;
173
- const templates = composer.shownTemplates();
142
+ const view = (ui) => {
143
+ const { composer, nav, gitBranches, commits, npm, updater } = ui;
144
+ const templates = composer.templates.shownTemplates();
174
145
  const change = ui.change;
175
146
  const sourceKind = (change && change.source) || '';
176
- const notes = noteCounts(api.review.store);
147
+ const notes = noteCounts(ui.review.store);
177
148
  return {
178
149
  item: ui.current(),
179
150
  index: nav.index,
180
- total: api.collection.items.length,
151
+ total: ui.collection.items.length,
181
152
  scroll: nav.scroll,
182
153
  listScroll: nav.listScroll,
183
- status: viewStatus(api),
154
+ status: viewStatus(ui),
184
155
  progressFrame: ui.progressFrame,
185
156
  layout: ui.layout,
186
- counts: counts(api, notes),
157
+ counts: counts(ui, notes),
187
158
  selection: nav.selection,
188
159
  pane: nav.pane,
189
160
  fileScope: nav.fileScope,
190
- files: fileList(api, notes),
161
+ files: fileList(ui),
191
162
  fileCursor: nav.fileCursor,
192
163
  reviewPath: nav.reviewPath,
193
164
  unitLine: nav.unitLine,
194
- unitLines: unitBodyLines(api),
165
+ unitLines: unitBodyLines(ui),
195
166
  branch: ui.branch,
196
167
  branches: gitBranches.branches,
197
168
  branchCursor: nav.branchCursor,
198
- commits: commitRows(api),
169
+ commits: commitRows(ui),
199
170
  commitCursor: nav.commitCursor,
200
171
  commitView: commits.commitView,
201
172
  npmCommands: npm.commands,
@@ -205,9 +176,12 @@ const view = (api) => {
205
176
  npmFollow: npm.followEnd,
206
177
  npmScroll: npm.logScroll,
207
178
  npmEditName: npm.editName,
179
+ npmEditField: npm.editField,
180
+ npmDraftName: npm.draftName,
181
+ npmDraftCommand: npm.draftCommand,
208
182
  npmRunning: npm.running,
209
183
  npmLogs: nav.pane === 'npm' ? npm.logLabel : '',
210
- repoName: repoNameOf(api),
184
+ repoName: repoNameOf(ui),
211
185
  rev: ui.rev || '',
212
186
  revShort: ui.revShort || '',
213
187
  sourceLabel: ui.sourceLabel || '',
@@ -215,23 +189,18 @@ const view = (api) => {
215
189
  mode: ui.mode,
216
190
  compose: composer.composeView(),
217
191
  noteText: composer.idleNoteText(),
218
- todos: composer.todoTexts(),
219
- todoFocus: composer.clampedTodoFocus(),
220
- todoEdit: composer.todoEditView(),
192
+ tasks: composer.tasks.taskTexts(),
193
+ taskSections: composer.tasks.taskSections(),
194
+ tasksFocus: composer.tasks.clampedTaskFocus(),
195
+ taskEdit: composer.tasks.taskEditView(),
221
196
  codeOverlay: composer.codeOverlayView(),
222
197
  templates,
223
- templateIndex: composer.clampedTemplateIndex(templates),
198
+ templateIndex: composer.templates.clampedTemplateIndex(templates),
224
199
  updateFrom: updater.from,
225
200
  updateTo: updater.to,
226
201
  dropName: gitBranches.dropName || commits.dropName || npm.dropName,
202
+ dashboard: nav.pane === 'dashboard' ? ui.dashboard.view() : null,
227
203
  };
228
204
  };
229
205
 
230
- const createView = (api) => ({
231
- fileList: () => fileList(api),
232
- counts: () => counts(api),
233
- viewStatus: () => viewStatus(api),
234
- view: () => view(api),
235
- });
236
-
237
- module.exports = { relabelStatus, createView };
206
+ module.exports = { fileList, counts, viewStatus, view };