reslop 0.2.1 → 0.2.3
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 +14 -1
- package/README.md +11 -1
- package/lib/ansi.js +120 -4
- package/lib/cli.js +3 -0
- package/lib/deps.js +43 -3
- package/lib/git-branches.js +47 -15
- package/lib/keys.js +10 -4
- package/lib/npm-commands.js +513 -0
- package/lib/render/branches.js +21 -28
- package/lib/render/chrome.js +137 -122
- package/lib/render/commits.js +604 -54
- package/lib/render/frame.js +38 -4
- package/lib/render/npm.js +228 -0
- package/lib/render/primitives.js +7 -36
- package/lib/report-model.js +127 -0
- package/lib/report-parse.js +94 -0
- package/lib/report-render.js +87 -0
- package/lib/report-run.js +164 -0
- package/lib/report-test.js +506 -0
- package/lib/report-tools.js +311 -0
- package/lib/session/actions.js +51 -2
- package/lib/session/commit.js +94 -19
- package/lib/session/compose.js +48 -5
- package/lib/session/dispatch.js +30 -2
- package/lib/session/files-pane.js +20 -4
- package/lib/session/lifecycle.js +4 -1
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +335 -0
- package/lib/session/pointer.js +8 -0
- package/lib/session/view.js +14 -3
- package/lib/session.js +10 -2
- package/package.json +6 -6
package/lib/render/commits.js
CHANGED
|
@@ -2,77 +2,132 @@
|
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
4
|
const primitives = require('./primitives.js');
|
|
5
|
+
const { wrapMultiline, cursorInWrap } = require('../wrap.js');
|
|
5
6
|
const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
|
|
6
7
|
|
|
7
8
|
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
8
|
-
const {
|
|
9
|
+
const { FILE_MARK, paneResult, paintCursorList } = primitives;
|
|
10
|
+
const { paintBodyFill, listWindowStart } = primitives;
|
|
9
11
|
|
|
10
12
|
const COMMIT_COL_ALIGN = {
|
|
11
13
|
sha: 'end',
|
|
12
|
-
|
|
14
|
+
refs: 'end',
|
|
13
15
|
date: 'start',
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
const COMMIT_COL_FG = {
|
|
17
19
|
sha: THEME.shaFg,
|
|
18
|
-
|
|
20
|
+
refs: THEME.warnFg,
|
|
19
21
|
date: THEME.mutedFg,
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const COMMIT_CURRENT_COL_FG = {
|
|
25
|
+
sha: THEME.shaDarkFg,
|
|
26
|
+
date: THEME.headerFg,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const REFS_MAX = 28;
|
|
30
|
+
const LIST_PAD = 1;
|
|
23
31
|
const COMMIT_TAIL = 1;
|
|
24
32
|
const commitRowFields = (entry) => ({
|
|
25
33
|
sha: entry.shortSha ?? '',
|
|
26
|
-
|
|
34
|
+
refs: truncateVisible(entry.refs ?? '', REFS_MAX),
|
|
27
35
|
date: entry.date ?? '',
|
|
28
36
|
});
|
|
29
37
|
|
|
30
|
-
const
|
|
31
|
-
|
|
38
|
+
const contentWidth = (width) => Math.max(1, width - COMMIT_TAIL);
|
|
39
|
+
|
|
40
|
+
const splitMessage = (message) => {
|
|
41
|
+
const text = `${message ?? ''}`;
|
|
42
|
+
const at = text.indexOf('\n');
|
|
43
|
+
if (at < 0) return { line: text, rest: '' };
|
|
44
|
+
return { line: text.slice(0, at), rest: text.slice(at + 1) };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const commitText = (entry) => {
|
|
48
|
+
if (!entry) return '';
|
|
49
|
+
if (entry.body) return `${entry.body}`;
|
|
50
|
+
return `${entry.subject ?? ''}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const commitLine = (entry) => splitMessage(commitText(entry)).line;
|
|
54
|
+
|
|
55
|
+
const viewedCommit = (entry, rev, revShort) => {
|
|
56
|
+
const want = `${rev ?? ''}`;
|
|
57
|
+
const short = `${revShort ?? ''}`;
|
|
58
|
+
if (entry && entry.pending) return !want && !short;
|
|
59
|
+
const sha = entry && entry.sha ? `${entry.sha}` : '';
|
|
60
|
+
const brief = entry && entry.shortSha ? `${entry.shortSha}` : '';
|
|
61
|
+
if (want && (sha === want || brief === want)) return true;
|
|
62
|
+
if (short && (sha === short || brief === short)) return true;
|
|
63
|
+
return false;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const FULL_INDENT = ' '.repeat(visibleWidth(` ${FILE_MARK} `));
|
|
67
|
+
|
|
68
|
+
const commitTone = (selected, current) => {
|
|
69
|
+
if (current) {
|
|
70
|
+
return {
|
|
71
|
+
current: true,
|
|
72
|
+
fgRgb: THEME.headerFg,
|
|
73
|
+
bgRgb: THEME.currentBg,
|
|
74
|
+
nameFg: THEME.mutedFg,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (selected) {
|
|
78
|
+
return {
|
|
79
|
+
current: false,
|
|
80
|
+
fgRgb: THEME.chromeFg,
|
|
81
|
+
bgRgb: THEME.buttonBg,
|
|
82
|
+
nameFg: THEME.buttonHotFg,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
current: false,
|
|
87
|
+
fgRgb: THEME.mutedFg,
|
|
88
|
+
bgRgb: THEME.ctxBg,
|
|
89
|
+
nameFg: THEME.chromeFg,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const paintCommitRow = (entry, width, color, selected, cols, current) => {
|
|
94
|
+
const inner = contentWidth(width);
|
|
32
95
|
const mark = selected ? FILE_MARK : ' ';
|
|
33
96
|
const prefix = ` ${mark} `;
|
|
34
|
-
const lead = ' ';
|
|
35
97
|
const parts = padColumns(commitRowFields(entry), cols, COMMIT_COL_ALIGN);
|
|
36
98
|
const suffix = joinColumns(parts, cols);
|
|
37
|
-
const rest = Math.max(1, inner - visibleWidth(prefix +
|
|
38
|
-
const subject = truncateVisible(entry
|
|
99
|
+
const rest = Math.max(1, inner - visibleWidth(prefix + suffix));
|
|
100
|
+
const subject = truncateVisible(commitLine(entry), rest);
|
|
39
101
|
const pad = Math.max(0, rest - visibleWidth(subject));
|
|
40
102
|
const after = `${' '.repeat(pad)}`;
|
|
41
|
-
const left = `${prefix}${
|
|
42
|
-
const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
43
|
-
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
44
|
-
const nameFg = selected ? THEME.buttonHotFg : THEME.chromeFg;
|
|
103
|
+
const left = `${prefix}${subject}${after}`;
|
|
45
104
|
const tail = ' '.repeat(COMMIT_TAIL);
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
out
|
|
52
|
-
out += paint(
|
|
53
|
-
out +=
|
|
54
|
-
out += paint(
|
|
55
|
-
|
|
105
|
+
const tone = commitTone(selected, current);
|
|
106
|
+
const tones = current
|
|
107
|
+
? { ...COMMIT_COL_FG, ...COMMIT_CURRENT_COL_FG }
|
|
108
|
+
: COMMIT_COL_FG;
|
|
109
|
+
if (!color) return `${left}${suffix}${tail}`;
|
|
110
|
+
let out = `${seq(tone.fgRgb, tone.bgRgb)}${EL}`;
|
|
111
|
+
out += paint(prefix, tone.fgRgb, tone.bgRgb, color);
|
|
112
|
+
out += paint(subject, tone.nameFg, tone.bgRgb, color);
|
|
113
|
+
out += paint(after, tone.fgRgb, tone.bgRgb, color);
|
|
114
|
+
out += paintColumns(parts, cols, tone.fgRgb, tone.bgRgb, color, tones);
|
|
115
|
+
out += paint(tail, tone.fgRgb, tone.bgRgb, color);
|
|
116
|
+
return out;
|
|
56
117
|
};
|
|
57
118
|
|
|
58
119
|
const paintCommitEdit = (compose, width, color, entry, cols) => {
|
|
59
|
-
const text =
|
|
120
|
+
const text = splitMessage(compose.text).line;
|
|
60
121
|
if (entry && cols) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
color,
|
|
65
|
-
true,
|
|
66
|
-
cols,
|
|
67
|
-
);
|
|
68
|
-
const inner = Math.max(1, width - COMMIT_EDGE - COMMIT_TAIL);
|
|
122
|
+
const shown = { ...entry, body: text, subject: text };
|
|
123
|
+
const row = paintCommitRow(shown, width, color, true, cols);
|
|
124
|
+
const inner = contentWidth(width);
|
|
69
125
|
const prefix = ` ${FILE_MARK} `;
|
|
70
|
-
const lead = ' ';
|
|
71
126
|
const parts = padColumns(commitRowFields(entry), cols, COMMIT_COL_ALIGN);
|
|
72
127
|
const suffix = joinColumns(parts, cols);
|
|
73
|
-
const rest = Math.max(1, inner - visibleWidth(prefix +
|
|
128
|
+
const rest = Math.max(1, inner - visibleWidth(prefix + suffix));
|
|
74
129
|
const clipped = truncateVisible(text, rest);
|
|
75
|
-
const prefixW = visibleWidth(prefix
|
|
130
|
+
const prefixW = visibleWidth(prefix);
|
|
76
131
|
const col = Math.min(compose.cursor ?? 0, visibleWidth(clipped));
|
|
77
132
|
return {
|
|
78
133
|
row,
|
|
@@ -80,50 +135,514 @@ const paintCommitEdit = (compose, width, color, entry, cols) => {
|
|
|
80
135
|
at: 'replace',
|
|
81
136
|
};
|
|
82
137
|
}
|
|
83
|
-
const inner =
|
|
138
|
+
const inner = contentWidth(width);
|
|
84
139
|
const prefix = ` ${FILE_MARK} `;
|
|
85
|
-
const
|
|
86
|
-
const prefixW = visibleWidth(prefix + lead);
|
|
140
|
+
const prefixW = visibleWidth(prefix);
|
|
87
141
|
const rest = Math.max(1, inner - prefixW);
|
|
88
142
|
const clipped = truncateVisible(text, rest);
|
|
89
143
|
const pad = Math.max(0, rest - visibleWidth(clipped));
|
|
90
144
|
const gap = ' '.repeat(pad);
|
|
145
|
+
const tail = ' '.repeat(COMMIT_TAIL);
|
|
91
146
|
const fgRgb = THEME.chromeFg;
|
|
92
147
|
const bgRgb = THEME.buttonBg;
|
|
93
148
|
const nameFg = THEME.buttonHotFg;
|
|
94
|
-
|
|
95
|
-
let row = `${prefix}${lead}${clipped}${gap}${edge}`;
|
|
149
|
+
let row = `${prefix}${clipped}${gap}${tail}`;
|
|
96
150
|
if (color) {
|
|
97
151
|
row = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
98
152
|
row += paint(prefix, fgRgb, bgRgb, color);
|
|
99
|
-
row += paint(lead, nameFg, bgRgb, color);
|
|
100
153
|
row += paint(clipped, nameFg, bgRgb, color);
|
|
101
154
|
row += paint(gap, fgRgb, bgRgb, color);
|
|
102
|
-
row +=
|
|
155
|
+
row += paint(tail, fgRgb, bgRgb, color);
|
|
103
156
|
}
|
|
104
157
|
const col = Math.min(compose.cursor ?? 0, visibleWidth(clipped));
|
|
105
158
|
return { row, cursor: { x: prefixW + col + 1 }, at: 'start' };
|
|
106
159
|
};
|
|
107
160
|
|
|
161
|
+
const pendingAt = (commits) => {
|
|
162
|
+
for (let i = 0; i < commits.length; i++) {
|
|
163
|
+
if (commits[i] && commits[i].pending) return i;
|
|
164
|
+
}
|
|
165
|
+
return -1;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const editsCursor = (compose) =>
|
|
169
|
+
compose.commitKind === 'amend' || compose.commitKind === 'reword';
|
|
170
|
+
|
|
171
|
+
const editsPending = (compose) =>
|
|
172
|
+
compose.commitKind === 'commit' || compose.commitKind === 'fixup';
|
|
173
|
+
|
|
108
174
|
const commitEditRow = (compose, commits, cursor, width, color, cols) => {
|
|
109
175
|
if (!compose || compose.kind !== 'commit') return null;
|
|
110
|
-
|
|
111
|
-
|
|
176
|
+
let at = -1;
|
|
177
|
+
if (editsCursor(compose)) at = cursor;
|
|
178
|
+
if (editsPending(compose)) at = pendingAt(commits);
|
|
179
|
+
if (at >= 0) {
|
|
180
|
+
const entry = commits[at];
|
|
112
181
|
if (!entry) return null;
|
|
113
|
-
|
|
182
|
+
const painted = paintCommitEdit(compose, width, color, entry, cols);
|
|
183
|
+
painted.index = at;
|
|
184
|
+
return painted;
|
|
114
185
|
}
|
|
115
186
|
return paintCommitEdit(compose, width, color);
|
|
116
187
|
};
|
|
117
188
|
|
|
118
|
-
const
|
|
189
|
+
const paintPieces = (pieces, width, color, selected, current) => {
|
|
190
|
+
const tone = commitTone(selected, current);
|
|
191
|
+
const inner = contentWidth(width);
|
|
192
|
+
const shown = [];
|
|
193
|
+
let used = 0;
|
|
194
|
+
for (const piece of pieces) {
|
|
195
|
+
const room = inner - used;
|
|
196
|
+
if (room <= 0) break;
|
|
197
|
+
const text = truncateVisible(piece.text, room);
|
|
198
|
+
if (!text) continue;
|
|
199
|
+
shown.push({ text, fg: piece.fg });
|
|
200
|
+
used += visibleWidth(text);
|
|
201
|
+
}
|
|
202
|
+
const pad = ' '.repeat(Math.max(0, inner - used));
|
|
203
|
+
const tail = ' '.repeat(COMMIT_TAIL);
|
|
204
|
+
if (!color) {
|
|
205
|
+
let plain = '';
|
|
206
|
+
for (const piece of shown) plain += piece.text;
|
|
207
|
+
return `${plain}${pad}${tail}`;
|
|
208
|
+
}
|
|
209
|
+
let out = `${seq(tone.fgRgb, tone.bgRgb)}${EL}`;
|
|
210
|
+
for (const piece of shown) {
|
|
211
|
+
out += paint(piece.text, piece.fg, tone.bgRgb, color);
|
|
212
|
+
}
|
|
213
|
+
out += paint(pad, tone.fgRgb, tone.bgRgb, color);
|
|
214
|
+
out += paint(tail, tone.fgRgb, tone.bgRgb, color);
|
|
215
|
+
return out;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const COL_GAP = 2;
|
|
219
|
+
const MIN_LEFT = 8;
|
|
220
|
+
|
|
221
|
+
const messageText = (entry) => {
|
|
222
|
+
const body = `${entry.body ?? ''}`.replace(/\s+$/, '');
|
|
223
|
+
if (body) return body;
|
|
224
|
+
return `${entry.subject ?? ''}`.replace(/\s+$/, '');
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const wrapKind = (text, width, kind) => {
|
|
228
|
+
const rows = wrapMultiline(text, Math.max(1, width));
|
|
229
|
+
const lines = [];
|
|
230
|
+
for (const row of rows) lines.push({ text: row, kind });
|
|
231
|
+
return lines;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const messagePartsFrom = (text, width) => {
|
|
235
|
+
const raw = `${text ?? ''}`;
|
|
236
|
+
if (!raw) return [{ text: '', kind: 'text' }];
|
|
237
|
+
const split = raw.split('\n');
|
|
238
|
+
const lines = wrapKind(split[0], width, 'text');
|
|
239
|
+
for (let i = 1; i < split.length; i++) {
|
|
240
|
+
const rest = wrapKind(split[i], width, 'body');
|
|
241
|
+
for (const line of rest) lines.push(line);
|
|
242
|
+
}
|
|
243
|
+
return lines;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const messageParts = (entry, width) =>
|
|
247
|
+
messagePartsFrom(messageText(entry), width);
|
|
248
|
+
|
|
249
|
+
const authorParts = (entry) => {
|
|
250
|
+
const name = `${entry.author ?? ''}`.trim();
|
|
251
|
+
const email = `${entry.email ?? ''}`.trim();
|
|
252
|
+
if (name && email) {
|
|
253
|
+
return [
|
|
254
|
+
{ text: name, kind: 'author' },
|
|
255
|
+
{ text: ` <${email}>`, kind: 'email' },
|
|
256
|
+
];
|
|
257
|
+
}
|
|
258
|
+
if (email) return [{ text: email, kind: 'email' }];
|
|
259
|
+
if (name) return [{ text: name, kind: 'author' }];
|
|
260
|
+
return [];
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const partsWidth = (parts) => {
|
|
264
|
+
let width = 0;
|
|
265
|
+
for (const part of parts) width += visibleWidth(part.text);
|
|
266
|
+
return width;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const timeNeed = (entry) => {
|
|
270
|
+
const when = `${entry.when ?? ''}`.trim();
|
|
271
|
+
const ago = `${entry.date ?? ''}`.trim();
|
|
272
|
+
if (when && ago) return visibleWidth(when) + COL_GAP + visibleWidth(ago);
|
|
273
|
+
return visibleWidth(when || ago);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const refsText = (entry) => `${entry.refs ?? ''}`.trim();
|
|
277
|
+
|
|
278
|
+
const rightNeed = (entry) =>
|
|
279
|
+
Math.max(
|
|
280
|
+
partsWidth(authorParts(entry)),
|
|
281
|
+
timeNeed(entry),
|
|
282
|
+
visibleWidth(refsText(entry)),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const spreadTime = (when, ago, width) => {
|
|
286
|
+
const agoW = visibleWidth(ago);
|
|
287
|
+
if (agoW >= width) {
|
|
288
|
+
return [{ text: truncateVisible(ago, width), kind: 'meta' }];
|
|
289
|
+
}
|
|
290
|
+
const room = width - agoW;
|
|
291
|
+
const minGap = Math.min(COL_GAP, room);
|
|
292
|
+
const left = truncateVisible(when, room - minGap);
|
|
293
|
+
const gap = width - visibleWidth(left) - agoW;
|
|
294
|
+
return [
|
|
295
|
+
{ text: left, kind: 'meta' },
|
|
296
|
+
{ text: ' '.repeat(gap), kind: 'meta' },
|
|
297
|
+
{ text: ago, kind: 'meta' },
|
|
298
|
+
];
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const alignEnd = (text, width, kind) => {
|
|
302
|
+
const shown = truncateVisible(text, width);
|
|
303
|
+
const pad = Math.max(0, width - visibleWidth(shown));
|
|
304
|
+
return [
|
|
305
|
+
{ text: ' '.repeat(pad), kind },
|
|
306
|
+
{ text: shown, kind },
|
|
307
|
+
];
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const timeParts = (entry, width) => {
|
|
311
|
+
const when = `${entry.when ?? ''}`.trim();
|
|
312
|
+
const ago = `${entry.date ?? ''}`.trim();
|
|
313
|
+
if (!width) return [];
|
|
314
|
+
if (when && ago) return spreadTime(when, ago, width);
|
|
315
|
+
if (ago) return alignEnd(ago, width, 'meta');
|
|
316
|
+
if (when) return [{ text: truncateVisible(when, width), kind: 'meta' }];
|
|
317
|
+
return [];
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const rightRows = (entry, rightW) => {
|
|
321
|
+
const rows = [authorParts(entry), timeParts(entry, rightW)];
|
|
322
|
+
const refs = refsText(entry);
|
|
323
|
+
if (refs) rows.push([{ text: refs, kind: 'refs' }]);
|
|
324
|
+
return rows;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const panelWidths = (need, leftNeed, inner) => {
|
|
328
|
+
const indent = visibleWidth(FULL_INDENT);
|
|
329
|
+
let rightW = need;
|
|
330
|
+
const room = Math.max(1, inner - indent);
|
|
331
|
+
if (!rightW || room <= MIN_LEFT) {
|
|
332
|
+
return { rightW: 0, leftW: room, gapW: 0 };
|
|
333
|
+
}
|
|
334
|
+
const reserve = Math.max(MIN_LEFT, leftNeed);
|
|
335
|
+
const minLeft = Math.min(reserve, room - COL_GAP - 1);
|
|
336
|
+
const maxRight = room - COL_GAP - minLeft;
|
|
337
|
+
if (rightW > maxRight) rightW = Math.max(1, maxRight);
|
|
338
|
+
return { rightW, leftW: room - COL_GAP - rightW, gapW: COL_GAP };
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const panelLeft = (entry, leftW, edit) => {
|
|
342
|
+
const parts = edit
|
|
343
|
+
? messagePartsFrom(edit.text ?? '', leftW)
|
|
344
|
+
: messageParts(entry, leftW);
|
|
345
|
+
return [{ text: entry.sha ?? '', kind: 'hash' }, ...parts];
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const commitMessageWidth = (entry, screenWidth) => {
|
|
349
|
+
const width = Math.max(20, screenWidth || 80);
|
|
350
|
+
const inner = contentWidth(width);
|
|
351
|
+
const indent = visibleWidth(FULL_INDENT);
|
|
352
|
+
if (!entry) return Math.max(1, inner - indent);
|
|
353
|
+
const hashW = visibleWidth(entry.sha ?? '');
|
|
354
|
+
const cols = panelWidths(rightNeed(entry), hashW, inner);
|
|
355
|
+
return Math.max(1, cols.leftW);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const prefixWidth = (first, selected) => {
|
|
359
|
+
const mark = first && selected ? FILE_MARK : ' ';
|
|
360
|
+
const prefix = first ? ` ${mark} ` : FULL_INDENT;
|
|
361
|
+
return visibleWidth(prefix);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const messageCaret = (text, cursor, width, row, first, selected) => {
|
|
365
|
+
const pos = cursorInWrap(`${text ?? ''}`, cursor ?? 0, width);
|
|
366
|
+
if (pos.row !== row) return null;
|
|
367
|
+
return { x: prefixWidth(first, selected) + pos.col + 1 };
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const pieceFg = (kind, tone) => {
|
|
371
|
+
if (tone.current) {
|
|
372
|
+
if (kind === 'hash') return THEME.shaDarkFg;
|
|
373
|
+
if (kind === 'refs') return THEME.warnFg;
|
|
374
|
+
if (kind === 'text' || kind === 'body') return THEME.mutedFg;
|
|
375
|
+
return tone.fgRgb;
|
|
376
|
+
}
|
|
377
|
+
if (kind === 'hash') return THEME.shaFg;
|
|
378
|
+
if (kind === 'author') return THEME.addLineFg;
|
|
379
|
+
if (kind === 'email') return THEME.mutedFg;
|
|
380
|
+
if (kind === 'refs') return THEME.warnFg;
|
|
381
|
+
if (kind === 'text') return tone.nameFg;
|
|
382
|
+
if (kind === 'body') return THEME.mutedFg;
|
|
383
|
+
return tone.fgRgb;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const clipParts = (parts, width, fallbackKind, tone) => {
|
|
387
|
+
const shown = [];
|
|
388
|
+
let used = 0;
|
|
389
|
+
for (const part of parts) {
|
|
390
|
+
const room = width - used;
|
|
391
|
+
if (room <= 0) break;
|
|
392
|
+
const text = truncateVisible(part.text ?? '', room);
|
|
393
|
+
if (!text) continue;
|
|
394
|
+
const kind = part.kind ?? fallbackKind;
|
|
395
|
+
shown.push({ text, fg: pieceFg(kind, tone) });
|
|
396
|
+
used += visibleWidth(text);
|
|
397
|
+
}
|
|
398
|
+
const pad = Math.max(0, width - used);
|
|
399
|
+
if (pad) shown.push({ text: ' '.repeat(pad), fg: tone.fgRgb });
|
|
400
|
+
return shown;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const paintPanelLine = (spec) => {
|
|
404
|
+
const tone = commitTone(spec.selected, spec.current);
|
|
405
|
+
const mark = spec.first && spec.selected ? FILE_MARK : ' ';
|
|
406
|
+
const prefix = spec.first ? ` ${mark} ` : FULL_INDENT;
|
|
407
|
+
const left = truncateVisible(spec.left, spec.leftW);
|
|
408
|
+
const leftPad = Math.max(0, spec.leftW - visibleWidth(left));
|
|
409
|
+
const gap = ' '.repeat(spec.gapW);
|
|
410
|
+
const right = Array.isArray(spec.right)
|
|
411
|
+
? spec.right
|
|
412
|
+
: [{ text: spec.right ?? '', kind: spec.rightKind }];
|
|
413
|
+
return paintPieces(
|
|
414
|
+
[
|
|
415
|
+
{ text: prefix, fg: tone.fgRgb },
|
|
416
|
+
{ text: left, fg: pieceFg(spec.leftKind, tone) },
|
|
417
|
+
{ text: `${' '.repeat(leftPad)}${gap}`, fg: tone.fgRgb },
|
|
418
|
+
...clipParts(right, spec.rightW, spec.rightKind, tone),
|
|
419
|
+
],
|
|
420
|
+
spec.width,
|
|
421
|
+
spec.color,
|
|
422
|
+
spec.selected,
|
|
423
|
+
spec.current,
|
|
424
|
+
);
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const lineRecord = (commit, row, hit, cursor = null) => ({
|
|
428
|
+
commit,
|
|
429
|
+
row,
|
|
430
|
+
hit,
|
|
431
|
+
cursor,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const commitLines = (entry, index, width, color, selected, edit, current) => {
|
|
435
|
+
const inner = contentWidth(width);
|
|
436
|
+
const hashW = visibleWidth(entry.sha ?? '');
|
|
437
|
+
const cols = panelWidths(rightNeed(entry), hashW, inner);
|
|
438
|
+
const rights = rightRows(entry, cols.rightW);
|
|
439
|
+
const left = panelLeft(entry, cols.leftW, edit);
|
|
440
|
+
const count = Math.max(left.length, rights.length);
|
|
441
|
+
const lines = [];
|
|
442
|
+
for (let row = 0; row < count; row++) {
|
|
443
|
+
const line = left[row] ?? { text: '', kind: 'text' };
|
|
444
|
+
const painted = paintPanelLine({
|
|
445
|
+
left: line.text,
|
|
446
|
+
right: rights[row] ?? [],
|
|
447
|
+
leftW: cols.leftW,
|
|
448
|
+
rightW: cols.rightW,
|
|
449
|
+
gapW: cols.gapW,
|
|
450
|
+
width,
|
|
451
|
+
color,
|
|
452
|
+
selected,
|
|
453
|
+
current,
|
|
454
|
+
first: row === 0,
|
|
455
|
+
leftKind: line.kind,
|
|
456
|
+
rightKind: 'meta',
|
|
457
|
+
});
|
|
458
|
+
const caret = edit
|
|
459
|
+
? messageCaret(
|
|
460
|
+
edit.text,
|
|
461
|
+
edit.cursor,
|
|
462
|
+
cols.leftW,
|
|
463
|
+
row - 1,
|
|
464
|
+
false,
|
|
465
|
+
selected,
|
|
466
|
+
)
|
|
467
|
+
: null;
|
|
468
|
+
lines.push(lineRecord(index, painted, true, caret));
|
|
469
|
+
}
|
|
470
|
+
return lines;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const draftLines = (text, cursor, width, color) => {
|
|
474
|
+
const inner = contentWidth(width);
|
|
475
|
+
const leftW = Math.max(1, inner - visibleWidth(FULL_INDENT));
|
|
476
|
+
const parts = messagePartsFrom(text, leftW);
|
|
477
|
+
const lines = [];
|
|
478
|
+
for (let row = 0; row < parts.length; row++) {
|
|
479
|
+
const line = parts[row];
|
|
480
|
+
const painted = paintPanelLine({
|
|
481
|
+
left: line.text,
|
|
482
|
+
right: '',
|
|
483
|
+
leftW,
|
|
484
|
+
rightW: 0,
|
|
485
|
+
gapW: 0,
|
|
486
|
+
width,
|
|
487
|
+
color,
|
|
488
|
+
selected: true,
|
|
489
|
+
first: row === 0,
|
|
490
|
+
leftKind: line.kind,
|
|
491
|
+
rightKind: 'meta',
|
|
492
|
+
});
|
|
493
|
+
const caret = messageCaret(text, cursor, leftW, row, row === 0, true);
|
|
494
|
+
lines.push(lineRecord(-1, painted, false, caret));
|
|
495
|
+
}
|
|
496
|
+
return lines;
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const composeEditsIndex = (compose, commits, index, cursor) => {
|
|
500
|
+
if (!compose || compose.kind !== 'commit') return false;
|
|
501
|
+
if (editsCursor(compose)) return index === cursor;
|
|
502
|
+
if (!editsPending(compose)) return false;
|
|
503
|
+
const at = pendingAt(commits);
|
|
504
|
+
return at >= 0 && index === at;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const composeShowsDraft = (compose, commits) => {
|
|
508
|
+
if (!compose || compose.kind !== 'commit') return false;
|
|
509
|
+
if (editsCursor(compose)) return false;
|
|
510
|
+
if (editsPending(compose)) return pendingAt(commits) < 0;
|
|
511
|
+
return true;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const gapLine = (width, color) =>
|
|
515
|
+
lineRecord(-1, paintBodyFill(width, color, 'files'), false);
|
|
516
|
+
|
|
517
|
+
const flattenCommits = (commits, cursor, width, color, compose, rev, short) => {
|
|
518
|
+
const lines = [];
|
|
519
|
+
const editing = compose && compose.kind === 'commit';
|
|
520
|
+
const pushGap = () => {
|
|
521
|
+
if (lines.length) lines.push(gapLine(width, color));
|
|
522
|
+
};
|
|
523
|
+
if (composeShowsDraft(compose, commits)) {
|
|
524
|
+
const draft = draftLines(
|
|
525
|
+
compose.text ?? '',
|
|
526
|
+
compose.cursor ?? 0,
|
|
527
|
+
width,
|
|
528
|
+
color,
|
|
529
|
+
);
|
|
530
|
+
for (const line of draft) lines.push(line);
|
|
531
|
+
}
|
|
532
|
+
for (let index = 0; index < commits.length; index++) {
|
|
533
|
+
pushGap();
|
|
534
|
+
const inplace = composeEditsIndex(compose, commits, index, cursor);
|
|
535
|
+
const selected = inplace || (!editing && index === cursor);
|
|
536
|
+
const edit = inplace
|
|
537
|
+
? { text: compose.text ?? '', cursor: compose.cursor ?? 0 }
|
|
538
|
+
: null;
|
|
539
|
+
const block = commitLines(
|
|
540
|
+
commits[index],
|
|
541
|
+
index,
|
|
542
|
+
width,
|
|
543
|
+
color,
|
|
544
|
+
selected,
|
|
545
|
+
edit,
|
|
546
|
+
viewedCommit(commits[index], rev, short),
|
|
547
|
+
);
|
|
548
|
+
for (const line of block) lines.push(line);
|
|
549
|
+
}
|
|
550
|
+
return lines;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const focusLine = (lines, cursor, compose) => {
|
|
554
|
+
if (compose && compose.kind === 'commit') {
|
|
555
|
+
for (let i = 0; i < lines.length; i++) {
|
|
556
|
+
if (lines[i].cursor) return i;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
for (let i = 0; i < lines.length; i++) {
|
|
560
|
+
const line = lines[i];
|
|
561
|
+
if (line.hit && line.commit === cursor) return i;
|
|
562
|
+
}
|
|
563
|
+
return 0;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const paintLineWindow = (
|
|
567
|
+
lines,
|
|
568
|
+
focus,
|
|
569
|
+
width,
|
|
570
|
+
color,
|
|
571
|
+
bodyH,
|
|
572
|
+
headerLines,
|
|
573
|
+
offset,
|
|
574
|
+
) => {
|
|
575
|
+
const pad = bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
|
|
576
|
+
const listH = bodyH - pad * 2;
|
|
577
|
+
const start = listWindowStart(focus, offset, listH, lines.length);
|
|
578
|
+
const blank = paintBodyFill(width, color, 'files');
|
|
579
|
+
const body = [];
|
|
580
|
+
const fileHits = [];
|
|
581
|
+
let textCursor = null;
|
|
582
|
+
if (pad) body.push(blank);
|
|
583
|
+
for (let i = 0; i < listH; i++) {
|
|
584
|
+
const line = lines[start + i];
|
|
585
|
+
if (!line) break;
|
|
586
|
+
body.push(line.row);
|
|
587
|
+
if (line.cursor) {
|
|
588
|
+
textCursor = { x: line.cursor.x, row: body.length - 1 };
|
|
589
|
+
}
|
|
590
|
+
if (!line.hit) continue;
|
|
591
|
+
const y = headerLines + pad + i + 1;
|
|
592
|
+
fileHits.push({ y, cursor: line.commit });
|
|
593
|
+
}
|
|
594
|
+
if (pad) body.push(blank);
|
|
595
|
+
return { body, fileHits, cursor: textCursor, offset: start };
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const paintFullCommits = (view, width, color, bodyH, headerLines) => {
|
|
119
599
|
const commits = view.commits ?? [];
|
|
120
|
-
const cols = measureColumns(commits, commitRowFields, COMMIT_COL_ALIGN);
|
|
121
600
|
const cursor = view.commitCursor ?? 0;
|
|
122
|
-
const compose = view.compose;
|
|
123
|
-
const
|
|
601
|
+
const compose = view.compose ?? null;
|
|
602
|
+
const lines = flattenCommits(
|
|
603
|
+
commits,
|
|
604
|
+
cursor,
|
|
605
|
+
width,
|
|
606
|
+
color,
|
|
607
|
+
compose,
|
|
608
|
+
view.rev,
|
|
609
|
+
view.revShort,
|
|
610
|
+
);
|
|
611
|
+
const focus = focusLine(lines, cursor, compose);
|
|
612
|
+
return paintLineWindow(
|
|
613
|
+
lines,
|
|
614
|
+
focus,
|
|
615
|
+
width,
|
|
616
|
+
color,
|
|
617
|
+
bodyH,
|
|
618
|
+
headerLines,
|
|
619
|
+
view.listScroll,
|
|
620
|
+
);
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const paintBriefCommits = (
|
|
624
|
+
commits,
|
|
625
|
+
cursor,
|
|
626
|
+
width,
|
|
627
|
+
color,
|
|
628
|
+
bodyH,
|
|
629
|
+
headerLines,
|
|
630
|
+
extra,
|
|
631
|
+
offset,
|
|
632
|
+
cols,
|
|
633
|
+
rev,
|
|
634
|
+
revShort,
|
|
635
|
+
) => {
|
|
124
636
|
const paintRow = (entry, rowWidth, rowColor, selected) =>
|
|
125
|
-
paintCommitRow(
|
|
126
|
-
|
|
637
|
+
paintCommitRow(
|
|
638
|
+
entry,
|
|
639
|
+
rowWidth,
|
|
640
|
+
rowColor,
|
|
641
|
+
selected,
|
|
642
|
+
cols,
|
|
643
|
+
viewedCommit(entry, rev, revShort),
|
|
644
|
+
);
|
|
645
|
+
return paintCursorList(
|
|
127
646
|
commits,
|
|
128
647
|
cursor,
|
|
129
648
|
width,
|
|
@@ -132,8 +651,33 @@ const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
|
|
|
132
651
|
headerLines,
|
|
133
652
|
paintRow,
|
|
134
653
|
extra,
|
|
135
|
-
|
|
654
|
+
offset,
|
|
136
655
|
);
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
|
|
659
|
+
const commits = view.commits ?? [];
|
|
660
|
+
const cols = measureColumns(commits, commitRowFields, COMMIT_COL_ALIGN);
|
|
661
|
+
const cursor = view.commitCursor ?? 0;
|
|
662
|
+
const full = view.commitView === 'full';
|
|
663
|
+
const extra = full
|
|
664
|
+
? null
|
|
665
|
+
: commitEditRow(view.compose, commits, cursor, width, color, cols);
|
|
666
|
+
const painted = full
|
|
667
|
+
? paintFullCommits(view, width, color, bodyH, headerLines)
|
|
668
|
+
: paintBriefCommits(
|
|
669
|
+
commits,
|
|
670
|
+
cursor,
|
|
671
|
+
width,
|
|
672
|
+
color,
|
|
673
|
+
bodyH,
|
|
674
|
+
headerLines,
|
|
675
|
+
extra,
|
|
676
|
+
view.listScroll,
|
|
677
|
+
cols,
|
|
678
|
+
view.rev,
|
|
679
|
+
view.revShort,
|
|
680
|
+
);
|
|
137
681
|
return paneResult({
|
|
138
682
|
body: painted.body,
|
|
139
683
|
fileHits: painted.fileHits,
|
|
@@ -144,4 +688,10 @@ const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
|
|
|
144
688
|
});
|
|
145
689
|
};
|
|
146
690
|
|
|
147
|
-
module.exports = {
|
|
691
|
+
module.exports = {
|
|
692
|
+
paintBodyCommits,
|
|
693
|
+
commitMessageWidth,
|
|
694
|
+
commitLine,
|
|
695
|
+
commitText,
|
|
696
|
+
splitMessage,
|
|
697
|
+
};
|