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.
- package/CHANGELOG.md +25 -1
- package/LICENSE +1 -1
- package/README.md +12 -13
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -12
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +48 -18
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +196 -404
- package/lib/deps.js +240 -399
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +138 -141
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +90 -206
- package/lib/editor.js +98 -55
- package/lib/files.js +86 -103
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +182 -344
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +157 -132
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +16 -26
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +22 -35
- package/lib/highlight/txt.js +9 -22
- package/lib/highlight.js +17 -46
- package/lib/keys.js +62 -85
- package/lib/manifest.js +67 -130
- package/lib/npm-commands.js +119 -189
- package/lib/remote.js +141 -197
- package/lib/render/branches.js +66 -73
- package/lib/render/chrome.js +264 -323
- package/lib/render/commits.js +187 -500
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -228
- package/lib/render/files.js +45 -99
- package/lib/render/frame.js +129 -183
- package/lib/render/notes.js +144 -160
- package/lib/render/npm.js +234 -112
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +131 -170
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -28
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -38
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -78
- package/lib/report-test.js +256 -141
- package/lib/report-tools.js +96 -161
- package/lib/review-merge.js +114 -152
- package/lib/review.js +115 -189
- package/lib/runs.js +225 -0
- package/lib/select.js +89 -75
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +82 -63
- package/lib/session/branches.js +193 -247
- package/lib/session/change-actions.js +171 -214
- package/lib/session/commit.js +280 -342
- package/lib/session/compose.js +559 -674
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +164 -157
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +85 -53
- package/lib/session/items.js +73 -104
- package/lib/session/lifecycle.js +403 -541
- package/lib/session/load.js +48 -68
- package/lib/session/navigation.js +96 -87
- package/lib/session/npm.js +409 -345
- package/lib/session/ops.js +196 -63
- package/lib/session/pointer.js +23 -15
- package/lib/session/progress.js +33 -21
- package/lib/session/review.js +33 -54
- package/lib/session/tasks.js +333 -0
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -136
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -97
- package/lib/session/view.js +52 -83
- package/lib/session/watch.js +208 -204
- package/lib/session.js +112 -237
- package/lib/update.js +45 -116
- package/lib/utilities.js +67 -89
- package/lib/wrap.js +33 -44
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/editor.js
CHANGED
|
@@ -6,6 +6,7 @@ const wrap = require('./wrap.js');
|
|
|
6
6
|
const { wrapMove } = wrap;
|
|
7
7
|
|
|
8
8
|
const UNDO_CAP = 100;
|
|
9
|
+
const WORD_RE = /[\p{L}\p{N}_]/u;
|
|
9
10
|
|
|
10
11
|
const graphemeIndex = (text, cursor) => {
|
|
11
12
|
const parts = graphemes(text);
|
|
@@ -24,10 +25,10 @@ const offsetAt = (parts, count) => {
|
|
|
24
25
|
return pos;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
const WORD_RE = /[\p{L}\p{N}_]/u;
|
|
28
|
-
|
|
29
28
|
const isWordChar = (ch) => WORD_RE.test(ch);
|
|
30
29
|
|
|
30
|
+
const isSeparator = (ch) => !isWordChar(ch);
|
|
31
|
+
|
|
31
32
|
const skipWhile = (parts, at, dir, pred) => {
|
|
32
33
|
if (dir > 0) {
|
|
33
34
|
while (at < parts.length && pred(parts[at])) at += 1;
|
|
@@ -38,20 +39,10 @@ const skipWhile = (parts, at, dir, pred) => {
|
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
const wordIndex = (parts, at, dir) => {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return skipWhile(parts, at, 1, word);
|
|
46
|
-
}
|
|
47
|
-
at = skipWhile(parts, at, 1, sep);
|
|
48
|
-
return skipWhile(parts, at, 1, word);
|
|
49
|
-
}
|
|
50
|
-
if (at > 0 && word(parts[at - 1])) {
|
|
51
|
-
return skipWhile(parts, at, -1, word);
|
|
52
|
-
}
|
|
53
|
-
at = skipWhile(parts, at, -1, sep);
|
|
54
|
-
return skipWhile(parts, at, -1, word);
|
|
42
|
+
const ahead = dir > 0 ? parts[at] : parts[at - 1];
|
|
43
|
+
const inWord = ahead !== undefined && isWordChar(ahead);
|
|
44
|
+
const start = inWord ? at : skipWhile(parts, at, dir, isSeparator);
|
|
45
|
+
return skipWhile(parts, start, dir, isWordChar);
|
|
55
46
|
};
|
|
56
47
|
|
|
57
48
|
class Editor {
|
|
@@ -62,32 +53,70 @@ class Editor {
|
|
|
62
53
|
this.redo = [];
|
|
63
54
|
this.wantCol = null;
|
|
64
55
|
this.scrollCol = 0;
|
|
56
|
+
this.anchor = null;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
snapshot() {
|
|
68
|
-
return { text: this.text, cursor: this.cursor };
|
|
60
|
+
return { text: this.text, cursor: this.cursor, anchor: this.anchor };
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
restore(snap) {
|
|
72
64
|
this.text = snap.text;
|
|
73
65
|
this.cursor = snap.cursor;
|
|
66
|
+
this.anchor = snap.anchor;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
hasSelect() {
|
|
70
|
+
return this.anchor !== null && this.anchor !== this.cursor;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
span() {
|
|
74
|
+
if (!this.hasSelect()) return { lo: this.cursor, hi: this.cursor };
|
|
75
|
+
const lo = Math.min(this.anchor, this.cursor);
|
|
76
|
+
const hi = Math.max(this.anchor, this.cursor);
|
|
77
|
+
return { lo, hi };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
selectedText() {
|
|
81
|
+
const { lo, hi } = this.span();
|
|
82
|
+
return this.text.slice(lo, hi);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
beginSelect() {
|
|
86
|
+
if (this.anchor === null) this.anchor = this.cursor;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
clearSelect() {
|
|
90
|
+
this.anchor = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
collapse(delta) {
|
|
94
|
+
const { lo, hi } = this.span();
|
|
95
|
+
this.anchor = null;
|
|
96
|
+
this.cursor = delta < 0 ? lo : hi;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
removeSpan() {
|
|
100
|
+
if (!this.hasSelect()) return false;
|
|
101
|
+
const { lo, hi } = this.span();
|
|
102
|
+
this.edit(this.text.slice(0, lo) + this.text.slice(hi), lo);
|
|
103
|
+
return true;
|
|
74
104
|
}
|
|
75
105
|
|
|
76
106
|
edit(nextText, nextCursor) {
|
|
77
107
|
this.undo.push(this.snapshot());
|
|
78
|
-
if (this.undo.length > UNDO_CAP)
|
|
79
|
-
this.undo = this.undo.slice(this.undo.length - UNDO_CAP);
|
|
80
|
-
}
|
|
108
|
+
if (this.undo.length > UNDO_CAP) this.undo.shift();
|
|
81
109
|
this.redo = [];
|
|
82
110
|
this.text = nextText;
|
|
83
111
|
this.cursor = nextCursor;
|
|
112
|
+
this.anchor = null;
|
|
84
113
|
}
|
|
85
114
|
|
|
86
115
|
insert(chunk) {
|
|
87
116
|
this.wantCol = null;
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
this.edit(
|
|
117
|
+
const { lo, hi } = this.span();
|
|
118
|
+
const next = this.text.slice(0, lo) + chunk + this.text.slice(hi);
|
|
119
|
+
this.edit(next, lo + chunk.length);
|
|
91
120
|
}
|
|
92
121
|
|
|
93
122
|
replace(text) {
|
|
@@ -95,33 +124,46 @@ class Editor {
|
|
|
95
124
|
this.edit(text, text.length);
|
|
96
125
|
}
|
|
97
126
|
|
|
127
|
+
place(offset) {
|
|
128
|
+
this.wantCol = null;
|
|
129
|
+
this.anchor = null;
|
|
130
|
+
this.cursor = Math.max(0, Math.min(offset, this.text.length));
|
|
131
|
+
}
|
|
132
|
+
|
|
98
133
|
backspace() {
|
|
99
134
|
this.wantCol = null;
|
|
100
|
-
|
|
135
|
+
if (this.removeSpan()) return;
|
|
136
|
+
const { parts, i, pos } = graphemeIndex(this.text, this.cursor);
|
|
101
137
|
if (i === 0) return;
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
this.edit(
|
|
138
|
+
const size = parts[i - 1].length;
|
|
139
|
+
const next = this.text.slice(0, pos - size) + this.text.slice(pos);
|
|
140
|
+
this.edit(next, this.cursor - size);
|
|
105
141
|
}
|
|
106
142
|
|
|
107
143
|
delete() {
|
|
108
144
|
this.wantCol = null;
|
|
109
|
-
|
|
145
|
+
if (this.removeSpan()) return;
|
|
146
|
+
const { parts, i, pos } = graphemeIndex(this.text, this.cursor);
|
|
110
147
|
if (i >= parts.length) return;
|
|
111
|
-
const
|
|
112
|
-
|
|
148
|
+
const next =
|
|
149
|
+
this.text.slice(0, pos) + this.text.slice(pos + parts[i].length);
|
|
150
|
+
this.edit(next, this.cursor);
|
|
113
151
|
}
|
|
114
152
|
|
|
115
|
-
move(delta) {
|
|
153
|
+
move(delta, extend) {
|
|
116
154
|
this.wantCol = null;
|
|
155
|
+
if (!extend && this.hasSelect()) return void this.collapse(delta);
|
|
156
|
+
if (extend) this.beginSelect();
|
|
117
157
|
const { parts, i } = graphemeIndex(this.text, this.cursor);
|
|
118
158
|
const next = i + delta;
|
|
119
159
|
if (next < 0 || next > parts.length) return;
|
|
120
160
|
this.cursor = offsetAt(parts, next);
|
|
121
161
|
}
|
|
122
162
|
|
|
123
|
-
moveWord(delta) {
|
|
163
|
+
moveWord(delta, extend) {
|
|
124
164
|
this.wantCol = null;
|
|
165
|
+
if (!extend && this.hasSelect()) return void this.collapse(delta);
|
|
166
|
+
if (extend) this.beginSelect();
|
|
125
167
|
const dir = delta < 0 ? -1 : 1;
|
|
126
168
|
const { parts, i } = graphemeIndex(this.text, this.cursor);
|
|
127
169
|
this.cursor = offsetAt(parts, wordIndex(parts, i, dir));
|
|
@@ -129,15 +171,14 @@ class Editor {
|
|
|
129
171
|
|
|
130
172
|
linePos() {
|
|
131
173
|
const before = this.text.slice(0, this.cursor);
|
|
132
|
-
const at = before.lastIndexOf('\n');
|
|
133
174
|
const line = before.split('\n').length - 1;
|
|
134
|
-
const col =
|
|
175
|
+
const col = before.length - before.lastIndexOf('\n') - 1;
|
|
135
176
|
return { line, col };
|
|
136
177
|
}
|
|
137
178
|
|
|
138
179
|
visibleLineCol() {
|
|
139
180
|
const { col, line } = this.linePos();
|
|
140
|
-
const row = this.text.split('\n')[line]
|
|
181
|
+
const row = this.text.split('\n')[line];
|
|
141
182
|
return visibleWidth(row.slice(0, col));
|
|
142
183
|
}
|
|
143
184
|
|
|
@@ -148,55 +189,57 @@ class Editor {
|
|
|
148
189
|
if (col >= this.scrollCol + room) this.scrollCol = col - room + 1;
|
|
149
190
|
}
|
|
150
191
|
|
|
151
|
-
moveLine(delta, width) {
|
|
192
|
+
moveLine(delta, width, extend) {
|
|
193
|
+
if (extend) this.beginSelect();
|
|
194
|
+
else this.clearSelect();
|
|
152
195
|
if (width !== undefined) {
|
|
153
|
-
const
|
|
154
|
-
|
|
196
|
+
const moved = wrapMove(
|
|
197
|
+
this.text,
|
|
198
|
+
this.cursor,
|
|
199
|
+
width,
|
|
200
|
+
delta,
|
|
201
|
+
this.wantCol,
|
|
202
|
+
);
|
|
155
203
|
this.wantCol = moved.col;
|
|
156
204
|
this.cursor = moved.cursor;
|
|
157
205
|
return;
|
|
158
206
|
}
|
|
159
207
|
const rows = this.text.split('\n');
|
|
160
208
|
const { line, col } = this.linePos();
|
|
161
|
-
|
|
162
|
-
if (next < 0) next = 0;
|
|
163
|
-
if (next >= rows.length) next = rows.length - 1;
|
|
209
|
+
const next = Math.min(Math.max(line + delta, 0), rows.length - 1);
|
|
164
210
|
if (next === line) return;
|
|
165
|
-
const row = rows[next];
|
|
166
|
-
const c = Math.min(col, row.length);
|
|
167
211
|
let cursor = 0;
|
|
168
212
|
for (let i = 0; i < next; i++) cursor += rows[i].length + 1;
|
|
169
|
-
this.cursor = cursor +
|
|
213
|
+
this.cursor = cursor + Math.min(col, rows[next].length);
|
|
170
214
|
}
|
|
171
215
|
|
|
172
|
-
home() {
|
|
216
|
+
home(extend) {
|
|
173
217
|
this.wantCol = null;
|
|
174
|
-
|
|
175
|
-
this.
|
|
218
|
+
if (extend) this.beginSelect();
|
|
219
|
+
else this.clearSelect();
|
|
220
|
+
this.cursor -= this.linePos().col;
|
|
176
221
|
}
|
|
177
222
|
|
|
178
|
-
end() {
|
|
223
|
+
end(extend) {
|
|
179
224
|
this.wantCol = null;
|
|
225
|
+
if (extend) this.beginSelect();
|
|
226
|
+
else this.clearSelect();
|
|
180
227
|
const { line, col } = this.linePos();
|
|
181
|
-
|
|
182
|
-
const row = rows[line] ?? '';
|
|
183
|
-
this.cursor += row.length - col;
|
|
228
|
+
this.cursor += this.text.split('\n')[line].length - col;
|
|
184
229
|
}
|
|
185
230
|
|
|
186
231
|
undoEdit() {
|
|
187
232
|
if (!this.undo.length) return;
|
|
188
233
|
this.wantCol = null;
|
|
189
|
-
const prev = this.undo.pop();
|
|
190
234
|
this.redo.push(this.snapshot());
|
|
191
|
-
this.restore(
|
|
235
|
+
this.restore(this.undo.pop());
|
|
192
236
|
}
|
|
193
237
|
|
|
194
238
|
redoEdit() {
|
|
195
239
|
if (!this.redo.length) return;
|
|
196
240
|
this.wantCol = null;
|
|
197
|
-
const next = this.redo.pop();
|
|
198
241
|
this.undo.push(this.snapshot());
|
|
199
|
-
this.restore(
|
|
242
|
+
this.restore(this.redo.pop());
|
|
200
243
|
}
|
|
201
244
|
}
|
|
202
245
|
|
package/lib/files.js
CHANGED
|
@@ -5,9 +5,40 @@ const { listFilePath } = manifest;
|
|
|
5
5
|
|
|
6
6
|
const REVIEW_DIR = '.review';
|
|
7
7
|
const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const TASKS_FILE = 'TODOs';
|
|
9
|
+
const REPO_TASKS_LABEL =
|
|
10
|
+
'Project Tasks: Backlog, Issues, Bug Reports, Feature Requests';
|
|
11
|
+
const TASK_KINDS = [
|
|
12
|
+
{ id: 'backlog', title: 'Backlog' },
|
|
13
|
+
{ id: 'issues', title: 'Issues' },
|
|
14
|
+
{ id: 'bugs', title: 'Bug Reports' },
|
|
15
|
+
{ id: 'features', title: 'Feature Requests' },
|
|
16
|
+
];
|
|
17
|
+
const TASK_KIND_IDS = new Set(TASK_KINDS.map((kind) => kind.id));
|
|
18
|
+
|
|
19
|
+
const taskKind = (todo) => {
|
|
20
|
+
const kind = todo && todo.kind;
|
|
21
|
+
return TASK_KIND_IDS.has(kind) ? kind : 'backlog';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const taskTitle = (id) => {
|
|
25
|
+
for (const kind of TASK_KINDS) {
|
|
26
|
+
if (kind.id === id) return kind.title;
|
|
27
|
+
}
|
|
28
|
+
return TASK_KINDS[0].title;
|
|
29
|
+
};
|
|
10
30
|
const TOTAL_LABEL = 'total';
|
|
31
|
+
const AGE_UNIT = {
|
|
32
|
+
second: 's',
|
|
33
|
+
minute: 'm',
|
|
34
|
+
hour: 'h',
|
|
35
|
+
day: 'd',
|
|
36
|
+
week: 'w',
|
|
37
|
+
month: 'mo',
|
|
38
|
+
year: 'y',
|
|
39
|
+
};
|
|
40
|
+
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
41
|
+
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
11
42
|
|
|
12
43
|
const isPathInScope = (rel, paths) => {
|
|
13
44
|
if (!paths.length) return true;
|
|
@@ -19,26 +50,23 @@ const isPathInScope = (rel, paths) => {
|
|
|
19
50
|
return false;
|
|
20
51
|
};
|
|
21
52
|
|
|
22
|
-
const itemPath = (item) =>
|
|
23
|
-
if (!item || !item.file) return '';
|
|
24
|
-
return item.file.newPath || item.file.oldPath || '';
|
|
25
|
-
};
|
|
53
|
+
const itemPath = (item) => item?.file?.newPath || item?.file?.oldPath || '';
|
|
26
54
|
|
|
27
55
|
const listPath = (item) => listFilePath(itemPath(item));
|
|
28
56
|
|
|
29
|
-
const
|
|
57
|
+
const isTaskItem = (item) => item?.origin === 'task';
|
|
30
58
|
|
|
31
59
|
const isReadOnlyOrigin = (origin) => origin === 'commit' || origin === 'pr';
|
|
32
60
|
|
|
33
|
-
const
|
|
61
|
+
const isTasksEntry = (entry) => entry?.kind === 'tasks';
|
|
34
62
|
|
|
35
|
-
const isTotalEntry = (entry) =>
|
|
63
|
+
const isTotalEntry = (entry) => entry?.kind === 'total';
|
|
36
64
|
|
|
37
|
-
const
|
|
38
|
-
origin: '
|
|
65
|
+
const makeTaskItem = () => ({
|
|
66
|
+
origin: 'task',
|
|
39
67
|
file: {
|
|
40
|
-
oldPath:
|
|
41
|
-
newPath:
|
|
68
|
+
oldPath: TASKS_FILE,
|
|
69
|
+
newPath: TASKS_FILE,
|
|
42
70
|
isNew: false,
|
|
43
71
|
isDeleted: false,
|
|
44
72
|
isBinary: false,
|
|
@@ -46,58 +74,36 @@ const makeTodoItem = () => ({
|
|
|
46
74
|
hunks: [],
|
|
47
75
|
},
|
|
48
76
|
hunk: null,
|
|
49
|
-
blockId: '
|
|
77
|
+
blockId: 'task',
|
|
50
78
|
patchAdd: '',
|
|
51
79
|
patchRevert: '',
|
|
52
80
|
});
|
|
53
81
|
|
|
54
82
|
const fileStatus = (origins) => {
|
|
55
|
-
const
|
|
56
|
-
const seen = ORIGIN_ORDER.filter((origin) => present.has(origin));
|
|
83
|
+
const seen = ORIGIN_ORDER.filter((origin) => origins.includes(origin));
|
|
57
84
|
const key = seen.join('+');
|
|
58
|
-
|
|
59
|
-
if (key === 'staged+unstaged') return 'partial';
|
|
60
|
-
return key;
|
|
85
|
+
return key === 'staged+unstaged' ? 'partial' : key;
|
|
61
86
|
};
|
|
62
87
|
|
|
63
88
|
const lineDelta = ({ hunk, blockId }) => {
|
|
64
89
|
const delta = { added: 0, removed: 0 };
|
|
65
|
-
if (!hunk) return delta;
|
|
66
|
-
const { lines } = hunk;
|
|
67
|
-
if (!lines) return delta;
|
|
90
|
+
if (!hunk || !hunk.lines) return delta;
|
|
68
91
|
const scoped = typeof blockId === 'number';
|
|
69
|
-
for (const
|
|
70
|
-
if (scoped &&
|
|
71
|
-
if (type === 'add') delta.added += 1;
|
|
72
|
-
else if (type === 'del') delta.removed += 1;
|
|
92
|
+
for (const line of hunk.lines) {
|
|
93
|
+
if (scoped && line.blockId !== blockId) continue;
|
|
94
|
+
if (line.type === 'add') delta.added += 1;
|
|
95
|
+
else if (line.type === 'del') delta.removed += 1;
|
|
73
96
|
}
|
|
74
97
|
return delta;
|
|
75
98
|
};
|
|
76
99
|
|
|
77
|
-
const AGE_UNIT = {
|
|
78
|
-
second: 's',
|
|
79
|
-
minute: 'm',
|
|
80
|
-
hour: 'h',
|
|
81
|
-
day: 'd',
|
|
82
|
-
week: 'w',
|
|
83
|
-
month: 'mo',
|
|
84
|
-
year: 'y',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
88
|
-
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
89
|
-
|
|
90
100
|
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
91
101
|
|
|
92
102
|
const shortAge = (text) => {
|
|
93
103
|
const raw = `${text ?? ''}`.trim();
|
|
94
|
-
if (!raw) return '';
|
|
95
104
|
if (/^yesterday$/i.test(raw)) return '1d ago';
|
|
96
105
|
const match = AGE_AGO.exec(raw);
|
|
97
|
-
if (match)
|
|
98
|
-
const unit = AGE_UNIT[match[2].toLowerCase()];
|
|
99
|
-
if (unit) return `${match[1]}${unit} ago`;
|
|
100
|
-
}
|
|
106
|
+
if (match) return shortUnit(match[1], match[2].toLowerCase());
|
|
101
107
|
const brief = AGE_SHORT.exec(raw);
|
|
102
108
|
if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
|
|
103
109
|
return raw;
|
|
@@ -117,6 +123,18 @@ const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
|
117
123
|
return shortUnit(Math.round(days / 365), 'year');
|
|
118
124
|
};
|
|
119
125
|
|
|
126
|
+
const emptyCounts = () => ({
|
|
127
|
+
remaining: 0,
|
|
128
|
+
added: 0,
|
|
129
|
+
removed: 0,
|
|
130
|
+
stagedAdded: 0,
|
|
131
|
+
stagedRemoved: 0,
|
|
132
|
+
unstagedAdded: 0,
|
|
133
|
+
unstagedRemoved: 0,
|
|
134
|
+
staged: 0,
|
|
135
|
+
unstaged: 0,
|
|
136
|
+
});
|
|
137
|
+
|
|
120
138
|
const addLineDelta = (group, origin, delta) => {
|
|
121
139
|
group.added += delta.added;
|
|
122
140
|
group.removed += delta.removed;
|
|
@@ -140,19 +158,12 @@ const originLineDelta = (entry) => {
|
|
|
140
158
|
}
|
|
141
159
|
const added = entry.added ?? 0;
|
|
142
160
|
const removed = entry.removed ?? 0;
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
stagedAdded: added,
|
|
146
|
-
stagedRemoved: removed,
|
|
147
|
-
unstagedAdded: 0,
|
|
148
|
-
unstagedRemoved: 0,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
161
|
+
const staged = entry.status === 'staged';
|
|
151
162
|
return {
|
|
152
|
-
stagedAdded: 0,
|
|
153
|
-
stagedRemoved: 0,
|
|
154
|
-
unstagedAdded: added,
|
|
155
|
-
unstagedRemoved: removed,
|
|
163
|
+
stagedAdded: staged ? added : 0,
|
|
164
|
+
stagedRemoved: staged ? removed : 0,
|
|
165
|
+
unstagedAdded: staged ? 0 : added,
|
|
166
|
+
unstagedRemoved: staged ? 0 : removed,
|
|
156
167
|
};
|
|
157
168
|
};
|
|
158
169
|
|
|
@@ -160,26 +171,12 @@ const fileEntries = (items) => {
|
|
|
160
171
|
const groups = new Map();
|
|
161
172
|
for (let i = 0; i < items.length; i++) {
|
|
162
173
|
const item = items[i];
|
|
163
|
-
if (
|
|
174
|
+
if (isTaskItem(item)) continue;
|
|
164
175
|
const rel = listPath(item);
|
|
165
176
|
if (!rel) continue;
|
|
166
177
|
if (!groups.has(rel)) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
origins: [],
|
|
170
|
-
firstIndex: i,
|
|
171
|
-
openIndex: i,
|
|
172
|
-
remaining: 0,
|
|
173
|
-
added: 0,
|
|
174
|
-
removed: 0,
|
|
175
|
-
stagedAdded: 0,
|
|
176
|
-
stagedRemoved: 0,
|
|
177
|
-
unstagedAdded: 0,
|
|
178
|
-
unstagedRemoved: 0,
|
|
179
|
-
staged: 0,
|
|
180
|
-
unstaged: 0,
|
|
181
|
-
date: '',
|
|
182
|
-
});
|
|
178
|
+
const fields = { origins: [], firstIndex: i, openIndex: i };
|
|
179
|
+
groups.set(rel, { path: rel, ...fields, ...emptyCounts(), date: '' });
|
|
183
180
|
}
|
|
184
181
|
const group = groups.get(rel);
|
|
185
182
|
const { origin } = item;
|
|
@@ -194,64 +191,50 @@ const fileEntries = (items) => {
|
|
|
194
191
|
}
|
|
195
192
|
}
|
|
196
193
|
const entries = [...groups.values()];
|
|
197
|
-
for (const group of entries)
|
|
198
|
-
group.status = fileStatus(group.origins);
|
|
199
|
-
}
|
|
194
|
+
for (const group of entries) group.status = fileStatus(group.origins);
|
|
200
195
|
entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
201
196
|
return entries;
|
|
202
197
|
};
|
|
203
198
|
|
|
204
199
|
const fileTotals = (files) => {
|
|
205
|
-
const total = {
|
|
206
|
-
|
|
207
|
-
kind: 'total',
|
|
208
|
-
status: '',
|
|
209
|
-
remaining: 0,
|
|
210
|
-
added: 0,
|
|
211
|
-
removed: 0,
|
|
212
|
-
stagedAdded: 0,
|
|
213
|
-
stagedRemoved: 0,
|
|
214
|
-
unstagedAdded: 0,
|
|
215
|
-
unstagedRemoved: 0,
|
|
216
|
-
staged: 0,
|
|
217
|
-
unstaged: 0,
|
|
218
|
-
};
|
|
200
|
+
const total = { path: TOTAL_LABEL, kind: 'total', status: '' };
|
|
201
|
+
Object.assign(total, emptyCounts());
|
|
219
202
|
for (const entry of files) {
|
|
220
|
-
if (
|
|
203
|
+
if (isTasksEntry(entry) || isTotalEntry(entry)) continue;
|
|
221
204
|
const remaining = entry.remaining ?? 0;
|
|
222
205
|
const staged = entry.staged ?? 0;
|
|
223
|
-
const added = entry.added ?? 0;
|
|
224
|
-
const removed = entry.removed ?? 0;
|
|
225
206
|
const lines = originLineDelta(entry);
|
|
226
207
|
const listed = entry.unstaged ?? Math.max(0, remaining - staged);
|
|
227
|
-
const untracked =
|
|
228
|
-
const unstaged = untracked && listed === 0 ? remaining : listed;
|
|
208
|
+
const untracked = entry.status === 'untracked';
|
|
229
209
|
total.remaining += remaining;
|
|
230
|
-
total.added += added;
|
|
231
|
-
total.removed += removed;
|
|
210
|
+
total.added += entry.added ?? 0;
|
|
211
|
+
total.removed += entry.removed ?? 0;
|
|
232
212
|
total.stagedAdded += lines.stagedAdded;
|
|
233
213
|
total.stagedRemoved += lines.stagedRemoved;
|
|
234
214
|
total.unstagedAdded += lines.unstagedAdded;
|
|
235
215
|
total.unstagedRemoved += lines.unstagedRemoved;
|
|
236
216
|
total.staged += staged;
|
|
237
|
-
total.unstaged +=
|
|
217
|
+
total.unstaged += untracked && listed === 0 ? remaining : listed;
|
|
238
218
|
}
|
|
239
219
|
return total;
|
|
240
220
|
};
|
|
241
221
|
|
|
242
222
|
module.exports = {
|
|
243
223
|
REVIEW_DIR,
|
|
244
|
-
|
|
245
|
-
|
|
224
|
+
TASKS_FILE,
|
|
225
|
+
REPO_TASKS_LABEL,
|
|
226
|
+
TASK_KINDS,
|
|
227
|
+
taskKind,
|
|
228
|
+
taskTitle,
|
|
246
229
|
TOTAL_LABEL,
|
|
247
230
|
isPathInScope,
|
|
248
231
|
itemPath,
|
|
249
232
|
listPath,
|
|
250
|
-
|
|
233
|
+
isTaskItem,
|
|
251
234
|
isReadOnlyOrigin,
|
|
252
|
-
|
|
235
|
+
isTasksEntry,
|
|
253
236
|
isTotalEntry,
|
|
254
|
-
|
|
237
|
+
makeTaskItem,
|
|
255
238
|
fileStatus,
|
|
256
239
|
fileEntries,
|
|
257
240
|
fileTotals,
|