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/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,12 +325,39 @@ 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
|
};
|
|
269
334
|
|
|
335
|
+
const codeEditHits = (rows, split, width, scrollCol = 0) => {
|
|
336
|
+
const { leftW, rightW } = splitWidths(width);
|
|
337
|
+
const hits = [];
|
|
338
|
+
for (let i = 0; i < rows.length; i++) {
|
|
339
|
+
const line = split ? rows[i].right : rows[i];
|
|
340
|
+
if (!line || line.editStart === undefined) continue;
|
|
341
|
+
const lineW = split ? rightW : width;
|
|
342
|
+
const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
|
|
343
|
+
const pane = split ? leftW + 1 : 0;
|
|
344
|
+
const textX = pane + gutterW + 1;
|
|
345
|
+
const end = split ? pane + rightW : width;
|
|
346
|
+
hits.push({
|
|
347
|
+
row: i,
|
|
348
|
+
x0: pane + 1,
|
|
349
|
+
textX,
|
|
350
|
+
x1: end + 1,
|
|
351
|
+
scroll: scrollCol,
|
|
352
|
+
start: line.editStart,
|
|
353
|
+
text: line.text ?? '',
|
|
354
|
+
limit: line.editEnd,
|
|
355
|
+
live: true,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return hits;
|
|
359
|
+
};
|
|
360
|
+
|
|
270
361
|
const codeInnerWidth = (width, layout = 'unified') => {
|
|
271
362
|
const lineW = layout === 'side' ? splitWidths(width).rightW : width;
|
|
272
363
|
const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
|
|
@@ -279,22 +370,42 @@ const paintBodyDiff = (view, width, color) => {
|
|
|
279
370
|
const overlay = view.codeOverlay;
|
|
280
371
|
const lang = detectLang(itemPath(item));
|
|
281
372
|
const splitBody = layout === 'side' && !item.file.isBinary;
|
|
373
|
+
const editing = Boolean(overlay && typeof overlay.cursor === 'number');
|
|
374
|
+
const picked = editing ? overlay.scrollCol : 0;
|
|
375
|
+
const scrollCol = picked ?? 0;
|
|
282
376
|
const raw = dropLeadingEmpty(bodyLines(item, layout, overlay), splitBody);
|
|
283
|
-
const lines = expandSoftRows(raw, width, splitBody);
|
|
377
|
+
const lines = expandSoftRows(raw, width, splitBody, !editing);
|
|
284
378
|
const { length } = lines;
|
|
285
379
|
const body = new Array(length);
|
|
286
380
|
if (splitBody) {
|
|
287
381
|
for (let i = 0; i < length; i++) {
|
|
288
|
-
body[i] = paintSplitRow(
|
|
382
|
+
body[i] = paintSplitRow(
|
|
383
|
+
lines[i],
|
|
384
|
+
width,
|
|
385
|
+
color,
|
|
386
|
+
item.origin,
|
|
387
|
+
lang,
|
|
388
|
+
scrollCol,
|
|
389
|
+
);
|
|
289
390
|
}
|
|
290
391
|
} else {
|
|
291
392
|
for (let i = 0; i < length; i++) {
|
|
292
|
-
body[i] = paintDiffLine(
|
|
393
|
+
body[i] = paintDiffLine(
|
|
394
|
+
lines[i],
|
|
395
|
+
width,
|
|
396
|
+
color,
|
|
397
|
+
item.origin,
|
|
398
|
+
lang,
|
|
399
|
+
scrollCol,
|
|
400
|
+
);
|
|
293
401
|
}
|
|
294
402
|
}
|
|
295
403
|
const edit = overlay ? overlay.cursor : null;
|
|
296
|
-
const cursor = codeCursorInRows(lines, splitBody, width, edit);
|
|
297
|
-
|
|
404
|
+
const cursor = codeCursorInRows(lines, splitBody, width, edit, scrollCol);
|
|
405
|
+
const editHits = editing
|
|
406
|
+
? codeEditHits(lines, splitBody, width, scrollCol)
|
|
407
|
+
: [];
|
|
408
|
+
return paneResult({ body, cursor, splitBody, editHits });
|
|
298
409
|
};
|
|
299
410
|
|
|
300
411
|
module.exports = {
|
|
@@ -304,4 +415,6 @@ module.exports = {
|
|
|
304
415
|
codeInnerWidth,
|
|
305
416
|
paintBodyDiff,
|
|
306
417
|
codeCursorInRows,
|
|
418
|
+
codeEditHits,
|
|
419
|
+
expandSoftRows,
|
|
307
420
|
};
|
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 {
|
|
8
|
+
const { isTasksEntry, 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,
|
|
@@ -30,7 +30,7 @@ const FILE_DATE_GAP = ' ';
|
|
|
30
30
|
const signedCount = (sign, n) => `${sign}${n ?? 0}`;
|
|
31
31
|
|
|
32
32
|
const fileRowFields = (entry) => {
|
|
33
|
-
if (
|
|
33
|
+
if (isTasksEntry(entry)) {
|
|
34
34
|
const n = `${entry.staged ?? 0}`;
|
|
35
35
|
const m = `${entry.remaining ?? 0}`;
|
|
36
36
|
return { add: '', del: '', status: '', n, m, date: '' };
|
|
@@ -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
|
|
|
@@ -105,7 +105,7 @@ const paintTodosRow = (entry, width, color, selected, cols) => {
|
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
const paintFileRow = (entry, width, color, selected, cols) => {
|
|
108
|
-
if (
|
|
108
|
+
if (isTasksEntry(entry)) {
|
|
109
109
|
return paintTodosRow(entry, width, color, selected, cols);
|
|
110
110
|
}
|
|
111
111
|
const inner = Math.max(1, width - 1);
|
|
@@ -132,27 +132,10 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
|
|
|
132
132
|
const files = view.files ?? [];
|
|
133
133
|
const cols = measureColumns(files, fileRowFields, FILE_COL_ALIGN);
|
|
134
134
|
const cursor = view.fileCursor ?? 0;
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
cursor,
|
|
140
|
-
width,
|
|
141
|
-
color,
|
|
142
|
-
bodyH,
|
|
143
|
-
headerLines,
|
|
144
|
-
paintRow,
|
|
145
|
-
null,
|
|
146
|
-
view.listScroll,
|
|
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
|
-
});
|
|
135
|
+
const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
|
|
136
|
+
const paintRow = (entry, selected) =>
|
|
137
|
+
paintFileRow(entry, width, color, selected, cols);
|
|
138
|
+
return paintCursorList(files, cursor, pane, paintRow);
|
|
156
139
|
};
|
|
157
140
|
|
|
158
141
|
module.exports = { paintBodyFiles };
|
package/lib/render/frame.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
-
const
|
|
4
|
+
const select = require('../select.js');
|
|
5
|
+
const { overlayRows, markEditorSelection } = select;
|
|
5
6
|
const primitives = require('./primitives.js');
|
|
6
7
|
const chrome = require('./chrome.js');
|
|
7
8
|
const files = require('./files.js');
|
|
@@ -15,8 +16,7 @@ const unit = require('./unit.js');
|
|
|
15
16
|
const { RESET } = ansi;
|
|
16
17
|
const { LIST_PANES, paintBodyFill, paneResult } = primitives;
|
|
17
18
|
const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
|
|
18
|
-
const {
|
|
19
|
-
const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
|
|
19
|
+
const { footerLayout, statusBranchHit, statusChoiceHits } = chrome;
|
|
20
20
|
const { paintNotePanel, paintBodyTodo } = notes;
|
|
21
21
|
const { paintBodyFiles } = files;
|
|
22
22
|
const { paintBodyBranches } = branches;
|
|
@@ -33,18 +33,11 @@ const bodyKind = (view) => {
|
|
|
33
33
|
if (pane === 'npm') return 'npm';
|
|
34
34
|
if (pane === 'unit') return 'unit';
|
|
35
35
|
if (!view.item) return 'empty';
|
|
36
|
-
if (view.item.origin === '
|
|
36
|
+
if (view.item.origin === 'task') return 'tasks';
|
|
37
37
|
return 'diff';
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const paintBodyEmpty = () =>
|
|
41
|
-
paneResult({
|
|
42
|
-
body: [],
|
|
43
|
-
fileHits: [],
|
|
44
|
-
todoOwners: [],
|
|
45
|
-
cursor: null,
|
|
46
|
-
splitBody: false,
|
|
47
|
-
});
|
|
40
|
+
const paintBodyEmpty = () => paneResult();
|
|
48
41
|
|
|
49
42
|
const BODY_PAINT = {
|
|
50
43
|
files: paintBodyFiles,
|
|
@@ -52,7 +45,7 @@ const BODY_PAINT = {
|
|
|
52
45
|
commits: paintBodyCommits,
|
|
53
46
|
npm: paintBodyNpm,
|
|
54
47
|
empty: paintBodyEmpty,
|
|
55
|
-
|
|
48
|
+
tasks: paintBodyTodo,
|
|
56
49
|
diff: paintBodyDiff,
|
|
57
50
|
unit: paintBodyUnit,
|
|
58
51
|
};
|
|
@@ -106,145 +99,131 @@ const revealScroll = (owners, focus, height, prefer) => {
|
|
|
106
99
|
return start;
|
|
107
100
|
};
|
|
108
101
|
|
|
109
|
-
const
|
|
102
|
+
const windowY = (win, row) => {
|
|
103
|
+
const at = row + win.shift - win.scroll;
|
|
104
|
+
if (at < 0 || at >= win.shown) return -1;
|
|
105
|
+
return win.headerLines + at + 1;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const mapEditHits = (hits, win) => {
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const hit of hits) {
|
|
111
|
+
const y = windowY(win, hit.row);
|
|
112
|
+
if (y >= 0) out.push({ ...hit, y });
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const mapBodyCursor = (cursor, win) => {
|
|
110
118
|
if (!cursor) return null;
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
119
|
+
const y = windowY(win, cursor.row);
|
|
120
|
+
if (y < 0) return null;
|
|
121
|
+
const point = { x: cursor.x, y };
|
|
122
|
+
if (typeof cursor.scroll === 'number') point.scroll = cursor.scroll;
|
|
123
|
+
return point;
|
|
117
124
|
};
|
|
118
125
|
|
|
119
|
-
const windowBody = (view,
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const splitBody = paintedBody.splitBody === true;
|
|
124
|
-
const pane = view.pane ?? 'diff';
|
|
125
|
-
const listPane = LIST_PANES.includes(pane);
|
|
126
|
-
const fillKind = bodyFillKind(listPane, splitBody);
|
|
126
|
+
const windowBody = (view, paintedBody, frame) => {
|
|
127
|
+
const { width, color, bodyH, headerLines } = frame;
|
|
128
|
+
const listPane = LIST_PANES.includes(view.pane ?? 'diff');
|
|
129
|
+
const fillKind = bodyFillKind(listPane, paintedBody.splitBody);
|
|
127
130
|
const filler = paintBodyFill(width, color, fillKind);
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
shift = 1;
|
|
135
|
-
}
|
|
136
|
-
for (let i = 0; i < sourceBody.length; i++) body[bodyAt++] = sourceBody[i];
|
|
137
|
-
const ownerLead = lead && sourceOwners.length;
|
|
138
|
-
const todoOwners = new Array(sourceOwners.length + (ownerLead ? 1 : 0));
|
|
139
|
-
let ownerAt = 0;
|
|
140
|
-
if (ownerLead) todoOwners[ownerAt++] = null;
|
|
141
|
-
for (let i = 0; i < sourceOwners.length; i++) {
|
|
142
|
-
todoOwners[ownerAt++] = sourceOwners[i];
|
|
143
|
-
}
|
|
144
|
-
const checkLead = lead && sourceChecks.length;
|
|
145
|
-
const todoChecks = new Array(sourceChecks.length + (checkLead ? 1 : 0));
|
|
146
|
-
let checkAt = 0;
|
|
147
|
-
if (checkLead) todoChecks[checkAt++] = false;
|
|
148
|
-
for (let i = 0; i < sourceChecks.length; i++) {
|
|
149
|
-
todoChecks[checkAt++] = sourceChecks[i];
|
|
150
|
-
}
|
|
151
|
-
const scrollRaw = view.scroll ?? 0;
|
|
131
|
+
const shift = !listPane && paintedBody.body.length > 0 ? 1 : 0;
|
|
132
|
+
const lead = (items, first) =>
|
|
133
|
+
shift && items.length ? [first, ...items] : items;
|
|
134
|
+
const body = lead(paintedBody.body, filler);
|
|
135
|
+
const taskOwners = lead(paintedBody.taskOwners, null);
|
|
136
|
+
const taskChecks = lead(paintedBody.taskChecks, false);
|
|
152
137
|
const scrollMax = Math.max(0, body.length - bodyH);
|
|
153
|
-
let scroll = listPane ? 0 : Math.min(
|
|
154
|
-
if (view.item && view.item.origin === '
|
|
155
|
-
scroll = revealScroll(
|
|
138
|
+
let scroll = listPane ? 0 : Math.min(view.scroll ?? 0, scrollMax);
|
|
139
|
+
if (view.item && view.item.origin === 'task') {
|
|
140
|
+
scroll = revealScroll(taskOwners, view.tasksFocus ?? 0, bodyH, scroll);
|
|
156
141
|
}
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
for (let i = 0; i <
|
|
161
|
-
|
|
162
|
-
for (let i = 0; i < copied; i++) {
|
|
163
|
-
const owner = todoOwners[scroll + i];
|
|
142
|
+
const slice = body.slice(scroll, scroll + bodyH);
|
|
143
|
+
const win = { shift, scroll, shown: slice.length, headerLines };
|
|
144
|
+
const taskHits = [];
|
|
145
|
+
for (let i = 0; i < slice.length; i++) {
|
|
146
|
+
const owner = taskOwners[scroll + i];
|
|
164
147
|
if (owner === undefined || owner === null) continue;
|
|
165
148
|
const y = headerLines + i + 1;
|
|
166
|
-
if (
|
|
167
|
-
|
|
149
|
+
if (taskChecks[scroll + i]) {
|
|
150
|
+
taskHits.push({ y, cursor: owner, x0: 1, x1: 4, check: true });
|
|
168
151
|
}
|
|
169
|
-
|
|
152
|
+
taskHits.push({ y, cursor: owner });
|
|
170
153
|
}
|
|
171
|
-
const cursor = mapBodyCursor(
|
|
172
|
-
|
|
173
|
-
|
|
154
|
+
const cursor = mapBodyCursor(paintedBody.cursor, win);
|
|
155
|
+
const editHits = mapEditHits(paintedBody.editHits, win);
|
|
156
|
+
while (slice.length < bodyH) slice.push(filler);
|
|
157
|
+
const { listScroll, fileHits } = paintedBody;
|
|
158
|
+
return {
|
|
159
|
+
slice,
|
|
174
160
|
scroll,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
161
|
+
scrollMax,
|
|
162
|
+
listScroll,
|
|
163
|
+
taskHits,
|
|
164
|
+
fileHits,
|
|
165
|
+
editHits,
|
|
166
|
+
cursor,
|
|
167
|
+
};
|
|
182
168
|
};
|
|
183
169
|
|
|
184
|
-
const notePointerHits = (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
170
|
+
const notePointerHits = (notes, bodyH) => {
|
|
171
|
+
const { notesWanted, noteRows, noteCursor } = notes;
|
|
172
|
+
const top = 2 + bodyH;
|
|
173
|
+
const x = noteCursor ? noteCursor.x : 0;
|
|
174
|
+
const y = noteCursor ? top + noteCursor.row : 0;
|
|
175
|
+
const cursor = noteCursor ? { x, y } : null;
|
|
189
176
|
const templateHits = [];
|
|
190
177
|
for (const hit of notesWanted.templateHits ?? []) {
|
|
191
178
|
if (hit.row >= noteRows.length) continue;
|
|
192
|
-
templateHits.push({ y:
|
|
179
|
+
templateHits.push({ y: top + hit.row, cursor: hit.cursor });
|
|
193
180
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const status = view.status ?? '';
|
|
201
|
-
rows.push(paintStatusLine(view, status, width, color));
|
|
202
|
-
rows.push(paintFooter(view, layout, width, color));
|
|
203
|
-
return overlayRows(rows, view.selection);
|
|
181
|
+
const editHits = [];
|
|
182
|
+
for (const hit of notesWanted.editHits ?? []) {
|
|
183
|
+
if (hit.row >= noteRows.length) continue;
|
|
184
|
+
editHits.push({ ...hit, y: top + hit.row });
|
|
185
|
+
}
|
|
186
|
+
return { cursor, templateHits, editHits };
|
|
204
187
|
};
|
|
205
188
|
|
|
206
189
|
const renderFrame = (view, options = {}) => {
|
|
207
190
|
const width = Math.max(20, options.width ?? 80);
|
|
208
191
|
const height = Math.max(6, options.height ?? 24);
|
|
209
192
|
const color = options.color ?? true;
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
const hidden = hiddenActions(view);
|
|
213
|
-
const dimmed = dimFooterActions(view);
|
|
214
|
-
const layout = layoutButtons(width, false, hidden, extra, dimmed);
|
|
193
|
+
const header = paintHeader(view, width, color);
|
|
194
|
+
const layout = footerLayout(view, width);
|
|
215
195
|
const notes = frameNotes(view, width, height, color);
|
|
216
|
-
const headerLines =
|
|
217
|
-
const bodyH = notes
|
|
196
|
+
const headerLines = 1;
|
|
197
|
+
const { bodyH } = notes;
|
|
218
198
|
const paintedBody = paintFrameBody(view, width, color, bodyH, headerLines);
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
const hits = notePointerHits(
|
|
230
|
-
notes.notesWanted,
|
|
231
|
-
notes.noteRows,
|
|
232
|
-
notes.noteCursor,
|
|
233
|
-
bodyH,
|
|
234
|
-
);
|
|
199
|
+
const frame = { width, color, bodyH, headerLines };
|
|
200
|
+
const windowed = windowBody(view, paintedBody, frame);
|
|
201
|
+
const rows = [
|
|
202
|
+
header,
|
|
203
|
+
...windowed.slice,
|
|
204
|
+
...notes.noteRows,
|
|
205
|
+
paintStatusLine(view, view.status ?? '', width, color),
|
|
206
|
+
paintFooter(view, layout, width, color),
|
|
207
|
+
];
|
|
208
|
+
const painted = overlayRows(rows, view.selection);
|
|
209
|
+
const hits = notePointerHits(notes, bodyH);
|
|
235
210
|
const statusY = painted.length - 1;
|
|
236
211
|
const branchHit = statusBranchHit(view, width, statusY);
|
|
237
212
|
const statusHits = branchHit ? [branchHit] : statusChoiceHits(view, statusY);
|
|
238
|
-
const { fileHits,
|
|
213
|
+
const { fileHits, taskHits, scroll, scrollMax, listScroll } = windowed;
|
|
239
214
|
const { templateHits } = hits;
|
|
215
|
+
const editHits = [...windowed.editHits, ...hits.editHits];
|
|
216
|
+
const shown = markEditorSelection(painted, view, editHits, color);
|
|
217
|
+
const text = shown.map((row) => (color ? row + RESET : row)).join('\n');
|
|
240
218
|
const footerY = painted.length;
|
|
241
219
|
return {
|
|
242
220
|
text,
|
|
243
|
-
rows:
|
|
221
|
+
rows: shown,
|
|
244
222
|
buttons: footerHits(view, layout),
|
|
245
223
|
fileHits,
|
|
246
|
-
|
|
224
|
+
taskHits,
|
|
247
225
|
templateHits,
|
|
226
|
+
editHits,
|
|
248
227
|
statusHits,
|
|
249
228
|
footerY,
|
|
250
229
|
height: footerY,
|
|
@@ -256,4 +235,4 @@ const renderFrame = (view, options = {}) => {
|
|
|
256
235
|
};
|
|
257
236
|
};
|
|
258
237
|
|
|
259
|
-
module.exports = {
|
|
238
|
+
module.exports = { renderFrame };
|