reslop 0.2.5 → 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 (105) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -8
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +39 -47
  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 +197 -402
  14. package/lib/deps.js +113 -240
  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 +76 -144
  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 +79 -178
  25. package/lib/editor.js +52 -69
  26. package/lib/files.js +71 -89
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +147 -272
  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 +155 -141
  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 +14 -14
  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 +19 -25
  44. package/lib/highlight/txt.js +7 -10
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +49 -82
  47. package/lib/manifest.js +67 -126
  48. package/lib/npm-commands.js +120 -169
  49. package/lib/remote.js +140 -190
  50. package/lib/render/branches.js +63 -61
  51. package/lib/render/chrome.js +220 -333
  52. package/lib/render/commits.js +160 -361
  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 -258
  58. package/lib/render/files.js +40 -85
  59. package/lib/render/frame.js +78 -117
  60. package/lib/render/notes.js +139 -210
  61. package/lib/render/npm.js +213 -177
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +72 -174
  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 -31
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -37
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -77
  72. package/lib/report-test.js +221 -99
  73. package/lib/report-tools.js +96 -154
  74. package/lib/review-merge.js +33 -41
  75. package/lib/review.js +68 -129
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +53 -91
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +61 -43
  80. package/lib/session/branches.js +166 -222
  81. package/lib/session/change-actions.js +49 -119
  82. package/lib/session/commit.js +274 -355
  83. package/lib/session/compose.js +556 -714
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +162 -170
  86. package/lib/session/files-pane.js +76 -44
  87. package/lib/session/items.js +6 -17
  88. package/lib/session/lifecycle.js +393 -466
  89. package/lib/session/load.js +1 -14
  90. package/lib/session/navigation.js +2 -2
  91. package/lib/session/npm.js +403 -420
  92. package/lib/session/ops.js +5 -13
  93. package/lib/session/pointer.js +16 -14
  94. package/lib/session/progress.js +39 -30
  95. package/lib/session/review.js +1 -3
  96. package/lib/session/tasks.js +276 -235
  97. package/lib/session/templates.js +51 -59
  98. package/lib/session/terminal.js +85 -132
  99. package/lib/session/view.js +47 -81
  100. package/lib/session/watch.js +196 -212
  101. package/lib/session.js +81 -161
  102. package/lib/update.js +44 -111
  103. package/lib/utilities.js +46 -88
  104. package/lib/wrap.js +29 -55
  105. package/package.json +1 -1
@@ -10,152 +10,105 @@ const LEAVE_TERM =
10
10
  '\x1b[?1006l\x1b[?1002l\x1b[?7h\x1b[0 q\x1b[?12l\x1b[?25h\x1b[?1049l';
11
11
 
12
12
  const sameCursor = (a, b) => {
13
- if (!a && !b) return true;
14
- if (!a || !b) return false;
13
+ if (!a || !b) return !a && !b;
15
14
  return a.x === b.x && a.y === b.y;
16
15
  };
17
16
 
18
- const hasTimer = (api, name) => api.timers.has(name);
17
+ const sameSize = (a, b) =>
18
+ a !== null && a.width === b.width && a.height === b.height;
19
+
20
+ class Terminal {
21
+ constructor({ stdin = null, stdout, proc = process }) {
22
+ this.stdin = stdin;
23
+ this.stdout = stdout;
24
+ this.proc = proc;
25
+ this.timers = new Map();
26
+ this.disposers = [];
27
+ this.open = false;
28
+ this.disposed = false;
29
+ this.resetCache();
30
+ }
19
31
 
20
- const cancel = (api, name) => {
21
- const id = api.timers.get(name);
22
- if (id === undefined) return;
23
- clearTimeout(id);
24
- api.timers.delete(name);
25
- };
32
+ resetCache() {
33
+ this.carry = '';
34
+ this.lastFrame = null;
35
+ this.lastCursor = null;
36
+ this.lastSize = null;
37
+ }
26
38
 
27
- const later = (api, name, fn, ms) => {
28
- if (api.state.disposed || api.timers.has(name)) return;
29
- const id = setTimeout(() => {
30
- api.timers.delete(name);
31
- fn();
32
- }, ms);
33
- if (typeof id.unref === 'function') id.unref();
34
- api.timers.set(name, id);
35
- };
39
+ hasTimer(name) {
40
+ return this.timers.has(name);
41
+ }
36
42
 
37
- const clearTimers = (api) => {
38
- for (const name of [...api.timers.keys()]) cancel(api, name);
39
- };
43
+ cancel(name) {
44
+ const id = this.timers.get(name);
45
+ if (id === undefined) return;
46
+ clearTimeout(id);
47
+ this.timers.delete(name);
48
+ }
40
49
 
41
- const stopListening = (api) => {
42
- while (api.disposers.length) {
43
- const stop = api.disposers.pop();
44
- stop();
50
+ later(name, fn, ms) {
51
+ if (this.disposed || this.timers.has(name)) return;
52
+ const id = setTimeout(() => {
53
+ this.timers.delete(name);
54
+ fn();
55
+ }, ms);
56
+ id.unref();
57
+ this.timers.set(name, id);
45
58
  }
46
- };
47
59
 
48
- const enter = (api) => {
49
- if (api.state.disposed) return;
50
- const { stdin, stdout, state } = api;
51
- if (stdin && stdin.setRawMode) stdin.setRawMode(true);
52
- if (stdin && typeof stdin.resume === 'function') stdin.resume();
53
- stdout.write(ENTER_TERM);
54
- state.open = true;
55
- };
60
+ clearTimers() {
61
+ for (const id of this.timers.values()) clearTimeout(id);
62
+ this.timers.clear();
63
+ }
56
64
 
57
- const leave = (api) => {
58
- if (!api.state.open) return;
59
- api.stdout.write(LEAVE_TERM);
60
- if (api.stdin && api.stdin.setRawMode) api.stdin.setRawMode(false);
61
- api.state.open = false;
62
- };
65
+ enter() {
66
+ if (this.disposed) return;
67
+ const stdin = this.stdin;
68
+ if (stdin && stdin.setRawMode) stdin.setRawMode(true);
69
+ if (stdin && stdin.resume) stdin.resume();
70
+ this.stdout.write(ENTER_TERM);
71
+ this.open = true;
72
+ }
63
73
 
64
- const resetCache = (api) => {
65
- api.state.carry = '';
66
- api.state.lastFrame = null;
67
- api.state.lastPresentedCursor = null;
68
- api.state.lastSize = null;
69
- };
74
+ leave() {
75
+ if (!this.open) return;
76
+ this.stdout.write(LEAVE_TERM);
77
+ if (this.stdin && this.stdin.setRawMode) this.stdin.setRawMode(false);
78
+ this.open = false;
79
+ }
70
80
 
71
- const close = (api) => {
72
- stopListening(api);
73
- clearTimers(api);
74
- leave(api);
75
- api.state.disposed = true;
76
- };
81
+ close() {
82
+ this.stopListening();
83
+ this.clearTimers();
84
+ this.leave();
85
+ this.disposed = true;
86
+ }
77
87
 
78
- const startListening = (api, handlers) => {
79
- if (api.state.disposed) return;
80
- const onData = (chunk) => handlers.onData(chunk);
81
- const onResize = () => handlers.onResize();
82
- api.disposers.push(listen(api.stdin, 'data', onData));
83
- api.disposers.push(watchResize(api.stdout, api.proc, onResize));
84
- };
88
+ startListening({ onData, onResize }) {
89
+ if (this.disposed) return;
90
+ this.disposers.push(listen(this.stdin, 'data', onData));
91
+ this.disposers.push(watchResize(this.stdout, this.proc, onResize));
92
+ }
85
93
 
86
- const paint = (api, frame, size, cursor) => {
87
- if (api.state.disposed) return;
88
- const lastSize = api.state.lastSize;
89
- const resized =
90
- !lastSize ||
91
- lastSize.width !== size.width ||
92
- lastSize.height !== size.height;
93
- if (
94
- !resized &&
95
- api.state.lastFrame &&
96
- api.state.lastFrame.text === frame.text
97
- ) {
98
- api.state.lastFrame = frame;
99
- if (sameCursor(api.state.lastPresentedCursor, cursor)) return;
100
- api.state.lastPresentedCursor = cursor;
101
- api.stdout.write(presentCursor(cursor));
102
- return;
94
+ stopListening() {
95
+ while (this.disposers.length) this.disposers.pop()();
103
96
  }
104
- api.state.lastSize = size;
105
- api.state.lastFrame = frame;
106
- api.state.lastPresentedCursor = cursor;
107
- api.stdout.write(presentRows(frame.rows, { clear: resized, cursor }));
108
- };
109
97
 
110
- const createTerminal = (options) => {
111
- const state = {
112
- open: false,
113
- disposed: false,
114
- carry: '',
115
- lastFrame: null,
116
- lastPresentedCursor: null,
117
- lastSize: null,
118
- };
119
- const api = {
120
- stdin: options.stdin ?? null,
121
- stdout: options.stdout,
122
- proc: options.proc ?? process,
123
- timers: new Map(),
124
- disposers: [],
125
- state,
126
- };
127
- return {
128
- get lastFrame() {
129
- return state.lastFrame;
130
- },
131
- get lastSize() {
132
- return state.lastSize;
133
- },
134
- get carry() {
135
- return state.carry;
136
- },
137
- set carry(value) {
138
- state.carry = value;
139
- },
140
- get disposed() {
141
- return state.disposed;
142
- },
143
- enter: () => enter(api),
144
- leave: () => leave(api),
145
- close: () => close(api),
146
- paint: (frame, size, cursor) => paint(api, frame, size, cursor),
147
- startListening: (handlers) => startListening(api, handlers),
148
- stopListening: () => stopListening(api),
149
- later: (name, fn, ms) => later(api, name, fn, ms),
150
- cancel: (name) => cancel(api, name),
151
- clearTimers: () => clearTimers(api),
152
- hasTimer: (name) => hasTimer(api, name),
153
- resetCache: () => resetCache(api),
154
- };
155
- };
98
+ paint(frame, size, cursor) {
99
+ if (this.disposed) return;
100
+ const resized = !sameSize(this.lastSize, size);
101
+ const last = this.lastFrame;
102
+ this.lastFrame = frame;
103
+ if (!resized && last && last.text === frame.text) {
104
+ if (sameCursor(this.lastCursor, cursor)) return;
105
+ this.lastCursor = cursor;
106
+ return void this.stdout.write(presentCursor(cursor));
107
+ }
108
+ this.lastSize = size;
109
+ this.lastCursor = cursor;
110
+ this.stdout.write(presentRows(frame.rows, { clear: resized, cursor }));
111
+ }
112
+ }
156
113
 
157
- module.exports = {
158
- ENTER_TERM,
159
- LEAVE_TERM,
160
- createTerminal,
161
- };
114
+ module.exports = { ENTER_TERM, LEAVE_TERM, Terminal };
@@ -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_TASKS_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 tasksEntry = (counts) => ({
29
- path: REPO_TASKS_LABEL,
30
- kind: 'tasks',
31
- status: 'tasks',
32
- remaining: counts.tasks,
33
- staged: counts.tasksDone,
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,20 +68,18 @@ 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(tasksEntry(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,
@@ -109,7 +90,7 @@ const counts = (api, notes = noteCounts(api.review.store)) => {
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
  }
@@ -119,29 +100,19 @@ const counts = (api, notes = noteCounts(api.review.store)) => {
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,
@@ -210,7 +181,7 @@ const view = (api) => {
210
181
  npmDraftCommand: npm.draftCommand,
211
182
  npmRunning: npm.running,
212
183
  npmLogs: nav.pane === 'npm' ? npm.logLabel : '',
213
- repoName: repoNameOf(api),
184
+ repoName: repoNameOf(ui),
214
185
  rev: ui.rev || '',
215
186
  revShort: ui.revShort || '',
216
187
  sourceLabel: ui.sourceLabel || '',
@@ -218,23 +189,18 @@ const view = (api) => {
218
189
  mode: ui.mode,
219
190
  compose: composer.composeView(),
220
191
  noteText: composer.idleNoteText(),
221
- tasks: composer.taskTexts(),
222
- tasksFocus: composer.clampedTaskFocus(),
223
- taskEdit: composer.taskEditView(),
192
+ tasks: composer.tasks.taskTexts(),
193
+ taskSections: composer.tasks.taskSections(),
194
+ tasksFocus: composer.tasks.clampedTaskFocus(),
195
+ taskEdit: composer.tasks.taskEditView(),
224
196
  codeOverlay: composer.codeOverlayView(),
225
197
  templates,
226
- templateIndex: composer.clampedTemplateIndex(templates),
198
+ templateIndex: composer.templates.clampedTemplateIndex(templates),
227
199
  updateFrom: updater.from,
228
200
  updateTo: updater.to,
229
201
  dropName: gitBranches.dropName || commits.dropName || npm.dropName,
202
+ dashboard: nav.pane === 'dashboard' ? ui.dashboard.view() : null,
230
203
  };
231
204
  };
232
205
 
233
- const createView = (api) => ({
234
- fileList: () => fileList(api),
235
- counts: () => counts(api),
236
- viewStatus: () => viewStatus(api),
237
- view: () => view(api),
238
- });
239
-
240
- module.exports = { createView };
206
+ module.exports = { fileList, counts, viewStatus, view };