reslop 0.2.1 → 0.2.4
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 +19 -4
- package/bin/reslop.js +4 -2
- package/lib/ansi.js +120 -4
- package/lib/api.js +2 -1
- package/lib/cli.js +21 -9
- package/lib/clipboard.js +6 -3
- package/lib/deps-diff.js +61 -115
- package/lib/deps.js +57 -54
- 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 +2 -1
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +4 -2
- package/lib/editor.js +22 -4
- package/lib/files.js +38 -12
- package/lib/git-branches.js +150 -165
- package/lib/git-deps.js +20 -60
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +2 -1
- package/lib/gitlab.js +2 -1
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +2 -1
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +2 -1
- package/lib/highlight/txt.js +2 -1
- package/lib/highlight.js +2 -1
- package/lib/keys.js +12 -6
- package/lib/manifest.js +4 -2
- package/lib/npm-commands.js +592 -0
- package/lib/remote.js +20 -13
- package/lib/render/branches.js +25 -41
- package/lib/render/chrome.js +163 -128
- package/lib/render/commits.js +727 -65
- package/lib/render/diff.js +130 -47
- package/lib/render/files.js +4 -12
- package/lib/render/frame.js +41 -13
- package/lib/render/notes.js +4 -11
- package/lib/render/npm.js +216 -0
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +34 -50
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +14 -11
- package/lib/report-model.js +129 -0
- package/lib/report-parse.js +89 -0
- package/lib/report-render.js +104 -0
- package/lib/report-run.js +166 -0
- package/lib/report-test.js +507 -0
- package/lib/report-tools.js +337 -0
- package/lib/review-merge.js +2 -1
- package/lib/review.js +6 -2
- package/lib/session/actions.js +77 -44
- package/lib/session/branches.js +13 -49
- package/lib/session/change-actions.js +2 -1
- package/lib/session/commit.js +113 -53
- package/lib/session/compose.js +83 -14
- package/lib/session/dispatch.js +42 -22
- package/lib/session/files-pane.js +16 -21
- package/lib/session/items.js +6 -3
- package/lib/session/lifecycle.js +12 -5
- package/lib/session/load.js +4 -2
- package/lib/session/navigation.js +2 -0
- package/lib/session/npm.js +388 -0
- package/lib/session/ops.js +15 -1
- package/lib/session/pointer.js +30 -3
- package/lib/session/review.js +2 -1
- package/lib/session/terminal.js +4 -2
- package/lib/session/update.js +2 -1
- package/lib/session/view.js +41 -4
- package/lib/session.js +44 -22
- package/lib/source.js +6 -3
- package/lib/update.js +5 -9
- package/lib/utilities.js +4 -2
- package/lib/wrap.js +3 -1
- package/package.json +6 -6
package/lib/render/diff.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
-
const
|
|
4
|
+
const diff = require('../diff/diff.js');
|
|
5
|
+
const { displayLines } = diff;
|
|
5
6
|
const highlight = require('../highlight.js');
|
|
6
|
-
const
|
|
7
|
+
const detect = require('../detect.js');
|
|
8
|
+
const { detectLang } = detect;
|
|
7
9
|
const files = require('../files.js');
|
|
8
10
|
const wrap = require('../wrap.js');
|
|
9
11
|
const primitives = require('./primitives.js');
|
|
@@ -11,7 +13,8 @@ const primitives = require('./primitives.js');
|
|
|
11
13
|
const { itemPath } = files;
|
|
12
14
|
const { tokenize, overlayTokens } = highlight;
|
|
13
15
|
const { wrapPlain } = wrap;
|
|
14
|
-
const { THEME, CODE_FG, codeFg, paint, visibleWidth
|
|
16
|
+
const { THEME, CODE_FG, codeFg, paint, visibleWidth } = ansi;
|
|
17
|
+
const { truncateVisible, graphemes, graphemeWidth } = ansi;
|
|
15
18
|
const { GUTTER_W, fill, splitWidths, paintSplitRule, paneResult } = primitives;
|
|
16
19
|
|
|
17
20
|
const STYLES = {
|
|
@@ -77,10 +80,43 @@ const STAGED_STYLES = {
|
|
|
77
80
|
ctx: STYLES.ctx,
|
|
78
81
|
};
|
|
79
82
|
|
|
80
|
-
const SOFT_WRAP = ['warn', 'note'];
|
|
81
83
|
const delCharBg = () => ansi.bg(THEME.delCharBg);
|
|
82
84
|
const addCharBg = () => ansi.bg(THEME.addCharBg);
|
|
83
85
|
|
|
86
|
+
const skipText = (text, skip) => {
|
|
87
|
+
if (skip <= 0) return text ?? '';
|
|
88
|
+
const parts = graphemes(text ?? '');
|
|
89
|
+
let used = 0;
|
|
90
|
+
let i = 0;
|
|
91
|
+
while (i < parts.length) {
|
|
92
|
+
const w = graphemeWidth(parts[i], used);
|
|
93
|
+
if (used + w > skip) break;
|
|
94
|
+
used += w;
|
|
95
|
+
i += 1;
|
|
96
|
+
}
|
|
97
|
+
let out = '';
|
|
98
|
+
for (; i < parts.length; i++) out += parts[i];
|
|
99
|
+
return out;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const clipPieces = (pieces, skip) => {
|
|
103
|
+
if (skip <= 0) return pieces;
|
|
104
|
+
const out = [];
|
|
105
|
+
let drop = skip;
|
|
106
|
+
for (const piece of pieces) {
|
|
107
|
+
const text = piece.text ?? '';
|
|
108
|
+
const width = visibleWidth(text);
|
|
109
|
+
if (drop >= width) {
|
|
110
|
+
drop -= width;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const shown = drop > 0 ? skipText(text, drop) : text;
|
|
114
|
+
drop = 0;
|
|
115
|
+
if (shown) out.push({ ...piece, text: shown });
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
};
|
|
119
|
+
|
|
84
120
|
const paintSpans = (pieces, lineStyle, budget, color) => {
|
|
85
121
|
let out = '';
|
|
86
122
|
let used = 0;
|
|
@@ -124,14 +160,14 @@ const paintAuditRule = (width, color) => {
|
|
|
124
160
|
return paint(bar, style.baseFg, style.baseBg, color);
|
|
125
161
|
};
|
|
126
162
|
|
|
127
|
-
const paintDiffLine = (line, width, color, origin, lang) => {
|
|
163
|
+
const paintDiffLine = (line, width, color, origin, lang, scrollCol = 0) => {
|
|
128
164
|
if (line.type === 'noteSep') return paintAuditRule(width, color);
|
|
129
165
|
if (line.type === 'note') return paintAuditNote(line, width, color);
|
|
130
166
|
const styles = origin === 'staged' ? STAGED_STYLES : STYLES;
|
|
131
167
|
const style = styles[line.type] ?? styles.ctx;
|
|
132
168
|
const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
|
|
133
169
|
const tokens = tokenize(lang, line.text ?? '');
|
|
134
|
-
const pieces = overlayTokens(tokens, diffSpans);
|
|
170
|
+
const pieces = clipPieces(overlayTokens(tokens, diffSpans), scrollCol);
|
|
135
171
|
const gutterW = Math.min(GUTTER_W, Math.max(0, width));
|
|
136
172
|
const label = `${line.mark ?? style.mark} `.slice(0, gutterW);
|
|
137
173
|
const gutter = paint(label, CODE_FG.punct, style.baseBg, color);
|
|
@@ -140,17 +176,24 @@ const paintDiffLine = (line, width, color, origin, lang) => {
|
|
|
140
176
|
return gutter + rest;
|
|
141
177
|
};
|
|
142
178
|
|
|
143
|
-
const paintSplitHalf = (line, width, color, origin, lang) => {
|
|
179
|
+
const paintSplitHalf = (line, width, color, origin, lang, scrollCol) => {
|
|
144
180
|
if (!line || width <= 0) {
|
|
145
181
|
return fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
146
182
|
}
|
|
147
|
-
return paintDiffLine(line, width, color, origin, lang);
|
|
183
|
+
return paintDiffLine(line, width, color, origin, lang, scrollCol);
|
|
148
184
|
};
|
|
149
185
|
|
|
150
|
-
const paintSplitRow = (row, width, color, origin, lang) => {
|
|
186
|
+
const paintSplitRow = (row, width, color, origin, lang, scrollCol) => {
|
|
151
187
|
const { leftW, rightW } = splitWidths(width);
|
|
152
|
-
const left = paintSplitHalf(row.left, leftW, color, origin, lang);
|
|
153
|
-
const right = paintSplitHalf(
|
|
188
|
+
const left = paintSplitHalf(row.left, leftW, color, origin, lang, scrollCol);
|
|
189
|
+
const right = paintSplitHalf(
|
|
190
|
+
row.right,
|
|
191
|
+
rightW,
|
|
192
|
+
color,
|
|
193
|
+
origin,
|
|
194
|
+
lang,
|
|
195
|
+
scrollCol,
|
|
196
|
+
);
|
|
154
197
|
return `${left}${paintSplitRule(color)}${right}`;
|
|
155
198
|
};
|
|
156
199
|
|
|
@@ -183,6 +226,34 @@ const dropLeadingEmpty = (lines, split) => {
|
|
|
183
226
|
return i ? lines.slice(i) : lines;
|
|
184
227
|
};
|
|
185
228
|
|
|
229
|
+
const cutSpans = (spans, count) => {
|
|
230
|
+
const taken = [];
|
|
231
|
+
const rest = [];
|
|
232
|
+
let left = count;
|
|
233
|
+
let i = 0;
|
|
234
|
+
while (i < spans.length) {
|
|
235
|
+
const span = spans[i];
|
|
236
|
+
const text = span.text ?? '';
|
|
237
|
+
if (left <= 0) {
|
|
238
|
+
rest.push(span);
|
|
239
|
+
i += 1;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (text.length <= left) {
|
|
243
|
+
taken.push(span);
|
|
244
|
+
left -= text.length;
|
|
245
|
+
i += 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const changed = span.changed === true;
|
|
249
|
+
taken.push({ text: text.slice(0, left), changed });
|
|
250
|
+
rest.push({ text: text.slice(left), changed });
|
|
251
|
+
left = 0;
|
|
252
|
+
i += 1;
|
|
253
|
+
}
|
|
254
|
+
return { taken, rest };
|
|
255
|
+
};
|
|
256
|
+
|
|
186
257
|
const wrapSoftLine = (line, width) => {
|
|
187
258
|
const gutterW = Math.min(GUTTER_W, Math.max(0, width));
|
|
188
259
|
const budget = Math.max(1, width - gutterW);
|
|
@@ -191,11 +262,14 @@ const wrapSoftLine = (line, width) => {
|
|
|
191
262
|
const { length } = rows;
|
|
192
263
|
const pieces = new Array(length);
|
|
193
264
|
let used = 0;
|
|
265
|
+
let spans = line.spans ?? [{ text: line.text ?? '', changed }];
|
|
194
266
|
for (let i = 0; i < length; i++) {
|
|
195
267
|
const text = rows[i];
|
|
196
268
|
const last = i === length - 1;
|
|
197
|
-
const
|
|
198
|
-
|
|
269
|
+
const cut = cutSpans(spans, text.length);
|
|
270
|
+
spans = cut.rest;
|
|
271
|
+
const nextSpans = cut.taken.length ? cut.taken : [{ text, changed }];
|
|
272
|
+
const next = { ...line, text, spans: nextSpans, editLast: last };
|
|
199
273
|
if (line.editStart !== undefined) {
|
|
200
274
|
next.editStart = line.editStart + used;
|
|
201
275
|
if (last) next.editEnd = line.editEnd;
|
|
@@ -207,40 +281,30 @@ const wrapSoftLine = (line, width) => {
|
|
|
207
281
|
return pieces;
|
|
208
282
|
};
|
|
209
283
|
|
|
210
|
-
const
|
|
211
|
-
if (!line) return false;
|
|
212
|
-
if (SOFT_WRAP.includes(line.type)) return true;
|
|
213
|
-
return line.wrap === true;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const expandSoftRows = (lines, width, split) => {
|
|
284
|
+
const expandSoftRows = (lines, width, split, wrapView) => {
|
|
217
285
|
const { leftW, rightW } = splitWidths(width);
|
|
218
286
|
const out = [];
|
|
287
|
+
const partsOf = (line, lineW) => {
|
|
288
|
+
if (!wrapView || !line || line.type === 'noteSep') return [line];
|
|
289
|
+
const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
|
|
290
|
+
const budget = Math.max(1, lineW - gutterW);
|
|
291
|
+
if (visibleWidth(line.text ?? '') <= budget) return [line];
|
|
292
|
+
return wrapSoftLine(line, lineW);
|
|
293
|
+
};
|
|
219
294
|
for (const row of lines) {
|
|
220
295
|
if (!split) {
|
|
221
|
-
|
|
222
|
-
out.push(row);
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
for (const part of wrapSoftLine(row, width)) out.push(part);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const right = row.right;
|
|
229
|
-
if (shouldWrap(right)) {
|
|
230
|
-
const parts = wrapSoftLine(right, rightW);
|
|
231
|
-
out.push({ left: row.left, right: parts[0] });
|
|
232
|
-
for (let i = 1; i < parts.length; i++) {
|
|
233
|
-
out.push({ left: null, right: parts[i] });
|
|
234
|
-
}
|
|
296
|
+
for (const part of partsOf(row, width)) out.push(part);
|
|
235
297
|
continue;
|
|
236
298
|
}
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
299
|
+
const leftParts = partsOf(row.left, leftW);
|
|
300
|
+
const rightParts = partsOf(row.right, rightW);
|
|
301
|
+
const n = Math.max(leftParts.length, rightParts.length);
|
|
302
|
+
for (let i = 0; i < n; i++) {
|
|
303
|
+
out.push({
|
|
304
|
+
left: leftParts[i] ?? null,
|
|
305
|
+
right: rightParts[i] ?? null,
|
|
306
|
+
});
|
|
242
307
|
}
|
|
243
|
-
out.push(row);
|
|
244
308
|
}
|
|
245
309
|
return out;
|
|
246
310
|
};
|
|
@@ -251,7 +315,7 @@ const onCodePiece = (line, cursor) => {
|
|
|
251
315
|
return cursor <= line.editEnd;
|
|
252
316
|
};
|
|
253
317
|
|
|
254
|
-
const codeCursorInRows = (rows, split, width, cursor) => {
|
|
318
|
+
const codeCursorInRows = (rows, split, width, cursor, scrollCol = 0) => {
|
|
255
319
|
if (cursor === undefined || cursor === null) return null;
|
|
256
320
|
const { leftW, rightW } = splitWidths(width);
|
|
257
321
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -261,8 +325,9 @@ const codeCursorInRows = (rows, split, width, cursor) => {
|
|
|
261
325
|
const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
|
|
262
326
|
const take = Math.min(cursor - line.editStart, line.text.length);
|
|
263
327
|
const col = visibleWidth(line.text.slice(0, take));
|
|
328
|
+
const shown = Math.max(0, col - scrollCol);
|
|
264
329
|
const x0 = split ? leftW + 1 : 0;
|
|
265
|
-
return { x: x0 + gutterW +
|
|
330
|
+
return { x: x0 + gutterW + shown + 1, row: i };
|
|
266
331
|
}
|
|
267
332
|
return null;
|
|
268
333
|
};
|
|
@@ -279,22 +344,39 @@ const paintBodyDiff = (view, width, color) => {
|
|
|
279
344
|
const overlay = view.codeOverlay;
|
|
280
345
|
const lang = detectLang(itemPath(item));
|
|
281
346
|
const splitBody = layout === 'side' && !item.file.isBinary;
|
|
347
|
+
const editing = Boolean(overlay && typeof overlay.cursor === 'number');
|
|
348
|
+
const picked = editing ? overlay.scrollCol : 0;
|
|
349
|
+
const scrollCol = picked ?? 0;
|
|
282
350
|
const raw = dropLeadingEmpty(bodyLines(item, layout, overlay), splitBody);
|
|
283
|
-
const lines = expandSoftRows(raw, width, splitBody);
|
|
351
|
+
const lines = expandSoftRows(raw, width, splitBody, !editing);
|
|
284
352
|
const { length } = lines;
|
|
285
353
|
const body = new Array(length);
|
|
286
354
|
if (splitBody) {
|
|
287
355
|
for (let i = 0; i < length; i++) {
|
|
288
|
-
body[i] = paintSplitRow(
|
|
356
|
+
body[i] = paintSplitRow(
|
|
357
|
+
lines[i],
|
|
358
|
+
width,
|
|
359
|
+
color,
|
|
360
|
+
item.origin,
|
|
361
|
+
lang,
|
|
362
|
+
scrollCol,
|
|
363
|
+
);
|
|
289
364
|
}
|
|
290
365
|
} else {
|
|
291
366
|
for (let i = 0; i < length; i++) {
|
|
292
|
-
body[i] = paintDiffLine(
|
|
367
|
+
body[i] = paintDiffLine(
|
|
368
|
+
lines[i],
|
|
369
|
+
width,
|
|
370
|
+
color,
|
|
371
|
+
item.origin,
|
|
372
|
+
lang,
|
|
373
|
+
scrollCol,
|
|
374
|
+
);
|
|
293
375
|
}
|
|
294
376
|
}
|
|
295
377
|
const edit = overlay ? overlay.cursor : null;
|
|
296
|
-
const cursor = codeCursorInRows(lines, splitBody, width, edit);
|
|
297
|
-
return paneResult({ body,
|
|
378
|
+
const cursor = codeCursorInRows(lines, splitBody, width, edit, scrollCol);
|
|
379
|
+
return paneResult({ body, cursor, splitBody });
|
|
298
380
|
};
|
|
299
381
|
|
|
300
382
|
module.exports = {
|
|
@@ -304,4 +386,5 @@ module.exports = {
|
|
|
304
386
|
codeInnerWidth,
|
|
305
387
|
paintBodyDiff,
|
|
306
388
|
codeCursorInRows,
|
|
389
|
+
expandSoftRows,
|
|
307
390
|
};
|
package/lib/render/files.js
CHANGED
|
@@ -5,9 +5,9 @@ const files = require('../files.js');
|
|
|
5
5
|
const primitives = require('./primitives.js');
|
|
6
6
|
const { measureColumns, padColumns } = primitives;
|
|
7
7
|
|
|
8
|
-
const { isTodosEntry } = files;
|
|
8
|
+
const { isTodosEntry, shortAge } = files;
|
|
9
9
|
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
10
|
-
const { fill, FILE_MARK,
|
|
10
|
+
const { fill, FILE_MARK, paintCursorList } = primitives;
|
|
11
11
|
|
|
12
12
|
const FILE_STATUS_FG = {
|
|
13
13
|
unstaged: THEME.delLineFg,
|
|
@@ -41,7 +41,7 @@ const fileRowFields = (entry) => {
|
|
|
41
41
|
const status = entry.status ?? '';
|
|
42
42
|
const n = `${staged ?? 0}`;
|
|
43
43
|
const m = `${remaining ?? 0}`;
|
|
44
|
-
const date = entry.date ?? '';
|
|
44
|
+
const date = shortAge(entry.date ?? '');
|
|
45
45
|
return { add, del, status, n, m, date };
|
|
46
46
|
};
|
|
47
47
|
|
|
@@ -134,7 +134,7 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
|
|
|
134
134
|
const cursor = view.fileCursor ?? 0;
|
|
135
135
|
const paintRow = (entry, rowWidth, rowColor, selected) =>
|
|
136
136
|
paintFileRow(entry, rowWidth, rowColor, selected, cols);
|
|
137
|
-
|
|
137
|
+
return paintCursorList(
|
|
138
138
|
files,
|
|
139
139
|
cursor,
|
|
140
140
|
width,
|
|
@@ -145,14 +145,6 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
|
|
|
145
145
|
null,
|
|
146
146
|
view.listScroll,
|
|
147
147
|
);
|
|
148
|
-
return paneResult({
|
|
149
|
-
body: painted.body,
|
|
150
|
-
fileHits: painted.fileHits,
|
|
151
|
-
todoOwners: [],
|
|
152
|
-
cursor: painted.cursor,
|
|
153
|
-
splitBody: false,
|
|
154
|
-
listScroll: painted.offset,
|
|
155
|
-
});
|
|
156
148
|
};
|
|
157
149
|
|
|
158
150
|
module.exports = { paintBodyFiles };
|
package/lib/render/frame.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
-
const
|
|
4
|
+
const select = require('../select.js');
|
|
5
|
+
const { overlayRows } = select;
|
|
5
6
|
const primitives = require('./primitives.js');
|
|
6
7
|
const chrome = require('./chrome.js');
|
|
7
8
|
const files = require('./files.js');
|
|
8
9
|
const branches = require('./branches.js');
|
|
9
10
|
const commits = require('./commits.js');
|
|
10
11
|
const notes = require('./notes.js');
|
|
12
|
+
const npm = require('./npm.js');
|
|
11
13
|
const diff = require('./diff.js');
|
|
12
14
|
const unit = require('./unit.js');
|
|
13
15
|
|
|
14
16
|
const { RESET } = ansi;
|
|
15
17
|
const { LIST_PANES, paintBodyFill, paneResult } = primitives;
|
|
16
|
-
const { paintHeader,
|
|
18
|
+
const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
|
|
17
19
|
const { layoutButtons, statusBranchHit, statusChoiceHits } = chrome;
|
|
18
20
|
const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
|
|
19
21
|
const { paintNotePanel, paintBodyTodo } = notes;
|
|
20
22
|
const { paintBodyFiles } = files;
|
|
21
23
|
const { paintBodyBranches } = branches;
|
|
22
24
|
const { paintBodyCommits } = commits;
|
|
25
|
+
const { paintBodyNpm } = npm;
|
|
23
26
|
const { paintBodyDiff } = diff;
|
|
24
27
|
const { paintBodyUnit } = unit;
|
|
25
28
|
|
|
@@ -28,25 +31,20 @@ const bodyKind = (view) => {
|
|
|
28
31
|
if (pane === 'files' || pane === 'branches' || pane === 'commits') {
|
|
29
32
|
return pane;
|
|
30
33
|
}
|
|
34
|
+
if (pane === 'npm') return 'npm';
|
|
31
35
|
if (pane === 'unit') return 'unit';
|
|
32
36
|
if (!view.item) return 'empty';
|
|
33
37
|
if (view.item.origin === 'todo') return 'todo';
|
|
34
38
|
return 'diff';
|
|
35
39
|
};
|
|
36
40
|
|
|
37
|
-
const paintBodyEmpty = () =>
|
|
38
|
-
paneResult({
|
|
39
|
-
body: [],
|
|
40
|
-
fileHits: [],
|
|
41
|
-
todoOwners: [],
|
|
42
|
-
cursor: null,
|
|
43
|
-
splitBody: false,
|
|
44
|
-
});
|
|
41
|
+
const paintBodyEmpty = () => paneResult();
|
|
45
42
|
|
|
46
43
|
const BODY_PAINT = {
|
|
47
44
|
files: paintBodyFiles,
|
|
48
45
|
branches: paintBodyBranches,
|
|
49
46
|
commits: paintBodyCommits,
|
|
47
|
+
npm: paintBodyNpm,
|
|
50
48
|
empty: paintBodyEmpty,
|
|
51
49
|
todo: paintBodyTodo,
|
|
52
50
|
diff: paintBodyDiff,
|
|
@@ -75,6 +73,33 @@ const bodyFillKind = (listPane, splitBody) => {
|
|
|
75
73
|
return 'plain';
|
|
76
74
|
};
|
|
77
75
|
|
|
76
|
+
const ownerSpan = (owners, focus) => {
|
|
77
|
+
let first = -1;
|
|
78
|
+
let last = -1;
|
|
79
|
+
for (let i = 0; i < owners.length; i++) {
|
|
80
|
+
if (owners[i] !== focus) continue;
|
|
81
|
+
if (first < 0) first = i;
|
|
82
|
+
last = i;
|
|
83
|
+
}
|
|
84
|
+
return { first, last };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const revealScroll = (owners, focus, height, prefer) => {
|
|
88
|
+
const span = ownerSpan(owners, focus);
|
|
89
|
+
if (span.first < 0) return prefer;
|
|
90
|
+
const room = Math.max(1, height);
|
|
91
|
+
let start = prefer;
|
|
92
|
+
if (span.first < start) start = span.first;
|
|
93
|
+
if (span.last >= start + room) {
|
|
94
|
+
const fits = span.last - span.first + 1 <= room;
|
|
95
|
+
start = fits ? span.last - room + 1 : span.first;
|
|
96
|
+
}
|
|
97
|
+
const maxStart = Math.max(0, owners.length - room);
|
|
98
|
+
if (start > maxStart) start = maxStart;
|
|
99
|
+
if (start < 0) start = 0;
|
|
100
|
+
return start;
|
|
101
|
+
};
|
|
102
|
+
|
|
78
103
|
const mapBodyCursor = (cursor, shift, scroll, headerLines, shown) => {
|
|
79
104
|
if (!cursor) return null;
|
|
80
105
|
const row = cursor.row + shift;
|
|
@@ -119,7 +144,10 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
|
|
|
119
144
|
}
|
|
120
145
|
const scrollRaw = view.scroll ?? 0;
|
|
121
146
|
const scrollMax = Math.max(0, body.length - bodyH);
|
|
122
|
-
|
|
147
|
+
let scroll = listPane ? 0 : Math.min(scrollRaw, scrollMax);
|
|
148
|
+
if (view.item && view.item.origin === 'todo') {
|
|
149
|
+
scroll = revealScroll(todoOwners, view.todoFocus ?? 0, bodyH, scroll);
|
|
150
|
+
}
|
|
123
151
|
const end = Math.min(body.length, scroll + bodyH);
|
|
124
152
|
const copied = Math.max(0, end - scroll);
|
|
125
153
|
const slice = new Array(bodyH);
|
|
@@ -165,7 +193,7 @@ const stackFrame = (view, width, color, rows, layout, windowed, notes) => {
|
|
|
165
193
|
for (const row of notes.noteRows) rows.push(row);
|
|
166
194
|
const status = view.status ?? '';
|
|
167
195
|
rows.push(paintStatusLine(view, status, width, color));
|
|
168
|
-
rows.push(
|
|
196
|
+
rows.push(paintFooter(view, layout, width, color));
|
|
169
197
|
return overlayRows(rows, view.selection);
|
|
170
198
|
};
|
|
171
199
|
|
|
@@ -207,7 +235,7 @@ const renderFrame = (view, options = {}) => {
|
|
|
207
235
|
return {
|
|
208
236
|
text,
|
|
209
237
|
rows: painted,
|
|
210
|
-
buttons: layout
|
|
238
|
+
buttons: footerHits(view, layout),
|
|
211
239
|
fileHits,
|
|
212
240
|
todoHits,
|
|
213
241
|
templateHits,
|
package/lib/render/notes.js
CHANGED
|
@@ -7,7 +7,7 @@ const primitives = require('./primitives.js');
|
|
|
7
7
|
|
|
8
8
|
const { REPO_TODOS_LABEL } = files;
|
|
9
9
|
const { wrapMultiline, cursorInWrap } = wrap;
|
|
10
|
-
const { THEME,
|
|
10
|
+
const { THEME, truncateVisible } = ansi;
|
|
11
11
|
const { paintBar, paintCheckBar, windowRows } = primitives;
|
|
12
12
|
const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
|
|
13
13
|
|
|
@@ -73,6 +73,7 @@ const paintTodoList = (texts, focus, width, color, edit) => {
|
|
|
73
73
|
fgRgb,
|
|
74
74
|
bgRgb,
|
|
75
75
|
color,
|
|
76
|
+
true,
|
|
76
77
|
);
|
|
77
78
|
rows.push(painted);
|
|
78
79
|
owners.push(i);
|
|
@@ -168,16 +169,8 @@ const paintNotePanel = (view, width, color, cap) => {
|
|
|
168
169
|
return paintIdleNote(view, width, color);
|
|
169
170
|
};
|
|
170
171
|
|
|
171
|
-
const paintTodoCaption = (width, color) =>
|
|
172
|
-
|
|
173
|
-
const clipped = truncateVisible(text, width);
|
|
174
|
-
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
175
|
-
const filled = clipped + ' '.repeat(pad);
|
|
176
|
-
if (!color) return filled;
|
|
177
|
-
const fgRgb = THEME.buttonHotFg;
|
|
178
|
-
const bgRgb = THEME.ctxBg;
|
|
179
|
-
return `${BOLD}${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
|
|
180
|
-
};
|
|
172
|
+
const paintTodoCaption = (width, color) =>
|
|
173
|
+
paintBar(` ${REPO_TODOS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
|
|
181
174
|
|
|
182
175
|
const paintBodyTodo = (view, width, color) => {
|
|
183
176
|
const texts = view.todos ?? [];
|