@optique/core 1.2.9 → 1.2.10

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.
@@ -16,6 +16,17 @@ function getDisplayWidth(text) {
16
16
  for (const { segment } of segmenter.segment(stripped)) width += graphemeWidth(segment);
17
17
  return width;
18
18
  }
19
+ /**
20
+ * Removes ANSI escape sequences (CSI and OSC) from a string, leaving only
21
+ * the text a terminal would print.
22
+ *
23
+ * @param text The string to strip.
24
+ * @returns The string without ANSI escape sequences.
25
+ * @internal
26
+ */
27
+ function stripAnsi(text) {
28
+ return text.replace(ansiRegex, "");
29
+ }
19
30
  const zeroWidthRegex = /^[\p{Cf}\p{Mn}\p{Me}]+$/u;
20
31
  const emojiPresentationRegex = /\p{Emoji_Presentation}/u;
21
32
  const emojiWithVS16Regex = /\p{Emoji}\uFE0F/u;
@@ -41,4 +52,5 @@ function isEastAsianWide(cp) {
41
52
  }
42
53
 
43
54
  //#endregion
44
- exports.getDisplayWidth = getDisplayWidth;
55
+ exports.getDisplayWidth = getDisplayWidth;
56
+ exports.stripAnsi = stripAnsi;
@@ -8,5 +8,14 @@
8
8
  * @internal
9
9
  */
10
10
  declare function getDisplayWidth(text: string): number;
11
+ /**
12
+ * Removes ANSI escape sequences (CSI and OSC) from a string, leaving only
13
+ * the text a terminal would print.
14
+ *
15
+ * @param text The string to strip.
16
+ * @returns The string without ANSI escape sequences.
17
+ * @internal
18
+ */
19
+ declare function stripAnsi(text: string): string;
11
20
  //#endregion
12
- export { getDisplayWidth };
21
+ export { getDisplayWidth, stripAnsi };
@@ -8,5 +8,14 @@
8
8
  * @internal
9
9
  */
10
10
  declare function getDisplayWidth(text: string): number;
11
+ /**
12
+ * Removes ANSI escape sequences (CSI and OSC) from a string, leaving only
13
+ * the text a terminal would print.
14
+ *
15
+ * @param text The string to strip.
16
+ * @returns The string without ANSI escape sequences.
17
+ * @internal
18
+ */
19
+ declare function stripAnsi(text: string): string;
11
20
  //#endregion
12
- export { getDisplayWidth };
21
+ export { getDisplayWidth, stripAnsi };
@@ -15,6 +15,17 @@ function getDisplayWidth(text) {
15
15
  for (const { segment } of segmenter.segment(stripped)) width += graphemeWidth(segment);
16
16
  return width;
17
17
  }
18
+ /**
19
+ * Removes ANSI escape sequences (CSI and OSC) from a string, leaving only
20
+ * the text a terminal would print.
21
+ *
22
+ * @param text The string to strip.
23
+ * @returns The string without ANSI escape sequences.
24
+ * @internal
25
+ */
26
+ function stripAnsi(text) {
27
+ return text.replace(ansiRegex, "");
28
+ }
18
29
  const zeroWidthRegex = /^[\p{Cf}\p{Mn}\p{Me}]+$/u;
19
30
  const emojiPresentationRegex = /\p{Emoji_Presentation}/u;
20
31
  const emojiWithVS16Regex = /\p{Emoji}\uFE0F/u;
@@ -40,4 +51,4 @@ function isEastAsianWide(cp) {
40
51
  }
41
52
 
42
53
  //#endregion
43
- export { getDisplayWidth };
54
+ export { getDisplayWidth, stripAnsi };
package/dist/doc.cjs CHANGED
@@ -342,13 +342,15 @@ function formatDocPage(programName, page, options = {}) {
342
342
  const suffix = typeof options.showDefault === "object" ? options.showDefault.suffix ?? "]" : "]";
343
343
  const prefixWidth = require_displaywidth.getDisplayWidth(prefix);
344
344
  const suffixWidth = require_displaywidth.getDisplayWidth(suffix);
345
+ let defaultPrefix = prefix;
345
346
  let defaultStartWidth;
346
347
  if (descColumnWidth != null) {
347
348
  const lastW = lastLineVisibleLength(description);
348
349
  const effectiveLastW = lastW + currentExtraOffset();
349
350
  if (effectiveLastW + prefixWidth >= descColumnWidth) {
350
351
  description += "\n";
351
- defaultStartWidth = prefixWidth;
352
+ defaultPrefix = prefix.trimStart();
353
+ defaultStartWidth = require_displaywidth.getDisplayWidth(defaultPrefix);
352
354
  } else defaultStartWidth = effectiveLastW + prefixWidth;
353
355
  }
354
356
  const defaultFormatOptions = {
@@ -358,7 +360,7 @@ function formatDocPage(programName, page, options = {}) {
358
360
  startWidth: defaultStartWidth
359
361
  };
360
362
  const defaultContent = require_message.formatMessage(entry.default, defaultFormatOptions);
361
- const defaultText = `${prefix}${defaultContent}${suffix}`;
363
+ const defaultText = `${defaultPrefix}${defaultContent}${suffix}`;
362
364
  const formattedDefault = options.colors ? `\x1b[2m${defaultText}\x1b[0m` : defaultText;
363
365
  description += formattedDefault;
364
366
  }
@@ -386,6 +388,7 @@ function formatDocPage(programName, page, options = {}) {
386
388
  const choicesPrefixWidth = require_displaywidth.getDisplayWidth(prefix);
387
389
  const choicesSuffixWidth = require_displaywidth.getDisplayWidth(suffix);
388
390
  const choicesLabelWidth = require_displaywidth.getDisplayWidth(label);
391
+ let choicesHeading = `${prefix}${label}`;
389
392
  let choicesStartWidth;
390
393
  if (descColumnWidth != null) {
391
394
  const lastW = lastLineVisibleLength(description);
@@ -393,7 +396,8 @@ function formatDocPage(programName, page, options = {}) {
393
396
  const prefixLabelLen = choicesPrefixWidth + choicesLabelWidth;
394
397
  if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
395
398
  description += "\n";
396
- choicesStartWidth = prefixLabelLen;
399
+ choicesHeading = choicesHeading.trimStart();
400
+ choicesStartWidth = require_displaywidth.getDisplayWidth(choicesHeading);
397
401
  } else choicesStartWidth = effectiveLastW + prefixLabelLen;
398
402
  }
399
403
  const choicesFormatOptions = {
@@ -403,7 +407,7 @@ function formatDocPage(programName, page, options = {}) {
403
407
  startWidth: choicesStartWidth
404
408
  };
405
409
  const choicesDisplay = require_message.formatMessage(truncatedTerms, choicesFormatOptions);
406
- const choicesText = `${prefix}${label}${choicesDisplay}${suffix}`;
410
+ const choicesText = `${choicesHeading}${choicesDisplay}${suffix}`;
407
411
  const formattedChoices = options.colors ? `\x1b[2m${choicesText}\x1b[0m` : choicesText;
408
412
  description += formattedChoices;
409
413
  }
package/dist/doc.js CHANGED
@@ -342,13 +342,15 @@ function formatDocPage(programName, page, options = {}) {
342
342
  const suffix = typeof options.showDefault === "object" ? options.showDefault.suffix ?? "]" : "]";
343
343
  const prefixWidth = getDisplayWidth(prefix);
344
344
  const suffixWidth = getDisplayWidth(suffix);
345
+ let defaultPrefix = prefix;
345
346
  let defaultStartWidth;
346
347
  if (descColumnWidth != null) {
347
348
  const lastW = lastLineVisibleLength(description);
348
349
  const effectiveLastW = lastW + currentExtraOffset();
349
350
  if (effectiveLastW + prefixWidth >= descColumnWidth) {
350
351
  description += "\n";
351
- defaultStartWidth = prefixWidth;
352
+ defaultPrefix = prefix.trimStart();
353
+ defaultStartWidth = getDisplayWidth(defaultPrefix);
352
354
  } else defaultStartWidth = effectiveLastW + prefixWidth;
353
355
  }
354
356
  const defaultFormatOptions = {
@@ -358,7 +360,7 @@ function formatDocPage(programName, page, options = {}) {
358
360
  startWidth: defaultStartWidth
359
361
  };
360
362
  const defaultContent = formatMessage(entry.default, defaultFormatOptions);
361
- const defaultText = `${prefix}${defaultContent}${suffix}`;
363
+ const defaultText = `${defaultPrefix}${defaultContent}${suffix}`;
362
364
  const formattedDefault = options.colors ? `\x1b[2m${defaultText}\x1b[0m` : defaultText;
363
365
  description += formattedDefault;
364
366
  }
@@ -386,6 +388,7 @@ function formatDocPage(programName, page, options = {}) {
386
388
  const choicesPrefixWidth = getDisplayWidth(prefix);
387
389
  const choicesSuffixWidth = getDisplayWidth(suffix);
388
390
  const choicesLabelWidth = getDisplayWidth(label);
391
+ let choicesHeading = `${prefix}${label}`;
389
392
  let choicesStartWidth;
390
393
  if (descColumnWidth != null) {
391
394
  const lastW = lastLineVisibleLength(description);
@@ -393,7 +396,8 @@ function formatDocPage(programName, page, options = {}) {
393
396
  const prefixLabelLen = choicesPrefixWidth + choicesLabelWidth;
394
397
  if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
395
398
  description += "\n";
396
- choicesStartWidth = prefixLabelLen;
399
+ choicesHeading = choicesHeading.trimStart();
400
+ choicesStartWidth = getDisplayWidth(choicesHeading);
397
401
  } else choicesStartWidth = effectiveLastW + prefixLabelLen;
398
402
  }
399
403
  const choicesFormatOptions = {
@@ -403,7 +407,7 @@ function formatDocPage(programName, page, options = {}) {
403
407
  startWidth: choicesStartWidth
404
408
  };
405
409
  const choicesDisplay = formatMessage(truncatedTerms, choicesFormatOptions);
406
- const choicesText = `${prefix}${label}${choicesDisplay}${suffix}`;
410
+ const choicesText = `${choicesHeading}${choicesDisplay}${suffix}`;
407
411
  const formattedChoices = options.colors ? `\x1b[2m${choicesText}\x1b[0m` : choicesText;
408
412
  description += formattedChoices;
409
413
  }
package/dist/message.cjs CHANGED
@@ -324,7 +324,8 @@ function formatMessage(msg, options = {}) {
324
324
  if (match == null) break;
325
325
  yield {
326
326
  text: match[0],
327
- width: require_displaywidth.getDisplayWidth(match[0])
327
+ width: require_displaywidth.getDisplayWidth(match[0]),
328
+ trimOnWrap: true
328
329
  };
329
330
  }
330
331
  }
@@ -332,7 +333,8 @@ function formatMessage(msg, options = {}) {
332
333
  const normalizedText = rawText.replace(/\n/g, " ");
333
334
  if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
334
335
  text: " ",
335
- width: 1
336
+ width: 1,
337
+ trimOnWrap: true
336
338
  };
337
339
  else {
338
340
  wordPattern.lastIndex = 0;
@@ -341,7 +343,8 @@ function formatMessage(msg, options = {}) {
341
343
  if (match == null) break;
342
344
  yield {
343
345
  text: match[0],
344
- width: require_displaywidth.getDisplayWidth(match[0])
346
+ width: require_displaywidth.getDisplayWidth(match[0]),
347
+ trimOnWrap: true
345
348
  };
346
349
  }
347
350
  }
@@ -381,7 +384,8 @@ function formatMessage(msg, options = {}) {
381
384
  } else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
382
385
  if (i > 0) yield {
383
386
  text: " ",
384
- width: 1
387
+ width: 1,
388
+ trimOnWrap: true
385
389
  };
386
390
  const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
387
391
  yield {
@@ -425,18 +429,40 @@ function formatMessage(msg, options = {}) {
425
429
  }
426
430
  let output = "";
427
431
  let totalWidth = options.startWidth ?? 0;
428
- for (const { text: text$1, width } of stream()) {
432
+ let pendingWrap = false;
433
+ let atWrappedLineStart = false;
434
+ for (const token of stream()) {
435
+ let { text: text$1, width } = token;
429
436
  if (width === -1) {
430
437
  output += text$1;
431
438
  totalWidth = 0;
439
+ pendingWrap = false;
440
+ atWrappedLineStart = false;
441
+ continue;
442
+ }
443
+ const wraps = pendingWrap || options.maxWidth != null && totalWidth > 0 && totalWidth + width > options.maxWidth;
444
+ if ((wraps || atWrappedLineStart) && token.trimOnWrap) {
445
+ const trimmed = text$1.trimStart();
446
+ width -= require_displaywidth.getDisplayWidth(text$1.slice(0, text$1.length - trimmed.length));
447
+ text$1 = trimmed;
448
+ if (text$1 === "") {
449
+ if (wraps) pendingWrap = true;
450
+ continue;
451
+ }
452
+ }
453
+ if (pendingWrap && require_displaywidth.stripAnsi(text$1) === "") {
454
+ output += text$1;
432
455
  continue;
433
456
  }
434
- if (options.maxWidth != null && totalWidth > 0 && totalWidth + width > options.maxWidth) {
457
+ if (wraps) {
435
458
  output += "\n";
436
459
  totalWidth = 0;
460
+ pendingWrap = false;
461
+ atWrappedLineStart = true;
437
462
  }
438
463
  output += text$1;
439
464
  totalWidth += width;
465
+ if (width > 0) atWrappedLineStart = false;
440
466
  }
441
467
  return output;
442
468
  }
package/dist/message.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getDisplayWidth } from "./displaywidth.js";
1
+ import { getDisplayWidth, stripAnsi } from "./displaywidth.js";
2
2
 
3
3
  //#region src/message.ts
4
4
  /**
@@ -324,7 +324,8 @@ function formatMessage(msg, options = {}) {
324
324
  if (match == null) break;
325
325
  yield {
326
326
  text: match[0],
327
- width: getDisplayWidth(match[0])
327
+ width: getDisplayWidth(match[0]),
328
+ trimOnWrap: true
328
329
  };
329
330
  }
330
331
  }
@@ -332,7 +333,8 @@ function formatMessage(msg, options = {}) {
332
333
  const normalizedText = rawText.replace(/\n/g, " ");
333
334
  if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
334
335
  text: " ",
335
- width: 1
336
+ width: 1,
337
+ trimOnWrap: true
336
338
  };
337
339
  else {
338
340
  wordPattern.lastIndex = 0;
@@ -341,7 +343,8 @@ function formatMessage(msg, options = {}) {
341
343
  if (match == null) break;
342
344
  yield {
343
345
  text: match[0],
344
- width: getDisplayWidth(match[0])
346
+ width: getDisplayWidth(match[0]),
347
+ trimOnWrap: true
345
348
  };
346
349
  }
347
350
  }
@@ -381,7 +384,8 @@ function formatMessage(msg, options = {}) {
381
384
  } else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
382
385
  if (i > 0) yield {
383
386
  text: " ",
384
- width: 1
387
+ width: 1,
388
+ trimOnWrap: true
385
389
  };
386
390
  const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
387
391
  yield {
@@ -425,18 +429,40 @@ function formatMessage(msg, options = {}) {
425
429
  }
426
430
  let output = "";
427
431
  let totalWidth = options.startWidth ?? 0;
428
- for (const { text: text$1, width } of stream()) {
432
+ let pendingWrap = false;
433
+ let atWrappedLineStart = false;
434
+ for (const token of stream()) {
435
+ let { text: text$1, width } = token;
429
436
  if (width === -1) {
430
437
  output += text$1;
431
438
  totalWidth = 0;
439
+ pendingWrap = false;
440
+ atWrappedLineStart = false;
441
+ continue;
442
+ }
443
+ const wraps = pendingWrap || options.maxWidth != null && totalWidth > 0 && totalWidth + width > options.maxWidth;
444
+ if ((wraps || atWrappedLineStart) && token.trimOnWrap) {
445
+ const trimmed = text$1.trimStart();
446
+ width -= getDisplayWidth(text$1.slice(0, text$1.length - trimmed.length));
447
+ text$1 = trimmed;
448
+ if (text$1 === "") {
449
+ if (wraps) pendingWrap = true;
450
+ continue;
451
+ }
452
+ }
453
+ if (pendingWrap && stripAnsi(text$1) === "") {
454
+ output += text$1;
432
455
  continue;
433
456
  }
434
- if (options.maxWidth != null && totalWidth > 0 && totalWidth + width > options.maxWidth) {
457
+ if (wraps) {
435
458
  output += "\n";
436
459
  totalWidth = 0;
460
+ pendingWrap = false;
461
+ atWrappedLineStart = true;
437
462
  }
438
463
  output += text$1;
439
464
  totalWidth += width;
465
+ if (width > 0) atWrappedLineStart = false;
440
466
  }
441
467
  return output;
442
468
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -221,7 +221,7 @@
221
221
  },
222
222
  "sideEffects": false,
223
223
  "devDependencies": {
224
- "@optique/env": "1.2.9",
224
+ "@optique/env": "1.2.10",
225
225
  "@types/node": "^24.0.0",
226
226
  "fast-check": "^4.7.0",
227
227
  "tsdown": "^0.13.0",