reslop 0.1.0 → 0.1.2
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 -9
- package/README.md +25 -40
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +0 -1
- package/lib/api.js +5 -0
- package/lib/cli.js +84 -79
- package/lib/clipboard.js +1 -1
- package/lib/deps.js +1440 -0
- package/lib/detect.js +4 -4
- package/lib/diff.js +40 -63
- package/lib/editor.js +1 -1
- package/lib/files.js +23 -4
- package/lib/git.js +367 -18
- package/lib/github.js +625 -0
- package/lib/highlight.js +149 -166
- package/lib/keys.js +34 -41
- package/lib/render.js +218 -143
- package/lib/review.js +52 -45
- package/lib/select.js +21 -31
- package/lib/session.js +298 -235
- package/lib/source.js +30 -0
- package/package.json +37 -18
package/lib/render.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const ansi = require('./ansi.js');
|
|
4
4
|
const { displayLines } = require('./diff.js');
|
|
5
|
-
const
|
|
5
|
+
const highlight = require('./highlight.js');
|
|
6
6
|
const { detectLang } = require('./detect.js');
|
|
7
|
-
const
|
|
7
|
+
const keys = require('./keys.js');
|
|
8
8
|
const { overlayRows } = require('./select.js');
|
|
9
9
|
const { itemPath } = require('./files.js');
|
|
10
|
-
const { isDoneMark } = require('./review.js');
|
|
11
10
|
const wrap = require('./wrap.js');
|
|
12
|
-
const { wrapPlain, wrapDoc, wrapMultiline, cursorInWrap, wrapMove } = wrap;
|
|
13
11
|
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
const {
|
|
12
|
+
const { tokenize, overlayTokens } = highlight;
|
|
13
|
+
const { ACTIONS, ACTION_IDS, actionLetter, buttonWord } = keys;
|
|
14
|
+
const { FILES_DISABLED } = keys;
|
|
15
|
+
const { wrapPlain, wrapMultiline, cursorInWrap } = wrap;
|
|
16
|
+
const { THEME, CODE_FG, codeFg, paint, visibleWidth } = ansi;
|
|
17
|
+
const { truncateVisible, fg, RESET, ESC, EL, seq } = ansi;
|
|
17
18
|
|
|
18
19
|
const STYLES = {
|
|
19
20
|
del: {
|
|
@@ -30,6 +31,27 @@ const STYLES = {
|
|
|
30
31
|
chBg: THEME.addCharBg,
|
|
31
32
|
mark: '+',
|
|
32
33
|
},
|
|
34
|
+
warn: {
|
|
35
|
+
baseFg: THEME.delLineFg,
|
|
36
|
+
baseBg: THEME.delLineBg,
|
|
37
|
+
chFg: THEME.delCharFg,
|
|
38
|
+
chBg: THEME.delCharBg,
|
|
39
|
+
mark: ' ',
|
|
40
|
+
},
|
|
41
|
+
note: {
|
|
42
|
+
baseFg: THEME.chromeFg,
|
|
43
|
+
baseBg: THEME.noteBg,
|
|
44
|
+
chFg: THEME.chromeFg,
|
|
45
|
+
chBg: THEME.noteBg,
|
|
46
|
+
mark: ' ',
|
|
47
|
+
},
|
|
48
|
+
noteSep: {
|
|
49
|
+
baseFg: THEME.mutedFg,
|
|
50
|
+
baseBg: THEME.chromeBg,
|
|
51
|
+
chFg: THEME.mutedFg,
|
|
52
|
+
chBg: THEME.chromeBg,
|
|
53
|
+
mark: ' ',
|
|
54
|
+
},
|
|
33
55
|
ctx: {
|
|
34
56
|
baseFg: THEME.ctxFg,
|
|
35
57
|
baseBg: THEME.ctxBg,
|
|
@@ -54,20 +76,35 @@ const STAGED_STYLES = {
|
|
|
54
76
|
chBg: THEME.stagedAddCharBg,
|
|
55
77
|
mark: '+',
|
|
56
78
|
},
|
|
79
|
+
warn: {
|
|
80
|
+
baseFg: THEME.stagedDelLineFg,
|
|
81
|
+
baseBg: THEME.stagedDelLineBg,
|
|
82
|
+
chFg: THEME.stagedDelCharFg,
|
|
83
|
+
chBg: THEME.stagedDelCharBg,
|
|
84
|
+
mark: ' ',
|
|
85
|
+
},
|
|
86
|
+
note: STYLES.note,
|
|
87
|
+
noteSep: STYLES.noteSep,
|
|
57
88
|
ctx: STYLES.ctx,
|
|
58
89
|
};
|
|
59
90
|
|
|
60
|
-
const HEADER_ROLE_FG =
|
|
91
|
+
const HEADER_ROLE_FG = {
|
|
61
92
|
repo: THEME.headerRepoFg,
|
|
62
93
|
dir: THEME.headerDirFg,
|
|
63
94
|
slash: THEME.headerSlashFg,
|
|
64
95
|
file: THEME.headerFileFg,
|
|
65
96
|
meta: THEME.headerMetaFg,
|
|
66
|
-
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const FILE_STATUS_FG = {
|
|
100
|
+
unstaged: THEME.delLineFg,
|
|
101
|
+
staged: THEME.addLineFg,
|
|
102
|
+
untracked: THEME.mutedFg,
|
|
103
|
+
partial: THEME.warnFg,
|
|
104
|
+
};
|
|
67
105
|
|
|
68
106
|
const INFO_STATUS = [
|
|
69
107
|
'copied',
|
|
70
|
-
'skipped',
|
|
71
108
|
'staged',
|
|
72
109
|
'unstaged',
|
|
73
110
|
'not staged',
|
|
@@ -76,7 +113,6 @@ const INFO_STATUS = [
|
|
|
76
113
|
'read only',
|
|
77
114
|
'last block',
|
|
78
115
|
'first block',
|
|
79
|
-
'all skipped',
|
|
80
116
|
'first file',
|
|
81
117
|
'last file',
|
|
82
118
|
'nothing to review',
|
|
@@ -96,12 +132,16 @@ const QUIT_PROMPT = ` ${QUIT_FINISH_LABEL} ${QUIT_CONTINUE_LABEL}`;
|
|
|
96
132
|
const GUTTER_W = 2;
|
|
97
133
|
const BUTTON_GAP = ' ';
|
|
98
134
|
const LOGO = '👁️';
|
|
99
|
-
const
|
|
100
|
-
const
|
|
135
|
+
const FILE_MARK = '▶';
|
|
136
|
+
const FILE_LIST_PAD = 1;
|
|
137
|
+
const CHECK_DONE = 'xX';
|
|
101
138
|
const TODO_CAPTION = 'TODO:';
|
|
102
139
|
const INPUT_MAX = 4;
|
|
103
140
|
const NOTE_MAX = 4;
|
|
104
141
|
const NOTE_PAD = 1;
|
|
142
|
+
const SOFT_WRAP = ['warn', 'note'];
|
|
143
|
+
const DEL_CHAR_BG = ansi.bg(THEME.delCharBg);
|
|
144
|
+
const ADD_CHAR_BG = ansi.bg(THEME.addCharBg);
|
|
105
145
|
|
|
106
146
|
const lineStyles = (origin) => (origin === 'staged' ? STAGED_STYLES : STYLES);
|
|
107
147
|
|
|
@@ -128,7 +168,32 @@ const paintSpans = (pieces, lineStyle, budget, color) => {
|
|
|
128
168
|
return { text: out, used };
|
|
129
169
|
};
|
|
130
170
|
|
|
171
|
+
const paintAuditNote = (line, width, color) => {
|
|
172
|
+
const style = STYLES.note;
|
|
173
|
+
const gutterW = Math.min(GUTTER_W, Math.max(0, width));
|
|
174
|
+
const label = `${style.mark} `.slice(0, gutterW);
|
|
175
|
+
const gutter = paint(label, style.baseFg, style.baseBg, color);
|
|
176
|
+
const budget = Math.max(0, width - gutterW);
|
|
177
|
+
const text = truncateVisible(line.text ?? '', budget);
|
|
178
|
+
const used = visibleWidth(text);
|
|
179
|
+
let out = gutter;
|
|
180
|
+
if (text) out += paint(text, style.baseFg, style.baseBg, color);
|
|
181
|
+
if (used < budget) {
|
|
182
|
+
out += fill(budget - used, style.baseFg, style.baseBg, color);
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const paintAuditRule = (width, color) => {
|
|
188
|
+
const style = STYLES.noteSep;
|
|
189
|
+
const n = Math.max(0, width);
|
|
190
|
+
const bar = '─'.repeat(n);
|
|
191
|
+
return paint(bar, style.baseFg, style.baseBg, color);
|
|
192
|
+
};
|
|
193
|
+
|
|
131
194
|
const paintDiffLine = (line, width, color, origin, lang) => {
|
|
195
|
+
if (line.type === 'noteSep') return paintAuditRule(width, color);
|
|
196
|
+
if (line.type === 'note') return paintAuditNote(line, width, color);
|
|
132
197
|
const styles = lineStyles(origin);
|
|
133
198
|
const style = styles[line.type] ?? styles.ctx;
|
|
134
199
|
const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
|
|
@@ -174,19 +239,14 @@ const paintSplitBlank = (width, color) => {
|
|
|
174
239
|
);
|
|
175
240
|
};
|
|
176
241
|
|
|
177
|
-
const
|
|
178
|
-
|
|
242
|
+
const paintBodyFill = (width, color, kind) => {
|
|
243
|
+
if (kind === 'files') {
|
|
179
244
|
const inner = Math.max(1, width - 1);
|
|
180
245
|
const main = fill(inner, THEME.ctxFg, THEME.ctxBg, color);
|
|
181
246
|
const edge = fill(1, THEME.ctxFg, THEME.ctxBg, color);
|
|
182
247
|
return main + edge;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const paintBodyFill = (width, color, kind) => {
|
|
188
|
-
const paint = FILL_KIND[kind];
|
|
189
|
-
if (paint) return paint(width, color);
|
|
248
|
+
}
|
|
249
|
+
if (kind === 'split') return paintSplitBlank(width, color);
|
|
190
250
|
return fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
191
251
|
};
|
|
192
252
|
|
|
@@ -206,22 +266,18 @@ const splitCheck = (text) => {
|
|
|
206
266
|
if (!match) return null;
|
|
207
267
|
const start = match.index;
|
|
208
268
|
const token = match[0];
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
after: text.slice(start + token.length),
|
|
214
|
-
};
|
|
269
|
+
const before = text.slice(0, start);
|
|
270
|
+
const mark = match[1];
|
|
271
|
+
const after = text.slice(start + token.length);
|
|
272
|
+
return { before, mark, token, after };
|
|
215
273
|
};
|
|
216
274
|
|
|
217
275
|
const paintCheckBar = (text, width, fgRgb, bgRgb, color) => {
|
|
218
276
|
const clipped = truncateVisible(text, width);
|
|
277
|
+
const parts = color ? splitCheck(clipped) : null;
|
|
278
|
+
if (!parts) return paintBar(text, width, fgRgb, bgRgb, color);
|
|
219
279
|
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
220
|
-
const
|
|
221
|
-
if (!color) return filled;
|
|
222
|
-
const parts = splitCheck(clipped);
|
|
223
|
-
if (!parts) return `${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
|
|
224
|
-
const done = isDoneMark(parts.mark);
|
|
280
|
+
const done = CHECK_DONE.includes(parts.mark);
|
|
225
281
|
const chipFg = done ? THEME.checkDoneFg : THEME.checkFg;
|
|
226
282
|
const chipBg = done ? THEME.checkDoneBg : THEME.checkBg;
|
|
227
283
|
const after = `${parts.after}${' '.repeat(pad)}`;
|
|
@@ -232,9 +288,15 @@ const paintCheckBar = (text, width, fgRgb, bgRgb, color) => {
|
|
|
232
288
|
return out;
|
|
233
289
|
};
|
|
234
290
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
291
|
+
const ORIGIN_LABEL = {
|
|
292
|
+
commit: (view) => view.revShort || 'commit',
|
|
293
|
+
pr: (view) => view.sourceLabel || 'pr',
|
|
294
|
+
todo: () => 'todo',
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const originLabel = (origin, view) => {
|
|
298
|
+
const named = ORIGIN_LABEL[origin];
|
|
299
|
+
if (named) return named(view);
|
|
238
300
|
return origin ?? 'unknown';
|
|
239
301
|
};
|
|
240
302
|
|
|
@@ -271,7 +333,7 @@ const headerTarget = (view) => {
|
|
|
271
333
|
if (!view.item) return { path: '', origin: '', n: 0, total: view.total ?? 0 };
|
|
272
334
|
return {
|
|
273
335
|
path: itemPath(view.item),
|
|
274
|
-
origin: originLabel(view.item.origin, view
|
|
336
|
+
origin: originLabel(view.item.origin, view),
|
|
275
337
|
n: view.index + 1,
|
|
276
338
|
total: view.total,
|
|
277
339
|
};
|
|
@@ -285,17 +347,20 @@ const headerPrefix = (view) => {
|
|
|
285
347
|
return ` ${LOGO} reslop: `;
|
|
286
348
|
};
|
|
287
349
|
|
|
350
|
+
const headerSuffix = ({ origin, n, total }) =>
|
|
351
|
+
origin ? ` ${origin} ${n}/${total}` : ` ${n}/${total}`;
|
|
352
|
+
|
|
288
353
|
const headerText = (view) => {
|
|
289
354
|
const prefix = headerPrefix(view);
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
return `${prefix}${path}${suffix}`;
|
|
355
|
+
const target = headerTarget(view);
|
|
356
|
+
return `${prefix}${target.path}${headerSuffix(target)}`;
|
|
293
357
|
};
|
|
294
358
|
|
|
295
359
|
const headerPieces = (view, width) => {
|
|
296
360
|
const repo = view.repoName || '';
|
|
297
|
-
const
|
|
298
|
-
const
|
|
361
|
+
const target = headerTarget(view);
|
|
362
|
+
const path = target.path;
|
|
363
|
+
const suffix = headerSuffix(target);
|
|
299
364
|
const right = ' ';
|
|
300
365
|
const chrome = ` ${LOGO} reslop: `;
|
|
301
366
|
const prefixW = visibleWidth(headerPrefix(view));
|
|
@@ -326,7 +391,7 @@ const headerPieces = (view, width) => {
|
|
|
326
391
|
const paintHeader = (view, width, color) => {
|
|
327
392
|
const pieces = headerPieces(view, width);
|
|
328
393
|
if (!color) {
|
|
329
|
-
return pieces.map((
|
|
394
|
+
return pieces.map((piece) => piece.text).join('');
|
|
330
395
|
}
|
|
331
396
|
const bgRgb = THEME.headerBg;
|
|
332
397
|
let out = `${seq(THEME.headerChromeFg, bgRgb)}${EL}`;
|
|
@@ -345,7 +410,8 @@ const bodyLines = (item, layout) => {
|
|
|
345
410
|
return [{ type: 'ctx', text, spans: [{ text, changed: false }] }];
|
|
346
411
|
}
|
|
347
412
|
if (!item.hunk) return [];
|
|
348
|
-
|
|
413
|
+
const radius = item.dep ? Number.MAX_SAFE_INTEGER : undefined;
|
|
414
|
+
return displayLines(item.hunk, item.blockId, layout, radius);
|
|
349
415
|
};
|
|
350
416
|
|
|
351
417
|
const sourceEmpty = (line) => !line || !(line.text ?? '').trim();
|
|
@@ -363,26 +429,6 @@ const dropLeadingEmpty = (lines, split) => {
|
|
|
363
429
|
return i ? lines.slice(i) : lines;
|
|
364
430
|
};
|
|
365
431
|
|
|
366
|
-
const helpLines = () => {
|
|
367
|
-
const lines = [' keys and buttons', ''];
|
|
368
|
-
for (const action of ACTIONS) {
|
|
369
|
-
const keys = actionHot(action);
|
|
370
|
-
let label = action.label;
|
|
371
|
-
if (action.id === 'layout') {
|
|
372
|
-
label = 'Mode (unified / mixed / side)';
|
|
373
|
-
}
|
|
374
|
-
lines.push(` ${keys.padEnd(16)} ${label}`);
|
|
375
|
-
}
|
|
376
|
-
lines.push(' enter open file / save note');
|
|
377
|
-
lines.push(' esc files, then quit');
|
|
378
|
-
lines.push(' del, backspace remove todo');
|
|
379
|
-
lines.push(' j, k, wheel scroll / move');
|
|
380
|
-
lines.push(' drag copy');
|
|
381
|
-
lines.push(' ctrl-z, ctrl-y undo / redo');
|
|
382
|
-
lines.push(' ?, h help');
|
|
383
|
-
return lines;
|
|
384
|
-
};
|
|
385
|
-
|
|
386
432
|
const BUTTON_MARK = {
|
|
387
433
|
bgRgb: THEME.buttonBg,
|
|
388
434
|
fgRgb: THEME.buttonFg,
|
|
@@ -414,27 +460,29 @@ const paintMarkedWord = (label, letter, color, mark) => {
|
|
|
414
460
|
return out;
|
|
415
461
|
};
|
|
416
462
|
|
|
417
|
-
const layoutButtons = (width, compact = false) => {
|
|
463
|
+
const layoutButtons = (width, compact = false, disabled = []) => {
|
|
418
464
|
const build = (short) => {
|
|
419
465
|
const hits = [];
|
|
420
466
|
const parts = [];
|
|
421
467
|
let x = 0;
|
|
422
|
-
for (const
|
|
468
|
+
for (const id of ACTION_IDS) {
|
|
469
|
+
const action = { id, ...ACTIONS[id] };
|
|
423
470
|
const letter = actionLetter(action);
|
|
424
471
|
const word = buttonWord(action);
|
|
425
472
|
const label = short ? letter : word;
|
|
426
473
|
const piece = `${BUTTON_GAP}${label}`;
|
|
427
474
|
const w = visibleWidth(piece);
|
|
428
475
|
if (x + w > width && parts.length) break;
|
|
429
|
-
|
|
430
|
-
|
|
476
|
+
const isDisabled = disabled.includes(id);
|
|
477
|
+
if (!isDisabled) hits.push({ id, x0: x, x1: x + w });
|
|
478
|
+
parts.push({ action, piece, letter, label, disabled: isDisabled });
|
|
431
479
|
x += w;
|
|
432
480
|
}
|
|
433
481
|
return { hits, parts, width: x };
|
|
434
482
|
};
|
|
435
483
|
if (compact) return build(true);
|
|
436
484
|
const words = build(false);
|
|
437
|
-
if (words.parts.length ===
|
|
485
|
+
if (words.parts.length === ACTION_IDS.length) return words;
|
|
438
486
|
return build(true);
|
|
439
487
|
};
|
|
440
488
|
|
|
@@ -443,7 +491,11 @@ const paintButtons = (layout, width, color) => {
|
|
|
443
491
|
let used = 0;
|
|
444
492
|
for (const part of layout.parts) {
|
|
445
493
|
out += paint(BUTTON_GAP, THEME.buttonFg, THEME.buttonBg, color);
|
|
446
|
-
|
|
494
|
+
if (part.disabled) {
|
|
495
|
+
out += paint(part.label, THEME.buttonFg, THEME.buttonBg, color);
|
|
496
|
+
} else {
|
|
497
|
+
out += paintMarkedWord(part.label, part.letter, color, BUTTON_MARK);
|
|
498
|
+
}
|
|
447
499
|
used += visibleWidth(part.piece);
|
|
448
500
|
}
|
|
449
501
|
if (used < width) {
|
|
@@ -452,21 +504,38 @@ const paintButtons = (layout, width, color) => {
|
|
|
452
504
|
return out;
|
|
453
505
|
};
|
|
454
506
|
|
|
455
|
-
const
|
|
456
|
-
|
|
507
|
+
const COUNTS_KIND = {
|
|
508
|
+
pr: ({ counts, notes, sourceLabel }) => {
|
|
509
|
+
const n = counts.pr ?? 0;
|
|
510
|
+
const label = sourceLabel || 'pr';
|
|
511
|
+
return ` pr ${label} ${n} ${notes}`;
|
|
512
|
+
},
|
|
513
|
+
commit: ({ counts, notes, revShort }) => {
|
|
514
|
+
const n = counts.commit ?? 0;
|
|
515
|
+
return ` commit ${revShort} ${n} ${notes}`;
|
|
516
|
+
},
|
|
517
|
+
worktree: ({ counts, notes }) => {
|
|
518
|
+
const staged = counts.staged ?? 0;
|
|
519
|
+
const unstaged = counts.unstaged ?? 0;
|
|
520
|
+
const untracked = counts.untracked ?? 0;
|
|
521
|
+
const gitCounts = `unstaged ${unstaged} untracked ${untracked}`;
|
|
522
|
+
const git = `staged ${staged} ${gitCounts}`;
|
|
523
|
+
return ` ${git} ${notes}`;
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const countsKind = (sourceKind, revShort) => {
|
|
528
|
+
if (sourceKind === 'pr') return 'pr';
|
|
529
|
+
if (revShort) return 'commit';
|
|
530
|
+
return 'worktree';
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const countsLine = (counts, revShort, sourceKind, sourceLabel) => {
|
|
457
534
|
const feedback = counts.feedback ?? 0;
|
|
458
535
|
const todo = counts.todo ?? 0;
|
|
459
536
|
const notes = `feedback ${feedback} todo ${todo}`;
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
const n = counts.commit ?? 0;
|
|
463
|
-
return ` commit ${revShort} ${n} ${notes}${extra}`;
|
|
464
|
-
}
|
|
465
|
-
const staged = counts.staged ?? 0;
|
|
466
|
-
const unstaged = counts.unstaged ?? 0;
|
|
467
|
-
const untracked = counts.untracked ?? 0;
|
|
468
|
-
const git = `staged ${staged} unstaged ${unstaged} untracked ${untracked}`;
|
|
469
|
-
return ` ${git} ${notes}${extra}`;
|
|
537
|
+
const kind = countsKind(sourceKind, revShort);
|
|
538
|
+
return COUNTS_KIND[kind]({ counts, notes, sourceLabel, revShort });
|
|
470
539
|
};
|
|
471
540
|
|
|
472
541
|
const paintQuitPrompt = (width, color) => {
|
|
@@ -484,8 +553,9 @@ const paintQuitPrompt = (width, color) => {
|
|
|
484
553
|
};
|
|
485
554
|
|
|
486
555
|
const paintStatusLine = (view, status, width, color) => {
|
|
487
|
-
|
|
488
|
-
|
|
556
|
+
const { mode, counts, revShort, sourceKind, sourceLabel } = view;
|
|
557
|
+
if (mode === 'confirmQuit') return paintQuitPrompt(width, color);
|
|
558
|
+
const left = countsLine(counts ?? {}, revShort, sourceKind, sourceLabel);
|
|
489
559
|
let right = '';
|
|
490
560
|
let msgFg = THEME.chromeFg;
|
|
491
561
|
if (status) {
|
|
@@ -524,11 +594,16 @@ const padStartVisible = (text, width) => {
|
|
|
524
594
|
return `${' '.repeat(n)}${text}`;
|
|
525
595
|
};
|
|
526
596
|
|
|
527
|
-
const
|
|
597
|
+
const fileRowFg = (status, selected) => {
|
|
598
|
+
const statusFg = FILE_STATUS_FG[status];
|
|
599
|
+
if (statusFg) return statusFg;
|
|
600
|
+
if (selected) return THEME.chromeFg;
|
|
601
|
+
return THEME.mutedFg;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const paintFileRow = (entry, width, color, selected, cols) => {
|
|
528
605
|
const inner = Math.max(1, width - 1);
|
|
529
|
-
|
|
530
|
-
if (here) mark = FILE_CURRENT;
|
|
531
|
-
else if (selected) mark = FILE_CURSOR;
|
|
606
|
+
const mark = selected ? FILE_MARK : ' ';
|
|
532
607
|
const prefix = ` ${mark} `;
|
|
533
608
|
const status = entry.status ?? '';
|
|
534
609
|
const count = `${entry.remaining ?? 0}`;
|
|
@@ -541,7 +616,7 @@ const paintFileRow = (entry, width, color, selected, here, cols) => {
|
|
|
541
616
|
const clipped = truncateVisible(entry.path, pathW);
|
|
542
617
|
const pad = Math.max(0, pathW - visibleWidth(clipped));
|
|
543
618
|
const line = `${prefix}${clipped}${' '.repeat(pad)}${suffix}`;
|
|
544
|
-
const fgRgb = selected
|
|
619
|
+
const fgRgb = fileRowFg(status, selected);
|
|
545
620
|
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
546
621
|
const row = paintBar(line, inner, fgRgb, bgRgb, color);
|
|
547
622
|
return row + fill(1, fgRgb, bgRgb, color);
|
|
@@ -643,8 +718,7 @@ const paintIdleNote = (view, width, color) => {
|
|
|
643
718
|
const text = view.noteText;
|
|
644
719
|
if (!text) return empty;
|
|
645
720
|
const inner = noteInnerWidth(width);
|
|
646
|
-
const
|
|
647
|
-
const wrapped = wrapMultiline(`${prefix}${text}`, inner);
|
|
721
|
+
const wrapped = wrapMultiline(`feedback: ${text}`, inner);
|
|
648
722
|
const rows = [];
|
|
649
723
|
for (const line of wrapped.slice(0, NOTE_MAX)) {
|
|
650
724
|
const painted = paintNoteLine(
|
|
@@ -662,7 +736,7 @@ const paintIdleNote = (view, width, color) => {
|
|
|
662
736
|
|
|
663
737
|
const paintNotePanel = (view, width, color, cap) => {
|
|
664
738
|
const empty = { rows: [], cursor: null, templateHits: [] };
|
|
665
|
-
if (
|
|
739
|
+
if ((view.pane ?? 'diff') === 'files') return empty;
|
|
666
740
|
const compose = view.compose;
|
|
667
741
|
if (compose && compose.kind === 'feedback') {
|
|
668
742
|
return paintComposeNote(view, width, color, cap);
|
|
@@ -670,32 +744,27 @@ const paintNotePanel = (view, width, color, cap) => {
|
|
|
670
744
|
return paintIdleNote(view, width, color);
|
|
671
745
|
};
|
|
672
746
|
|
|
673
|
-
const paintBodyHelp = (width, color) => {
|
|
674
|
-
const body = [];
|
|
675
|
-
for (const line of helpLines()) {
|
|
676
|
-
for (const row of wrapPlain(line, width)) {
|
|
677
|
-
body.push(paintBar(row, width, THEME.chromeFg, THEME.ctxBg, color));
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
return body;
|
|
681
|
-
};
|
|
682
|
-
|
|
683
747
|
const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
|
|
684
748
|
const files = view.files ?? [];
|
|
685
749
|
const cols = fileColumns(files);
|
|
686
750
|
const cursor = view.fileCursor ?? 0;
|
|
687
|
-
const
|
|
751
|
+
const pad = bodyH >= FILE_LIST_PAD * 2 + 1 ? FILE_LIST_PAD : 0;
|
|
752
|
+
const listH = bodyH - pad * 2;
|
|
753
|
+
const start = cursor >= listH ? cursor - listH + 1 : 0;
|
|
754
|
+
const blank = paintBodyFill(width, color, 'files');
|
|
688
755
|
const body = [];
|
|
689
756
|
const fileHits = [];
|
|
690
|
-
|
|
757
|
+
if (pad) body.push(blank);
|
|
758
|
+
for (let i = 0; i < listH; i++) {
|
|
691
759
|
const idx = start + i;
|
|
692
760
|
const entry = files[idx];
|
|
693
761
|
if (!entry) break;
|
|
694
762
|
const selected = idx === cursor;
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
fileHits.push({ y
|
|
763
|
+
body.push(paintFileRow(entry, width, color, selected, cols));
|
|
764
|
+
const y = headerLines + pad + i + 1;
|
|
765
|
+
fileHits.push({ y, cursor: idx });
|
|
698
766
|
}
|
|
767
|
+
if (pad) body.push(blank);
|
|
699
768
|
return { body, fileHits };
|
|
700
769
|
};
|
|
701
770
|
|
|
@@ -722,10 +791,39 @@ const paintBodyTodo = (view, width, color) => {
|
|
|
722
791
|
return { body, todoOwners, cursor };
|
|
723
792
|
};
|
|
724
793
|
|
|
794
|
+
const wrapSoftLine = (line, width) => {
|
|
795
|
+
const gutterW = Math.min(GUTTER_W, Math.max(0, width));
|
|
796
|
+
const budget = Math.max(1, width - gutterW);
|
|
797
|
+
const rows = wrapPlain(line.text ?? '', budget);
|
|
798
|
+
return rows.map((text) => {
|
|
799
|
+
const spans = [{ text, changed: false }];
|
|
800
|
+
return { ...line, text, spans };
|
|
801
|
+
});
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const expandSoftRows = (lines, width, split) => {
|
|
805
|
+
const wrapWidth = split ? splitWidths(width).leftW : width;
|
|
806
|
+
const out = [];
|
|
807
|
+
for (const row of lines) {
|
|
808
|
+
const line = split ? row.left || row.right : row;
|
|
809
|
+
if (!line || !SOFT_WRAP.includes(line.type)) {
|
|
810
|
+
out.push(row);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
const parts = wrapSoftLine(line, wrapWidth);
|
|
814
|
+
for (const part of parts) {
|
|
815
|
+
if (split) out.push({ left: part, right: part });
|
|
816
|
+
else out.push(part);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
};
|
|
821
|
+
|
|
725
822
|
const paintBodyDiff = (item, layout, width, color) => {
|
|
726
823
|
const lang = detectLang(itemPath(item));
|
|
727
824
|
const splitBody = layout === 'side' && !item.file.isBinary;
|
|
728
|
-
const
|
|
825
|
+
const raw = dropLeadingEmpty(bodyLines(item, layout), splitBody);
|
|
826
|
+
const lines = expandSoftRows(raw, width, splitBody);
|
|
729
827
|
const body = [];
|
|
730
828
|
if (splitBody) {
|
|
731
829
|
for (const row of lines) {
|
|
@@ -740,7 +838,6 @@ const paintBodyDiff = (item, layout, width, color) => {
|
|
|
740
838
|
};
|
|
741
839
|
|
|
742
840
|
const bodyKind = (view) => {
|
|
743
|
-
if (view.help) return 'help';
|
|
744
841
|
if ((view.pane ?? 'diff') === 'files') return 'files';
|
|
745
842
|
if (!view.item) return 'empty';
|
|
746
843
|
if (view.item.origin === 'todo') return 'todo';
|
|
@@ -748,11 +845,6 @@ const bodyKind = (view) => {
|
|
|
748
845
|
};
|
|
749
846
|
|
|
750
847
|
const BODY_PAINT = {
|
|
751
|
-
help: (view, width, color) => ({
|
|
752
|
-
body: paintBodyHelp(width, color),
|
|
753
|
-
fileHits: [],
|
|
754
|
-
todoOwners: [],
|
|
755
|
-
}),
|
|
756
848
|
files: (view, width, color, bodyH, headerLines) => ({
|
|
757
849
|
...paintBodyFiles(view, width, color, bodyH, headerLines),
|
|
758
850
|
todoOwners: [],
|
|
@@ -806,10 +898,10 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
|
|
|
806
898
|
const todoOwners = paintedBody.todoOwners;
|
|
807
899
|
const splitBody = paintedBody.splitBody === true;
|
|
808
900
|
const pane = view.pane ?? 'diff';
|
|
809
|
-
const listPane = pane === 'files'
|
|
901
|
+
const listPane = pane === 'files';
|
|
810
902
|
const fillKind = bodyFillKind(listPane, splitBody);
|
|
811
903
|
let shift = 0;
|
|
812
|
-
if (!listPane &&
|
|
904
|
+
if (!listPane && body.length > 0) {
|
|
813
905
|
body.unshift(paintBodyFill(width, color, fillKind));
|
|
814
906
|
if (todoOwners.length) todoOwners.unshift(null);
|
|
815
907
|
shift = 1;
|
|
@@ -884,7 +976,9 @@ const renderFrame = (view, options = {}) => {
|
|
|
884
976
|
const height = Math.max(6, options.height ?? 24);
|
|
885
977
|
const color = options.color ?? true;
|
|
886
978
|
const rows = [paintHeader(view, width, color)];
|
|
887
|
-
const
|
|
979
|
+
const pane = view.pane ?? 'diff';
|
|
980
|
+
const disabled = pane === 'files' ? FILES_DISABLED : [];
|
|
981
|
+
const layout = layoutButtons(width, false, disabled);
|
|
888
982
|
const notes = frameNotes(view, width, height, color);
|
|
889
983
|
const headerLines = rows.length;
|
|
890
984
|
const bodyH = notes.bodyH;
|
|
@@ -928,37 +1022,18 @@ const presentRows = (rows, options = {}) => {
|
|
|
928
1022
|
return parts.join('');
|
|
929
1023
|
};
|
|
930
1024
|
|
|
931
|
-
const DEL_CHAR_BG = ansi.bg(THEME.delCharBg);
|
|
932
|
-
const ADD_CHAR_BG = ansi.bg(THEME.addCharBg);
|
|
933
|
-
const DEL_LINE_FG = ansi.fg(THEME.delLineFg);
|
|
934
|
-
const ADD_LINE_FG = ansi.fg(THEME.addLineFg);
|
|
935
|
-
|
|
936
1025
|
module.exports = {
|
|
937
|
-
LOGO,
|
|
938
|
-
FILE_CURSOR,
|
|
939
|
-
FILE_CURRENT,
|
|
940
1026
|
QUIT_FINISH,
|
|
941
1027
|
QUIT_CONTINUE,
|
|
942
1028
|
QUIT_PROMPT,
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
renderFrame,
|
|
946
|
-
presentCursor,
|
|
947
|
-
presentRows,
|
|
948
|
-
layoutButtons,
|
|
1029
|
+
DEL_CHAR_BG,
|
|
1030
|
+
ADD_CHAR_BG,
|
|
949
1031
|
paintDiffLine,
|
|
950
|
-
paintSplitRow,
|
|
951
|
-
bodyLines,
|
|
952
1032
|
headerText,
|
|
953
|
-
|
|
954
|
-
wrapDoc,
|
|
955
|
-
wrapMultiline,
|
|
956
|
-
cursorInWrap,
|
|
957
|
-
wrapMove,
|
|
958
|
-
noteInnerWidth,
|
|
1033
|
+
layoutButtons,
|
|
959
1034
|
todoInnerWidth,
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1035
|
+
noteInnerWidth,
|
|
1036
|
+
renderFrame,
|
|
1037
|
+
presentCursor,
|
|
1038
|
+
presentRows,
|
|
964
1039
|
};
|