@bahulam/code 2.6.13 → 2.6.15
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/package.json +4 -4
- package/src/agents/scaffold.mjs +1 -0
- package/src/commands/agent.mjs +3 -2
- package/src/core/approval-log.mjs +45 -4
- package/src/core/approval.mjs +265 -41
- package/src/core/file-diff.mjs +1 -1
- package/src/core/headless.mjs +14 -3
- package/src/core/local-agent.mjs +3 -2
- package/src/core/risk-tier.mjs +53 -2
- package/src/core/safety.mjs +61 -4
- package/src/core/tool-executor.mjs +38 -16
- package/src/core/trust.mjs +5 -3
- package/src/index.mjs +1 -1
- package/src/terminal/agents.mjs +194 -18
- package/src/terminal/repl-render.mjs +126 -11
- package/src/terminal/repl-state.mjs +2 -0
- package/src/terminal/repl.mjs +553 -88
- package/src/terminal/tool-display.mjs +154 -2
- package/src/ui/approval.mjs +211 -14
- package/src/ui/icons.mjs +11 -5
- package/src/ui/input-dock.mjs +214 -29
- package/src/ui/slash-commands.mjs +10 -0
- package/src/ui/tool-card.mjs +261 -30
- package/src/ui/tool-details.mjs +206 -14
- package/src/ui/transcript-block.mjs +2 -3
package/src/ui/tool-card.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
toolDisplaySummary,
|
|
30
30
|
formatShellCommand,
|
|
31
31
|
shellCommandDisplay,
|
|
32
|
+
shellCommandProfile,
|
|
32
33
|
} from '../terminal/tool-display.mjs';
|
|
33
34
|
|
|
34
35
|
// ── Family → label colorizer ─────────────────────────────────────────────
|
|
@@ -50,8 +51,10 @@ function formatArgs(tool, args, cwd) {
|
|
|
50
51
|
const summary = toolDisplaySummary(tool, args || {}, { cwd });
|
|
51
52
|
if (!summary) return '';
|
|
52
53
|
if (tool === 'shell') {
|
|
54
|
+
const profile = shellCommandProfile(summary, { cwd });
|
|
55
|
+
if (profile.compact) return compactShellProfile(profile);
|
|
53
56
|
const display = shellCommandDisplay(summary, { cwd });
|
|
54
|
-
const command = formatShellCommand(display.command, paintShellAdapter)
|
|
57
|
+
const command = `${paint.text.dim('$')} ${formatShellCommand(display.command, paintShellAdapter)}`;
|
|
55
58
|
return display.cwdLabel
|
|
56
59
|
? `${command} ${paint.text.dim('in')} ${paint.brand.data(display.cwdLabel)}`
|
|
57
60
|
: command;
|
|
@@ -150,6 +153,10 @@ export function summarizeResult(tool, data) {
|
|
|
150
153
|
if (exit != null && exit !== 0) {
|
|
151
154
|
return { text: `exit ${exit}`, tone: 'danger' };
|
|
152
155
|
}
|
|
156
|
+
if (tool === 'shell') {
|
|
157
|
+
const structured = structuredOutputSummary(data.output_preview || data.output);
|
|
158
|
+
if (structured) return structured;
|
|
159
|
+
}
|
|
153
160
|
const head = firstOutputLine(data).slice(0, 100);
|
|
154
161
|
return { text: head || 'ok', tone: 'success' };
|
|
155
162
|
}
|
|
@@ -179,34 +186,92 @@ export function summarizeResult(tool, data) {
|
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
188
|
|
|
189
|
+
function structuredOutputSummary(output) {
|
|
190
|
+
const raw = String(output || '').trim();
|
|
191
|
+
if (!raw || !/^[\[{]/.test(raw)) return null;
|
|
192
|
+
let value;
|
|
193
|
+
try {
|
|
194
|
+
value = JSON.parse(raw);
|
|
195
|
+
} catch {
|
|
196
|
+
const first = raw.split('\n').find(line => /^[\[{]/.test(line.trim()));
|
|
197
|
+
if (!first) return null;
|
|
198
|
+
try { value = JSON.parse(first.trim()); } catch { return null; }
|
|
199
|
+
}
|
|
200
|
+
return summarizeJsonOutput(value);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function summarizeJsonOutput(value) {
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
return { text: `json array · ${value.length} item${value.length === 1 ? '' : 's'}`, tone: 'success' };
|
|
206
|
+
}
|
|
207
|
+
if (!value || typeof value !== 'object') return null;
|
|
208
|
+
|
|
209
|
+
if ('service' in value && 'profile' in value && 'inSync' in value) {
|
|
210
|
+
const service = String(value.service || 'service');
|
|
211
|
+
const profile = value.profile ? ` · ${value.profile}` : '';
|
|
212
|
+
const status = value.inSync === true ? 'in sync'
|
|
213
|
+
: value.inSync === false ? 'out of sync'
|
|
214
|
+
: 'sync status unknown';
|
|
215
|
+
const diffs = Array.isArray(value.diff) ? value.diff
|
|
216
|
+
: Array.isArray(value.diffs) ? value.diffs
|
|
217
|
+
: [];
|
|
218
|
+
const diffText = diffs.length ? ` · ${diffs.length} diff${diffs.length === 1 ? '' : 's'}` : '';
|
|
219
|
+
return {
|
|
220
|
+
text: `${service} ${status}${profile}${diffText}`,
|
|
221
|
+
tone: value.inSync === false ? 'warn' : 'success',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const keys = Object.keys(value).slice(0, 4);
|
|
226
|
+
return {
|
|
227
|
+
text: keys.length ? `json · ${keys.join(', ')}` : 'json object',
|
|
228
|
+
tone: 'success',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
182
232
|
export function formatCompactFileDiff(result, {
|
|
183
233
|
indent = ' ',
|
|
184
|
-
maxLines =
|
|
185
|
-
maxFiles =
|
|
234
|
+
maxLines = Infinity,
|
|
235
|
+
maxFiles = Infinity,
|
|
186
236
|
columns = term().columns || 120,
|
|
237
|
+
showFileHeader = false,
|
|
187
238
|
} = {}) {
|
|
188
|
-
const diffs = fileDiffs(result)
|
|
239
|
+
const diffs = fileDiffs(result)
|
|
240
|
+
.map(normalizeFileDiff)
|
|
241
|
+
.filter(diff => diff && (diff.redacted || diff?.hunks?.length));
|
|
189
242
|
if (!diffs.length) return '';
|
|
190
243
|
|
|
191
244
|
const out = [];
|
|
192
245
|
let shown = 0;
|
|
193
246
|
let truncated = false;
|
|
247
|
+
const lineLimit = Number.isFinite(maxLines) ? Math.max(0, Math.floor(maxLines)) : Infinity;
|
|
248
|
+
const fileLimit = Number.isFinite(maxFiles) ? Math.max(0, Math.floor(maxFiles)) : diffs.length;
|
|
194
249
|
const lineBudget = Math.max(40, columns - visibleWidth(indent) - 4);
|
|
195
250
|
|
|
196
|
-
for (const diff of diffs.slice(0,
|
|
197
|
-
if (diffs.length > 1) {
|
|
198
|
-
if (shown >=
|
|
251
|
+
for (const diff of diffs.slice(0, fileLimit)) {
|
|
252
|
+
if (showFileHeader || diffs.length > 1) {
|
|
253
|
+
if (shown >= lineLimit) { truncated = true; break; }
|
|
199
254
|
out.push(`${indent}${paint.brand.primary(diff.relative_path || diff.path || 'file')} ${paint.text.dim(diffDelta(diff))}`);
|
|
200
255
|
shown++;
|
|
201
256
|
}
|
|
202
257
|
|
|
258
|
+
if (diff.redacted) {
|
|
259
|
+
if (shown >= lineLimit) { truncated = true; break; }
|
|
260
|
+
const subject = (showFileHeader || diffs.length > 1)
|
|
261
|
+
? ''
|
|
262
|
+
: `${[diff.relative_path || diff.path || 'file', diffDelta(diff)].filter(Boolean).join(' ')} · `;
|
|
263
|
+
out.push(`${indent}${paint.text.dim(`${subject}diff redacted for sensitive config`)}`);
|
|
264
|
+
shown++;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
203
268
|
for (const hunk of diff.hunks || []) {
|
|
204
|
-
if (shown >=
|
|
269
|
+
if (shown >= lineLimit) { truncated = true; break; }
|
|
205
270
|
out.push(`${indent}${paint.text.dim(`@@ -${hunk.old_start},${hunk.old_count} +${hunk.new_start},${hunk.new_count} @@`)}`);
|
|
206
271
|
shown++;
|
|
207
272
|
|
|
208
273
|
for (const line of hunk.lines || []) {
|
|
209
|
-
if (shown >=
|
|
274
|
+
if (shown >= lineLimit) { truncated = true; break; }
|
|
210
275
|
out.push(`${indent}${paintDiffLine(line, lineBudget)}`);
|
|
211
276
|
shown++;
|
|
212
277
|
}
|
|
@@ -215,7 +280,7 @@ export function formatCompactFileDiff(result, {
|
|
|
215
280
|
if (truncated) break;
|
|
216
281
|
}
|
|
217
282
|
|
|
218
|
-
if (diffs.length >
|
|
283
|
+
if (diffs.length > fileLimit) truncated = true;
|
|
219
284
|
if (truncated) out.push(`${indent}${paint.text.dim('… diff preview truncated; use /last to expand')}`);
|
|
220
285
|
return out.join('\n');
|
|
221
286
|
}
|
|
@@ -224,9 +289,112 @@ function fileDiffs(result) {
|
|
|
224
289
|
if (!result) return [];
|
|
225
290
|
if (Array.isArray(result.file_diffs)) return result.file_diffs;
|
|
226
291
|
if (result.file_diff) return [result.file_diff];
|
|
292
|
+
if (result.type === 'file_diff' || result.hunks || result.unified) return [result];
|
|
227
293
|
return [];
|
|
228
294
|
}
|
|
229
295
|
|
|
296
|
+
function normalizeFileDiff(diff) {
|
|
297
|
+
if (!diff) return null;
|
|
298
|
+
return {
|
|
299
|
+
...diff,
|
|
300
|
+
hunks: normalizeHunks(diff),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeHunks(diff) {
|
|
305
|
+
if (Array.isArray(diff?.hunks) && diff.hunks.length) {
|
|
306
|
+
return diff.hunks.map(normalizeHunk).filter(hunk => hunk.lines.length);
|
|
307
|
+
}
|
|
308
|
+
if (diff?.unified) return parseUnifiedHunks(diff.unified);
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function normalizeHunk(hunk = {}) {
|
|
313
|
+
const lines = Array.isArray(hunk.lines)
|
|
314
|
+
? hunk.lines.map(normalizeDiffLine).filter(Boolean)
|
|
315
|
+
: typeof hunk.body === 'string'
|
|
316
|
+
? parseDiffBody(hunk.body)
|
|
317
|
+
: [];
|
|
318
|
+
const oldCount = hunk.old_count ?? hunk.old_lines ?? hunk.oldCount ?? countDiffLines(lines, 'old');
|
|
319
|
+
const newCount = hunk.new_count ?? hunk.new_lines ?? hunk.newCount ?? countDiffLines(lines, 'new');
|
|
320
|
+
return {
|
|
321
|
+
...hunk,
|
|
322
|
+
old_start: hunk.old_start ?? hunk.oldStart ?? 1,
|
|
323
|
+
old_count: oldCount,
|
|
324
|
+
new_start: hunk.new_start ?? hunk.newStart ?? 1,
|
|
325
|
+
new_count: newCount,
|
|
326
|
+
lines,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function normalizeDiffLine(line) {
|
|
331
|
+
if (typeof line === 'string') return parseUnifiedLine(line);
|
|
332
|
+
if (!line || typeof line !== 'object') return null;
|
|
333
|
+
const rawType = String(line.type || line.kind || '').toLowerCase();
|
|
334
|
+
const text = String(line.text ?? line.content ?? line.value ?? '');
|
|
335
|
+
if (rawType === 'add' || rawType === 'added' || rawType === '+') return { ...line, type: 'add', text };
|
|
336
|
+
if (rawType === 'remove' || rawType === 'removed' || rawType === 'delete' || rawType === '-') return { ...line, type: 'remove', text };
|
|
337
|
+
if (rawType === 'context' || rawType === 'same' || rawType === ' ') return { ...line, type: 'context', text };
|
|
338
|
+
return parseUnifiedLine(text);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function parseDiffBody(body) {
|
|
342
|
+
return String(body || '')
|
|
343
|
+
.replace(/\r\n?/g, '\n')
|
|
344
|
+
.split('\n')
|
|
345
|
+
.filter(line => line && !line.startsWith('@@'))
|
|
346
|
+
.map(parseUnifiedLine)
|
|
347
|
+
.filter(Boolean);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function parseUnifiedHunks(unified) {
|
|
351
|
+
const hunks = [];
|
|
352
|
+
let current = null;
|
|
353
|
+
for (const raw of String(unified || '').replace(/\r\n?/g, '\n').split('\n')) {
|
|
354
|
+
if (raw.startsWith('--- ') || raw.startsWith('+++ ')) continue;
|
|
355
|
+
const header = raw.match(/^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/);
|
|
356
|
+
if (header) {
|
|
357
|
+
current = {
|
|
358
|
+
old_start: Number(header[1]) || 1,
|
|
359
|
+
old_count: Number(header[2] || 1),
|
|
360
|
+
new_start: Number(header[3]) || 1,
|
|
361
|
+
new_count: Number(header[4] || 1),
|
|
362
|
+
lines: [],
|
|
363
|
+
};
|
|
364
|
+
hunks.push(current);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (!current) {
|
|
368
|
+
if (!raw || (!raw.startsWith('+') && !raw.startsWith('-') && !raw.startsWith(' '))) continue;
|
|
369
|
+
current = { old_start: 1, old_count: 0, new_start: 1, new_count: 0, lines: [] };
|
|
370
|
+
hunks.push(current);
|
|
371
|
+
}
|
|
372
|
+
const line = parseUnifiedLine(raw);
|
|
373
|
+
if (line) current.lines.push(line);
|
|
374
|
+
}
|
|
375
|
+
for (const hunk of hunks) {
|
|
376
|
+
if (!hunk.old_count) hunk.old_count = countDiffLines(hunk.lines, 'old');
|
|
377
|
+
if (!hunk.new_count) hunk.new_count = countDiffLines(hunk.lines, 'new');
|
|
378
|
+
}
|
|
379
|
+
return hunks.filter(hunk => hunk.lines.length);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function parseUnifiedLine(raw) {
|
|
383
|
+
const line = String(raw ?? '');
|
|
384
|
+
if (!line && raw !== '') return null;
|
|
385
|
+
if (line.startsWith('+') && !line.startsWith('+++')) return { type: 'add', text: line.slice(1) };
|
|
386
|
+
if (line.startsWith('-') && !line.startsWith('---')) return { type: 'remove', text: line.slice(1) };
|
|
387
|
+
if (line.startsWith(' ')) return { type: 'context', text: line.slice(1) };
|
|
388
|
+
return { type: 'context', text: line };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function countDiffLines(lines, side) {
|
|
392
|
+
return lines.filter(line => {
|
|
393
|
+
if (side === 'old') return line.type !== 'add';
|
|
394
|
+
return line.type !== 'remove';
|
|
395
|
+
}).length;
|
|
396
|
+
}
|
|
397
|
+
|
|
230
398
|
function paintDiffLine(line, maxWidth) {
|
|
231
399
|
const text = truncatePlain(String(line?.text ?? ''), Math.max(20, maxWidth - 2));
|
|
232
400
|
if (line?.type === 'add') return paint.state.success(`+ ${text}`);
|
|
@@ -303,20 +471,41 @@ function tone(text, t) {
|
|
|
303
471
|
export function formatCardHead(tool, args, opts = {}) {
|
|
304
472
|
const cwd = opts.cwd || safeCwd();
|
|
305
473
|
const cols = opts.columns || term().columns || 120;
|
|
306
|
-
const indent = opts.indent
|
|
474
|
+
const indent = opts.indent ?? (tool === 'shell' ? '' : ' ');
|
|
307
475
|
|
|
308
476
|
const label = toolDisplayLabel(tool);
|
|
309
477
|
const argsText = formatArgs(tool, args, cwd);
|
|
478
|
+
const leadText = formatHeadLead(tool, label);
|
|
310
479
|
|
|
311
|
-
const leadVisible = visibleWidth(`${indent}${
|
|
480
|
+
const leadVisible = visibleWidth(`${indent}${leadText}`);
|
|
312
481
|
const budget = Math.max(20, cols - leadVisible - 4);
|
|
313
482
|
|
|
483
|
+
if (tool === 'shell') {
|
|
484
|
+
const profile = shellCommandProfile(toolDisplaySummary(tool, args || {}, { cwd }), { cwd });
|
|
485
|
+
if (profile.compact) {
|
|
486
|
+
const head = `${indent}${leadText}`;
|
|
487
|
+
if (profile.preview) {
|
|
488
|
+
const fullArgs = compactShellProfile(profile);
|
|
489
|
+
if (visibleWidth(fullArgs) <= budget) return `${head} ${fullArgs}`;
|
|
490
|
+
const previewTail = `${paint.text.dim(' · preview:')} ${paint.text.primary(profile.preview)}`;
|
|
491
|
+
const baseArgs = compactShellProfile(profile, { includePreview: false, includeDetails: false });
|
|
492
|
+
const baseBudget = budget - visibleWidth(previewTail);
|
|
493
|
+
if (baseBudget >= 12) {
|
|
494
|
+
const baseTruncated = truncateEndVisible(baseArgs, baseBudget);
|
|
495
|
+
return `${head} ${baseTruncated}${previewTail}`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const argsTruncated = truncateMiddle(argsText, budget);
|
|
499
|
+
return argsTruncated ? `${head} ${argsTruncated}` : head;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
314
503
|
if (tool === 'shell' && visibleWidth(argsText) > budget) {
|
|
315
504
|
const wrapWidth = Math.max(32, cols - visibleWidth(indent) - 4);
|
|
316
505
|
const display = shellCommandDisplay(toolDisplaySummary(tool, args || {}, { cwd }), { cwd });
|
|
317
506
|
const commandLines = wrapCommand(display.command, wrapWidth)
|
|
318
|
-
.map(line => `${indent}${paint.text.dim('
|
|
319
|
-
const head = `${indent}${
|
|
507
|
+
.map((line, index) => `${indent}${paint.text.dim(index === 0 ? '$ ' : '> ')}${formatShellCommand(line, paintShellAdapter)}`);
|
|
508
|
+
const head = `${indent}${leadText}`;
|
|
320
509
|
const cwdLine = display.cwdLabel
|
|
321
510
|
? `\n${indent}${paint.text.dim(' in ')}${paint.brand.data(display.cwdLabel)}`
|
|
322
511
|
: '';
|
|
@@ -325,10 +514,27 @@ export function formatCardHead(tool, args, opts = {}) {
|
|
|
325
514
|
|
|
326
515
|
const argsTruncated = truncateMiddle(argsText, budget);
|
|
327
516
|
|
|
328
|
-
const head = `${indent}${
|
|
517
|
+
const head = `${indent}${leadText}`;
|
|
329
518
|
return argsTruncated ? `${head} ${argsTruncated}` : head;
|
|
330
519
|
}
|
|
331
520
|
|
|
521
|
+
function formatHeadLead(tool, label) {
|
|
522
|
+
if (tool !== 'shell') return paintLabel(tool, label);
|
|
523
|
+
return `${paint.text.dim('• shell ·')} ${paintLabel(tool, label)}`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function compactShellProfile(profile, { includePreview = true, includeDetails = true } = {}) {
|
|
527
|
+
const previewSuffix = profile.preview ? ` · preview: ${profile.preview}` : '';
|
|
528
|
+
const summary = previewSuffix && profile.summary.endsWith(previewSuffix)
|
|
529
|
+
? profile.summary.slice(0, -previewSuffix.length)
|
|
530
|
+
: profile.summary;
|
|
531
|
+
const parts = [`${paint.text.dim('$')} ${paint.text.primary(summary)}`];
|
|
532
|
+
if (profile.cwdLabel) parts.push(`${paint.text.dim('in')} ${paint.brand.data(profile.cwdLabel)}`);
|
|
533
|
+
if (includeDetails) parts.push(paint.text.dim(profile.detailHint || 'details: F2 or /last'));
|
|
534
|
+
if (includePreview && profile.preview) parts.push(`${paint.text.dim('preview:')} ${paint.text.primary(profile.preview)}`);
|
|
535
|
+
return parts.join(' · ');
|
|
536
|
+
}
|
|
537
|
+
|
|
332
538
|
/**
|
|
333
539
|
* Render a full card with outcome.
|
|
334
540
|
*
|
|
@@ -346,7 +552,7 @@ export function formatCard({ tool, args, result, durationMs, indent, columns, cw
|
|
|
346
552
|
|
|
347
553
|
if (!summary.text && !duration) return head;
|
|
348
554
|
|
|
349
|
-
const arrow =
|
|
555
|
+
const arrow = outcomeLead(tool);
|
|
350
556
|
const body = summary.text ? tone(summary.text, summary.tone) : '';
|
|
351
557
|
// Hide the duration tail when the tool was effectively instant (<200ms).
|
|
352
558
|
// For fast reads, "1ms" / "0ms" was noise that broke the prose feel.
|
|
@@ -374,6 +580,19 @@ export function formatCard({ tool, args, result, durationMs, indent, columns, cw
|
|
|
374
580
|
return `${head}\n${gutterIndent}${arrow} ${body}${tail}`;
|
|
375
581
|
}
|
|
376
582
|
|
|
583
|
+
function outcomeLead(tool) {
|
|
584
|
+
return isShellOutcomeTool(tool)
|
|
585
|
+
? `${paint.text.dim('result')} ${paint.text.dim('—')}`
|
|
586
|
+
: paint.text.dim('—');
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function isShellOutcomeTool(tool) {
|
|
590
|
+
return [
|
|
591
|
+
'shell', 'run_tests', 'validate_build', 'lint_check',
|
|
592
|
+
'validate_file', 'validate_structure',
|
|
593
|
+
].includes(String(tool || '').toLowerCase());
|
|
594
|
+
}
|
|
595
|
+
|
|
377
596
|
function isInlineOutcomeTool(tool) {
|
|
378
597
|
return [
|
|
379
598
|
'read_file', 'read_files', 'read_batch', 'get_file_info',
|
|
@@ -393,26 +612,38 @@ function truncateMiddle(text, max) {
|
|
|
393
612
|
return paint.text.muted(`${head}…${tail}`);
|
|
394
613
|
}
|
|
395
614
|
|
|
615
|
+
function truncateEndVisible(text, max) {
|
|
616
|
+
if (!text) return '';
|
|
617
|
+
if (visibleWidth(text) <= max) return text;
|
|
618
|
+
const plain = text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
619
|
+
const limit = Math.max(1, Math.floor(max));
|
|
620
|
+
if (limit <= 1) return '';
|
|
621
|
+
return paint.text.muted(`${plain.slice(0, limit - 1)}…`);
|
|
622
|
+
}
|
|
623
|
+
|
|
396
624
|
function wrapCommand(command, width) {
|
|
397
625
|
const text = String(command || '');
|
|
398
626
|
if (!text) return ['(empty command)'];
|
|
399
627
|
const lines = [];
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
628
|
+
for (const physicalLine of text.replace(/\r\n?/g, '\n').split('\n')) {
|
|
629
|
+
let line = '';
|
|
630
|
+
for (const token of physicalLine.match(/\S+\s*/g) || [physicalLine]) {
|
|
631
|
+
const next = line + token;
|
|
632
|
+
if (line && visibleWidth(next.trimEnd()) > width) {
|
|
633
|
+
lines.push(line.trimEnd());
|
|
634
|
+
line = token;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (!line && visibleWidth(token.trimEnd()) > width) {
|
|
638
|
+
lines.push(...chunkLongToken(token.trimEnd(), width));
|
|
639
|
+
line = '';
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
line = next;
|
|
412
643
|
}
|
|
413
|
-
line
|
|
644
|
+
if (line.trimEnd()) lines.push(line.trimEnd());
|
|
645
|
+
else if (!physicalLine.trim()) lines.push('');
|
|
414
646
|
}
|
|
415
|
-
if (line.trimEnd()) lines.push(line.trimEnd());
|
|
416
647
|
return lines.length ? lines : ['(empty command)'];
|
|
417
648
|
}
|
|
418
649
|
|