reslop 0.2.3 → 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.
- package/CHANGELOG.md +27 -1
- package/README.md +8 -4
- package/bin/reslop.js +4 -2
- package/lib/api.js +2 -1
- package/lib/capabilities.js +0 -4
- package/lib/cli.js +19 -10
- package/lib/clipboard.js +45 -4
- package/lib/deps-diff.js +61 -118
- package/lib/deps.js +166 -235
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +68 -2
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +15 -30
- package/lib/editor.js +91 -13
- package/lib/files.js +54 -27
- package/lib/git-branches.js +107 -154
- package/lib/git-deps.js +78 -155
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +9 -8
- package/lib/gitlab.js +5 -4
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/core.js +11 -0
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +3 -12
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +5 -11
- package/lib/highlight/txt.js +3 -12
- package/lib/highlight.js +2 -1
- package/lib/keys.js +22 -12
- package/lib/manifest.js +4 -6
- package/lib/npm-commands.js +82 -24
- package/lib/remote.js +21 -20
- package/lib/render/branches.js +9 -27
- package/lib/render/chrome.js +109 -35
- package/lib/render/commits.js +216 -216
- package/lib/render/diff.js +160 -47
- package/lib/render/files.js +9 -26
- package/lib/render/frame.js +97 -118
- package/lib/render/notes.js +74 -26
- package/lib/render/npm.js +104 -30
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +128 -52
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +17 -11
- package/lib/report-model.js +6 -4
- package/lib/report-parse.js +8 -14
- package/lib/report-render.js +38 -21
- package/lib/report-run.js +12 -11
- package/lib/report-test.js +45 -51
- package/lib/report-tools.js +44 -25
- package/lib/review-merge.js +102 -131
- package/lib/review.js +69 -78
- package/lib/select.js +52 -0
- package/lib/session/actions.js +72 -87
- package/lib/session/branches.js +18 -52
- package/lib/session/change-actions.js +175 -147
- package/lib/session/commit.js +65 -61
- package/lib/session/compose.js +152 -83
- package/lib/session/dispatch.js +53 -46
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +30 -51
- package/lib/session/items.js +73 -90
- package/lib/session/lifecycle.js +37 -98
- package/lib/session/load.js +52 -57
- package/lib/session/navigation.js +105 -95
- package/lib/session/npm.js +205 -71
- package/lib/session/ops.js +200 -45
- package/lib/session/pointer.js +33 -8
- package/lib/session/progress.js +9 -6
- package/lib/session/review.js +35 -53
- package/lib/session/tasks.js +292 -0
- package/lib/session/terminal.js +17 -19
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -96
- package/lib/session/view.js +45 -16
- package/lib/session/watch.js +47 -27
- package/lib/session.js +66 -97
- package/lib/source.js +6 -3
- package/lib/update.js +5 -13
- package/lib/utilities.js +25 -3
- package/lib/wrap.js +18 -1
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/diff/inline.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const { graphemes } = ansi;
|
|
4
5
|
|
|
5
6
|
const LCS_CELL_LIMIT = 250000;
|
|
6
7
|
const NOISY_TINY_SPANS = 6;
|
|
7
8
|
const WORD_REFINE_MIN = 0.6;
|
|
9
|
+
const WHOLE_MIN_SHARED = 0.25;
|
|
8
10
|
const PIECE_RE = /[A-Za-z_$][\w$]*|\d+|\s+|./g;
|
|
9
11
|
const CAMEL_RE = /[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+|./g;
|
|
10
12
|
|
|
@@ -279,6 +281,65 @@ const spansFromWordOps = (ops) => {
|
|
|
279
281
|
};
|
|
280
282
|
};
|
|
281
283
|
|
|
284
|
+
const opRuns = (ops) => {
|
|
285
|
+
const runs = [];
|
|
286
|
+
for (const op of ops) {
|
|
287
|
+
const eq = op.type === 'eq';
|
|
288
|
+
let run = runs[runs.length - 1];
|
|
289
|
+
if (!run || run.eq !== eq) {
|
|
290
|
+
run = { eq, del: [], add: [] };
|
|
291
|
+
runs.push(run);
|
|
292
|
+
}
|
|
293
|
+
if (op.type !== 'add') run.del.push(op.text);
|
|
294
|
+
if (op.type !== 'del') run.add.push(op.text);
|
|
295
|
+
}
|
|
296
|
+
return runs;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const textSize = (parts) => parts.join('').length;
|
|
300
|
+
|
|
301
|
+
const editSize = (run) => Math.max(textSize(run.del), textSize(run.add));
|
|
302
|
+
|
|
303
|
+
const absorbTinyEquals = (runs) => {
|
|
304
|
+
for (let k = 1; k < runs.length - 1; k++) {
|
|
305
|
+
const [before, eq, after] = runs.slice(k - 1, k + 2);
|
|
306
|
+
if (!eq.eq) continue;
|
|
307
|
+
const size = textSize(eq.del);
|
|
308
|
+
if (size > editSize(before) || size > editSize(after)) continue;
|
|
309
|
+
const del = [...before.del, ...eq.del, ...after.del];
|
|
310
|
+
const add = [...before.add, ...eq.add, ...after.add];
|
|
311
|
+
runs.splice(k - 1, 3, { eq: false, del, add });
|
|
312
|
+
k = 0;
|
|
313
|
+
}
|
|
314
|
+
return runs;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const runOps = (runs) => {
|
|
318
|
+
const ops = [];
|
|
319
|
+
for (const run of runs) {
|
|
320
|
+
if (run.eq) {
|
|
321
|
+
for (const text of run.del) ops.push({ type: 'eq', text });
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
for (const text of run.del) ops.push({ type: 'del', text });
|
|
325
|
+
for (const text of run.add) ops.push({ type: 'add', text });
|
|
326
|
+
}
|
|
327
|
+
return ops;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const cleanOps = (ops) => runOps(absorbTinyEquals(opRuns(ops)));
|
|
331
|
+
|
|
332
|
+
const sharedShare = (spans) => {
|
|
333
|
+
let shared = 0;
|
|
334
|
+
let total = 0;
|
|
335
|
+
for (const span of spans) {
|
|
336
|
+
const size = span.text.replace(/\s/g, '').length;
|
|
337
|
+
total += size;
|
|
338
|
+
if (!span.changed) shared += size;
|
|
339
|
+
}
|
|
340
|
+
return total === 0 ? 1 : shared / total;
|
|
341
|
+
};
|
|
342
|
+
|
|
282
343
|
const diffChars = (oldText, newText) => {
|
|
283
344
|
const a = splitPieces(oldText);
|
|
284
345
|
const b = splitPieces(newText);
|
|
@@ -295,10 +356,15 @@ const diffChars = (oldText, newText) => {
|
|
|
295
356
|
} else if (midA.length * midB.length > LCS_CELL_LIMIT) {
|
|
296
357
|
mid = wholeChanged(midA, midB);
|
|
297
358
|
} else {
|
|
298
|
-
mid = spansFromWordOps(lcsOps(midA, midB));
|
|
359
|
+
mid = spansFromWordOps(cleanOps(lcsOps(midA, midB)));
|
|
299
360
|
}
|
|
300
361
|
const oldSpans = joinAffix(prefix, mid.oldSpans, suffix);
|
|
301
362
|
const newSpans = joinAffix(prefix, mid.newSpans, suffix);
|
|
363
|
+
const oldShare = sharedShare(oldSpans);
|
|
364
|
+
const newShare = sharedShare(newSpans);
|
|
365
|
+
if (oldShare < WHOLE_MIN_SHARED && newShare < WHOLE_MIN_SHARED) {
|
|
366
|
+
return wholeChanged(a, b);
|
|
367
|
+
}
|
|
302
368
|
return { oldSpans, newSpans };
|
|
303
369
|
};
|
|
304
370
|
|
package/lib/diff/patch.js
CHANGED
package/lib/diff/unit.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
3
|
+
const align = require('./align.js');
|
|
4
|
+
const { attachInline } = align;
|
|
5
|
+
const files = require('../files.js');
|
|
6
|
+
const { listPath } = files;
|
|
5
7
|
|
|
6
8
|
const splitEditor = (text) => {
|
|
7
9
|
const lines = `${text}`.split('\n');
|
|
@@ -126,43 +128,26 @@ const sameBlock = (line, item) => {
|
|
|
126
128
|
return line.blockId === item.blockId;
|
|
127
129
|
};
|
|
128
130
|
|
|
129
|
-
const
|
|
130
|
-
let start =
|
|
131
|
+
const changedLineRange = (lines, item, belongs) => {
|
|
132
|
+
let start = -1;
|
|
131
133
|
let end = 0;
|
|
132
|
-
|
|
133
|
-
if (!item) return { start, end };
|
|
134
|
+
if (!item) return { start: 0, end };
|
|
134
135
|
for (let i = 0; i < lines.length; i++) {
|
|
135
136
|
const line = lines[i];
|
|
136
|
-
if (!
|
|
137
|
-
if (
|
|
138
|
-
if (!found) {
|
|
139
|
-
start = i;
|
|
140
|
-
found = true;
|
|
141
|
-
}
|
|
137
|
+
if (line.type === 'ctx' || !belongs(line, item)) continue;
|
|
138
|
+
if (start < 0) start = i;
|
|
142
139
|
end = i;
|
|
143
140
|
}
|
|
144
|
-
return { start, end };
|
|
141
|
+
return { start: Math.max(start, 0), end };
|
|
145
142
|
};
|
|
146
143
|
|
|
144
|
+
const blockLineRange = (lines, item) =>
|
|
145
|
+
changedLineRange(lines, item, sameBlock);
|
|
146
|
+
|
|
147
147
|
const blockLineIndex = (lines, item) => blockLineRange(lines, item).start;
|
|
148
148
|
|
|
149
|
-
const hunkLineRange = (lines, item) =>
|
|
150
|
-
|
|
151
|
-
let end = 0;
|
|
152
|
-
let found = false;
|
|
153
|
-
if (!item) return { start, end };
|
|
154
|
-
for (let i = 0; i < lines.length; i++) {
|
|
155
|
-
const line = lines[i];
|
|
156
|
-
if (!sameHunk(line.item, item)) continue;
|
|
157
|
-
if (line.type === 'ctx') continue;
|
|
158
|
-
if (!found) {
|
|
159
|
-
start = i;
|
|
160
|
-
found = true;
|
|
161
|
-
}
|
|
162
|
-
end = i;
|
|
163
|
-
}
|
|
164
|
-
return { start, end };
|
|
165
|
-
};
|
|
149
|
+
const hunkLineRange = (lines, item) =>
|
|
150
|
+
changedLineRange(lines, item, (line) => sameHunk(line.item, item));
|
|
166
151
|
|
|
167
152
|
const fileOffset = (lines, at) => {
|
|
168
153
|
let offset = 0;
|
package/lib/editor.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
3
|
+
const ansi = require('./ansi.js');
|
|
4
|
+
const { graphemes, visibleWidth } = ansi;
|
|
5
|
+
const wrap = require('./wrap.js');
|
|
6
|
+
const { wrapMove } = wrap;
|
|
5
7
|
|
|
6
8
|
const UNDO_CAP = 100;
|
|
7
9
|
|
|
@@ -59,15 +61,53 @@ class Editor {
|
|
|
59
61
|
this.undo = [];
|
|
60
62
|
this.redo = [];
|
|
61
63
|
this.wantCol = null;
|
|
64
|
+
this.scrollCol = 0;
|
|
65
|
+
this.anchor = null;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
snapshot() {
|
|
65
|
-
return { text: this.text, cursor: this.cursor };
|
|
69
|
+
return { text: this.text, cursor: this.cursor, anchor: this.anchor };
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
restore(snap) {
|
|
69
73
|
this.text = snap.text;
|
|
70
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;
|
|
71
111
|
}
|
|
72
112
|
|
|
73
113
|
edit(nextText, nextCursor) {
|
|
@@ -78,13 +118,17 @@ class Editor {
|
|
|
78
118
|
this.redo = [];
|
|
79
119
|
this.text = nextText;
|
|
80
120
|
this.cursor = nextCursor;
|
|
121
|
+
this.anchor = null;
|
|
81
122
|
}
|
|
82
123
|
|
|
83
124
|
insert(chunk) {
|
|
84
125
|
this.wantCol = null;
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
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);
|
|
88
132
|
}
|
|
89
133
|
|
|
90
134
|
replace(text) {
|
|
@@ -92,8 +136,16 @@ class Editor {
|
|
|
92
136
|
this.edit(text, text.length);
|
|
93
137
|
}
|
|
94
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
|
+
|
|
95
146
|
backspace() {
|
|
96
147
|
this.wantCol = null;
|
|
148
|
+
if (this.removeSpan()) return;
|
|
97
149
|
const { parts, i } = graphemeIndex(this.text, this.cursor);
|
|
98
150
|
if (i === 0) return;
|
|
99
151
|
const drop = parts[i - 1];
|
|
@@ -103,22 +155,27 @@ class Editor {
|
|
|
103
155
|
|
|
104
156
|
delete() {
|
|
105
157
|
this.wantCol = null;
|
|
158
|
+
if (this.removeSpan()) return;
|
|
106
159
|
const { parts, i } = graphemeIndex(this.text, this.cursor);
|
|
107
160
|
if (i >= parts.length) return;
|
|
108
161
|
const nextParts = [...parts.slice(0, i), ...parts.slice(i + 1)];
|
|
109
162
|
this.edit(nextParts.join(''), this.cursor);
|
|
110
163
|
}
|
|
111
164
|
|
|
112
|
-
move(delta) {
|
|
165
|
+
move(delta, extend) {
|
|
113
166
|
this.wantCol = null;
|
|
167
|
+
if (!extend && this.hasSelect()) return void this.collapse(delta);
|
|
168
|
+
if (extend) this.beginSelect();
|
|
114
169
|
const { parts, i } = graphemeIndex(this.text, this.cursor);
|
|
115
170
|
const next = i + delta;
|
|
116
171
|
if (next < 0 || next > parts.length) return;
|
|
117
172
|
this.cursor = offsetAt(parts, next);
|
|
118
173
|
}
|
|
119
174
|
|
|
120
|
-
moveWord(delta) {
|
|
175
|
+
moveWord(delta, extend) {
|
|
121
176
|
this.wantCol = null;
|
|
177
|
+
if (!extend && this.hasSelect()) return void this.collapse(delta);
|
|
178
|
+
if (extend) this.beginSelect();
|
|
122
179
|
const dir = delta < 0 ? -1 : 1;
|
|
123
180
|
const { parts, i } = graphemeIndex(this.text, this.cursor);
|
|
124
181
|
this.cursor = offsetAt(parts, wordIndex(parts, i, dir));
|
|
@@ -132,7 +189,22 @@ class Editor {
|
|
|
132
189
|
return { line, col };
|
|
133
190
|
}
|
|
134
191
|
|
|
135
|
-
|
|
192
|
+
visibleLineCol() {
|
|
193
|
+
const { col, line } = this.linePos();
|
|
194
|
+
const row = this.text.split('\n')[line] ?? '';
|
|
195
|
+
return visibleWidth(row.slice(0, col));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
reveal(width) {
|
|
199
|
+
const col = this.visibleLineCol();
|
|
200
|
+
const room = Math.max(1, width);
|
|
201
|
+
if (col < this.scrollCol) this.scrollCol = col;
|
|
202
|
+
if (col >= this.scrollCol + room) this.scrollCol = col - room + 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
moveLine(delta, width, extend) {
|
|
206
|
+
if (!extend) this.clearSelect();
|
|
207
|
+
else this.beginSelect();
|
|
136
208
|
if (width !== undefined) {
|
|
137
209
|
const col = this.wantCol;
|
|
138
210
|
const moved = wrapMove(this.text, this.cursor, width, delta, col);
|
|
@@ -142,8 +214,10 @@ class Editor {
|
|
|
142
214
|
}
|
|
143
215
|
const rows = this.text.split('\n');
|
|
144
216
|
const { line, col } = this.linePos();
|
|
145
|
-
|
|
146
|
-
if (next < 0
|
|
217
|
+
let next = line + delta;
|
|
218
|
+
if (next < 0) next = 0;
|
|
219
|
+
if (next >= rows.length) next = rows.length - 1;
|
|
220
|
+
if (next === line) return;
|
|
147
221
|
const row = rows[next];
|
|
148
222
|
const c = Math.min(col, row.length);
|
|
149
223
|
let cursor = 0;
|
|
@@ -151,14 +225,18 @@ class Editor {
|
|
|
151
225
|
this.cursor = cursor + c;
|
|
152
226
|
}
|
|
153
227
|
|
|
154
|
-
home() {
|
|
228
|
+
home(extend) {
|
|
155
229
|
this.wantCol = null;
|
|
230
|
+
if (!extend) this.clearSelect();
|
|
231
|
+
else this.beginSelect();
|
|
156
232
|
const { col } = this.linePos();
|
|
157
233
|
this.cursor -= col;
|
|
158
234
|
}
|
|
159
235
|
|
|
160
|
-
end() {
|
|
236
|
+
end(extend) {
|
|
161
237
|
this.wantCol = null;
|
|
238
|
+
if (!extend) this.clearSelect();
|
|
239
|
+
else this.beginSelect();
|
|
162
240
|
const { line, col } = this.linePos();
|
|
163
241
|
const rows = this.text.split('\n');
|
|
164
242
|
const row = rows[line] ?? '';
|
package/lib/files.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const manifest = require('./manifest.js');
|
|
4
|
+
const { listFilePath } = manifest;
|
|
4
5
|
|
|
5
6
|
const REVIEW_DIR = '.review';
|
|
6
7
|
const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
|
|
7
|
-
const
|
|
8
|
-
const
|
|
8
|
+
const TASKS_FILE = 'TODOs';
|
|
9
|
+
const REPO_TASKS_LABEL =
|
|
10
|
+
'Project Tasks: Backlog, Issues, Bug Reports, Feature Requests';
|
|
9
11
|
const TOTAL_LABEL = 'total';
|
|
10
12
|
|
|
11
13
|
const isPathInScope = (rel, paths) => {
|
|
@@ -25,19 +27,19 @@ const itemPath = (item) => {
|
|
|
25
27
|
|
|
26
28
|
const listPath = (item) => listFilePath(itemPath(item));
|
|
27
29
|
|
|
28
|
-
const
|
|
30
|
+
const isTaskItem = (item) => !!(item && item.origin === 'task');
|
|
29
31
|
|
|
30
32
|
const isReadOnlyOrigin = (origin) => origin === 'commit' || origin === 'pr';
|
|
31
33
|
|
|
32
|
-
const
|
|
34
|
+
const isTasksEntry = (entry) => !!(entry && entry.kind === 'tasks');
|
|
33
35
|
|
|
34
36
|
const isTotalEntry = (entry) => !!(entry && entry.kind === 'total');
|
|
35
37
|
|
|
36
|
-
const
|
|
37
|
-
origin: '
|
|
38
|
+
const makeTaskItem = () => ({
|
|
39
|
+
origin: 'task',
|
|
38
40
|
file: {
|
|
39
|
-
oldPath:
|
|
40
|
-
newPath:
|
|
41
|
+
oldPath: TASKS_FILE,
|
|
42
|
+
newPath: TASKS_FILE,
|
|
41
43
|
isNew: false,
|
|
42
44
|
isDeleted: false,
|
|
43
45
|
isBinary: false,
|
|
@@ -45,7 +47,7 @@ const makeTodoItem = () => ({
|
|
|
45
47
|
hunks: [],
|
|
46
48
|
},
|
|
47
49
|
hunk: null,
|
|
48
|
-
blockId: '
|
|
50
|
+
blockId: 'task',
|
|
49
51
|
patchAdd: '',
|
|
50
52
|
patchRevert: '',
|
|
51
53
|
});
|
|
@@ -73,23 +75,47 @@ const lineDelta = ({ hunk, blockId }) => {
|
|
|
73
75
|
return delta;
|
|
74
76
|
};
|
|
75
77
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
const AGE_UNIT = {
|
|
79
|
+
second: 's',
|
|
80
|
+
minute: 'm',
|
|
81
|
+
hour: 'h',
|
|
82
|
+
day: 'd',
|
|
83
|
+
week: 'w',
|
|
84
|
+
month: 'mo',
|
|
85
|
+
year: 'y',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
89
|
+
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
90
|
+
|
|
91
|
+
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
92
|
+
|
|
93
|
+
const shortAge = (text) => {
|
|
94
|
+
const raw = `${text ?? ''}`.trim();
|
|
95
|
+
if (!raw) return '';
|
|
96
|
+
if (/^yesterday$/i.test(raw)) return '1d ago';
|
|
97
|
+
const match = AGE_AGO.exec(raw);
|
|
98
|
+
if (match) {
|
|
99
|
+
const unit = AGE_UNIT[match[2].toLowerCase()];
|
|
100
|
+
if (unit) return `${match[1]}${unit} ago`;
|
|
101
|
+
}
|
|
102
|
+
const brief = AGE_SHORT.exec(raw);
|
|
103
|
+
if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
|
|
104
|
+
return raw;
|
|
79
105
|
};
|
|
80
106
|
|
|
81
107
|
const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
82
108
|
const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
|
|
83
|
-
if (diff < 90) return
|
|
109
|
+
if (diff < 90) return shortUnit(diff, 'second');
|
|
84
110
|
const minutes = Math.round(diff / 60);
|
|
85
|
-
if (minutes < 90) return
|
|
111
|
+
if (minutes < 90) return shortUnit(minutes, 'minute');
|
|
86
112
|
const hours = Math.round(minutes / 60);
|
|
87
|
-
if (hours < 36) return
|
|
113
|
+
if (hours < 36) return shortUnit(hours, 'hour');
|
|
88
114
|
const days = Math.round(hours / 24);
|
|
89
|
-
if (days < 14) return
|
|
90
|
-
if (days < 70) return
|
|
91
|
-
if (days < 365) return
|
|
92
|
-
return
|
|
115
|
+
if (days < 14) return shortUnit(days, 'day');
|
|
116
|
+
if (days < 70) return shortUnit(Math.round(days / 7), 'week');
|
|
117
|
+
if (days < 365) return shortUnit(Math.round(days / 30), 'month');
|
|
118
|
+
return shortUnit(Math.round(days / 365), 'year');
|
|
93
119
|
};
|
|
94
120
|
|
|
95
121
|
const addLineDelta = (group, origin, delta) => {
|
|
@@ -135,7 +161,7 @@ const fileEntries = (items) => {
|
|
|
135
161
|
const groups = new Map();
|
|
136
162
|
for (let i = 0; i < items.length; i++) {
|
|
137
163
|
const item = items[i];
|
|
138
|
-
if (
|
|
164
|
+
if (isTaskItem(item)) continue;
|
|
139
165
|
const rel = listPath(item);
|
|
140
166
|
if (!rel) continue;
|
|
141
167
|
if (!groups.has(rel)) {
|
|
@@ -192,7 +218,7 @@ const fileTotals = (files) => {
|
|
|
192
218
|
unstaged: 0,
|
|
193
219
|
};
|
|
194
220
|
for (const entry of files) {
|
|
195
|
-
if (
|
|
221
|
+
if (isTasksEntry(entry) || isTotalEntry(entry)) continue;
|
|
196
222
|
const remaining = entry.remaining ?? 0;
|
|
197
223
|
const staged = entry.staged ?? 0;
|
|
198
224
|
const added = entry.added ?? 0;
|
|
@@ -216,19 +242,20 @@ const fileTotals = (files) => {
|
|
|
216
242
|
|
|
217
243
|
module.exports = {
|
|
218
244
|
REVIEW_DIR,
|
|
219
|
-
|
|
220
|
-
|
|
245
|
+
TASKS_FILE,
|
|
246
|
+
REPO_TASKS_LABEL,
|
|
221
247
|
TOTAL_LABEL,
|
|
222
248
|
isPathInScope,
|
|
223
249
|
itemPath,
|
|
224
250
|
listPath,
|
|
225
|
-
|
|
251
|
+
isTaskItem,
|
|
226
252
|
isReadOnlyOrigin,
|
|
227
|
-
|
|
253
|
+
isTasksEntry,
|
|
228
254
|
isTotalEntry,
|
|
229
|
-
|
|
255
|
+
makeTaskItem,
|
|
230
256
|
fileStatus,
|
|
231
257
|
fileEntries,
|
|
232
258
|
fileTotals,
|
|
233
259
|
relativeAge,
|
|
260
|
+
shortAge,
|
|
234
261
|
};
|