reslop 0.2.4 → 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 (67) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +1 -2
  3. package/lib/capabilities.js +0 -4
  4. package/lib/clipboard.js +39 -1
  5. package/lib/deps-diff.js +0 -3
  6. package/lib/deps.js +144 -176
  7. package/lib/diff/inline.js +66 -1
  8. package/lib/diff/unit.js +11 -28
  9. package/lib/editor.js +69 -9
  10. package/lib/files.js +17 -16
  11. package/lib/git-deps.js +58 -95
  12. package/lib/github.js +7 -7
  13. package/lib/gitlab.js +3 -3
  14. package/lib/highlight/core.js +11 -0
  15. package/lib/highlight/dot.js +2 -12
  16. package/lib/highlight/log.js +4 -11
  17. package/lib/highlight/txt.js +2 -12
  18. package/lib/keys.js +22 -12
  19. package/lib/manifest.js +0 -4
  20. package/lib/npm-commands.js +1 -22
  21. package/lib/remote.js +1 -7
  22. package/lib/render/branches.js +4 -13
  23. package/lib/render/chrome.js +85 -31
  24. package/lib/render/commits.js +95 -207
  25. package/lib/render/diff.js +31 -1
  26. package/lib/render/files.js +7 -16
  27. package/lib/render/frame.js +95 -110
  28. package/lib/render/notes.js +71 -16
  29. package/lib/render/npm.js +104 -18
  30. package/lib/render/primitives.js +108 -45
  31. package/lib/render/unit.js +4 -1
  32. package/lib/report-parse.js +0 -1
  33. package/lib/report-run.js +0 -1
  34. package/lib/report-test.js +40 -47
  35. package/lib/report-tools.js +0 -7
  36. package/lib/review-merge.js +101 -131
  37. package/lib/review.js +64 -77
  38. package/lib/select.js +52 -0
  39. package/lib/session/actions.js +23 -22
  40. package/lib/session/branches.js +5 -3
  41. package/lib/session/change-actions.js +173 -146
  42. package/lib/session/commit.js +32 -13
  43. package/lib/session/compose.js +120 -77
  44. package/lib/session/dispatch.js +36 -21
  45. package/lib/session/edit-click.js +57 -0
  46. package/lib/session/files-pane.js +24 -24
  47. package/lib/session/items.js +69 -89
  48. package/lib/session/lifecycle.js +31 -96
  49. package/lib/session/load.js +51 -58
  50. package/lib/session/navigation.js +105 -96
  51. package/lib/session/npm.js +158 -77
  52. package/lib/session/ops.js +192 -51
  53. package/lib/session/pointer.js +11 -5
  54. package/lib/session/progress.js +9 -6
  55. package/lib/session/review.js +35 -54
  56. package/lib/session/tasks.js +292 -0
  57. package/lib/session/terminal.js +13 -17
  58. package/lib/session/unit.js +3 -4
  59. package/lib/session/update.js +76 -97
  60. package/lib/session/view.js +17 -14
  61. package/lib/session/watch.js +47 -27
  62. package/lib/session.js +34 -79
  63. package/lib/update.js +1 -5
  64. package/lib/utilities.js +21 -1
  65. package/lib/wrap.js +15 -0
  66. package/package.json +1 -1
  67. package/lib/session/todos.js +0 -293
package/lib/editor.js CHANGED
@@ -62,15 +62,52 @@ class Editor {
62
62
  this.redo = [];
63
63
  this.wantCol = null;
64
64
  this.scrollCol = 0;
65
+ this.anchor = null;
65
66
  }
66
67
 
67
68
  snapshot() {
68
- return { text: this.text, cursor: this.cursor };
69
+ return { text: this.text, cursor: this.cursor, anchor: this.anchor };
69
70
  }
70
71
 
71
72
  restore(snap) {
72
73
  this.text = snap.text;
73
74
  this.cursor = snap.cursor;
75
+ this.anchor = snap.anchor ?? null;
76
+ }
77
+
78
+ hasSelect() {
79
+ return this.anchor !== null && this.anchor !== this.cursor;
80
+ }
81
+
82
+ selectedText() {
83
+ if (!this.hasSelect()) return '';
84
+ const lo = Math.min(this.anchor, this.cursor);
85
+ const hi = Math.max(this.anchor, this.cursor);
86
+ return this.text.slice(lo, hi);
87
+ }
88
+
89
+ beginSelect() {
90
+ if (this.anchor === null) this.anchor = this.cursor;
91
+ }
92
+
93
+ clearSelect() {
94
+ this.anchor = null;
95
+ }
96
+
97
+ collapse(delta) {
98
+ const lo = Math.min(this.anchor, this.cursor);
99
+ const hi = Math.max(this.anchor, this.cursor);
100
+ this.anchor = null;
101
+ this.cursor = delta < 0 ? lo : hi;
102
+ }
103
+
104
+ removeSpan() {
105
+ if (!this.hasSelect()) return false;
106
+ const lo = Math.min(this.anchor, this.cursor);
107
+ const hi = Math.max(this.anchor, this.cursor);
108
+ const next = this.text.slice(0, lo) + this.text.slice(hi);
109
+ this.edit(next, lo);
110
+ return true;
74
111
  }
75
112
 
76
113
  edit(nextText, nextCursor) {
@@ -81,13 +118,17 @@ class Editor {
81
118
  this.redo = [];
82
119
  this.text = nextText;
83
120
  this.cursor = nextCursor;
121
+ this.anchor = null;
84
122
  }
85
123
 
86
124
  insert(chunk) {
87
125
  this.wantCol = null;
88
- const before = this.text.slice(0, this.cursor);
89
- const after = this.text.slice(this.cursor);
90
- this.edit(before + chunk + after, this.cursor + chunk.length);
126
+ const selected = this.hasSelect();
127
+ const lo = selected ? Math.min(this.anchor, this.cursor) : this.cursor;
128
+ const hi = selected ? Math.max(this.anchor, this.cursor) : this.cursor;
129
+ const before = this.text.slice(0, lo);
130
+ const after = this.text.slice(hi);
131
+ this.edit(before + chunk + after, lo + chunk.length);
91
132
  }
92
133
 
93
134
  replace(text) {
@@ -95,8 +136,16 @@ class Editor {
95
136
  this.edit(text, text.length);
96
137
  }
97
138
 
139
+ place(offset) {
140
+ const next = Math.max(0, Math.min(offset, this.text.length));
141
+ this.wantCol = null;
142
+ this.anchor = null;
143
+ this.cursor = next;
144
+ }
145
+
98
146
  backspace() {
99
147
  this.wantCol = null;
148
+ if (this.removeSpan()) return;
100
149
  const { parts, i } = graphemeIndex(this.text, this.cursor);
101
150
  if (i === 0) return;
102
151
  const drop = parts[i - 1];
@@ -106,22 +155,27 @@ class Editor {
106
155
 
107
156
  delete() {
108
157
  this.wantCol = null;
158
+ if (this.removeSpan()) return;
109
159
  const { parts, i } = graphemeIndex(this.text, this.cursor);
110
160
  if (i >= parts.length) return;
111
161
  const nextParts = [...parts.slice(0, i), ...parts.slice(i + 1)];
112
162
  this.edit(nextParts.join(''), this.cursor);
113
163
  }
114
164
 
115
- move(delta) {
165
+ move(delta, extend) {
116
166
  this.wantCol = null;
167
+ if (!extend && this.hasSelect()) return void this.collapse(delta);
168
+ if (extend) this.beginSelect();
117
169
  const { parts, i } = graphemeIndex(this.text, this.cursor);
118
170
  const next = i + delta;
119
171
  if (next < 0 || next > parts.length) return;
120
172
  this.cursor = offsetAt(parts, next);
121
173
  }
122
174
 
123
- moveWord(delta) {
175
+ moveWord(delta, extend) {
124
176
  this.wantCol = null;
177
+ if (!extend && this.hasSelect()) return void this.collapse(delta);
178
+ if (extend) this.beginSelect();
125
179
  const dir = delta < 0 ? -1 : 1;
126
180
  const { parts, i } = graphemeIndex(this.text, this.cursor);
127
181
  this.cursor = offsetAt(parts, wordIndex(parts, i, dir));
@@ -148,7 +202,9 @@ class Editor {
148
202
  if (col >= this.scrollCol + room) this.scrollCol = col - room + 1;
149
203
  }
150
204
 
151
- moveLine(delta, width) {
205
+ moveLine(delta, width, extend) {
206
+ if (!extend) this.clearSelect();
207
+ else this.beginSelect();
152
208
  if (width !== undefined) {
153
209
  const col = this.wantCol;
154
210
  const moved = wrapMove(this.text, this.cursor, width, delta, col);
@@ -169,14 +225,18 @@ class Editor {
169
225
  this.cursor = cursor + c;
170
226
  }
171
227
 
172
- home() {
228
+ home(extend) {
173
229
  this.wantCol = null;
230
+ if (!extend) this.clearSelect();
231
+ else this.beginSelect();
174
232
  const { col } = this.linePos();
175
233
  this.cursor -= col;
176
234
  }
177
235
 
178
- end() {
236
+ end(extend) {
179
237
  this.wantCol = null;
238
+ if (!extend) this.clearSelect();
239
+ else this.beginSelect();
180
240
  const { line, col } = this.linePos();
181
241
  const rows = this.text.split('\n');
182
242
  const row = rows[line] ?? '';
package/lib/files.js CHANGED
@@ -5,8 +5,9 @@ const { listFilePath } = manifest;
5
5
 
6
6
  const REVIEW_DIR = '.review';
7
7
  const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
8
- const TODO_FILE = 'TODOs';
9
- const REPO_TODOS_LABEL = 'Project Backlog and Issues';
8
+ const TASKS_FILE = 'TODOs';
9
+ const REPO_TASKS_LABEL =
10
+ 'Project Tasks: Backlog, Issues, Bug Reports, Feature Requests';
10
11
  const TOTAL_LABEL = 'total';
11
12
 
12
13
  const isPathInScope = (rel, paths) => {
@@ -26,19 +27,19 @@ const itemPath = (item) => {
26
27
 
27
28
  const listPath = (item) => listFilePath(itemPath(item));
28
29
 
29
- const isTodoItem = (item) => !!(item && item.origin === 'todo');
30
+ const isTaskItem = (item) => !!(item && item.origin === 'task');
30
31
 
31
32
  const isReadOnlyOrigin = (origin) => origin === 'commit' || origin === 'pr';
32
33
 
33
- const isTodosEntry = (entry) => !!(entry && entry.kind === 'todos');
34
+ const isTasksEntry = (entry) => !!(entry && entry.kind === 'tasks');
34
35
 
35
36
  const isTotalEntry = (entry) => !!(entry && entry.kind === 'total');
36
37
 
37
- const makeTodoItem = () => ({
38
- origin: 'todo',
38
+ const makeTaskItem = () => ({
39
+ origin: 'task',
39
40
  file: {
40
- oldPath: TODO_FILE,
41
- newPath: TODO_FILE,
41
+ oldPath: TASKS_FILE,
42
+ newPath: TASKS_FILE,
42
43
  isNew: false,
43
44
  isDeleted: false,
44
45
  isBinary: false,
@@ -46,7 +47,7 @@ const makeTodoItem = () => ({
46
47
  hunks: [],
47
48
  },
48
49
  hunk: null,
49
- blockId: 'todo',
50
+ blockId: 'task',
50
51
  patchAdd: '',
51
52
  patchRevert: '',
52
53
  });
@@ -160,7 +161,7 @@ const fileEntries = (items) => {
160
161
  const groups = new Map();
161
162
  for (let i = 0; i < items.length; i++) {
162
163
  const item = items[i];
163
- if (isTodoItem(item)) continue;
164
+ if (isTaskItem(item)) continue;
164
165
  const rel = listPath(item);
165
166
  if (!rel) continue;
166
167
  if (!groups.has(rel)) {
@@ -217,7 +218,7 @@ const fileTotals = (files) => {
217
218
  unstaged: 0,
218
219
  };
219
220
  for (const entry of files) {
220
- if (isTodosEntry(entry) || isTotalEntry(entry)) continue;
221
+ if (isTasksEntry(entry) || isTotalEntry(entry)) continue;
221
222
  const remaining = entry.remaining ?? 0;
222
223
  const staged = entry.staged ?? 0;
223
224
  const added = entry.added ?? 0;
@@ -241,17 +242,17 @@ const fileTotals = (files) => {
241
242
 
242
243
  module.exports = {
243
244
  REVIEW_DIR,
244
- TODO_FILE,
245
- REPO_TODOS_LABEL,
245
+ TASKS_FILE,
246
+ REPO_TASKS_LABEL,
246
247
  TOTAL_LABEL,
247
248
  isPathInScope,
248
249
  itemPath,
249
250
  listPath,
250
- isTodoItem,
251
+ isTaskItem,
251
252
  isReadOnlyOrigin,
252
- isTodosEntry,
253
+ isTasksEntry,
253
254
  isTotalEntry,
254
- makeTodoItem,
255
+ makeTaskItem,
255
256
  fileStatus,
256
257
  fileEntries,
257
258
  fileTotals,
package/lib/git-deps.js CHANGED
@@ -127,73 +127,61 @@ const hasDepFile = (items) => {
127
127
  return false;
128
128
  };
129
129
 
130
- const collectAudit = (top) => {
131
- const lock = path.join(top, LOCKFILE);
132
- if (!fs.existsSync(lock)) return null;
133
- const args = ['audit', '--json', '--package-lock-only'];
134
- const text = runNpmJson(top, args, AUDIT_MS);
135
- return parseAuditReport(text);
130
+ const NPM_REPORT = {
131
+ audit: {
132
+ file: LOCKFILE,
133
+ args: ['audit', '--json', '--package-lock-only'],
134
+ parse: parseAuditReport,
135
+ },
136
+ outdated: {
137
+ file: MANIFEST,
138
+ args: ['outdated', '--json', '--long'],
139
+ parse: parseOutdatedReport,
140
+ },
136
141
  };
137
142
 
138
- const collectOutdated = (top) => {
139
- const manifest = path.join(top, MANIFEST);
140
- if (!fs.existsSync(manifest)) return null;
141
- const args = ['outdated', '--json', '--long'];
142
- const text = runNpmJson(top, args, AUDIT_MS);
143
- return parseOutdatedReport(text);
143
+ const collectReport = (top, kind) => {
144
+ const { file, args, parse } = NPM_REPORT[kind];
145
+ if (!fs.existsSync(path.join(top, file))) return null;
146
+ return parse(runNpmJson(top, args, AUDIT_MS));
144
147
  };
145
148
 
146
- const collectAuditAsync = async (top, extra, signal) => {
147
- if (extra.auditMap !== undefined) return extra.auditMap;
148
- const lock = path.join(top, LOCKFILE);
149
- if (!fs.existsSync(lock)) return null;
150
- const args = ['audit', '--json', '--package-lock-only'];
151
- const text = await runNpmJsonAsync(top, args, AUDIT_MS, signal);
152
- return parseAuditReport(text);
149
+ const collectReportAsync = async (top, kind, signal) => {
150
+ const { file, args, parse } = NPM_REPORT[kind];
151
+ if (!fs.existsSync(path.join(top, file))) return null;
152
+ return parse(await runNpmJsonAsync(top, args, AUDIT_MS, signal));
153
153
  };
154
154
 
155
- const collectOutdatedAsync = async (top, extra, signal) => {
156
- if (extra.outdatedMap !== undefined) return extra.outdatedMap;
157
- const manifest = path.join(top, MANIFEST);
158
- if (!fs.existsSync(manifest)) return null;
159
- const args = ['outdated', '--json', '--long'];
160
- const text = await runNpmJsonAsync(top, args, AUDIT_MS, signal);
161
- return parseOutdatedReport(text);
162
- };
163
-
164
- const pickNpmMap = (override, collect, top) => {
165
- if (override !== undefined) return override;
166
- return collect(top);
155
+ const extrasNeeds = (items, rev, extra) => {
156
+ const live = extra.audit === true && !rev;
157
+ const hasDepDiff = hasDepFile(items);
158
+ const audit = live || (Boolean(extra.audit) && hasDepDiff);
159
+ return { live, scan: live || hasDepDiff, audit };
167
160
  };
168
161
 
169
162
  const foldLoaded = (items, top, rev, extra = {}) => {
170
163
  const readSides = (origin, rel) => readDepSides(top, rev, origin, rel);
171
- const live = extra.audit === true && !rev;
172
- const hasDepDiff = hasDepFile(items);
164
+ const needs = extrasNeeds(items, rev, extra);
173
165
  const collect = extra.collect !== false;
174
- let usedNames = extra.usedNames ?? null;
175
- let audit = extra.auditMap;
176
- let outdated = extra.outdatedMap;
177
- let exportEntries = extra.exportEntries;
178
- if (collect && usedNames === null && (hasDepDiff || live)) {
179
- usedNames = collectUsedNames(top);
180
- }
181
- if (collect && exportEntries === undefined && (hasDepDiff || live)) {
166
+ const scan = collect && needs.scan;
167
+ const usedNames = extra.usedNames ?? (scan ? collectUsedNames(top) : null);
168
+ let { exportEntries, auditMap: audit, outdatedMap: outdated } = extra;
169
+ if (scan && exportEntries === undefined) {
182
170
  exportEntries = collectExportMeta(top);
183
171
  }
184
- if (collect && live) {
185
- audit = pickNpmMap(extra.auditMap, collectAudit, top);
186
- outdated = pickNpmMap(extra.outdatedMap, collectOutdated, top);
187
- } else if (collect && extra.audit && hasDepDiff) {
188
- audit = pickNpmMap(extra.auditMap, collectAudit, top);
172
+ if (collect && needs.audit && audit === undefined) {
173
+ audit = collectReport(top, 'audit');
174
+ }
175
+ if (collect && needs.live && outdated === undefined) {
176
+ outdated = collectReport(top, 'outdated');
189
177
  }
190
- if (audit === undefined) audit = null;
191
- if (outdated === undefined) outdated = null;
178
+ audit ??= null;
179
+ outdated ??= null;
192
180
  const folded = foldDepItems(items, readSides, usedNames, audit, outdated, {
193
181
  root: top,
194
182
  exportEntries,
195
183
  });
196
- if (!live) return folded;
184
+ if (!needs.live) return folded;
197
185
  if (!isPathInScope(MANIFEST, extra.paths ?? [])) return folded;
198
186
  const pkgText = worktreeText(top, MANIFEST);
199
187
  const lockText = worktreeText(top, LOCKFILE);
@@ -201,12 +189,8 @@ const foldLoaded = (items, top, rev, extra = {}) => {
201
189
  return mergeProposedItems(folded, pkgText, outdated, audit, options);
202
190
  };
203
191
 
204
- const needsExtras = (parsed, rev, extra) => {
205
- const live = extra.audit === true && !rev;
206
- if (live) return true;
207
- if (!extra.audit) return false;
208
- return hasDepFile(parsed);
209
- };
192
+ const needsExtras = (parsed, rev, extra) =>
193
+ extrasNeeds(parsed, rev, extra).audit;
210
194
 
211
195
  const finishLoad = (snapshot, extra) => {
212
196
  const options = { ...extra, paths: extra.paths ?? [] };
@@ -219,42 +203,29 @@ const finishLoad = (snapshot, extra) => {
219
203
  return { ...snapshot, items, pending };
220
204
  };
221
205
 
222
- const extrasMetaOf = (parsed, rev, extra, top, signal) => {
223
- const live = extra.audit === true && !rev;
224
- const hasDepDiff = hasDepFile(parsed);
225
- const needScan = live || hasDepDiff;
226
- let usedNames = Promise.resolve(null);
227
- if (needScan) {
228
- usedNames = Promise.resolve().then(() => collectUsedNames(top));
229
- }
230
- let exportEntries = Promise.resolve(undefined);
231
- if (extra.exportEntries !== undefined) {
232
- exportEntries = Promise.resolve(extra.exportEntries);
233
- } else if (needScan) {
234
- exportEntries = Promise.resolve().then(() => collectExportMeta(top));
235
- }
236
- let audit = Promise.resolve(null);
237
- if (live || (extra.audit && hasDepDiff)) {
238
- audit = collectAuditAsync(top, extra, signal);
239
- }
240
- let outdated = Promise.resolve(null);
241
- if (live) {
242
- outdated = collectOutdatedAsync(top, extra, signal);
243
- }
244
- return { usedNames, audit, outdated, exportEntries };
206
+ const later = (fn) => Promise.resolve().then(fn);
207
+
208
+ const reportTask = (wanted, known, collect) => {
209
+ if (!wanted) return null;
210
+ return known === undefined ? collect() : known;
245
211
  };
246
212
 
247
213
  const loadExtras = async (snapshot, extra = {}) => {
248
- const top = snapshot.top;
249
- const rev = snapshot.rev;
250
- const parsed = snapshot.parsed;
251
- const signal = extra.signal;
252
- const tasks = extrasMetaOf(parsed, rev, extra, top, signal);
214
+ const { top, rev, parsed } = snapshot;
215
+ const { signal } = extra;
216
+ const needs = extrasNeeds(parsed, rev, extra);
217
+ const known = extra.exportEntries;
253
218
  const [usedNames, audit, outdated, exportEntries] = await Promise.all([
254
- tasks.usedNames,
255
- tasks.audit,
256
- tasks.outdated,
257
- tasks.exportEntries,
219
+ needs.scan ? later(() => collectUsedNames(top)) : null,
220
+ reportTask(needs.audit, extra.auditMap, () =>
221
+ collectReportAsync(top, 'audit', signal),
222
+ ),
223
+ reportTask(needs.live, extra.outdatedMap, () =>
224
+ collectReportAsync(top, 'outdated', signal),
225
+ ),
226
+ known === undefined && needs.scan
227
+ ? later(() => collectExportMeta(top))
228
+ : known,
258
229
  ]);
259
230
  if (signal && signal.aborted) throw abortError();
260
231
  const folded = foldLoaded(parsed, top, rev, {
@@ -498,25 +469,17 @@ const revertFile = (top, rel, items) => {
498
469
  };
499
470
 
500
471
  module.exports = {
501
- hasProposed,
502
472
  itemsForPath,
503
473
  finishLoad,
504
474
  loadExtras,
505
475
  captureFile,
506
476
  restoreFile,
507
477
  applyProposedUpdate,
508
- applyProposedUpdateAsync,
509
- applyDepChange,
510
478
  addItem,
511
479
  addItemAsync,
512
480
  unstageItem,
513
481
  revertDepItem,
514
482
  revertFile,
515
- AUDIT_MS,
516
483
  INSTALL_MS,
517
- runNpm,
518
- runNpmAsync,
519
- runNpmJson,
520
- runNpmJsonAsync,
521
484
  proposedNpmPlan,
522
485
  };
package/lib/github.js CHANGED
@@ -216,26 +216,26 @@ const summaryToTodo = (record) => {
216
216
 
217
217
  const discussionToNotes = (discussion, items, paths = []) => {
218
218
  const feedback = [];
219
- const todos = [];
219
+ const tasks = [];
220
220
  const resolvedById = discussion.resolvedById ?? new Map();
221
221
  const reviewComments = discussion.reviewComments ?? [];
222
222
  for (const comment of reviewComments) {
223
223
  const note = reviewCommentToNote(comment, items, resolvedById, paths);
224
224
  if (!note) continue;
225
225
  if (note.kind === 'feedback') feedback.push(note);
226
- else todos.push(note);
226
+ else tasks.push(note);
227
227
  }
228
228
  const reviews = discussion.reviews ?? [];
229
229
  for (const review of reviews) {
230
- const todo = summaryToTodo(review);
231
- if (todo) todos.push(todo);
230
+ const task = summaryToTodo(review);
231
+ if (task) tasks.push(task);
232
232
  }
233
233
  const issueComments = discussion.issueComments ?? [];
234
234
  for (const comment of issueComments) {
235
- const todo = summaryToTodo(comment);
236
- if (todo) todos.push(todo);
235
+ const task = summaryToTodo(comment);
236
+ if (task) tasks.push(task);
237
237
  }
238
- return { feedback, todos };
238
+ return { feedback, tasks };
239
239
  };
240
240
 
241
241
  const loadReviewThreads = async (pr, options) => {
package/lib/gitlab.js CHANGED
@@ -161,7 +161,7 @@ const noteToImported = (note, items, paths) => {
161
161
 
162
162
  const discussionToNotes = (discussions, items, paths = []) => {
163
163
  const feedback = [];
164
- const todos = [];
164
+ const tasks = [];
165
165
  const threads = discussions ?? [];
166
166
  for (const discussion of threads) {
167
167
  if (!isRecord(discussion)) continue;
@@ -170,10 +170,10 @@ const discussionToNotes = (discussions, items, paths = []) => {
170
170
  const mapped = noteToImported(note, items, paths);
171
171
  if (!mapped) continue;
172
172
  if (mapped.kind === 'feedback') feedback.push(mapped);
173
- else todos.push(mapped);
173
+ else tasks.push(mapped);
174
174
  }
175
175
  }
176
- return { feedback, todos };
176
+ return { feedback, tasks };
177
177
  };
178
178
 
179
179
  const loadDiscussion = async (mr, options) => {
@@ -41,6 +41,16 @@ const emit = (out, style, text) => {
41
41
  out.push({ text, style: style || 'plain' });
42
42
  };
43
43
 
44
+ const highlightLines = (highlightLine) => (source) => {
45
+ const lines = source.split('\n');
46
+ const out = [];
47
+ for (let i = 0; i < lines.length; i += 1) {
48
+ if (i) emit(out, 'plain', '\n');
49
+ for (const t of highlightLine(lines[i])) out.push(t);
50
+ }
51
+ return out;
52
+ };
53
+
44
54
  const tokensText = (out) => {
45
55
  let s = '';
46
56
  for (const t of out) s += t.text;
@@ -177,6 +187,7 @@ module.exports = {
177
187
  ANY_QUOTE,
178
188
  EXPECT_STYLE,
179
189
  emit,
190
+ highlightLines,
180
191
  tokensText,
181
192
  wordStyle,
182
193
  peekIdent,
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('./core.js');
4
- const { emit } = core;
4
+ const { emit, highlightLines } = core;
5
5
 
6
6
  const highlightDotLine = (line) => {
7
7
  if (!line) return [];
@@ -30,16 +30,6 @@ const highlightDotLine = (line) => {
30
30
  return out;
31
31
  };
32
32
 
33
- const highlight = (source) => {
34
- const lines = source.split('\n');
35
- const out = [];
36
- for (let i = 0; i < lines.length; i += 1) {
37
- if (i) emit(out, 'plain', '\n');
38
- for (const t of highlightDotLine(lines[i])) out.push(t);
39
- }
40
- return out;
41
- };
42
-
43
33
  module.exports = {
44
34
  langs: ['dot'],
45
35
  aliases: {
@@ -51,5 +41,5 @@ module.exports = {
51
41
  yml: 'dot',
52
42
  toml: 'dot',
53
43
  },
54
- highlight,
44
+ highlight: highlightLines(highlightDotLine),
55
45
  };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('./core.js');
4
- const { emit } = core;
4
+ const { emit, highlightLines } = core;
5
5
 
6
6
  const LOG_DATETIME_RE = new RegExp(
7
7
  '^(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}' +
@@ -46,14 +46,7 @@ const highlightLogLine = (line) => {
46
46
  return out;
47
47
  };
48
48
 
49
- const highlight = (source) => {
50
- const lines = source.split('\n');
51
- const out = [];
52
- for (let i = 0; i < lines.length; i += 1) {
53
- if (i) emit(out, 'plain', '\n');
54
- for (const t of highlightLogLine(lines[i])) out.push(t);
55
- }
56
- return out;
49
+ module.exports = {
50
+ langs: ['log'],
51
+ highlight: highlightLines(highlightLogLine),
57
52
  };
58
-
59
- module.exports = { langs: ['log'], highlight };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('./core.js');
4
- const { emit } = core;
4
+ const { emit, highlightLines } = core;
5
5
 
6
6
  const highlightTxtLine = (line) => {
7
7
  if (!line) return [];
@@ -20,18 +20,8 @@ const highlightTxtLine = (line) => {
20
20
  return out;
21
21
  };
22
22
 
23
- const highlight = (source) => {
24
- const lines = source.split('\n');
25
- const out = [];
26
- for (let i = 0; i < lines.length; i += 1) {
27
- if (i) emit(out, 'plain', '\n');
28
- for (const t of highlightTxtLine(lines[i])) out.push(t);
29
- }
30
- return out;
31
- };
32
-
33
23
  module.exports = {
34
24
  langs: ['txt', 'py', 'md'],
35
25
  aliases: { text: 'txt', plain: 'txt', markdown: 'md', python: 'py' },
36
- highlight,
26
+ highlight: highlightLines(highlightTxtLine),
37
27
  };