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
@@ -2,209 +2,193 @@
2
2
 
3
3
  const ansi = require('../ansi.js');
4
4
  const wrap = require('../wrap.js');
5
- const files = require('../files.js');
6
5
  const primitives = require('./primitives.js');
6
+ const { wrapMultiline, wrapDoc, cursorInWrap } = wrap;
7
+ const { THEME, paint, visibleWidth, truncateVisible, seq, EL, RESET } = ansi;
8
+ const { isListPane, paneResult, editSpan } = primitives;
7
9
 
8
- const { REPO_TODOS_LABEL } = files;
9
- const { wrapMultiline, cursorInWrap } = wrap;
10
- const { THEME, truncateVisible } = ansi;
11
- const { paintBar, paintCheckBar, windowRows } = primitives;
12
- const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
13
-
14
- const NOTE_COMPOSE = ['feedback'];
15
10
  const INPUT_MAX = 4;
16
11
  const NOTE_MAX = 4;
17
12
  const NOTE_PAD = 1;
13
+ const CHECK_DONE = 'xX';
14
+ const CHECK_TOKEN = /\[([ xX])\]/;
15
+ const CHECK_LEAD = /^\[[ xX]\] /;
16
+ const TODO_LEAD_W = '[ ] '.length;
17
+ const NO_NOTE = { rows: [], cursor: null, templateHits: [], editHits: [] };
18
+
19
+ const paintBar = (text, width, fgRgb, bgRgb, color) => {
20
+ const clipped = truncateVisible(text, width);
21
+ const pad = Math.max(0, width - visibleWidth(clipped));
22
+ const filled = clipped + ' '.repeat(pad);
23
+ if (!color) return filled;
24
+ return `${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
25
+ };
18
26
 
19
- const todoInnerWidth = (width) => Math.max(1, width - 2);
27
+ const checkChip = (mark, fgRgb, bgRgb, plain) => {
28
+ if (plain) return { fgRgb, bgRgb };
29
+ if (CHECK_DONE.includes(mark)) {
30
+ return { fgRgb: THEME.checkDoneFg, bgRgb: THEME.checkDoneBg };
31
+ }
32
+ return { fgRgb: THEME.checkFg, bgRgb: THEME.checkBg };
33
+ };
20
34
 
21
- const checkLeadLen = (text) => {
22
- const match = CHECK_LEAD.exec(text);
23
- return match ? match[0].length : 0;
35
+ const paintCheckBar = (text, width, fgRgb, bgRgb, color, plain = false) => {
36
+ const clipped = truncateVisible(text, width);
37
+ const match = color ? CHECK_TOKEN.exec(clipped) : null;
38
+ if (!match) return paintBar(text, width, fgRgb, bgRgb, color);
39
+ const token = match[0];
40
+ const pad = Math.max(0, width - visibleWidth(clipped));
41
+ const before = clipped.slice(0, match.index);
42
+ const after = clipped.slice(match.index + token.length) + ' '.repeat(pad);
43
+ const chip = checkChip(match[1], fgRgb, bgRgb, plain);
44
+ let out = `${seq(fgRgb, bgRgb)}${EL}`;
45
+ out += paint(before, fgRgb, bgRgb, color);
46
+ out += paint(token, chip.fgRgb, chip.bgRgb, color);
47
+ return out + paint(after, fgRgb, bgRgb, color);
24
48
  };
25
49
 
26
- const TODO_LEAD_W = checkLeadLen('[ ] ');
50
+ const windowRows = (rows, focus, cap) => {
51
+ if (rows.length <= cap) return { rows, offset: 0 };
52
+ const start = Math.min(Math.max(0, focus - cap + 1), rows.length - cap);
53
+ return { rows: rows.slice(start, start + cap), offset: start };
54
+ };
55
+
56
+ const todoInnerWidth = (width) => Math.max(1, width - 2);
27
57
 
28
58
  const todoBodyWidth = (width) =>
29
59
  Math.max(1, todoInnerWidth(width) - TODO_LEAD_W);
30
60
 
31
- const wrapTodoItem = (text, inner) => {
32
- const lead = checkLeadLen(text);
33
- if (!lead) return wrapMultiline(text, inner);
34
- const mark = text.slice(0, lead);
35
- const body = text.slice(lead);
36
- const bodyWidth = Math.max(1, inner - lead);
37
- const hang = ' '.repeat(lead);
38
- const wrapped = wrapMultiline(body, bodyWidth);
39
- const { length } = wrapped;
40
- const lines = new Array(length);
41
- for (let i = 0; i < length; i++) {
42
- const prefix = i === 0 ? mark : hang;
43
- lines[i] = `${prefix}${wrapped[i]}`;
44
- }
45
- return lines;
61
+ const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
62
+
63
+ const todoParts = (text, inner) => {
64
+ const match = CHECK_LEAD.exec(text);
65
+ const lead = match ? match[0].length : 0;
66
+ return { lead, body: text.slice(lead), bodyW: Math.max(1, inner - lead) };
67
+ };
68
+
69
+ const wrapTodoItem = (text, todo) => {
70
+ const mark = text.slice(0, todo.lead);
71
+ const hang = ' '.repeat(todo.lead);
72
+ const lines = wrapMultiline(todo.body, todo.bodyW);
73
+ return lines.map((line, index) => `${index ? hang : mark}${line}`);
74
+ };
75
+
76
+ const todoEdit = (todo, edit, rowStart, width) => {
77
+ const pos = cursorInWrap(todo.body, edit.cursor ?? 0, todo.bodyW);
78
+ const cursor = { x: pos.col + todo.lead + 2, row: rowStart + pos.row };
79
+ const x0 = todo.lead + 1;
80
+ const doc = wrapDoc(todo.body, todo.bodyW);
81
+ const hits = doc.map((piece, line) => ({
82
+ row: rowStart + line,
83
+ ...editSpan(x0, x0 + 1, width + 1, piece.start, piece.text),
84
+ }));
85
+ return { cursor, hits };
46
86
  };
47
87
 
48
- const todoCursor = (text, edit, inner, rowStart) => {
49
- const lead = checkLeadLen(text);
50
- const body = text.slice(lead);
51
- const bodyWidth = Math.max(1, inner - lead);
52
- const pos = cursorInWrap(body, edit.cursor ?? 0, bodyWidth);
53
- return { x: pos.col + lead + 2, row: rowStart + pos.row };
88
+ const taskLists = (view) => {
89
+ const sections = view.taskSections;
90
+ if (sections && sections.length) return sections;
91
+ const tasks = view.tasks ?? [];
92
+ const texts = !tasks.length && view.noteText ? [view.noteText] : tasks;
93
+ return [{ title: '', texts }];
54
94
  };
55
95
 
56
- const paintTodoList = (texts, focus, width, color, edit) => {
96
+ const paintSectionHead = (title, width, color) =>
97
+ paintBar(` ${title}`, width, THEME.mutedFg, THEME.ctxBg, color);
98
+
99
+ const paintBodyTodo = (view, width, color) => {
100
+ const focus = view.tasksFocus ?? 0;
57
101
  const inner = todoInnerWidth(width);
58
- const rows = [];
59
- const owners = [];
60
- const checks = [];
102
+ const body = [];
103
+ const taskOwners = [];
104
+ const taskChecks = [];
105
+ let editHits = [];
61
106
  let cursor = null;
62
- for (let i = 0; i < texts.length; i++) {
63
- const selected = i === focus;
64
- const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
65
- const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
66
- const text = texts[i];
67
- const wrapped = wrapTodoItem(text, inner);
68
- const rowStart = rows.length;
69
- for (let line = 0; line < wrapped.length; line++) {
70
- const painted = paintCheckBar(
71
- ` ${wrapped[line]}`,
72
- width,
73
- fgRgb,
74
- bgRgb,
75
- color,
76
- true,
77
- );
78
- rows.push(painted);
79
- owners.push(i);
80
- checks.push(line === 0);
107
+ let index = 0;
108
+ for (const section of taskLists(view)) {
109
+ if (section.title) {
110
+ body.push(paintSectionHead(section.title, width, color));
111
+ taskOwners.push(null);
112
+ taskChecks.push(false);
113
+ }
114
+ for (const text of section.texts) {
115
+ const selected = index === focus;
116
+ const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
117
+ const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
118
+ const todo = todoParts(text, inner);
119
+ const rowStart = body.length;
120
+ const wrapped = wrapTodoItem(text, todo);
121
+ for (let line = 0; line < wrapped.length; line++) {
122
+ const row = ` ${wrapped[line]}`;
123
+ body.push(paintCheckBar(row, width, fgRgb, bgRgb, color, true));
124
+ taskOwners.push(index);
125
+ taskChecks.push(line === 0);
126
+ }
127
+ if (view.taskEdit && selected) {
128
+ const edit = todoEdit(todo, view.taskEdit, rowStart, width);
129
+ cursor = edit.cursor;
130
+ editHits = edit.hits;
131
+ }
132
+ index += 1;
81
133
  }
82
- if (!edit || !selected) continue;
83
- cursor = todoCursor(text, edit, inner, rowStart);
84
134
  }
85
- return { rows, owners, checks, cursor };
135
+ return paneResult({ body, editHits, taskOwners, taskChecks, cursor });
86
136
  };
87
137
 
88
- const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
89
-
90
- const paintNoteLine = (text, width, fgRgb, bgRgb, color, withCheck) => {
91
- const inner = noteInnerWidth(width);
92
- const clipped = truncateVisible(text, inner);
93
- const line = `${' '.repeat(NOTE_PAD)}${clipped}`;
94
- if (withCheck) return paintCheckBar(line, width, fgRgb, bgRgb, color);
95
- return paintBar(line, width, fgRgb, bgRgb, color);
96
- };
97
-
98
- const paintTemplateRow = (text, selected, width, color) => {
99
- const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
100
- const bgRgb = selected ? THEME.buttonBg : THEME.noteBg;
101
- return paintNoteLine(text, width, fgRgb, bgRgb, color);
102
- };
138
+ const noteLine = (text, width) =>
139
+ `${' '.repeat(NOTE_PAD)}${truncateVisible(text, noteInnerWidth(width))}`;
103
140
 
104
141
  const paintComposeNote = (view, width, color, cap) => {
105
142
  const inner = noteInnerWidth(width);
106
- const bgRgb = THEME.noteBg;
107
- const compose = view.compose;
108
- const wrapped = wrapMultiline(compose.text, inner);
143
+ const { compose } = view;
144
+ const doc = wrapDoc(compose.text ?? '', inner);
109
145
  const { row, col } = cursorInWrap(compose.text, compose.cursor ?? 0, inner);
110
- const shown = windowRows(wrapped, row, INPUT_MAX);
146
+ const shown = windowRows(doc, row, INPUT_MAX);
147
+ const templates = view.templates ?? [];
148
+ const room = Math.max(0, cap - shown.rows.length);
149
+ const count = Math.min(templates.length, room);
150
+ const focus = view.templateIndex ?? -1;
111
151
  const rows = [];
112
152
  const templateHits = [];
113
- if (compose.kind === 'feedback') {
114
- const all = view.templates ?? [];
115
- const room =
116
- cap === undefined ? all.length : Math.max(0, cap - shown.rows.length);
117
- const n = Math.min(all.length, room);
118
- const focus = view.templateIndex ?? -1;
119
- for (let i = 0; i < n; i++) {
120
- const text = all[i].text ?? '';
121
- const selected = i === focus;
122
- rows.push(paintTemplateRow(text, selected, width, color));
123
- templateHits.push({ row: i, cursor: i });
124
- }
153
+ for (let i = 0; i < count; i++) {
154
+ const selected = i === focus;
155
+ const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
156
+ const bgRgb = selected ? THEME.buttonBg : THEME.noteBg;
157
+ const line = noteLine(templates[i].text ?? '', width);
158
+ rows.push(paintBar(line, width, fgRgb, bgRgb, color));
159
+ templateHits.push({ row: i, cursor: i });
125
160
  }
126
- const templateCount = rows.length;
127
- for (const line of shown.rows) {
128
- rows.push(paintNoteLine(line, width, THEME.chromeFg, bgRgb, color));
161
+ const editHits = [];
162
+ for (const piece of shown.rows) {
163
+ const span = editSpan(1, NOTE_PAD + 1, width + 1, piece.start, piece.text);
164
+ editHits.push({ row: rows.length, ...span });
165
+ const line = noteLine(piece.text, width);
166
+ rows.push(paintBar(line, width, THEME.chromeFg, THEME.noteBg, color));
129
167
  }
130
- return {
131
- rows,
132
- cursor: {
133
- x: NOTE_PAD + col + 1,
134
- row: templateCount + row - shown.offset,
135
- },
136
- templateHits,
137
- };
168
+ const cursor = { x: NOTE_PAD + col + 1, row: count + row - shown.offset };
169
+ return { rows, cursor, templateHits, editHits };
138
170
  };
139
171
 
140
172
  const paintIdleNote = (view, width, color) => {
141
- const empty = { rows: [], cursor: null, templateHits: [] };
142
- const text = view.noteText;
143
- if (!text) return empty;
144
- const inner = noteInnerWidth(width);
145
- const wrapped = wrapMultiline(`feedback: ${text}`, inner);
146
- const rows = [];
147
- for (const line of wrapped.slice(0, NOTE_MAX)) {
148
- const painted = paintNoteLine(
149
- line,
150
- width,
151
- THEME.chromeFg,
152
- THEME.noteBg,
153
- color,
154
- true,
155
- );
156
- rows.push(painted);
157
- }
158
- return { rows, cursor: null, templateHits: [] };
173
+ if (!view.noteText) return NO_NOTE;
174
+ const note = `feedback: ${view.noteText}`;
175
+ const wrapped = wrapMultiline(note, noteInnerWidth(width));
176
+ const rows = wrapped.slice(0, NOTE_MAX).map((text) => {
177
+ const line = noteLine(text, width);
178
+ return paintCheckBar(line, width, THEME.chromeFg, THEME.noteBg, color);
179
+ });
180
+ return { rows, cursor: null, templateHits: [], editHits: [] };
159
181
  };
160
182
 
161
183
  const paintNotePanel = (view, width, color, cap) => {
162
- const empty = { rows: [], cursor: null, templateHits: [] };
163
- const compose = view.compose;
164
- if (compose && NOTE_COMPOSE.includes(compose.kind)) {
184
+ if (view.compose?.kind === 'feedback') {
165
185
  return paintComposeNote(view, width, color, cap);
166
186
  }
167
- const pane = view.pane ?? 'diff';
168
- if (LIST_PANES.includes(pane)) return empty;
187
+ if (isListPane(view.pane)) return NO_NOTE;
169
188
  return paintIdleNote(view, width, color);
170
189
  };
171
190
 
172
- const paintTodoCaption = (width, color) =>
173
- paintBar(` ${REPO_TODOS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
174
-
175
- const paintBodyTodo = (view, width, color) => {
176
- const texts = view.todos ?? [];
177
- let lines = texts;
178
- if (!lines.length && view.noteText) lines = [view.noteText];
179
- const painted = paintTodoList(
180
- lines,
181
- view.todoFocus ?? 0,
182
- width,
183
- color,
184
- view.todoEdit,
185
- );
186
- const caption = paintTodoCaption(width, color);
187
- const body = [caption, ...painted.rows];
188
- const todoOwners = [null, ...painted.owners];
189
- const todoChecks = [false, ...painted.checks];
190
- let cursor = null;
191
- if (painted.cursor) {
192
- cursor = { x: painted.cursor.x, row: painted.cursor.row + 1 };
193
- }
194
- const fileHits = [];
195
- const splitBody = false;
196
- return paneResult({
197
- body,
198
- fileHits,
199
- todoOwners,
200
- todoChecks,
201
- cursor,
202
- splitBody,
203
- });
204
- };
205
-
206
191
  module.exports = {
207
- todoInnerWidth,
208
192
  todoBodyWidth,
209
193
  noteInnerWidth,
210
194
  paintNotePanel,