@optique/core 1.3.0-dev.2542 → 1.3.0-dev.2558
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 +26 -44
- package/dist/facade.js +27 -45
- package/dist/internal/terminal.cjs +6 -1
- package/dist/internal/terminal.d.cts +3 -2
- package/dist/internal/terminal.d.ts +3 -2
- package/dist/internal/terminal.js +3 -2
- package/dist/message-renderer.cjs +34 -0
- package/dist/message-renderer.d.cts +16 -1
- package/dist/message-renderer.d.ts +16 -1
- package/dist/message-renderer.js +34 -1
- package/dist/terminal-internal.cjs +43 -16
- package/dist/terminal-internal.d.cts +22 -2
- package/dist/terminal-internal.d.ts +22 -2
- package/dist/terminal-internal.js +43 -17
- 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 +72 -37
- package/dist/usage.js +73 -38
- package/package.json +2 -2
|
@@ -17,11 +17,29 @@ function valuesFormatter(theme) {
|
|
|
17
17
|
children: [defaultTerminalTheme.values(term, context)]
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
+
/** Default callback identity survives any number of per-pass caches. */
|
|
21
|
+
const defaultFormatters = new WeakSet(Object.values(defaultTerminalTheme).filter((value) => typeof value === "function"));
|
|
20
22
|
function formatTerminalTerm(term, theme = {}, quotes = false, usage) {
|
|
23
|
+
return formatTerminalLeaf(term, theme, quotes, usage).fragment;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Renders a semantic leaf and records the selected callback's provenance.
|
|
27
|
+
* @param term The semantic leaf to render.
|
|
28
|
+
* @param theme The optional role overrides.
|
|
29
|
+
* @param quotes Whether values should use their quoted representation.
|
|
30
|
+
* @param usage The leaf's position within a usage expression.
|
|
31
|
+
* @returns The fragment and whether its callback has default origin.
|
|
32
|
+
* @throws {TypeError} If the term is invalid or is a message-level hard break.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function formatTerminalLeaf(term, theme = {}, quotes = false, usage) {
|
|
21
36
|
const quote = (s) => quotes ? `\`${s}\`` : s;
|
|
22
37
|
let text;
|
|
23
38
|
switch (term.type) {
|
|
24
|
-
case "text": return
|
|
39
|
+
case "text": return {
|
|
40
|
+
fragment: terminalText(term.text),
|
|
41
|
+
defaultOrigin: true
|
|
42
|
+
};
|
|
25
43
|
case "lineBreak": throw new TypeError("Hard breaks must be rendered at the message level.");
|
|
26
44
|
case "optionName":
|
|
27
45
|
text = quote(term.optionName);
|
|
@@ -65,19 +83,25 @@ function formatTerminalTerm(term, theme = {}, quotes = false, usage) {
|
|
|
65
83
|
usage,
|
|
66
84
|
format: (child) => formatTerminalTerm(child, theme, quotes, usage)
|
|
67
85
|
};
|
|
86
|
+
function render(formatter, value) {
|
|
87
|
+
return {
|
|
88
|
+
fragment: formatter(value, ctx),
|
|
89
|
+
defaultOrigin: defaultFormatters.has(formatter)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
68
92
|
switch (term.type) {
|
|
69
|
-
case "optionName": return (theme.optionName ?? defaultTerminalTheme.optionName
|
|
70
|
-
case "optionNames": return (theme.optionNames ?? defaultTerminalTheme.optionNames
|
|
71
|
-
case "metavar": return (theme.metavar ?? defaultTerminalTheme.metavar
|
|
72
|
-
case "value": return (theme.value ?? defaultTerminalTheme.value
|
|
73
|
-
case "values": return valuesFormatter(theme)
|
|
74
|
-
case "envVar": return (theme.envVar ?? defaultTerminalTheme.envVar
|
|
75
|
-
case "commandLine": return (theme.commandLine ?? defaultTerminalTheme.commandLine
|
|
76
|
-
case "url": return (theme.url ?? defaultTerminalTheme.url
|
|
77
|
-
case "programName": return (theme.programName ?? defaultTerminalTheme.programName
|
|
78
|
-
case "label": return (theme.label ?? defaultTerminalTheme.label
|
|
79
|
-
case "syntaxPunctuation": return (theme.syntaxPunctuation ?? defaultTerminalTheme.syntaxPunctuation
|
|
80
|
-
case "errorLabel": return (theme.errorLabel ?? defaultTerminalTheme.errorLabel
|
|
93
|
+
case "optionName": return render(theme.optionName ?? defaultTerminalTheme.optionName, term);
|
|
94
|
+
case "optionNames": return render(theme.optionNames ?? defaultTerminalTheme.optionNames, term);
|
|
95
|
+
case "metavar": return render(theme.metavar ?? defaultTerminalTheme.metavar, term);
|
|
96
|
+
case "value": return render(theme.value ?? defaultTerminalTheme.value, term);
|
|
97
|
+
case "values": return render(valuesFormatter(theme), term);
|
|
98
|
+
case "envVar": return render(theme.envVar ?? defaultTerminalTheme.envVar, term);
|
|
99
|
+
case "commandLine": return render(theme.commandLine ?? defaultTerminalTheme.commandLine, term);
|
|
100
|
+
case "url": return render(theme.url ?? defaultTerminalTheme.url, term);
|
|
101
|
+
case "programName": return render(theme.programName ?? defaultTerminalTheme.programName, term);
|
|
102
|
+
case "label": return render(theme.label ?? defaultTerminalTheme.label, term);
|
|
103
|
+
case "syntaxPunctuation": return render(theme.syntaxPunctuation ?? defaultTerminalTheme.syntaxPunctuation, term);
|
|
104
|
+
case "errorLabel": return render(theme.errorLabel ?? defaultTerminalTheme.errorLabel, term);
|
|
81
105
|
}
|
|
82
106
|
}
|
|
83
107
|
const colorNames = [
|
|
@@ -225,14 +249,14 @@ function serializeTokens(tokens, colors = false, resetSuffix = "", ambient = {})
|
|
|
225
249
|
* @since 1.3.0
|
|
226
250
|
* @internal
|
|
227
251
|
*/
|
|
228
|
-
function renderTerminalTerm(term, theme, colors, usage, ambient) {
|
|
229
|
-
return serializeTokens(fragmentTokens(formatTerminalTerm(term, theme, false, usage)), colors,
|
|
252
|
+
function renderTerminalTerm(term, theme, colors, usage, ambient, resetSuffix = "") {
|
|
253
|
+
return serializeTokens(fragmentTokens(formatTerminalTerm(term, theme, false, usage)), colors, resetSuffix, ambient);
|
|
230
254
|
}
|
|
231
255
|
/** Memoizes semantic leaves for one layout pass, including measurement. */
|
|
232
256
|
function cacheTerminalTheme(theme = {}) {
|
|
233
257
|
function cache(formatter) {
|
|
234
258
|
const fragments = /* @__PURE__ */ new Map();
|
|
235
|
-
|
|
259
|
+
const cached = (term, context) => {
|
|
236
260
|
const key = JSON.stringify([
|
|
237
261
|
term,
|
|
238
262
|
context.quotes,
|
|
@@ -245,6 +269,8 @@ function cacheTerminalTheme(theme = {}) {
|
|
|
245
269
|
}
|
|
246
270
|
return fragment;
|
|
247
271
|
};
|
|
272
|
+
if (defaultFormatters.has(formatter)) defaultFormatters.add(cached);
|
|
273
|
+
return cached;
|
|
248
274
|
}
|
|
249
275
|
return {
|
|
250
276
|
optionName: cache(theme.optionName ?? defaultTerminalTheme.optionName),
|
|
@@ -267,4 +293,4 @@ function cacheTerminalTheme(theme = {}) {
|
|
|
267
293
|
}
|
|
268
294
|
|
|
269
295
|
//#endregion
|
|
270
|
-
export { cacheTerminalTheme, formatTerminalTerm, fragmentTokens, renderTerminalTerm, serializeTokens, styleCode, terminalText };
|
|
296
|
+
export { cacheTerminalTheme, formatTerminalLeaf, formatTerminalTerm, fragmentTokens, renderTerminalTerm, serializeTokens, styleCode, terminalText };
|
|
@@ -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
|
@@ -204,23 +204,17 @@ function formatUsage(programName, usage, options = {}) {
|
|
|
204
204
|
if (lines.length > 0) return lines.join("\n");
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
} : {
|
|
219
|
-
text: " ",
|
|
220
|
-
width: 1,
|
|
221
|
-
scopes: []
|
|
222
|
-
};
|
|
223
|
-
return require_terminal_internal.serializeTokens([...programTokens, separator], options.colors) + wrapUsageTokens(terms, options, separator.width === -1 ? 0 : width + 1);
|
|
207
|
+
return wrapUsageTokens(function* () {
|
|
208
|
+
yield* usageLeafTokens({
|
|
209
|
+
type: "programName",
|
|
210
|
+
programName
|
|
211
|
+
}, options);
|
|
212
|
+
yield {
|
|
213
|
+
...usageSpace,
|
|
214
|
+
programBoundary: true
|
|
215
|
+
};
|
|
216
|
+
yield* formatUsageTerms(usage, options);
|
|
217
|
+
}(), options);
|
|
224
218
|
}
|
|
225
219
|
/**
|
|
226
220
|
* Normalizes a usage description by flattening nested exclusive terms,
|
|
@@ -450,14 +444,23 @@ function filterUsageForDisplay(usage, isHidden = isUsageHidden) {
|
|
|
450
444
|
}
|
|
451
445
|
return terms;
|
|
452
446
|
}
|
|
447
|
+
const usageSpace = {
|
|
448
|
+
text: " ",
|
|
449
|
+
width: 1,
|
|
450
|
+
scopes: [],
|
|
451
|
+
separator: true
|
|
452
|
+
};
|
|
453
|
+
function* usageLeafTokens(term, options, usage) {
|
|
454
|
+
const { fragment, defaultOrigin } = require_terminal_internal.formatTerminalLeaf(term, options.theme, false, usage);
|
|
455
|
+
for (const token of require_terminal_internal.fragmentTokens(fragment)) yield {
|
|
456
|
+
...token,
|
|
457
|
+
legacyWhitespace: defaultOrigin
|
|
458
|
+
};
|
|
459
|
+
}
|
|
453
460
|
function* formatUsageTerms(terms, options) {
|
|
454
461
|
let i = 0;
|
|
455
462
|
for (const t of terms) {
|
|
456
|
-
if (i > 0) yield
|
|
457
|
-
text: " ",
|
|
458
|
-
width: 1,
|
|
459
|
-
scopes: []
|
|
460
|
-
};
|
|
463
|
+
if (i > 0) yield usageSpace;
|
|
461
464
|
yield* formatUsageTermInternal(t, options);
|
|
462
465
|
i++;
|
|
463
466
|
}
|
|
@@ -483,21 +486,57 @@ function formatUsageTerm(term, options = {}) {
|
|
|
483
486
|
if (visibleTerms.length < 1) return "";
|
|
484
487
|
return wrapUsageTokens(formatUsageTermInternal(visibleTerms[0], options), options);
|
|
485
488
|
}
|
|
486
|
-
function wrapUsageTokens(input, options
|
|
487
|
-
return require_terminal_internal.serializeTokens(layoutUsageTokens(input, options
|
|
489
|
+
function wrapUsageTokens(input, options) {
|
|
490
|
+
return require_terminal_internal.serializeTokens(layoutUsageTokens(input, options), options.colors);
|
|
488
491
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
+
/** Keeps one layout-owned space between nonempty outputs on the same line. */
|
|
493
|
+
function* normalizeUsageSeparators(input) {
|
|
494
|
+
let previous;
|
|
495
|
+
let pending = [];
|
|
492
496
|
for (const token of input) {
|
|
497
|
+
if (token.separator || token.text === "") {
|
|
498
|
+
pending.push(token);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
let separated = false;
|
|
502
|
+
for (const item of pending) if (!item.separator) yield item;
|
|
503
|
+
else if (!separated && previous != null && previous.width !== -1 && token.width !== -1) {
|
|
504
|
+
yield item;
|
|
505
|
+
separated = true;
|
|
506
|
+
}
|
|
507
|
+
pending = [];
|
|
508
|
+
yield token;
|
|
509
|
+
previous = token;
|
|
510
|
+
}
|
|
511
|
+
for (const item of pending) if (!item.separator) yield item;
|
|
512
|
+
}
|
|
513
|
+
function layoutUsageTokens(input, options) {
|
|
514
|
+
let lineWidth = 0;
|
|
515
|
+
const output = [];
|
|
516
|
+
const tokens = [...normalizeUsageSeparators(input)];
|
|
517
|
+
for (const [index, token] of tokens.entries()) {
|
|
518
|
+
if (token.programBoundary && options.maxWidth != null) {
|
|
519
|
+
const next = tokens.find((item, i) => i > index && item.text !== "");
|
|
520
|
+
if (next != null && lineWidth + token.width + next.width > options.maxWidth) {
|
|
521
|
+
output.push({
|
|
522
|
+
text: "\n",
|
|
523
|
+
width: -1,
|
|
524
|
+
scopes: []
|
|
525
|
+
});
|
|
526
|
+
lineWidth = 0;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
493
530
|
if (token.width === -1) {
|
|
494
531
|
output.push(token);
|
|
495
532
|
lineWidth = 0;
|
|
496
533
|
continue;
|
|
497
534
|
}
|
|
498
|
-
if (options.maxWidth != null && lineWidth > 0 && lineWidth + token.width > options.maxWidth) {
|
|
499
|
-
|
|
500
|
-
|
|
535
|
+
if (options.maxWidth != null && lineWidth > 0 && token.width > 0 && lineWidth + token.width > options.maxWidth) {
|
|
536
|
+
let lastIndex = output.length - 1;
|
|
537
|
+
while (lastIndex >= 0 && output[lastIndex].text === "") lastIndex--;
|
|
538
|
+
const last = output[lastIndex];
|
|
539
|
+
if (last?.text.endsWith(" ") && (last.separator || last.legacyWhitespace && (!options.colors || last.scopes.length === 0))) output[lastIndex] = {
|
|
501
540
|
...last,
|
|
502
541
|
text: last.text.slice(0, -1),
|
|
503
542
|
width: last.width - 1
|
|
@@ -508,7 +547,7 @@ function layoutUsageTokens(input, options, initialWidth = 0) {
|
|
|
508
547
|
scopes: []
|
|
509
548
|
});
|
|
510
549
|
lineWidth = 0;
|
|
511
|
-
if (token.text === " ") continue;
|
|
550
|
+
if (token.separator || token.legacyWhitespace && token.text === " ") continue;
|
|
512
551
|
}
|
|
513
552
|
output.push(token);
|
|
514
553
|
lineWidth += token.width;
|
|
@@ -517,7 +556,7 @@ function layoutUsageTokens(input, options, initialWidth = 0) {
|
|
|
517
556
|
}
|
|
518
557
|
function* formatUsageTermInternal(term, options) {
|
|
519
558
|
function leaf(t, usage) {
|
|
520
|
-
return
|
|
559
|
+
return usageLeafTokens(t, options, usage);
|
|
521
560
|
}
|
|
522
561
|
function punctuation(punctuation$1, kind) {
|
|
523
562
|
return leaf({
|
|
@@ -526,11 +565,7 @@ function* formatUsageTermInternal(term, options) {
|
|
|
526
565
|
kind
|
|
527
566
|
});
|
|
528
567
|
}
|
|
529
|
-
const space =
|
|
530
|
-
text: " ",
|
|
531
|
-
width: 1,
|
|
532
|
-
scopes: []
|
|
533
|
-
};
|
|
568
|
+
const space = usageSpace;
|
|
534
569
|
if (term.type === "argument") yield* leaf({
|
|
535
570
|
type: "metavar",
|
|
536
571
|
metavar: term.metavar
|