@optique/core 1.3.0-dev.2542 → 1.3.0-dev.2554
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/dist/displaywidth.cjs +12 -2
- package/dist/displaywidth.d.cts +8 -1
- package/dist/displaywidth.d.ts +8 -1
- package/dist/displaywidth.js +11 -2
- package/dist/doc.cjs +63 -83
- package/dist/doc.js +63 -83
- package/dist/facade.cjs +12 -12
- package/dist/facade.js +12 -12
- package/dist/internal/terminal.cjs +5 -1
- package/dist/internal/terminal.d.cts +2 -1
- package/dist/internal/terminal.d.ts +2 -1
- package/dist/internal/terminal.js +2 -1
- package/dist/terminal-internal.d.cts +2 -0
- package/dist/terminal-internal.d.ts +2 -0
- package/dist/text-layout.cjs +87 -0
- package/dist/text-layout.d.cts +54 -0
- package/dist/text-layout.d.ts +54 -0
- package/dist/text-layout.js +84 -0
- package/dist/usage.cjs +34 -8
- package/dist/usage.js +34 -8
- package/package.json +2 -2
package/dist/displaywidth.cjs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
//#region src/displaywidth.ts
|
|
3
3
|
const ansiRegex = /\x1B(?:\[[0-9;:]*[@-~]|\][^\x1B\x07]*(?:\x1B\\|\x07))/g;
|
|
4
|
+
/**
|
|
5
|
+
* Removes ANSI CSI and OSC sequences before measuring visible text.
|
|
6
|
+
* @param text Rendered terminal text.
|
|
7
|
+
* @returns Text without terminal escape sequences.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
function stripAnsi(text) {
|
|
11
|
+
return text.replace(ansiRegex, "");
|
|
12
|
+
}
|
|
4
13
|
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
5
14
|
/**
|
|
6
15
|
* Computes the terminal display width of a string, accounting for
|
|
@@ -11,7 +20,7 @@ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
|
11
20
|
* @internal
|
|
12
21
|
*/
|
|
13
22
|
function getDisplayWidth(text) {
|
|
14
|
-
const stripped = text
|
|
23
|
+
const stripped = stripAnsi(text);
|
|
15
24
|
let width = 0;
|
|
16
25
|
for (const { segment } of segmenter.segment(stripped)) width += graphemeWidth(segment);
|
|
17
26
|
return width;
|
|
@@ -41,4 +50,5 @@ function isEastAsianWide(cp) {
|
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
//#endregion
|
|
44
|
-
exports.getDisplayWidth = getDisplayWidth;
|
|
53
|
+
exports.getDisplayWidth = getDisplayWidth;
|
|
54
|
+
exports.stripAnsi = stripAnsi;
|
package/dist/displaywidth.d.cts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
//#region src/displaywidth.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Removes ANSI CSI and OSC sequences before measuring visible text.
|
|
4
|
+
* @param text Rendered terminal text.
|
|
5
|
+
* @returns Text without terminal escape sequences.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
declare function stripAnsi(text: string): string;
|
|
2
9
|
/**
|
|
3
10
|
* Computes the terminal display width of a string, accounting for
|
|
4
11
|
* East Asian wide characters, combining marks, emoji, and ANSI escapes.
|
|
@@ -9,4 +16,4 @@
|
|
|
9
16
|
*/
|
|
10
17
|
declare function getDisplayWidth(text: string): number;
|
|
11
18
|
//#endregion
|
|
12
|
-
export { getDisplayWidth };
|
|
19
|
+
export { getDisplayWidth, stripAnsi };
|
package/dist/displaywidth.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
//#region src/displaywidth.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Removes ANSI CSI and OSC sequences before measuring visible text.
|
|
4
|
+
* @param text Rendered terminal text.
|
|
5
|
+
* @returns Text without terminal escape sequences.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
declare function stripAnsi(text: string): string;
|
|
2
9
|
/**
|
|
3
10
|
* Computes the terminal display width of a string, accounting for
|
|
4
11
|
* East Asian wide characters, combining marks, emoji, and ANSI escapes.
|
|
@@ -9,4 +16,4 @@
|
|
|
9
16
|
*/
|
|
10
17
|
declare function getDisplayWidth(text: string): number;
|
|
11
18
|
//#endregion
|
|
12
|
-
export { getDisplayWidth };
|
|
19
|
+
export { getDisplayWidth, stripAnsi };
|
package/dist/displaywidth.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
//#region src/displaywidth.ts
|
|
2
2
|
const ansiRegex = /\x1B(?:\[[0-9;:]*[@-~]|\][^\x1B\x07]*(?:\x1B\\|\x07))/g;
|
|
3
|
+
/**
|
|
4
|
+
* Removes ANSI CSI and OSC sequences before measuring visible text.
|
|
5
|
+
* @param text Rendered terminal text.
|
|
6
|
+
* @returns Text without terminal escape sequences.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
function stripAnsi(text) {
|
|
10
|
+
return text.replace(ansiRegex, "");
|
|
11
|
+
}
|
|
3
12
|
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
4
13
|
/**
|
|
5
14
|
* Computes the terminal display width of a string, accounting for
|
|
@@ -10,7 +19,7 @@ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
|
10
19
|
* @internal
|
|
11
20
|
*/
|
|
12
21
|
function getDisplayWidth(text) {
|
|
13
|
-
const stripped = text
|
|
22
|
+
const stripped = stripAnsi(text);
|
|
14
23
|
let width = 0;
|
|
15
24
|
for (const { segment } of segmenter.segment(stripped)) width += graphemeWidth(segment);
|
|
16
25
|
return width;
|
|
@@ -40,4 +49,4 @@ function isEastAsianWide(cp) {
|
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
//#endregion
|
|
43
|
-
export { getDisplayWidth };
|
|
52
|
+
export { getDisplayWidth, stripAnsi };
|
package/dist/doc.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_message_registry = require('./message-registry.cjs');
|
|
2
|
-
const require_displaywidth = require('./displaywidth.cjs');
|
|
3
2
|
const require_terminal_internal = require('./terminal-internal.cjs');
|
|
4
3
|
const require_message = require('./message.cjs');
|
|
5
4
|
const require_message_renderer = require('./message-renderer.cjs');
|
|
5
|
+
const require_text_layout = require('./text-layout.cjs');
|
|
6
6
|
const require_validate = require('./validate.cjs');
|
|
7
7
|
const require_usage = require('./usage.cjs');
|
|
8
8
|
|
|
@@ -251,8 +251,8 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
251
251
|
}
|
|
252
252
|
return rendered;
|
|
253
253
|
};
|
|
254
|
-
const usageLabel = page.usage != null && options.showUsage !== false ? label("Usage:", "usage")
|
|
255
|
-
const usageLabelWidth =
|
|
254
|
+
const usageLabel = page.usage != null && options.showUsage !== false ? require_text_layout.spaceAfterLabel(label("Usage:", "usage")) : "";
|
|
255
|
+
const usageLabelWidth = require_text_layout.measureText(usageLabel).lastLineWidth;
|
|
256
256
|
const defaultStyle = require_terminal_internal.styleCode(options.theme?.annotationStyles?.default ?? { dim: true });
|
|
257
257
|
const choicesStyle = require_terminal_internal.styleCode(options.theme?.annotationStyles?.choices ?? { dim: true });
|
|
258
258
|
require_validate.validateProgramName(programName);
|
|
@@ -276,6 +276,18 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
276
276
|
}
|
|
277
277
|
const hasContent = (msg) => Array.isArray(msg) && msg.length > 0;
|
|
278
278
|
const needsDescriptionColumn = (entry) => hasContent(entry.description) || (options.showDefault === true || typeof options.showDefault === "object") && hasContent(entry.default) || (options.showChoices === true || typeof options.showChoices === "object") && hasContent(entry.choices);
|
|
279
|
+
const annotations = /* @__PURE__ */ new Map();
|
|
280
|
+
const annotation = (kind) => {
|
|
281
|
+
const cached = annotations.get(kind);
|
|
282
|
+
if (cached != null) return cached;
|
|
283
|
+
const config = kind === "default" ? options.showDefault : options.showChoices;
|
|
284
|
+
const prefix = punctuation(typeof config === "object" ? config.prefix ?? (kind === "default" ? " [" : " (") : kind === "default" ? " [" : " (", kind === "default" ? "defaultPrefix" : "choicesPrefix");
|
|
285
|
+
const suffix = punctuation(typeof config === "object" ? config.suffix ?? (kind === "default" ? "]" : ")") : kind === "default" ? "]" : ")", kind === "default" ? "defaultSuffix" : "choicesSuffix");
|
|
286
|
+
const choicesLabel = kind === "choices" ? label(typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ", "choices") : "";
|
|
287
|
+
const layout = require_text_layout.measureAnnotation(prefix + choicesLabel, suffix);
|
|
288
|
+
annotations.set(kind, layout);
|
|
289
|
+
return layout;
|
|
290
|
+
};
|
|
279
291
|
const automaticTermWidth = () => {
|
|
280
292
|
let widest;
|
|
281
293
|
for (const section of page.sections) for (const entry of section.entries) {
|
|
@@ -286,7 +298,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
286
298
|
optionsSeparator: ", ",
|
|
287
299
|
context: "doc"
|
|
288
300
|
});
|
|
289
|
-
const width =
|
|
301
|
+
const width = require_text_layout.measureText(rendered).maxLineWidth;
|
|
290
302
|
widest = widest == null ? width : Math.max(widest, width);
|
|
291
303
|
}
|
|
292
304
|
return widest;
|
|
@@ -303,16 +315,9 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
303
315
|
}
|
|
304
316
|
if (options.maxWidth != null) {
|
|
305
317
|
let minDescWidth = 1;
|
|
306
|
-
if (needsDescColumn) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
minDescWidth = Math.max(minDescWidth, require_displaywidth.getDisplayWidth(prefix));
|
|
310
|
-
}
|
|
311
|
-
if (options.showChoices && page.sections.some((s) => s.entries.some((e) => hasContent(e.choices)))) {
|
|
312
|
-
const prefix = punctuation(typeof options.showChoices === "object" ? options.showChoices.prefix ?? " (" : " (", "choicesPrefix");
|
|
313
|
-
const choicesLabelText = label(typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ", "choices");
|
|
314
|
-
minDescWidth = Math.max(minDescWidth, require_displaywidth.getDisplayWidth(prefix) + require_displaywidth.getDisplayWidth(choicesLabelText));
|
|
315
|
-
}
|
|
318
|
+
if (needsDescColumn) for (const kind of ["default", "choices"]) {
|
|
319
|
+
const enabled = kind === "default" ? options.showDefault : options.showChoices;
|
|
320
|
+
if (enabled && page.sections.some((section) => section.entries.some((entry) => hasContent(entry[kind])))) minDescWidth = Math.max(minDescWidth, annotation(kind).minWidth);
|
|
316
321
|
}
|
|
317
322
|
const splitEntryMin = termIndent + 2 + Math.max(2, 2 * minDescWidth - 1);
|
|
318
323
|
const fixedEntryMin = termIndent + 2 + termWidth + minDescWidth;
|
|
@@ -321,11 +326,17 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
321
326
|
type: "programName",
|
|
322
327
|
programName
|
|
323
328
|
}, options.theme))].map((token) => token.width)) : 0;
|
|
324
|
-
const usageMin = page.usage != null && showUsage ? usageLabelWidth + Math.max(programNameWidth, Math.min(maxVisibleAtomicWidth(page.usage, options.theme), programNameWidth + usageLabelWidth)) : 1;
|
|
329
|
+
const usageMin = page.usage != null && showUsage ? Math.max(require_text_layout.measureText(usageLabel).maxLineWidth, usageLabelWidth + Math.max(programNameWidth, Math.min(maxVisibleAtomicWidth(page.usage, options.theme), programNameWidth + usageLabelWidth))) : 1;
|
|
325
330
|
let sectionMin = 1;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
331
|
+
for (const kind of [
|
|
332
|
+
"examples",
|
|
333
|
+
"author",
|
|
334
|
+
"bugs"
|
|
335
|
+
]) {
|
|
336
|
+
if (!hasContent(page[kind])) continue;
|
|
337
|
+
const title = kind === "examples" ? "Examples:" : kind === "author" ? "Author:" : "Bugs:";
|
|
338
|
+
sectionMin = Math.max(sectionMin, 3, require_text_layout.measureText(label(title, kind)).maxLineWidth);
|
|
339
|
+
}
|
|
329
340
|
const minWidth = Math.max(entryMin, usageMin, sectionMin);
|
|
330
341
|
if (options.maxWidth < minWidth) throw new RangeError(`maxWidth must be at least ${minWidth}, got ${options.maxWidth}.`);
|
|
331
342
|
if (needsDescColumn && minDescWidth > 1) {
|
|
@@ -383,17 +394,21 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
383
394
|
output += sectionLabel;
|
|
384
395
|
}
|
|
385
396
|
for (const entry of section.entries) {
|
|
386
|
-
const
|
|
397
|
+
const termOptions = {
|
|
387
398
|
colors: options.colors,
|
|
388
399
|
theme: options.theme,
|
|
389
400
|
optionsSeparator: ", ",
|
|
390
|
-
context: "doc"
|
|
391
|
-
|
|
401
|
+
context: "doc"
|
|
402
|
+
};
|
|
403
|
+
const unwrappedTerm = require_usage.formatUsageTerm(entry.term, termOptions);
|
|
404
|
+
const multilineReplacement = require_text_layout.measureText(unwrappedTerm).lineCount > 1;
|
|
405
|
+
const term = options.maxWidth == null ? unwrappedTerm : require_usage.formatUsageTerm(entry.term, {
|
|
406
|
+
...termOptions,
|
|
407
|
+
maxWidth: options.maxWidth - termIndent
|
|
392
408
|
});
|
|
393
409
|
const descColumnWidth = options.maxWidth == null ? void 0 : options.maxWidth - termIndent - effectiveTermWidth - 2;
|
|
394
|
-
const termVisibleWidth =
|
|
410
|
+
const termVisibleWidth = require_text_layout.measureText(term).lastLineWidth;
|
|
395
411
|
const extraTermOffset = descColumnWidth != null ? Math.max(0, termVisibleWidth - effectiveTermWidth) : 0;
|
|
396
|
-
const currentExtraOffset = () => description.includes("\n") ? 0 : extraTermOffset;
|
|
397
412
|
const descFormatOptions = {
|
|
398
413
|
colors: options.colors,
|
|
399
414
|
quotes: !options.colors,
|
|
@@ -401,35 +416,26 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
401
416
|
initialWidth: extraTermOffset > 0 ? extraTermOffset : void 0
|
|
402
417
|
};
|
|
403
418
|
let description = entry.description == null ? "" : formatMessage(entry.description, descFormatOptions);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
424
|
-
const defaultContent = formatMessage(entry.default, defaultFormatOptions, options.theme?.annotationStyles?.default ?? { dim: true });
|
|
425
|
-
const defaultText = `${prefix}${defaultContent}${opaqueFormatter && options.colors ? defaultStyle : ""}${suffix}`;
|
|
426
|
-
const formattedDefault = options.colors ? `${defaultStyle}${defaultText}${defaultStyle ? "\x1B[0m" : ""}` : defaultText;
|
|
427
|
-
description += formattedDefault;
|
|
428
|
-
}
|
|
419
|
+
let cursor = require_text_layout.placeText(description, {
|
|
420
|
+
line: "",
|
|
421
|
+
column: extraTermOffset
|
|
422
|
+
}).cursor;
|
|
423
|
+
const appendAnnotation = (content, layout, style, ambient, quotes) => {
|
|
424
|
+
const prefix = require_text_layout.placeText(layout.prefix, cursor, descColumnWidth, 1);
|
|
425
|
+
if (prefix.text !== layout.prefix) description += "\n";
|
|
426
|
+
const rendered = formatMessage(content, {
|
|
427
|
+
colors: options.colors ? { resetSuffix: style } : false,
|
|
428
|
+
quotes,
|
|
429
|
+
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - layout.suffixMetrics.firstLineWidth,
|
|
430
|
+
initialWidth: descColumnWidth != null || layout.prefixMetrics.lineCount > 1 ? prefix.cursor.column : void 0
|
|
431
|
+
}, ambient);
|
|
432
|
+
const suffix = require_text_layout.placeText(layout.suffix, require_text_layout.placeText(rendered, prefix.cursor).cursor, descColumnWidth);
|
|
433
|
+
const annotationText = `${layout.prefix}${rendered}${opaqueFormatter && options.colors ? style : ""}${suffix.text}`;
|
|
434
|
+
description += options.colors ? `${style}${annotationText}${style ? "\x1B[0m" : ""}` : annotationText;
|
|
435
|
+
cursor = suffix.cursor;
|
|
436
|
+
};
|
|
437
|
+
if (options.showDefault && hasContent(entry.default)) appendAnnotation(entry.default, annotation("default"), defaultStyle, defaultAmbient, !options.colors);
|
|
429
438
|
if (options.showChoices && hasContent(entry.choices)) {
|
|
430
|
-
const prefix = punctuation(typeof options.showChoices === "object" ? options.showChoices.prefix ?? " (" : " (", "choicesPrefix");
|
|
431
|
-
const suffix = punctuation(typeof options.showChoices === "object" ? options.showChoices.suffix ?? ")" : ")", "choicesSuffix");
|
|
432
|
-
const choicesLabelText = label(typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ", "choices");
|
|
433
439
|
const maxItems = typeof options.showChoices === "object" ? options.showChoices.maxItems ?? 8 : 8;
|
|
434
440
|
const terms = Array.isArray(entry.choices) ? entry.choices : [];
|
|
435
441
|
let truncatedTerms = terms;
|
|
@@ -447,31 +453,10 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
447
453
|
}
|
|
448
454
|
if (truncated) truncatedTerms = [...terms.slice(0, cutIndex), require_message.text(", ...")];
|
|
449
455
|
}
|
|
450
|
-
|
|
451
|
-
const choicesSuffixWidth = require_displaywidth.getDisplayWidth(suffix);
|
|
452
|
-
const choicesLabelWidth = require_displaywidth.getDisplayWidth(choicesLabelText);
|
|
453
|
-
let choicesStartWidth;
|
|
454
|
-
if (descColumnWidth != null) {
|
|
455
|
-
const lastW = lastLineVisibleLength(description);
|
|
456
|
-
const effectiveLastW = lastW + currentExtraOffset();
|
|
457
|
-
const prefixLabelLen = choicesPrefixWidth + choicesLabelWidth;
|
|
458
|
-
if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
|
|
459
|
-
description += "\n";
|
|
460
|
-
choicesStartWidth = prefixLabelLen;
|
|
461
|
-
} else choicesStartWidth = effectiveLastW + prefixLabelLen;
|
|
462
|
-
}
|
|
463
|
-
const choicesFormatOptions = {
|
|
464
|
-
colors: options.colors ? { resetSuffix: choicesStyle } : false,
|
|
465
|
-
quotes: false,
|
|
466
|
-
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - choicesSuffixWidth,
|
|
467
|
-
initialWidth: choicesStartWidth
|
|
468
|
-
};
|
|
469
|
-
const choicesDisplay = formatMessage(truncatedTerms, choicesFormatOptions, options.theme?.annotationStyles?.choices ?? { dim: true });
|
|
470
|
-
const choicesText = `${prefix}${choicesLabelText}${choicesDisplay}${opaqueFormatter && options.colors ? choicesStyle : ""}${suffix}`;
|
|
471
|
-
const formattedChoices = options.colors ? `${choicesStyle}${choicesText}${choicesStyle ? "\x1B[0m" : ""}` : choicesText;
|
|
472
|
-
description += formattedChoices;
|
|
456
|
+
appendAnnotation(truncatedTerms, annotation("choices"), choicesStyle, choicesAmbient, false);
|
|
473
457
|
}
|
|
474
|
-
|
|
458
|
+
const paddedTerm = ansiAwareRightPad(term, effectiveTermWidth);
|
|
459
|
+
output += `${" ".repeat(termIndent)}${multilineReplacement ? indentLines(paddedTerm, termIndent) : paddedTerm}${description === "" ? "" : ` ${indentLines(description, termIndent + effectiveTermWidth + 2)}`}\n`;
|
|
475
460
|
}
|
|
476
461
|
}
|
|
477
462
|
if (hasContent(page.examples)) {
|
|
@@ -530,21 +515,16 @@ function indentLines(text$1, indent) {
|
|
|
530
515
|
* contribute to the rendered width.
|
|
531
516
|
*/
|
|
532
517
|
function maxVisibleAtomicWidth(usage, theme) {
|
|
533
|
-
return
|
|
518
|
+
return usage.reduce((widest, term) => Math.max(widest, require_text_layout.measureText(require_usage.formatUsageTerm(term, {
|
|
534
519
|
theme,
|
|
535
520
|
maxWidth: 1
|
|
536
|
-
})
|
|
521
|
+
})).maxLineWidth), 0);
|
|
537
522
|
}
|
|
538
523
|
function ansiAwareRightPad(text$1, length, char = " ") {
|
|
539
|
-
const visibleWidth =
|
|
524
|
+
const visibleWidth = require_text_layout.measureText(text$1).lastLineWidth;
|
|
540
525
|
if (visibleWidth >= length) return text$1;
|
|
541
526
|
return text$1 + char.repeat(length - visibleWidth);
|
|
542
527
|
}
|
|
543
|
-
function lastLineVisibleLength(text$1) {
|
|
544
|
-
const lastNewline = text$1.lastIndexOf("\n");
|
|
545
|
-
const lastLine = lastNewline === -1 ? text$1 : text$1.slice(lastNewline + 1);
|
|
546
|
-
return require_displaywidth.getDisplayWidth(lastLine);
|
|
547
|
-
}
|
|
548
528
|
|
|
549
529
|
//#endregion
|
|
550
530
|
exports.cloneDocEntry = cloneDocEntry;
|
package/dist/doc.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { messageRenderers } from "./message-registry.js";
|
|
2
|
-
import { getDisplayWidth } from "./displaywidth.js";
|
|
3
2
|
import { cacheTerminalTheme, formatTerminalTerm, fragmentTokens, renderTerminalTerm, styleCode } from "./terminal-internal.js";
|
|
4
3
|
import { cloneMessage, text } from "./message.js";
|
|
5
4
|
import { resolveMessageFormatter } from "./message-renderer.js";
|
|
5
|
+
import { measureAnnotation, measureText, placeText, spaceAfterLabel } from "./text-layout.js";
|
|
6
6
|
import { validateLabel, validateProgramName } from "./validate.js";
|
|
7
7
|
import { cloneUsageTerm, formatUsage, formatUsageTerm, isDocHidden } from "./usage.js";
|
|
8
8
|
|
|
@@ -251,8 +251,8 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
251
251
|
}
|
|
252
252
|
return rendered;
|
|
253
253
|
};
|
|
254
|
-
const usageLabel = page.usage != null && options.showUsage !== false ? label("Usage:", "usage")
|
|
255
|
-
const usageLabelWidth =
|
|
254
|
+
const usageLabel = page.usage != null && options.showUsage !== false ? spaceAfterLabel(label("Usage:", "usage")) : "";
|
|
255
|
+
const usageLabelWidth = measureText(usageLabel).lastLineWidth;
|
|
256
256
|
const defaultStyle = styleCode(options.theme?.annotationStyles?.default ?? { dim: true });
|
|
257
257
|
const choicesStyle = styleCode(options.theme?.annotationStyles?.choices ?? { dim: true });
|
|
258
258
|
validateProgramName(programName);
|
|
@@ -276,6 +276,18 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
276
276
|
}
|
|
277
277
|
const hasContent = (msg) => Array.isArray(msg) && msg.length > 0;
|
|
278
278
|
const needsDescriptionColumn = (entry) => hasContent(entry.description) || (options.showDefault === true || typeof options.showDefault === "object") && hasContent(entry.default) || (options.showChoices === true || typeof options.showChoices === "object") && hasContent(entry.choices);
|
|
279
|
+
const annotations = /* @__PURE__ */ new Map();
|
|
280
|
+
const annotation = (kind) => {
|
|
281
|
+
const cached = annotations.get(kind);
|
|
282
|
+
if (cached != null) return cached;
|
|
283
|
+
const config = kind === "default" ? options.showDefault : options.showChoices;
|
|
284
|
+
const prefix = punctuation(typeof config === "object" ? config.prefix ?? (kind === "default" ? " [" : " (") : kind === "default" ? " [" : " (", kind === "default" ? "defaultPrefix" : "choicesPrefix");
|
|
285
|
+
const suffix = punctuation(typeof config === "object" ? config.suffix ?? (kind === "default" ? "]" : ")") : kind === "default" ? "]" : ")", kind === "default" ? "defaultSuffix" : "choicesSuffix");
|
|
286
|
+
const choicesLabel = kind === "choices" ? label(typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ", "choices") : "";
|
|
287
|
+
const layout = measureAnnotation(prefix + choicesLabel, suffix);
|
|
288
|
+
annotations.set(kind, layout);
|
|
289
|
+
return layout;
|
|
290
|
+
};
|
|
279
291
|
const automaticTermWidth = () => {
|
|
280
292
|
let widest;
|
|
281
293
|
for (const section of page.sections) for (const entry of section.entries) {
|
|
@@ -286,7 +298,7 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
286
298
|
optionsSeparator: ", ",
|
|
287
299
|
context: "doc"
|
|
288
300
|
});
|
|
289
|
-
const width =
|
|
301
|
+
const width = measureText(rendered).maxLineWidth;
|
|
290
302
|
widest = widest == null ? width : Math.max(widest, width);
|
|
291
303
|
}
|
|
292
304
|
return widest;
|
|
@@ -303,16 +315,9 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
303
315
|
}
|
|
304
316
|
if (options.maxWidth != null) {
|
|
305
317
|
let minDescWidth = 1;
|
|
306
|
-
if (needsDescColumn) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
minDescWidth = Math.max(minDescWidth, getDisplayWidth(prefix));
|
|
310
|
-
}
|
|
311
|
-
if (options.showChoices && page.sections.some((s) => s.entries.some((e) => hasContent(e.choices)))) {
|
|
312
|
-
const prefix = punctuation(typeof options.showChoices === "object" ? options.showChoices.prefix ?? " (" : " (", "choicesPrefix");
|
|
313
|
-
const choicesLabelText = label(typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ", "choices");
|
|
314
|
-
minDescWidth = Math.max(minDescWidth, getDisplayWidth(prefix) + getDisplayWidth(choicesLabelText));
|
|
315
|
-
}
|
|
318
|
+
if (needsDescColumn) for (const kind of ["default", "choices"]) {
|
|
319
|
+
const enabled = kind === "default" ? options.showDefault : options.showChoices;
|
|
320
|
+
if (enabled && page.sections.some((section) => section.entries.some((entry) => hasContent(entry[kind])))) minDescWidth = Math.max(minDescWidth, annotation(kind).minWidth);
|
|
316
321
|
}
|
|
317
322
|
const splitEntryMin = termIndent + 2 + Math.max(2, 2 * minDescWidth - 1);
|
|
318
323
|
const fixedEntryMin = termIndent + 2 + termWidth + minDescWidth;
|
|
@@ -321,11 +326,17 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
321
326
|
type: "programName",
|
|
322
327
|
programName
|
|
323
328
|
}, options.theme))].map((token) => token.width)) : 0;
|
|
324
|
-
const usageMin = page.usage != null && showUsage ? usageLabelWidth + Math.max(programNameWidth, Math.min(maxVisibleAtomicWidth(page.usage, options.theme), programNameWidth + usageLabelWidth)) : 1;
|
|
329
|
+
const usageMin = page.usage != null && showUsage ? Math.max(measureText(usageLabel).maxLineWidth, usageLabelWidth + Math.max(programNameWidth, Math.min(maxVisibleAtomicWidth(page.usage, options.theme), programNameWidth + usageLabelWidth))) : 1;
|
|
325
330
|
let sectionMin = 1;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
331
|
+
for (const kind of [
|
|
332
|
+
"examples",
|
|
333
|
+
"author",
|
|
334
|
+
"bugs"
|
|
335
|
+
]) {
|
|
336
|
+
if (!hasContent(page[kind])) continue;
|
|
337
|
+
const title = kind === "examples" ? "Examples:" : kind === "author" ? "Author:" : "Bugs:";
|
|
338
|
+
sectionMin = Math.max(sectionMin, 3, measureText(label(title, kind)).maxLineWidth);
|
|
339
|
+
}
|
|
329
340
|
const minWidth = Math.max(entryMin, usageMin, sectionMin);
|
|
330
341
|
if (options.maxWidth < minWidth) throw new RangeError(`maxWidth must be at least ${minWidth}, got ${options.maxWidth}.`);
|
|
331
342
|
if (needsDescColumn && minDescWidth > 1) {
|
|
@@ -383,17 +394,21 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
383
394
|
output += sectionLabel;
|
|
384
395
|
}
|
|
385
396
|
for (const entry of section.entries) {
|
|
386
|
-
const
|
|
397
|
+
const termOptions = {
|
|
387
398
|
colors: options.colors,
|
|
388
399
|
theme: options.theme,
|
|
389
400
|
optionsSeparator: ", ",
|
|
390
|
-
context: "doc"
|
|
391
|
-
|
|
401
|
+
context: "doc"
|
|
402
|
+
};
|
|
403
|
+
const unwrappedTerm = formatUsageTerm(entry.term, termOptions);
|
|
404
|
+
const multilineReplacement = measureText(unwrappedTerm).lineCount > 1;
|
|
405
|
+
const term = options.maxWidth == null ? unwrappedTerm : formatUsageTerm(entry.term, {
|
|
406
|
+
...termOptions,
|
|
407
|
+
maxWidth: options.maxWidth - termIndent
|
|
392
408
|
});
|
|
393
409
|
const descColumnWidth = options.maxWidth == null ? void 0 : options.maxWidth - termIndent - effectiveTermWidth - 2;
|
|
394
|
-
const termVisibleWidth =
|
|
410
|
+
const termVisibleWidth = measureText(term).lastLineWidth;
|
|
395
411
|
const extraTermOffset = descColumnWidth != null ? Math.max(0, termVisibleWidth - effectiveTermWidth) : 0;
|
|
396
|
-
const currentExtraOffset = () => description.includes("\n") ? 0 : extraTermOffset;
|
|
397
412
|
const descFormatOptions = {
|
|
398
413
|
colors: options.colors,
|
|
399
414
|
quotes: !options.colors,
|
|
@@ -401,35 +416,26 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
401
416
|
initialWidth: extraTermOffset > 0 ? extraTermOffset : void 0
|
|
402
417
|
};
|
|
403
418
|
let description = entry.description == null ? "" : formatMessage(entry.description, descFormatOptions);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
424
|
-
const defaultContent = formatMessage(entry.default, defaultFormatOptions, options.theme?.annotationStyles?.default ?? { dim: true });
|
|
425
|
-
const defaultText = `${prefix}${defaultContent}${opaqueFormatter && options.colors ? defaultStyle : ""}${suffix}`;
|
|
426
|
-
const formattedDefault = options.colors ? `${defaultStyle}${defaultText}${defaultStyle ? "\x1B[0m" : ""}` : defaultText;
|
|
427
|
-
description += formattedDefault;
|
|
428
|
-
}
|
|
419
|
+
let cursor = placeText(description, {
|
|
420
|
+
line: "",
|
|
421
|
+
column: extraTermOffset
|
|
422
|
+
}).cursor;
|
|
423
|
+
const appendAnnotation = (content, layout, style, ambient, quotes) => {
|
|
424
|
+
const prefix = placeText(layout.prefix, cursor, descColumnWidth, 1);
|
|
425
|
+
if (prefix.text !== layout.prefix) description += "\n";
|
|
426
|
+
const rendered = formatMessage(content, {
|
|
427
|
+
colors: options.colors ? { resetSuffix: style } : false,
|
|
428
|
+
quotes,
|
|
429
|
+
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - layout.suffixMetrics.firstLineWidth,
|
|
430
|
+
initialWidth: descColumnWidth != null || layout.prefixMetrics.lineCount > 1 ? prefix.cursor.column : void 0
|
|
431
|
+
}, ambient);
|
|
432
|
+
const suffix = placeText(layout.suffix, placeText(rendered, prefix.cursor).cursor, descColumnWidth);
|
|
433
|
+
const annotationText = `${layout.prefix}${rendered}${opaqueFormatter && options.colors ? style : ""}${suffix.text}`;
|
|
434
|
+
description += options.colors ? `${style}${annotationText}${style ? "\x1B[0m" : ""}` : annotationText;
|
|
435
|
+
cursor = suffix.cursor;
|
|
436
|
+
};
|
|
437
|
+
if (options.showDefault && hasContent(entry.default)) appendAnnotation(entry.default, annotation("default"), defaultStyle, defaultAmbient, !options.colors);
|
|
429
438
|
if (options.showChoices && hasContent(entry.choices)) {
|
|
430
|
-
const prefix = punctuation(typeof options.showChoices === "object" ? options.showChoices.prefix ?? " (" : " (", "choicesPrefix");
|
|
431
|
-
const suffix = punctuation(typeof options.showChoices === "object" ? options.showChoices.suffix ?? ")" : ")", "choicesSuffix");
|
|
432
|
-
const choicesLabelText = label(typeof options.showChoices === "object" ? options.showChoices.label ?? "choices: " : "choices: ", "choices");
|
|
433
439
|
const maxItems = typeof options.showChoices === "object" ? options.showChoices.maxItems ?? 8 : 8;
|
|
434
440
|
const terms = Array.isArray(entry.choices) ? entry.choices : [];
|
|
435
441
|
let truncatedTerms = terms;
|
|
@@ -447,31 +453,10 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
447
453
|
}
|
|
448
454
|
if (truncated) truncatedTerms = [...terms.slice(0, cutIndex), text(", ...")];
|
|
449
455
|
}
|
|
450
|
-
|
|
451
|
-
const choicesSuffixWidth = getDisplayWidth(suffix);
|
|
452
|
-
const choicesLabelWidth = getDisplayWidth(choicesLabelText);
|
|
453
|
-
let choicesStartWidth;
|
|
454
|
-
if (descColumnWidth != null) {
|
|
455
|
-
const lastW = lastLineVisibleLength(description);
|
|
456
|
-
const effectiveLastW = lastW + currentExtraOffset();
|
|
457
|
-
const prefixLabelLen = choicesPrefixWidth + choicesLabelWidth;
|
|
458
|
-
if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
|
|
459
|
-
description += "\n";
|
|
460
|
-
choicesStartWidth = prefixLabelLen;
|
|
461
|
-
} else choicesStartWidth = effectiveLastW + prefixLabelLen;
|
|
462
|
-
}
|
|
463
|
-
const choicesFormatOptions = {
|
|
464
|
-
colors: options.colors ? { resetSuffix: choicesStyle } : false,
|
|
465
|
-
quotes: false,
|
|
466
|
-
maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - choicesSuffixWidth,
|
|
467
|
-
initialWidth: choicesStartWidth
|
|
468
|
-
};
|
|
469
|
-
const choicesDisplay = formatMessage(truncatedTerms, choicesFormatOptions, options.theme?.annotationStyles?.choices ?? { dim: true });
|
|
470
|
-
const choicesText = `${prefix}${choicesLabelText}${choicesDisplay}${opaqueFormatter && options.colors ? choicesStyle : ""}${suffix}`;
|
|
471
|
-
const formattedChoices = options.colors ? `${choicesStyle}${choicesText}${choicesStyle ? "\x1B[0m" : ""}` : choicesText;
|
|
472
|
-
description += formattedChoices;
|
|
456
|
+
appendAnnotation(truncatedTerms, annotation("choices"), choicesStyle, choicesAmbient, false);
|
|
473
457
|
}
|
|
474
|
-
|
|
458
|
+
const paddedTerm = ansiAwareRightPad(term, effectiveTermWidth);
|
|
459
|
+
output += `${" ".repeat(termIndent)}${multilineReplacement ? indentLines(paddedTerm, termIndent) : paddedTerm}${description === "" ? "" : ` ${indentLines(description, termIndent + effectiveTermWidth + 2)}`}\n`;
|
|
475
460
|
}
|
|
476
461
|
}
|
|
477
462
|
if (hasContent(page.examples)) {
|
|
@@ -530,21 +515,16 @@ function indentLines(text$1, indent) {
|
|
|
530
515
|
* contribute to the rendered width.
|
|
531
516
|
*/
|
|
532
517
|
function maxVisibleAtomicWidth(usage, theme) {
|
|
533
|
-
return
|
|
518
|
+
return usage.reduce((widest, term) => Math.max(widest, measureText(formatUsageTerm(term, {
|
|
534
519
|
theme,
|
|
535
520
|
maxWidth: 1
|
|
536
|
-
})
|
|
521
|
+
})).maxLineWidth), 0);
|
|
537
522
|
}
|
|
538
523
|
function ansiAwareRightPad(text$1, length, char = " ") {
|
|
539
|
-
const visibleWidth =
|
|
524
|
+
const visibleWidth = measureText(text$1).lastLineWidth;
|
|
540
525
|
if (visibleWidth >= length) return text$1;
|
|
541
526
|
return text$1 + char.repeat(length - visibleWidth);
|
|
542
527
|
}
|
|
543
|
-
function lastLineVisibleLength(text$1) {
|
|
544
|
-
const lastNewline = text$1.lastIndexOf("\n");
|
|
545
|
-
const lastLine = lastNewline === -1 ? text$1 : text$1.slice(lastNewline + 1);
|
|
546
|
-
return getDisplayWidth(lastLine);
|
|
547
|
-
}
|
|
548
528
|
|
|
549
529
|
//#endregion
|
|
550
530
|
export { cloneDocEntry, deduplicateDocEntries, deduplicateDocFragments, formatDocPage, isDocEntryHidden };
|
package/dist/facade.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_internal_annotations = require('./internal/annotations.cjs');
|
|
2
|
-
const require_displaywidth = require('./displaywidth.cjs');
|
|
3
2
|
const require_terminal_internal = require('./terminal-internal.cjs');
|
|
4
3
|
const require_message = require('./message.cjs');
|
|
5
4
|
const require_message_renderer = require('./message-renderer.cjs');
|
|
5
|
+
const require_text_layout = require('./text-layout.cjs');
|
|
6
6
|
const require_validate = require('./validate.cjs');
|
|
7
7
|
const require_usage = require('./usage.cjs');
|
|
8
8
|
const require_doc = require('./doc.cjs');
|
|
@@ -521,10 +521,10 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
|
|
|
521
521
|
messageFormatter,
|
|
522
522
|
theme
|
|
523
523
|
});
|
|
524
|
-
const errorLabel = () => require_terminal_internal.renderTerminalTerm({
|
|
524
|
+
const errorLabel = () => require_text_layout.spaceAfterLabel(require_terminal_internal.renderTerminalTerm({
|
|
525
525
|
type: "errorLabel",
|
|
526
526
|
label: "Error:"
|
|
527
|
-
}, theme, colors)
|
|
527
|
+
}, theme, colors));
|
|
528
528
|
const shellName = completionArgs[0] || "";
|
|
529
529
|
const args = completionArgs.slice(1);
|
|
530
530
|
const callOnError = (code, error) => onError(code, error);
|
|
@@ -536,7 +536,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
|
|
|
536
536
|
colors,
|
|
537
537
|
quotes: !colors,
|
|
538
538
|
maxWidth,
|
|
539
|
-
initialWidth:
|
|
539
|
+
initialWidth: require_text_layout.measureText(prefix).lastLineWidth
|
|
540
540
|
})}\n`);
|
|
541
541
|
if (completionParser) {
|
|
542
542
|
const displayName = isOptionMode ? completionOptionDisplayName ?? "--completion" : completionCommandDisplayName ?? "completion";
|
|
@@ -570,7 +570,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
|
|
|
570
570
|
colors,
|
|
571
571
|
quotes: !colors,
|
|
572
572
|
maxWidth,
|
|
573
|
-
initialWidth:
|
|
573
|
+
initialWidth: require_text_layout.measureText(prefix).lastLineWidth
|
|
574
574
|
})}`);
|
|
575
575
|
return require_mode_dispatch.dispatchByMode(parser.mode, () => {
|
|
576
576
|
const result = callOnError(1, error);
|
|
@@ -886,17 +886,17 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
886
886
|
messageFormatter,
|
|
887
887
|
theme
|
|
888
888
|
});
|
|
889
|
-
const errorLabel = () => require_terminal_internal.renderTerminalTerm({
|
|
889
|
+
const errorLabel = () => require_text_layout.spaceAfterLabel(require_terminal_internal.renderTerminalTerm({
|
|
890
890
|
type: "errorLabel",
|
|
891
891
|
label: "Error:"
|
|
892
|
-
}, theme, colors)
|
|
892
|
+
}, theme, colors));
|
|
893
893
|
let usagePrefix;
|
|
894
|
-
const usageLabel = () => usagePrefix ??= require_terminal_internal.renderTerminalTerm({
|
|
894
|
+
const usageLabel = () => usagePrefix ??= require_text_layout.spaceAfterLabel(require_terminal_internal.renderTerminalTerm({
|
|
895
895
|
type: "label",
|
|
896
896
|
label: "Usage:",
|
|
897
897
|
kind: "usageSummary"
|
|
898
|
-
}, theme, colors)
|
|
899
|
-
const usageLabelWidth = () =>
|
|
898
|
+
}, theme, colors));
|
|
899
|
+
const usageLabelWidth = () => require_text_layout.measureText(usageLabel()).lastLineWidth;
|
|
900
900
|
const norm = (c) => c === true ? {} : c;
|
|
901
901
|
const helpCommandConfig = norm(options.help?.command);
|
|
902
902
|
const helpOptionConfig = norm(options.help?.option);
|
|
@@ -1059,7 +1059,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
1059
1059
|
}), usageLabelWidth())}`);
|
|
1060
1060
|
const prefix = errorLabel();
|
|
1061
1061
|
const errorMessage = formatMessage(validationError, {
|
|
1062
|
-
initialWidth:
|
|
1062
|
+
initialWidth: require_text_layout.measureText(prefix).lastLineWidth,
|
|
1063
1063
|
maxWidth,
|
|
1064
1064
|
colors,
|
|
1065
1065
|
quotes: !colors
|
|
@@ -1179,7 +1179,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
1179
1179
|
}), usageLabelWidth())}`);
|
|
1180
1180
|
const prefix = errorLabel();
|
|
1181
1181
|
const errorMessage = formatMessage(classified.error, {
|
|
1182
|
-
initialWidth:
|
|
1182
|
+
initialWidth: require_text_layout.measureText(prefix).lastLineWidth,
|
|
1183
1183
|
maxWidth,
|
|
1184
1184
|
colors,
|
|
1185
1185
|
quotes: !colors
|
package/dist/facade.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { injectAnnotations } from "./internal/annotations.js";
|
|
2
|
-
import { getDisplayWidth } from "./displaywidth.js";
|
|
3
2
|
import { renderTerminalTerm } from "./terminal-internal.js";
|
|
4
3
|
import { commandLine, lineBreak, message, optionName, text, value } from "./message.js";
|
|
5
4
|
import { resolveMessageFormatter } from "./message-renderer.js";
|
|
5
|
+
import { measureText, spaceAfterLabel } from "./text-layout.js";
|
|
6
6
|
import { validateCommandNames, validateContextIds, validateMetaNameCollisions, validateOptionNames, validateProgramName } from "./validate.js";
|
|
7
7
|
import { cloneUsage, formatUsage, isSuggestionHidden, normalizeUsage } from "./usage.js";
|
|
8
8
|
import { formatDocPage } from "./doc.js";
|
|
@@ -521,10 +521,10 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
|
|
|
521
521
|
messageFormatter,
|
|
522
522
|
theme
|
|
523
523
|
});
|
|
524
|
-
const errorLabel = () => renderTerminalTerm({
|
|
524
|
+
const errorLabel = () => spaceAfterLabel(renderTerminalTerm({
|
|
525
525
|
type: "errorLabel",
|
|
526
526
|
label: "Error:"
|
|
527
|
-
}, theme, colors)
|
|
527
|
+
}, theme, colors));
|
|
528
528
|
const shellName = completionArgs[0] || "";
|
|
529
529
|
const args = completionArgs.slice(1);
|
|
530
530
|
const callOnError = (code, error) => onError(code, error);
|
|
@@ -536,7 +536,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
|
|
|
536
536
|
colors,
|
|
537
537
|
quotes: !colors,
|
|
538
538
|
maxWidth,
|
|
539
|
-
initialWidth:
|
|
539
|
+
initialWidth: measureText(prefix).lastLineWidth
|
|
540
540
|
})}\n`);
|
|
541
541
|
if (completionParser) {
|
|
542
542
|
const displayName = isOptionMode ? completionOptionDisplayName ?? "--completion" : completionCommandDisplayName ?? "completion";
|
|
@@ -570,7 +570,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
|
|
|
570
570
|
colors,
|
|
571
571
|
quotes: !colors,
|
|
572
572
|
maxWidth,
|
|
573
|
-
initialWidth:
|
|
573
|
+
initialWidth: measureText(prefix).lastLineWidth
|
|
574
574
|
})}`);
|
|
575
575
|
return dispatchByMode(parser.mode, () => {
|
|
576
576
|
const result = callOnError(1, error);
|
|
@@ -886,17 +886,17 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
886
886
|
messageFormatter,
|
|
887
887
|
theme
|
|
888
888
|
});
|
|
889
|
-
const errorLabel = () => renderTerminalTerm({
|
|
889
|
+
const errorLabel = () => spaceAfterLabel(renderTerminalTerm({
|
|
890
890
|
type: "errorLabel",
|
|
891
891
|
label: "Error:"
|
|
892
|
-
}, theme, colors)
|
|
892
|
+
}, theme, colors));
|
|
893
893
|
let usagePrefix;
|
|
894
|
-
const usageLabel = () => usagePrefix ??= renderTerminalTerm({
|
|
894
|
+
const usageLabel = () => usagePrefix ??= spaceAfterLabel(renderTerminalTerm({
|
|
895
895
|
type: "label",
|
|
896
896
|
label: "Usage:",
|
|
897
897
|
kind: "usageSummary"
|
|
898
|
-
}, theme, colors)
|
|
899
|
-
const usageLabelWidth = () =>
|
|
898
|
+
}, theme, colors));
|
|
899
|
+
const usageLabelWidth = () => measureText(usageLabel()).lastLineWidth;
|
|
900
900
|
const norm = (c) => c === true ? {} : c;
|
|
901
901
|
const helpCommandConfig = norm(options.help?.command);
|
|
902
902
|
const helpOptionConfig = norm(options.help?.option);
|
|
@@ -1059,7 +1059,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
1059
1059
|
}), usageLabelWidth())}`);
|
|
1060
1060
|
const prefix = errorLabel();
|
|
1061
1061
|
const errorMessage = formatMessage(validationError, {
|
|
1062
|
-
initialWidth:
|
|
1062
|
+
initialWidth: measureText(prefix).lastLineWidth,
|
|
1063
1063
|
maxWidth,
|
|
1064
1064
|
colors,
|
|
1065
1065
|
quotes: !colors
|
|
@@ -1179,7 +1179,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
|
|
|
1179
1179
|
}), usageLabelWidth())}`);
|
|
1180
1180
|
const prefix = errorLabel();
|
|
1181
1181
|
const errorMessage = formatMessage(classified.error, {
|
|
1182
|
-
initialWidth:
|
|
1182
|
+
initialWidth: measureText(prefix).lastLineWidth,
|
|
1183
1183
|
maxWidth,
|
|
1184
1184
|
colors,
|
|
1185
1185
|
quotes: !colors
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
const require_displaywidth = require('../displaywidth.cjs');
|
|
2
2
|
const require_terminal_internal = require('../terminal-internal.cjs');
|
|
3
3
|
const require_message_renderer = require('../message-renderer.cjs');
|
|
4
|
+
const require_text_layout = require('../text-layout.cjs');
|
|
4
5
|
|
|
5
6
|
exports.getDisplayWidth = require_displaywidth.getDisplayWidth;
|
|
7
|
+
exports.measureText = require_text_layout.measureText;
|
|
8
|
+
exports.placeText = require_text_layout.placeText;
|
|
6
9
|
exports.renderTerminalTerm = require_terminal_internal.renderTerminalTerm;
|
|
7
|
-
exports.resolveMessageFormatter = require_message_renderer.resolveMessageFormatter;
|
|
10
|
+
exports.resolveMessageFormatter = require_message_renderer.resolveMessageFormatter;
|
|
11
|
+
exports.spaceAfterLabel = require_text_layout.spaceAfterLabel;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDisplayWidth } from "../displaywidth.cjs";
|
|
2
2
|
import { resolveMessageFormatter } from "../message-renderer.cjs";
|
|
3
3
|
import { renderTerminalTerm } from "../terminal-internal.cjs";
|
|
4
|
-
|
|
4
|
+
import { measureText, placeText, spaceAfterLabel } from "../text-layout.cjs";
|
|
5
|
+
export { getDisplayWidth, measureText, placeText, renderTerminalTerm, resolveMessageFormatter, spaceAfterLabel };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDisplayWidth } from "../displaywidth.js";
|
|
2
2
|
import { resolveMessageFormatter } from "../message-renderer.js";
|
|
3
3
|
import { renderTerminalTerm } from "../terminal-internal.js";
|
|
4
|
-
|
|
4
|
+
import { measureText, placeText, spaceAfterLabel } from "../text-layout.js";
|
|
5
|
+
export { getDisplayWidth, measureText, placeText, renderTerminalTerm, resolveMessageFormatter, spaceAfterLabel };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDisplayWidth } from "../displaywidth.js";
|
|
2
2
|
import { renderTerminalTerm } from "../terminal-internal.js";
|
|
3
3
|
import { resolveMessageFormatter } from "../message-renderer.js";
|
|
4
|
+
import { measureText, placeText, spaceAfterLabel } from "../text-layout.js";
|
|
4
5
|
|
|
5
|
-
export { getDisplayWidth, renderTerminalTerm, resolveMessageFormatter };
|
|
6
|
+
export { getDisplayWidth, measureText, placeText, renderTerminalTerm, resolveMessageFormatter, spaceAfterLabel };
|
|
@@ -10,6 +10,8 @@ interface TerminalToken {
|
|
|
10
10
|
readonly text: string;
|
|
11
11
|
readonly width: number;
|
|
12
12
|
readonly scopes: readonly Scope[];
|
|
13
|
+
/** Layout-owned space, removable beside an explicit hard break. */
|
|
14
|
+
readonly separator?: boolean;
|
|
13
15
|
}
|
|
14
16
|
declare function terminalText(text: string): TerminalFragment;
|
|
15
17
|
declare function formatTerminalTerm(term: TerminalTerm, theme?: TerminalTheme, quotes?: boolean, usage?: TerminalFormatContext["usage"]): TerminalFragment;
|
|
@@ -10,6 +10,8 @@ interface TerminalToken {
|
|
|
10
10
|
readonly text: string;
|
|
11
11
|
readonly width: number;
|
|
12
12
|
readonly scopes: readonly Scope[];
|
|
13
|
+
/** Layout-owned space, removable beside an explicit hard break. */
|
|
14
|
+
readonly separator?: boolean;
|
|
13
15
|
}
|
|
14
16
|
declare function terminalText(text: string): TerminalFragment;
|
|
15
17
|
declare function formatTerminalTerm(term: TerminalTerm, theme?: TerminalTheme, quotes?: boolean, usage?: TerminalFormatContext["usage"]): TerminalFragment;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const require_displaywidth = require('./displaywidth.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/text-layout.ts
|
|
4
|
+
/**
|
|
5
|
+
* Measures physical lines after removing terminal escape sequences.
|
|
6
|
+
* Empty text occupies one empty line; a trailing LF adds an empty final line.
|
|
7
|
+
* @param text Rendered text, possibly containing ANSI styles or OSC links.
|
|
8
|
+
* @returns First, last and widest line widths, and the number of lines.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
function measureText(text) {
|
|
12
|
+
const lines = require_displaywidth.stripAnsi(text).split("\n");
|
|
13
|
+
const widths = lines.map(require_displaywidth.getDisplayWidth);
|
|
14
|
+
return {
|
|
15
|
+
firstLineWidth: widths[0],
|
|
16
|
+
lastLineWidth: widths[widths.length - 1],
|
|
17
|
+
maxLineWidth: widths.reduce((widest, width) => Math.max(widest, width), 0),
|
|
18
|
+
lineCount: lines.length
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Appends rendered text to a cursor, optionally moving it to a fresh line.
|
|
23
|
+
* Measures the actual join so combining sequences crossing the boundary are
|
|
24
|
+
* handled correctly. Hard breaks reset both the line and any external offset.
|
|
25
|
+
* This never reflows the text itself or changes its escape sequences.
|
|
26
|
+
* @param text Text to append.
|
|
27
|
+
* @param cursor The current line and column.
|
|
28
|
+
* @param maxWidth Optional column budget.
|
|
29
|
+
* @param trailingRoom Space to leave after a single-line block. An annotation
|
|
30
|
+
* prefix reserves one column for content; a multiline prefix already has a
|
|
31
|
+
* hard break, so its first line needs no reservation.
|
|
32
|
+
* @returns The placed text (possibly with a leading LF) and resulting cursor.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function placeText(text, cursor, maxWidth, trailingRoom = 0) {
|
|
36
|
+
const plain = require_displaywidth.stripAnsi(text);
|
|
37
|
+
const lines = plain.split("\n");
|
|
38
|
+
const offset = cursor.column - require_displaywidth.getDisplayWidth(cursor.line);
|
|
39
|
+
const joinedWidth = offset + require_displaywidth.getDisplayWidth(cursor.line + lines[0]);
|
|
40
|
+
const room = lines.length === 1 ? trailingRoom : 0;
|
|
41
|
+
const breaks = maxWidth != null && (cursor.column > 0 || room > 0) && (joinedWidth > cursor.column || room > 0) && joinedWidth + room > maxWidth;
|
|
42
|
+
const lastLine = lines[lines.length - 1];
|
|
43
|
+
return {
|
|
44
|
+
text: breaks ? "\n" + text : text,
|
|
45
|
+
cursor: lines.length > 1 || breaks ? {
|
|
46
|
+
line: lastLine,
|
|
47
|
+
column: require_displaywidth.getDisplayWidth(lastLine)
|
|
48
|
+
} : {
|
|
49
|
+
line: cursor.line + plain,
|
|
50
|
+
column: joinedWidth
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Measures an annotation's fixed text without invoking theme callbacks.
|
|
56
|
+
* @param prefix The already joined prefix and optional label.
|
|
57
|
+
* @param suffix The closing text.
|
|
58
|
+
* @returns Geometry reserving at least one content column after the suffix's
|
|
59
|
+
* first-line width has been deducted from the formatter budget.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
function measureAnnotation(prefix, suffix) {
|
|
63
|
+
const prefixMetrics = measureText(prefix);
|
|
64
|
+
const suffixMetrics = measureText(suffix);
|
|
65
|
+
return {
|
|
66
|
+
prefix,
|
|
67
|
+
suffix,
|
|
68
|
+
prefixMetrics,
|
|
69
|
+
suffixMetrics,
|
|
70
|
+
minWidth: Math.max(prefixMetrics.maxLineWidth, suffixMetrics.maxLineWidth, suffixMetrics.firstLineWidth + 1)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Adds a separating space only when a label occupies its final physical line.
|
|
75
|
+
* @param label The rendered label, including any styles and hard breaks.
|
|
76
|
+
* @returns The label with spacing for the text that follows it.
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
function spaceAfterLabel(label) {
|
|
80
|
+
return measureText(label).lastLineWidth > 0 ? label + " " : label;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
exports.measureAnnotation = measureAnnotation;
|
|
85
|
+
exports.measureText = measureText;
|
|
86
|
+
exports.placeText = placeText;
|
|
87
|
+
exports.spaceAfterLabel = spaceAfterLabel;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/text-layout.d.ts
|
|
2
|
+
/** Physical-line geometry at a rendered-text assembly boundary. @internal */
|
|
3
|
+
interface TextMetrics {
|
|
4
|
+
readonly firstLineWidth: number;
|
|
5
|
+
readonly lastLineWidth: number;
|
|
6
|
+
readonly maxLineWidth: number;
|
|
7
|
+
readonly lineCount: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Measures physical lines after removing terminal escape sequences.
|
|
11
|
+
* Empty text occupies one empty line; a trailing LF adds an empty final line.
|
|
12
|
+
* @param text Rendered text, possibly containing ANSI styles or OSC links.
|
|
13
|
+
* @returns First, last and widest line widths, and the number of lines.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
declare function measureText(text: string): TextMetrics;
|
|
17
|
+
/**
|
|
18
|
+
* The current visible line and its occupied column. The column may include
|
|
19
|
+
* an external offset for text unavailable to this renderer (initialWidth).
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
interface TextCursor {
|
|
23
|
+
readonly line: string;
|
|
24
|
+
readonly column: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Appends rendered text to a cursor, optionally moving it to a fresh line.
|
|
28
|
+
* Measures the actual join so combining sequences crossing the boundary are
|
|
29
|
+
* handled correctly. Hard breaks reset both the line and any external offset.
|
|
30
|
+
* This never reflows the text itself or changes its escape sequences.
|
|
31
|
+
* @param text Text to append.
|
|
32
|
+
* @param cursor The current line and column.
|
|
33
|
+
* @param maxWidth Optional column budget.
|
|
34
|
+
* @param trailingRoom Space to leave after a single-line block. An annotation
|
|
35
|
+
* prefix reserves one column for content; a multiline prefix already has a
|
|
36
|
+
* hard break, so its first line needs no reservation.
|
|
37
|
+
* @returns The placed text (possibly with a leading LF) and resulting cursor.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
declare function placeText(text: string, cursor: TextCursor, maxWidth?: number, trailingRoom?: number): {
|
|
41
|
+
readonly text: string;
|
|
42
|
+
readonly cursor: TextCursor;
|
|
43
|
+
};
|
|
44
|
+
/** Fixed annotation geometry shared by preflight and placement. @internal */
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Adds a separating space only when a label occupies its final physical line.
|
|
48
|
+
* @param label The rendered label, including any styles and hard breaks.
|
|
49
|
+
* @returns The label with spacing for the text that follows it.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
declare function spaceAfterLabel(label: string): string;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { measureText, placeText, spaceAfterLabel };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/text-layout.d.ts
|
|
2
|
+
/** Physical-line geometry at a rendered-text assembly boundary. @internal */
|
|
3
|
+
interface TextMetrics {
|
|
4
|
+
readonly firstLineWidth: number;
|
|
5
|
+
readonly lastLineWidth: number;
|
|
6
|
+
readonly maxLineWidth: number;
|
|
7
|
+
readonly lineCount: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Measures physical lines after removing terminal escape sequences.
|
|
11
|
+
* Empty text occupies one empty line; a trailing LF adds an empty final line.
|
|
12
|
+
* @param text Rendered text, possibly containing ANSI styles or OSC links.
|
|
13
|
+
* @returns First, last and widest line widths, and the number of lines.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
declare function measureText(text: string): TextMetrics;
|
|
17
|
+
/**
|
|
18
|
+
* The current visible line and its occupied column. The column may include
|
|
19
|
+
* an external offset for text unavailable to this renderer (initialWidth).
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
interface TextCursor {
|
|
23
|
+
readonly line: string;
|
|
24
|
+
readonly column: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Appends rendered text to a cursor, optionally moving it to a fresh line.
|
|
28
|
+
* Measures the actual join so combining sequences crossing the boundary are
|
|
29
|
+
* handled correctly. Hard breaks reset both the line and any external offset.
|
|
30
|
+
* This never reflows the text itself or changes its escape sequences.
|
|
31
|
+
* @param text Text to append.
|
|
32
|
+
* @param cursor The current line and column.
|
|
33
|
+
* @param maxWidth Optional column budget.
|
|
34
|
+
* @param trailingRoom Space to leave after a single-line block. An annotation
|
|
35
|
+
* prefix reserves one column for content; a multiline prefix already has a
|
|
36
|
+
* hard break, so its first line needs no reservation.
|
|
37
|
+
* @returns The placed text (possibly with a leading LF) and resulting cursor.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
declare function placeText(text: string, cursor: TextCursor, maxWidth?: number, trailingRoom?: number): {
|
|
41
|
+
readonly text: string;
|
|
42
|
+
readonly cursor: TextCursor;
|
|
43
|
+
};
|
|
44
|
+
/** Fixed annotation geometry shared by preflight and placement. @internal */
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Adds a separating space only when a label occupies its final physical line.
|
|
48
|
+
* @param label The rendered label, including any styles and hard breaks.
|
|
49
|
+
* @returns The label with spacing for the text that follows it.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
declare function spaceAfterLabel(label: string): string;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { measureText, placeText, spaceAfterLabel };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { getDisplayWidth, stripAnsi } from "./displaywidth.js";
|
|
2
|
+
|
|
3
|
+
//#region src/text-layout.ts
|
|
4
|
+
/**
|
|
5
|
+
* Measures physical lines after removing terminal escape sequences.
|
|
6
|
+
* Empty text occupies one empty line; a trailing LF adds an empty final line.
|
|
7
|
+
* @param text Rendered text, possibly containing ANSI styles or OSC links.
|
|
8
|
+
* @returns First, last and widest line widths, and the number of lines.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
function measureText(text) {
|
|
12
|
+
const lines = stripAnsi(text).split("\n");
|
|
13
|
+
const widths = lines.map(getDisplayWidth);
|
|
14
|
+
return {
|
|
15
|
+
firstLineWidth: widths[0],
|
|
16
|
+
lastLineWidth: widths[widths.length - 1],
|
|
17
|
+
maxLineWidth: widths.reduce((widest, width) => Math.max(widest, width), 0),
|
|
18
|
+
lineCount: lines.length
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Appends rendered text to a cursor, optionally moving it to a fresh line.
|
|
23
|
+
* Measures the actual join so combining sequences crossing the boundary are
|
|
24
|
+
* handled correctly. Hard breaks reset both the line and any external offset.
|
|
25
|
+
* This never reflows the text itself or changes its escape sequences.
|
|
26
|
+
* @param text Text to append.
|
|
27
|
+
* @param cursor The current line and column.
|
|
28
|
+
* @param maxWidth Optional column budget.
|
|
29
|
+
* @param trailingRoom Space to leave after a single-line block. An annotation
|
|
30
|
+
* prefix reserves one column for content; a multiline prefix already has a
|
|
31
|
+
* hard break, so its first line needs no reservation.
|
|
32
|
+
* @returns The placed text (possibly with a leading LF) and resulting cursor.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function placeText(text, cursor, maxWidth, trailingRoom = 0) {
|
|
36
|
+
const plain = stripAnsi(text);
|
|
37
|
+
const lines = plain.split("\n");
|
|
38
|
+
const offset = cursor.column - getDisplayWidth(cursor.line);
|
|
39
|
+
const joinedWidth = offset + getDisplayWidth(cursor.line + lines[0]);
|
|
40
|
+
const room = lines.length === 1 ? trailingRoom : 0;
|
|
41
|
+
const breaks = maxWidth != null && (cursor.column > 0 || room > 0) && (joinedWidth > cursor.column || room > 0) && joinedWidth + room > maxWidth;
|
|
42
|
+
const lastLine = lines[lines.length - 1];
|
|
43
|
+
return {
|
|
44
|
+
text: breaks ? "\n" + text : text,
|
|
45
|
+
cursor: lines.length > 1 || breaks ? {
|
|
46
|
+
line: lastLine,
|
|
47
|
+
column: getDisplayWidth(lastLine)
|
|
48
|
+
} : {
|
|
49
|
+
line: cursor.line + plain,
|
|
50
|
+
column: joinedWidth
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Measures an annotation's fixed text without invoking theme callbacks.
|
|
56
|
+
* @param prefix The already joined prefix and optional label.
|
|
57
|
+
* @param suffix The closing text.
|
|
58
|
+
* @returns Geometry reserving at least one content column after the suffix's
|
|
59
|
+
* first-line width has been deducted from the formatter budget.
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
function measureAnnotation(prefix, suffix) {
|
|
63
|
+
const prefixMetrics = measureText(prefix);
|
|
64
|
+
const suffixMetrics = measureText(suffix);
|
|
65
|
+
return {
|
|
66
|
+
prefix,
|
|
67
|
+
suffix,
|
|
68
|
+
prefixMetrics,
|
|
69
|
+
suffixMetrics,
|
|
70
|
+
minWidth: Math.max(prefixMetrics.maxLineWidth, suffixMetrics.maxLineWidth, suffixMetrics.firstLineWidth + 1)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Adds a separating space only when a label occupies its final physical line.
|
|
75
|
+
* @param label The rendered label, including any styles and hard breaks.
|
|
76
|
+
* @returns The label with spacing for the text that follows it.
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
function spaceAfterLabel(label) {
|
|
80
|
+
return measureText(label).lastLineWidth > 0 ? label + " " : label;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { measureAnnotation, measureText, placeText, spaceAfterLabel };
|
package/dist/usage.cjs
CHANGED
|
@@ -196,8 +196,8 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
196
196
|
if (usage.length > 0 && usage.slice(0, -1).every((t) => t.type === "command") && lastTerm.type === "exclusive" && lastTerm.terms.every((t) => t.length > 0 && (t[0].type === "command" || t[0].type === "option" || t[0].type === "argument" || t[0].type === "optional" && t[0].terms.length === 1 && (t[0].terms[0].type === "command" || t[0].terms[0].type === "option" || t[0].terms[0].type === "argument")))) {
|
|
197
197
|
const lines = [];
|
|
198
198
|
for (let command of lastTerm.terms) {
|
|
199
|
-
const firstTerm = command[0];
|
|
200
|
-
if (firstTerm?.type === "command" && isUsageHidden(firstTerm.hidden)) continue;
|
|
199
|
+
const firstTerm$1 = command[0];
|
|
200
|
+
if (firstTerm$1?.type === "command" && isUsageHidden(firstTerm$1.hidden)) continue;
|
|
201
201
|
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
202
202
|
lines.push(formatUsage(programName, command, options));
|
|
203
203
|
}
|
|
@@ -208,10 +208,13 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
208
208
|
type: "programName",
|
|
209
209
|
programName
|
|
210
210
|
}, options.theme)), options);
|
|
211
|
-
const terms = [...formatUsageTerms(usage, options)];
|
|
211
|
+
const terms = [...normalizeUsageSeparators(formatUsageTerms(usage, options))];
|
|
212
212
|
if (terms.length === 0) return require_terminal_internal.serializeTokens(programTokens, options.colors);
|
|
213
213
|
const width = programTokens.reduce((n, token) => token.width === -1 ? 0 : n + token.width, 0);
|
|
214
|
-
const
|
|
214
|
+
const firstTerm = terms.find((token) => token.text !== "");
|
|
215
|
+
const lastProgram = programTokens.findLast((token) => token.text !== "");
|
|
216
|
+
if (lastProgram == null || firstTerm == null || lastProgram.width === -1 || firstTerm.width === -1) return require_terminal_internal.serializeTokens(programTokens, options.colors) + wrapUsageTokens(terms, options, width);
|
|
217
|
+
const separator = options.maxWidth != null && width + 1 + firstTerm.width > options.maxWidth ? {
|
|
215
218
|
text: "\n",
|
|
216
219
|
width: -1,
|
|
217
220
|
scopes: []
|
|
@@ -456,7 +459,8 @@ function* formatUsageTerms(terms, options) {
|
|
|
456
459
|
if (i > 0) yield {
|
|
457
460
|
text: " ",
|
|
458
461
|
width: 1,
|
|
459
|
-
scopes: []
|
|
462
|
+
scopes: [],
|
|
463
|
+
separator: true
|
|
460
464
|
};
|
|
461
465
|
yield* formatUsageTermInternal(t, options);
|
|
462
466
|
i++;
|
|
@@ -486,16 +490,37 @@ function formatUsageTerm(term, options = {}) {
|
|
|
486
490
|
function wrapUsageTokens(input, options, initialWidth = 0) {
|
|
487
491
|
return require_terminal_internal.serializeTokens(layoutUsageTokens(input, options, initialWidth), options.colors);
|
|
488
492
|
}
|
|
493
|
+
/** Keeps one layout-owned space between nonempty outputs on the same line. */
|
|
494
|
+
function* normalizeUsageSeparators(input) {
|
|
495
|
+
let previous;
|
|
496
|
+
let pending = [];
|
|
497
|
+
for (const token of input) {
|
|
498
|
+
if (token.separator || token.text === "") {
|
|
499
|
+
pending.push(token);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
let separated = false;
|
|
503
|
+
for (const item of pending) if (!item.separator) yield item;
|
|
504
|
+
else if (!separated && previous != null && previous.width !== -1 && token.width !== -1) {
|
|
505
|
+
yield item;
|
|
506
|
+
separated = true;
|
|
507
|
+
}
|
|
508
|
+
pending = [];
|
|
509
|
+
yield token;
|
|
510
|
+
previous = token;
|
|
511
|
+
}
|
|
512
|
+
for (const item of pending) if (!item.separator) yield item;
|
|
513
|
+
}
|
|
489
514
|
function layoutUsageTokens(input, options, initialWidth = 0) {
|
|
490
515
|
let lineWidth = initialWidth;
|
|
491
516
|
const output = [];
|
|
492
|
-
for (const token of input) {
|
|
517
|
+
for (const token of normalizeUsageSeparators(input)) {
|
|
493
518
|
if (token.width === -1) {
|
|
494
519
|
output.push(token);
|
|
495
520
|
lineWidth = 0;
|
|
496
521
|
continue;
|
|
497
522
|
}
|
|
498
|
-
if (options.maxWidth != null && lineWidth > 0 && lineWidth + token.width > options.maxWidth) {
|
|
523
|
+
if (options.maxWidth != null && lineWidth > 0 && token.width > 0 && lineWidth + token.width > options.maxWidth) {
|
|
499
524
|
const last = output.at(-1);
|
|
500
525
|
if (last?.text.endsWith(" ") && (!options.colors || last.scopes.length === 0)) output[output.length - 1] = {
|
|
501
526
|
...last,
|
|
@@ -529,7 +554,8 @@ function* formatUsageTermInternal(term, options) {
|
|
|
529
554
|
const space = {
|
|
530
555
|
text: " ",
|
|
531
556
|
width: 1,
|
|
532
|
-
scopes: []
|
|
557
|
+
scopes: [],
|
|
558
|
+
separator: true
|
|
533
559
|
};
|
|
534
560
|
if (term.type === "argument") yield* leaf({
|
|
535
561
|
type: "metavar",
|
package/dist/usage.js
CHANGED
|
@@ -196,8 +196,8 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
196
196
|
if (usage.length > 0 && usage.slice(0, -1).every((t) => t.type === "command") && lastTerm.type === "exclusive" && lastTerm.terms.every((t) => t.length > 0 && (t[0].type === "command" || t[0].type === "option" || t[0].type === "argument" || t[0].type === "optional" && t[0].terms.length === 1 && (t[0].terms[0].type === "command" || t[0].terms[0].type === "option" || t[0].terms[0].type === "argument")))) {
|
|
197
197
|
const lines = [];
|
|
198
198
|
for (let command of lastTerm.terms) {
|
|
199
|
-
const firstTerm = command[0];
|
|
200
|
-
if (firstTerm?.type === "command" && isUsageHidden(firstTerm.hidden)) continue;
|
|
199
|
+
const firstTerm$1 = command[0];
|
|
200
|
+
if (firstTerm$1?.type === "command" && isUsageHidden(firstTerm$1.hidden)) continue;
|
|
201
201
|
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
202
202
|
lines.push(formatUsage(programName, command, options));
|
|
203
203
|
}
|
|
@@ -208,10 +208,13 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
208
208
|
type: "programName",
|
|
209
209
|
programName
|
|
210
210
|
}, options.theme)), options);
|
|
211
|
-
const terms = [...formatUsageTerms(usage, options)];
|
|
211
|
+
const terms = [...normalizeUsageSeparators(formatUsageTerms(usage, options))];
|
|
212
212
|
if (terms.length === 0) return serializeTokens(programTokens, options.colors);
|
|
213
213
|
const width = programTokens.reduce((n, token) => token.width === -1 ? 0 : n + token.width, 0);
|
|
214
|
-
const
|
|
214
|
+
const firstTerm = terms.find((token) => token.text !== "");
|
|
215
|
+
const lastProgram = programTokens.findLast((token) => token.text !== "");
|
|
216
|
+
if (lastProgram == null || firstTerm == null || lastProgram.width === -1 || firstTerm.width === -1) return serializeTokens(programTokens, options.colors) + wrapUsageTokens(terms, options, width);
|
|
217
|
+
const separator = options.maxWidth != null && width + 1 + firstTerm.width > options.maxWidth ? {
|
|
215
218
|
text: "\n",
|
|
216
219
|
width: -1,
|
|
217
220
|
scopes: []
|
|
@@ -456,7 +459,8 @@ function* formatUsageTerms(terms, options) {
|
|
|
456
459
|
if (i > 0) yield {
|
|
457
460
|
text: " ",
|
|
458
461
|
width: 1,
|
|
459
|
-
scopes: []
|
|
462
|
+
scopes: [],
|
|
463
|
+
separator: true
|
|
460
464
|
};
|
|
461
465
|
yield* formatUsageTermInternal(t, options);
|
|
462
466
|
i++;
|
|
@@ -486,16 +490,37 @@ function formatUsageTerm(term, options = {}) {
|
|
|
486
490
|
function wrapUsageTokens(input, options, initialWidth = 0) {
|
|
487
491
|
return serializeTokens(layoutUsageTokens(input, options, initialWidth), options.colors);
|
|
488
492
|
}
|
|
493
|
+
/** Keeps one layout-owned space between nonempty outputs on the same line. */
|
|
494
|
+
function* normalizeUsageSeparators(input) {
|
|
495
|
+
let previous;
|
|
496
|
+
let pending = [];
|
|
497
|
+
for (const token of input) {
|
|
498
|
+
if (token.separator || token.text === "") {
|
|
499
|
+
pending.push(token);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
let separated = false;
|
|
503
|
+
for (const item of pending) if (!item.separator) yield item;
|
|
504
|
+
else if (!separated && previous != null && previous.width !== -1 && token.width !== -1) {
|
|
505
|
+
yield item;
|
|
506
|
+
separated = true;
|
|
507
|
+
}
|
|
508
|
+
pending = [];
|
|
509
|
+
yield token;
|
|
510
|
+
previous = token;
|
|
511
|
+
}
|
|
512
|
+
for (const item of pending) if (!item.separator) yield item;
|
|
513
|
+
}
|
|
489
514
|
function layoutUsageTokens(input, options, initialWidth = 0) {
|
|
490
515
|
let lineWidth = initialWidth;
|
|
491
516
|
const output = [];
|
|
492
|
-
for (const token of input) {
|
|
517
|
+
for (const token of normalizeUsageSeparators(input)) {
|
|
493
518
|
if (token.width === -1) {
|
|
494
519
|
output.push(token);
|
|
495
520
|
lineWidth = 0;
|
|
496
521
|
continue;
|
|
497
522
|
}
|
|
498
|
-
if (options.maxWidth != null && lineWidth > 0 && lineWidth + token.width > options.maxWidth) {
|
|
523
|
+
if (options.maxWidth != null && lineWidth > 0 && token.width > 0 && lineWidth + token.width > options.maxWidth) {
|
|
499
524
|
const last = output.at(-1);
|
|
500
525
|
if (last?.text.endsWith(" ") && (!options.colors || last.scopes.length === 0)) output[output.length - 1] = {
|
|
501
526
|
...last,
|
|
@@ -529,7 +554,8 @@ function* formatUsageTermInternal(term, options) {
|
|
|
529
554
|
const space = {
|
|
530
555
|
text: " ",
|
|
531
556
|
width: 1,
|
|
532
|
-
scopes: []
|
|
557
|
+
scopes: [],
|
|
558
|
+
separator: true
|
|
533
559
|
};
|
|
534
560
|
if (term.type === "argument") yield* leaf({
|
|
535
561
|
type: "metavar",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/core",
|
|
3
|
-
"version": "1.3.0-dev.
|
|
3
|
+
"version": "1.3.0-dev.2554",
|
|
4
4
|
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
},
|
|
238
238
|
"sideEffects": false,
|
|
239
239
|
"devDependencies": {
|
|
240
|
-
"@optique/env": "1.3.0-dev.
|
|
240
|
+
"@optique/env": "1.3.0-dev.2554+5fdcbbad",
|
|
241
241
|
"@types/node": "^24.0.0",
|
|
242
242
|
"fast-check": "^4.7.0",
|
|
243
243
|
"tsdown": "^0.13.0",
|