reslop 0.1.4 → 0.1.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.
package/lib/review.js CHANGED
@@ -16,6 +16,8 @@ const FEED_BLOCK_ONLY = /^block (\d+)$/;
16
16
  const FEED_META = /^<!-- reslop:(.*) -->\s*$/;
17
17
  const FEED_SUFFIX = /^(.*) - (.+):(\d+):(\d+):(\d+)$/;
18
18
  const CHECK_ITEM = /^- \[([ xX])\] (.*)$/;
19
+ const CODE_HEAD = /^- \[([ xX])\] code `([^`]+)`\s*$/;
20
+ const FENCE_LINE = /^(`{3,})\s*$/;
19
21
  const FILE_QUOTE = /^> (.+)$/;
20
22
  const STATUS_LINE = /^status:\s*(\S+)\s*$/;
21
23
  const GIT_ORIGINS = ['unstaged', 'staged', 'untracked', 'commit', 'pr'];
@@ -28,6 +30,7 @@ const AGENT_INSTRUCTIONS = [
28
30
  'Execute reviews with `status: ready` or `status: partial`.',
29
31
  'Do not start `editing` or `done` review files.',
30
32
  'Work through unchecked items. After finishing one, mark it `[x]`.',
33
+ 'For `code` items, replace added lines at that location with the fence.',
31
34
  'Set `status` to `partial` if some remain, or `done` if all are `[x]`.',
32
35
  'Do not redo items already marked `[x]`.',
33
36
  ].join('\n');
@@ -169,13 +172,14 @@ const createStore = (reviewPath, templates = []) => ({
169
172
  dirty: false,
170
173
  nextTodoId: 1,
171
174
  feedback: new Map(),
175
+ code: new Map(),
172
176
  todos: [],
173
177
  templates: [...templates],
174
178
  status: 'editing',
175
179
  });
176
180
 
177
181
  const noteCounts = (store) => {
178
- const counts = { feedback: 0, todo: 0 };
182
+ const counts = { feedback: 0, todo: 0, code: 0 };
179
183
  if (!store) return counts;
180
184
  for (const todo of store.todos) {
181
185
  if (todo.text.trim()) counts.todo += 1;
@@ -183,12 +187,13 @@ const noteCounts = (store) => {
183
187
  for (const note of store.feedback.values()) {
184
188
  if (note.text.trim()) counts.feedback += 1;
185
189
  }
190
+ counts.code = store.code.size;
186
191
  return counts;
187
192
  };
188
193
 
189
194
  const hasNotes = (store) => {
190
195
  const counts = noteCounts(store);
191
- return counts.feedback > 0 || counts.todo > 0;
196
+ return counts.feedback > 0 || counts.todo > 0 || counts.code > 0;
192
197
  };
193
198
 
194
199
  const rememberTemplate = (store, prevText, nextText) => {
@@ -224,6 +229,29 @@ const setFeedback = (store, key, note) => {
224
229
  store.dirty = true;
225
230
  };
226
231
 
232
+ const setCode = (store, key, note) => {
233
+ const prev = store.code.get(key);
234
+ if (note.clear) {
235
+ if (!prev) return;
236
+ store.code.delete(key);
237
+ store.dirty = true;
238
+ return;
239
+ }
240
+ const text = note.text ?? '';
241
+ if (prev && prev.text === text) return;
242
+ store.code.set(key, {
243
+ file: note.file,
244
+ oldStart: note.oldStart ?? 0,
245
+ newStart: note.newStart ?? 0,
246
+ blockId: note.blockId ?? 0,
247
+ origin: note.origin ?? '',
248
+ header: note.header ?? '',
249
+ text,
250
+ done: note.done ?? prev?.done ?? false,
251
+ });
252
+ store.dirty = true;
253
+ };
254
+
227
255
  const addTodo = (store, file, text = '') => {
228
256
  const id = store.nextTodoId;
229
257
  store.nextTodoId += 1;
@@ -330,6 +358,20 @@ const storeFeedback = (store, note, key) => {
330
358
  store.feedback.set(key || feedbackKey(next), next);
331
359
  };
332
360
 
361
+ const storeCode = (store, note, key) => {
362
+ const next = {
363
+ file: note.file,
364
+ oldStart: note.oldStart ?? 0,
365
+ newStart: note.newStart ?? 0,
366
+ blockId: note.blockId ?? 0,
367
+ origin: note.origin ?? '',
368
+ header: note.header ?? '',
369
+ text: note.text,
370
+ done: note.done ?? false,
371
+ };
372
+ store.code.set(key || feedbackKey(next), next);
373
+ };
374
+
333
375
  const feedbackLine = (note) => {
334
376
  const loc = feedbackKey(note);
335
377
  return `${checkboxLine(note.text, note.done)} - ${loc}`;
@@ -368,7 +410,7 @@ const parseFeedSuffix = (textItem) => {
368
410
  const fileGroup = (groups, file) => {
369
411
  let group = groups.get(file);
370
412
  if (!group) {
371
- group = { todos: [], feedback: [] };
413
+ group = { todos: [], feedback: [], code: [] };
372
414
  groups.set(file, group);
373
415
  }
374
416
  return group;
@@ -385,6 +427,10 @@ const groupReviewNotes = (store) => {
385
427
  if (!note.text.trim()) continue;
386
428
  fileGroup(groups, note.file).feedback.push({ key, note });
387
429
  }
430
+ for (const key of store.code.keys()) {
431
+ const note = store.code.get(key);
432
+ fileGroup(groups, note.file).code.push({ key, note });
433
+ }
388
434
  return groups;
389
435
  };
390
436
 
@@ -405,6 +451,26 @@ const reviewHeader = (store) => {
405
451
  ];
406
452
  };
407
453
 
454
+ const fenceFor = (text) => {
455
+ let n = 3;
456
+ for (const line of text.split('\n')) {
457
+ const match = /^(`{3,})/.exec(line);
458
+ if (!match) continue;
459
+ if (match[1].length >= n) n = match[1].length + 1;
460
+ }
461
+ return '`'.repeat(n);
462
+ };
463
+
464
+ const emitCodeItem = (lines, note) => {
465
+ const loc = feedbackKey(note);
466
+ const mark = note.done ? 'x' : ' ';
467
+ const fence = fenceFor(note.text);
468
+ lines.push(`- [${mark}] code \`${loc}\``);
469
+ lines.push(fence);
470
+ if (note.text !== '') lines.push(note.text);
471
+ lines.push(fence);
472
+ };
473
+
408
474
  const emitReviewGroups = (lines, groups) => {
409
475
  for (const file of groups.keys()) {
410
476
  const group = groups.get(file);
@@ -416,6 +482,9 @@ const emitReviewGroups = (lines, groups) => {
416
482
  for (const item of group.feedback) {
417
483
  lines.push(feedbackLine(item.note));
418
484
  }
485
+ for (const item of group.code) {
486
+ emitCodeItem(lines, item.note);
487
+ }
419
488
  lines.push('');
420
489
  }
421
490
  };
@@ -543,18 +612,90 @@ const applyReviewLine = (store, file, section, pending, line) => {
543
612
  return { file, section, pending };
544
613
  };
545
614
 
615
+ const startCodePending = (line) => {
616
+ const match = CODE_HEAD.exec(line);
617
+ if (!match) return null;
618
+ const loc = parseLoc(match[2]);
619
+ if (!loc) return null;
620
+ return {
621
+ done: isDoneMark(match[1]),
622
+ origin: loc.origin,
623
+ file: loc.file,
624
+ oldStart: loc.oldStart,
625
+ newStart: loc.newStart,
626
+ blockId: loc.blockId,
627
+ open: '',
628
+ lines: [],
629
+ };
630
+ };
631
+
632
+ const storeCodeNote = (store, pending, text) => {
633
+ storeCode(store, {
634
+ file: pending.file,
635
+ oldStart: pending.oldStart,
636
+ newStart: pending.newStart,
637
+ blockId: pending.blockId,
638
+ origin: pending.origin,
639
+ header: '',
640
+ text,
641
+ done: pending.done,
642
+ });
643
+ };
644
+
645
+ const flushPendingCode = (store, pending) => {
646
+ if (!pending) return;
647
+ const text = pending.open ? pending.lines.join('\n') : '';
648
+ storeCodeNote(store, pending, text);
649
+ };
650
+
651
+ const takeCodeLine = (store, pending, line) => {
652
+ const fence = FENCE_LINE.exec(line);
653
+ if (!pending.open) {
654
+ if (fence) {
655
+ pending.open = fence[1];
656
+ return pending;
657
+ }
658
+ if (!line.trim()) return pending;
659
+ storeCodeNote(store, pending, '');
660
+ return 'fallthrough';
661
+ }
662
+ if (fence && fence[1].length >= pending.open.length) {
663
+ storeCodeNote(store, pending, pending.lines.join('\n'));
664
+ return null;
665
+ }
666
+ pending.lines.push(line);
667
+ return pending;
668
+ };
669
+
546
670
  const parseReview = (text, reviewPath, templates = []) => {
547
671
  const store = createStore(reviewPath, templates);
548
672
  store.status = parseFrontmatterStatus(text) || 'editing';
549
673
  let file = '';
550
674
  let section = '';
551
675
  let pending = null;
676
+ let pendingCode = null;
552
677
  for (const line of text.split('\n')) {
678
+ if (pendingCode) {
679
+ const nextCode = takeCodeLine(store, pendingCode, line);
680
+ if (nextCode === 'fallthrough') {
681
+ pendingCode = null;
682
+ } else {
683
+ pendingCode = nextCode;
684
+ continue;
685
+ }
686
+ }
687
+ const started = startCodePending(line);
688
+ if (started) {
689
+ pending = flushPendingFeedback(store, pending);
690
+ pendingCode = started;
691
+ continue;
692
+ }
553
693
  const next = applyReviewLine(store, file, section, pending, line);
554
694
  file = next.file;
555
695
  section = next.section;
556
696
  pending = next.pending;
557
697
  }
698
+ flushPendingCode(store, pendingCode);
558
699
  flushPendingFeedback(store, pending);
559
700
  store.dirty = false;
560
701
  return store;
@@ -679,6 +820,7 @@ module.exports = {
679
820
  hasNotes,
680
821
  rememberTemplate,
681
822
  setFeedback,
823
+ setCode,
682
824
  addTodo,
683
825
  removeTodo,
684
826
  setTodoText,
package/lib/select.js CHANGED
@@ -7,12 +7,6 @@ const { fg: fgSeq, bg: bgSeq, seq } = ansi;
7
7
  const { stripAnsi, graphemes, graphemeWidth } = ansi;
8
8
 
9
9
  const CSI_FINAL = /[@-~]/;
10
- const SGR_RESET = 0;
11
- const SGR_FG = 38;
12
- const SGR_BG = 48;
13
- const SGR_DEFAULT_FG = 39;
14
- const SGR_DEFAULT_BG = 49;
15
- const SGR_TRUECOLOR = 2;
16
10
 
17
11
  const isRange = (sel) => {
18
12
  if (!sel || !sel.start || !sel.end) return false;
@@ -81,7 +75,8 @@ const parseParams = (body) => {
81
75
  };
82
76
 
83
77
  const applyRgb = (state, field, params, i) => {
84
- const isTruecolor = params[i + 1] === SGR_TRUECOLOR;
78
+ const TRUECOLOR = 2;
79
+ const isTruecolor = params[i + 1] === TRUECOLOR;
85
80
  const hasRgb = isTruecolor && i + 4 < params.length;
86
81
  if (!hasRgb) return 1;
87
82
  state[field] = [params[i + 2], params[i + 3], params[i + 4]];
@@ -89,18 +84,18 @@ const applyRgb = (state, field, params, i) => {
89
84
  };
90
85
 
91
86
  const SGR = {
92
- [SGR_RESET]: (state) => {
87
+ 0: (state) => {
93
88
  state.fg = null;
94
89
  state.bg = null;
95
90
  return 1;
96
91
  },
97
- [SGR_FG]: (state, params, i) => applyRgb(state, 'fg', params, i),
98
- [SGR_BG]: (state, params, i) => applyRgb(state, 'bg', params, i),
99
- [SGR_DEFAULT_FG]: (state) => {
92
+ 38: (state, params, i) => applyRgb(state, 'fg', params, i),
93
+ 48: (state, params, i) => applyRgb(state, 'bg', params, i),
94
+ 39: (state) => {
100
95
  state.fg = null;
101
96
  return 1;
102
97
  },
103
- [SGR_DEFAULT_BG]: (state) => {
98
+ 49: (state) => {
104
99
  state.bg = null;
105
100
  return 1;
106
101
  },