@optique/core 1.1.12 → 1.1.13

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.
@@ -513,12 +513,16 @@ function analyzeNoMatchContext(parsers) {
513
513
  * construction. This is a programmer error, not a user error.
514
514
  */
515
515
  var DuplicateOptionError = class extends Error {
516
+ /** The option name shared by multiple fields. */
517
+ optionName;
518
+ /** The fields declaring the option. */
519
+ sources;
516
520
  constructor(optionName$1, sources) {
517
521
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
518
522
  super(`Duplicate option name "${optionName$1}" found in fields: ${sourceNames.join(", ")}. Each option name must be unique within a parser combinator.`);
523
+ this.name = "DuplicateOptionError";
519
524
  this.optionName = optionName$1;
520
525
  this.sources = sources;
521
- this.name = "DuplicateOptionError";
522
526
  }
523
527
  };
524
528
  /**
@@ -526,6 +530,8 @@ var DuplicateOptionError = class extends Error {
526
530
  * during parser construction. This is a programmer error, not a user error.
527
531
  */
528
532
  var DuplicateCommandNameError = class extends TypeError {
533
+ commandName;
534
+ sources;
529
535
  constructor(commandName, sources) {
530
536
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
531
537
  super(`Duplicate command name "${commandName}" found in parsers: ${sourceNames.join(", ")}. Each command name or alias must be unique within active parser alternatives.`);
@@ -102,7 +102,9 @@ interface OrErrorOptions {
102
102
  * construction. This is a programmer error, not a user error.
103
103
  */
104
104
  declare class DuplicateOptionError extends Error {
105
+ /** The option name shared by multiple fields. */
105
106
  readonly optionName: string;
107
+ /** The fields declaring the option. */
106
108
  readonly sources: readonly (string | symbol)[];
107
109
  constructor(optionName: string, sources: readonly (string | symbol)[]);
108
110
  }
@@ -102,7 +102,9 @@ interface OrErrorOptions {
102
102
  * construction. This is a programmer error, not a user error.
103
103
  */
104
104
  declare class DuplicateOptionError extends Error {
105
+ /** The option name shared by multiple fields. */
105
106
  readonly optionName: string;
107
+ /** The fields declaring the option. */
106
108
  readonly sources: readonly (string | symbol)[];
107
109
  constructor(optionName: string, sources: readonly (string | symbol)[]);
108
110
  }
@@ -513,12 +513,16 @@ function analyzeNoMatchContext(parsers) {
513
513
  * construction. This is a programmer error, not a user error.
514
514
  */
515
515
  var DuplicateOptionError = class extends Error {
516
+ /** The option name shared by multiple fields. */
517
+ optionName;
518
+ /** The fields declaring the option. */
519
+ sources;
516
520
  constructor(optionName$1, sources) {
517
521
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
518
522
  super(`Duplicate option name "${optionName$1}" found in fields: ${sourceNames.join(", ")}. Each option name must be unique within a parser combinator.`);
523
+ this.name = "DuplicateOptionError";
519
524
  this.optionName = optionName$1;
520
525
  this.sources = sources;
521
- this.name = "DuplicateOptionError";
522
526
  }
523
527
  };
524
528
  /**
@@ -526,6 +530,8 @@ var DuplicateOptionError = class extends Error {
526
530
  * during parser construction. This is a programmer error, not a user error.
527
531
  */
528
532
  var DuplicateCommandNameError = class extends TypeError {
533
+ commandName;
534
+ sources;
529
535
  constructor(commandName, sources) {
530
536
  const sourceNames = sources.map((s) => typeof s === "symbol" ? s.description ?? s.toString() : s);
531
537
  super(`Duplicate command name "${commandName}" found in parsers: ${sourceNames.join(", ")}. Each command name or alias must be unique within active parser alternatives.`);
@@ -0,0 +1,73 @@
1
+
2
+ //#region src/internal/joined-file-suggestion.ts
3
+ const joinedFiles = /* @__PURE__ */ new WeakMap();
4
+ /**
5
+ * Keeps a joined file fallback's origin available to the Bash encoder.
6
+ * Other shells retain the existing literal representation.
7
+ * @param prefix The option name and delimiter.
8
+ * @param file The original file suggestion.
9
+ * @param value The value prefix being completed.
10
+ * @returns The literal fallback with private file metadata.
11
+ * @internal
12
+ */
13
+ function joinedFileSuggestion(prefix, file, value) {
14
+ const literal = {
15
+ kind: "literal",
16
+ text: prefix + (file.pattern ?? ""),
17
+ description: file.description
18
+ };
19
+ joinedFiles.set(literal, {
20
+ files: [{
21
+ prefix,
22
+ file: file.pattern === value && !/[?*]/.test(value) && !(file.prefix != null && file.prefix !== "" && value.startsWith(file.prefix)) ? {
23
+ ...file,
24
+ pattern: void 0
25
+ } : file
26
+ }],
27
+ literal: false
28
+ });
29
+ return literal;
30
+ }
31
+ /**
32
+ * Retrieves the originating file suggestion for a joined literal fallback.
33
+ * @param suggestion The suggestion to inspect.
34
+ * @returns Its private file metadata, if present.
35
+ * @internal
36
+ */
37
+ function getJoinedFileSuggestion(suggestion) {
38
+ return joinedFiles.get(suggestion);
39
+ }
40
+ /**
41
+ * Preserves file origins when equal literal fallbacks are deduplicated.
42
+ * @param target The retained suggestion.
43
+ * @param source The duplicate suggestion.
44
+ * @internal
45
+ */
46
+ function mergeJoinedFileSuggestions(target, source) {
47
+ const a = joinedFiles.get(target);
48
+ const b = joinedFiles.get(source);
49
+ if (a === void 0 && b === void 0) return;
50
+ const files = [...a?.files ?? [], ...b?.files ?? []];
51
+ const seen = /* @__PURE__ */ new Set();
52
+ joinedFiles.set(target, {
53
+ literal: (a?.literal ?? true) || (b?.literal ?? true),
54
+ files: files.filter(({ prefix, file }) => {
55
+ const key = JSON.stringify([
56
+ prefix,
57
+ file.type,
58
+ file.pattern,
59
+ file.prefix,
60
+ file.includeHidden === true,
61
+ file.extensions?.toSorted()
62
+ ]);
63
+ if (seen.has(key)) return false;
64
+ seen.add(key);
65
+ return true;
66
+ })
67
+ });
68
+ }
69
+
70
+ //#endregion
71
+ exports.getJoinedFileSuggestion = getJoinedFileSuggestion;
72
+ exports.joinedFileSuggestion = joinedFileSuggestion;
73
+ exports.mergeJoinedFileSuggestions = mergeJoinedFileSuggestions;
@@ -0,0 +1,70 @@
1
+ //#region src/internal/joined-file-suggestion.ts
2
+ const joinedFiles = /* @__PURE__ */ new WeakMap();
3
+ /**
4
+ * Keeps a joined file fallback's origin available to the Bash encoder.
5
+ * Other shells retain the existing literal representation.
6
+ * @param prefix The option name and delimiter.
7
+ * @param file The original file suggestion.
8
+ * @param value The value prefix being completed.
9
+ * @returns The literal fallback with private file metadata.
10
+ * @internal
11
+ */
12
+ function joinedFileSuggestion(prefix, file, value) {
13
+ const literal = {
14
+ kind: "literal",
15
+ text: prefix + (file.pattern ?? ""),
16
+ description: file.description
17
+ };
18
+ joinedFiles.set(literal, {
19
+ files: [{
20
+ prefix,
21
+ file: file.pattern === value && !/[?*]/.test(value) && !(file.prefix != null && file.prefix !== "" && value.startsWith(file.prefix)) ? {
22
+ ...file,
23
+ pattern: void 0
24
+ } : file
25
+ }],
26
+ literal: false
27
+ });
28
+ return literal;
29
+ }
30
+ /**
31
+ * Retrieves the originating file suggestion for a joined literal fallback.
32
+ * @param suggestion The suggestion to inspect.
33
+ * @returns Its private file metadata, if present.
34
+ * @internal
35
+ */
36
+ function getJoinedFileSuggestion(suggestion) {
37
+ return joinedFiles.get(suggestion);
38
+ }
39
+ /**
40
+ * Preserves file origins when equal literal fallbacks are deduplicated.
41
+ * @param target The retained suggestion.
42
+ * @param source The duplicate suggestion.
43
+ * @internal
44
+ */
45
+ function mergeJoinedFileSuggestions(target, source) {
46
+ const a = joinedFiles.get(target);
47
+ const b = joinedFiles.get(source);
48
+ if (a === void 0 && b === void 0) return;
49
+ const files = [...a?.files ?? [], ...b?.files ?? []];
50
+ const seen = /* @__PURE__ */ new Set();
51
+ joinedFiles.set(target, {
52
+ literal: (a?.literal ?? true) || (b?.literal ?? true),
53
+ files: files.filter(({ prefix, file }) => {
54
+ const key = JSON.stringify([
55
+ prefix,
56
+ file.type,
57
+ file.pattern,
58
+ file.prefix,
59
+ file.includeHidden === true,
60
+ file.extensions?.toSorted()
61
+ ]);
62
+ if (seen.has(key)) return false;
63
+ seen.add(key);
64
+ return true;
65
+ })
66
+ });
67
+ }
68
+
69
+ //#endregion
70
+ export { getJoinedFileSuggestion, joinedFileSuggestion, mergeJoinedFileSuggestions };
@@ -637,6 +637,14 @@ type Suggestion = {
637
637
  * The current prefix/pattern for fallback when native completion is unavailable.
638
638
  */
639
639
  readonly pattern?: string;
640
+ /**
641
+ * Literal text at the start of `pattern` that is preserved on insertion
642
+ * but excluded from native filesystem matching, such as `output=`.
643
+ * Shell adapters ignore it if it does not lead both the pattern and
644
+ * the current word. Regenerate installed scripts to use this field.
645
+ * @since 1.1.13
646
+ */
647
+ readonly prefix?: string;
640
648
  /**
641
649
  * The type of file system entries to complete.
642
650
  */
@@ -637,6 +637,14 @@ type Suggestion = {
637
637
  * The current prefix/pattern for fallback when native completion is unavailable.
638
638
  */
639
639
  readonly pattern?: string;
640
+ /**
641
+ * Literal text at the start of `pattern` that is preserved on insertion
642
+ * but excluded from native filesystem matching, such as `output=`.
643
+ * Shell adapters ignore it if it does not lead both the pattern and
644
+ * the current word. Regenerate installed scripts to use this field.
645
+ * @since 1.1.13
646
+ */
647
+ readonly prefix?: string;
640
648
  /**
641
649
  * The type of file system entries to complete.
642
650
  */
@@ -3,6 +3,7 @@ const require_message = require('./message.cjs');
3
3
  const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
4
4
  const require_internal_parser = require('./internal/parser.cjs');
5
5
  const require_annotation_state = require('./annotation-state.cjs');
6
+ const require_joined_file_suggestion = require('./internal/joined-file-suggestion.cjs');
6
7
  const require_command_doc_metadata = require('./internal/command-doc-metadata.cjs');
7
8
  const require_passthrough = require('./internal/passthrough.cjs');
8
9
  const require_execution_context = require('./execution-context.cjs');
@@ -1311,6 +1312,7 @@ function multiple(parser, options = {}) {
1311
1312
  "file",
1312
1313
  suggestion.type,
1313
1314
  suggestion.pattern ?? "",
1315
+ suggestion.prefix ?? "",
1314
1316
  suggestion.includeHidden === true,
1315
1317
  suggestion.extensions == null ? "" : suggestion.extensions.toSorted().join("\0"),
1316
1318
  description
@@ -1318,14 +1320,16 @@ function multiple(parser, options = {}) {
1318
1320
  };
1319
1321
  return require_mode_dispatch.dispatchIterableByMode(parser.mode, function* () {
1320
1322
  const selectedValues = collectSelectedValuesSync();
1321
- const emitted = /* @__PURE__ */ new Set();
1323
+ const emitted = /* @__PURE__ */ new Map();
1322
1324
  const yieldUnique = function* (suggestions) {
1323
1325
  for (const s of suggestions) {
1324
1326
  const key = suggestionKey(s);
1325
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1326
- emitted.add(key);
1327
+ if (!shouldInclude(selectedValues, s)) continue;
1328
+ const existing = emitted.get(key);
1329
+ if (existing === void 0) {
1330
+ emitted.set(key, s);
1327
1331
  yield s;
1328
- }
1332
+ } else require_joined_file_suggestion.mergeJoinedFileSuggestions(existing, s);
1329
1333
  }
1330
1334
  };
1331
1335
  let shouldTryFallback = false;
@@ -1338,14 +1342,16 @@ function multiple(parser, options = {}) {
1338
1342
  if (shouldTryFallback) yield* yieldUnique(syncParser.suggest(require_execution_context.withChildContext(context, itemIndex, suggestFallbackState), prefix));
1339
1343
  }, async function* () {
1340
1344
  const selectedValues = await collectSelectedValuesAsync();
1341
- const emitted = /* @__PURE__ */ new Set();
1345
+ const emitted = /* @__PURE__ */ new Map();
1342
1346
  const yieldUnique = async function* (suggestions) {
1343
1347
  for await (const s of suggestions) {
1344
1348
  const key = suggestionKey(s);
1345
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1346
- emitted.add(key);
1349
+ if (!shouldInclude(selectedValues, s)) continue;
1350
+ const existing = emitted.get(key);
1351
+ if (existing === void 0) {
1352
+ emitted.set(key, s);
1347
1353
  yield s;
1348
- }
1354
+ } else require_joined_file_suggestion.mergeJoinedFileSuggestions(existing, s);
1349
1355
  }
1350
1356
  };
1351
1357
  let shouldTryFallback = false;
package/dist/modifiers.js CHANGED
@@ -3,6 +3,7 @@ import { formatMessage, message, text } from "./message.js";
3
3
  import { dispatchByMode, dispatchIterableByMode, mapModeValue, wrapForMode } from "./internal/mode-dispatch.js";
4
4
  import { defineInheritedAnnotationParser, defineParseLanes, defineSourceBindingOnlyAnnotationCompletionParser, getOwnParseLanes, unmatchedNonCliDependencySourceStateMarker } from "./internal/parser.js";
5
5
  import { getDelegatedAnnotationState, hasDelegatedAnnotationCarrier, isAnnotationWrappedInitialState, normalizeDelegatedAnnotationState, normalizeNestedDelegatedAnnotationState } from "./annotation-state.js";
6
+ import { mergeJoinedFileSuggestions } from "./internal/joined-file-suggestion.js";
6
7
  import { getCommandDocMetadata, withCommandDocMetadata } from "./internal/command-doc-metadata.js";
7
8
  import { defineKnownCompletionLookup, defineOptionMatch, definePassThroughPriority, getKnownCompletion, getOptionMatch, getPassThroughPriority } from "./internal/passthrough.js";
8
9
  import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
@@ -1311,6 +1312,7 @@ function multiple(parser, options = {}) {
1311
1312
  "file",
1312
1313
  suggestion.type,
1313
1314
  suggestion.pattern ?? "",
1315
+ suggestion.prefix ?? "",
1314
1316
  suggestion.includeHidden === true,
1315
1317
  suggestion.extensions == null ? "" : suggestion.extensions.toSorted().join("\0"),
1316
1318
  description
@@ -1318,14 +1320,16 @@ function multiple(parser, options = {}) {
1318
1320
  };
1319
1321
  return dispatchIterableByMode(parser.mode, function* () {
1320
1322
  const selectedValues = collectSelectedValuesSync();
1321
- const emitted = /* @__PURE__ */ new Set();
1323
+ const emitted = /* @__PURE__ */ new Map();
1322
1324
  const yieldUnique = function* (suggestions) {
1323
1325
  for (const s of suggestions) {
1324
1326
  const key = suggestionKey(s);
1325
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1326
- emitted.add(key);
1327
+ if (!shouldInclude(selectedValues, s)) continue;
1328
+ const existing = emitted.get(key);
1329
+ if (existing === void 0) {
1330
+ emitted.set(key, s);
1327
1331
  yield s;
1328
- }
1332
+ } else mergeJoinedFileSuggestions(existing, s);
1329
1333
  }
1330
1334
  };
1331
1335
  let shouldTryFallback = false;
@@ -1338,14 +1342,16 @@ function multiple(parser, options = {}) {
1338
1342
  if (shouldTryFallback) yield* yieldUnique(syncParser.suggest(withChildContext(context, itemIndex, suggestFallbackState), prefix));
1339
1343
  }, async function* () {
1340
1344
  const selectedValues = await collectSelectedValuesAsync();
1341
- const emitted = /* @__PURE__ */ new Set();
1345
+ const emitted = /* @__PURE__ */ new Map();
1342
1346
  const yieldUnique = async function* (suggestions) {
1343
1347
  for await (const s of suggestions) {
1344
1348
  const key = suggestionKey(s);
1345
- if (shouldInclude(selectedValues, s) && !emitted.has(key)) {
1346
- emitted.add(key);
1349
+ if (!shouldInclude(selectedValues, s)) continue;
1350
+ const existing = emitted.get(key);
1351
+ if (existing === void 0) {
1352
+ emitted.set(key, s);
1347
1353
  yield s;
1348
- }
1354
+ } else mergeJoinedFileSuggestions(existing, s);
1349
1355
  }
1350
1356
  };
1351
1357
  let shouldTryFallback = false;
@@ -6,6 +6,7 @@ const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
6
6
  const require_internal_dependency = require('./internal/dependency.cjs');
7
7
  const require_dependency_runtime = require('./dependency-runtime.cjs');
8
8
  const require_annotation_state = require('./annotation-state.cjs');
9
+ const require_joined_file_suggestion = require('./internal/joined-file-suggestion.cjs');
9
10
  const require_command_doc_metadata = require('./internal/command-doc-metadata.cjs');
10
11
  const require_passthrough = require('./internal/passthrough.cjs');
11
12
  const require_command_alias = require('./internal/command-alias.cjs');
@@ -246,33 +247,33 @@ function* getSuggestionsWithDependency(valueParser, prefix, dependencyRegistry,
246
247
  }
247
248
  yield* valueParser.suggest(prefix);
248
249
  }
250
+ /** Returns the joined prefix accepted by option parsing. */
251
+ function joinedOptionPrefix(names, input) {
252
+ return names.filter((name) => name.startsWith("--") || name.startsWith("/") || name.startsWith("-") && name.length > 2).map((name) => name + (name.startsWith("/") ? ":" : "=")).find((prefix) => input.startsWith(prefix));
253
+ }
249
254
  /**
250
255
  * Internal sync helper for option suggest functionality.
251
256
  * @internal
252
257
  */
253
258
  function* suggestOptionSync(optionNames$1, valueParser, hidden, description, context, prefix) {
254
259
  if (hidden || context.optionsTerminated) return;
255
- const equalsIndex = prefix.indexOf("=");
256
- if (equalsIndex >= 0) {
257
- const optionPart = prefix.slice(0, equalsIndex);
258
- const valuePart = prefix.slice(equalsIndex + 1);
260
+ const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
261
+ const joinedPrefix = joinedOptionPrefix(optionNames$1, prefix);
262
+ if (joinedPrefix !== void 0 && !expectingValue) {
263
+ const optionPart = joinedPrefix.slice(0, -1);
264
+ const valuePart = prefix.slice(joinedPrefix.length);
259
265
  if (optionNames$1.includes(optionPart)) {
260
266
  if (valueParser && valueParser.suggest) {
261
267
  const valueSuggestions = getSuggestionsWithDependency(valueParser, valuePart, context.dependencyRegistry, context.exec);
262
268
  for (const suggestion of valueSuggestions) if (suggestion.kind === "literal") yield {
263
269
  kind: "literal",
264
- text: `${optionPart}=${suggestion.text}`,
265
- description: suggestion.description
266
- };
267
- else yield {
268
- kind: "literal",
269
- text: `${optionPart}=${suggestion.pattern || ""}`,
270
+ text: `${joinedPrefix}${suggestion.text}`,
270
271
  description: suggestion.description
271
272
  };
273
+ else yield require_joined_file_suggestion.joinedFileSuggestion(joinedPrefix, suggestion, valuePart);
272
274
  }
273
275
  }
274
276
  } else {
275
- const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
276
277
  if (!expectingValue && (prefix.startsWith("--") || prefix.startsWith("-") || prefix.startsWith("/") || prefix.startsWith("+"))) {
277
278
  for (const optionName$1 of optionNames$1) if (optionName$1.startsWith(prefix)) {
278
279
  if (prefix === "-" && optionName$1.length !== 2) continue;
@@ -337,27 +338,23 @@ async function* getSuggestionsWithDependencyAsync(valueParser, prefix, dependenc
337
338
  */
338
339
  async function* suggestOptionAsync(optionNames$1, valueParser, hidden, description, context, prefix) {
339
340
  if (hidden || context.optionsTerminated) return;
340
- const equalsIndex = prefix.indexOf("=");
341
- if (equalsIndex >= 0) {
342
- const optionPart = prefix.slice(0, equalsIndex);
343
- const valuePart = prefix.slice(equalsIndex + 1);
341
+ const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
342
+ const joinedPrefix = joinedOptionPrefix(optionNames$1, prefix);
343
+ if (joinedPrefix !== void 0 && !expectingValue) {
344
+ const optionPart = joinedPrefix.slice(0, -1);
345
+ const valuePart = prefix.slice(joinedPrefix.length);
344
346
  if (optionNames$1.includes(optionPart)) {
345
347
  if (valueParser && valueParser.suggest) {
346
348
  const valueSuggestions = getSuggestionsWithDependencyAsync(valueParser, valuePart, context.dependencyRegistry, context.exec);
347
349
  for await (const suggestion of valueSuggestions) if (suggestion.kind === "literal") yield {
348
350
  kind: "literal",
349
- text: `${optionPart}=${suggestion.text}`,
350
- description: suggestion.description
351
- };
352
- else yield {
353
- kind: "literal",
354
- text: `${optionPart}=${suggestion.pattern || ""}`,
351
+ text: `${joinedPrefix}${suggestion.text}`,
355
352
  description: suggestion.description
356
353
  };
354
+ else yield require_joined_file_suggestion.joinedFileSuggestion(joinedPrefix, suggestion, valuePart);
357
355
  }
358
356
  }
359
357
  } else {
360
- const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
361
358
  if (!expectingValue && (prefix.startsWith("--") || prefix.startsWith("-") || prefix.startsWith("/") || prefix.startsWith("+"))) {
362
359
  for (const optionName$1 of optionNames$1) if (optionName$1.startsWith(prefix)) {
363
360
  if (prefix === "-" && optionName$1.length !== 2) continue;
@@ -6,6 +6,7 @@ import { dispatchByMode, dispatchIterableByMode, wrapForMode } from "./internal/
6
6
  import { getDefaultValuesFunction, getDependencyIds, getSnapshottedDefaultDependencyValues, isDerivedValueParser, suggestWithDependency } from "./internal/dependency.js";
7
7
  import { replayDerivedParser, replayDerivedParserAsync } from "./dependency-runtime.js";
8
8
  import { getWrappedChildParseState, getWrappedChildState, isAnnotationWrappedInitialState, normalizeInjectedAnnotationState } from "./annotation-state.js";
9
+ import { joinedFileSuggestion } from "./internal/joined-file-suggestion.js";
9
10
  import { getCommandDocMetadata, withCommandDocMetadata } from "./internal/command-doc-metadata.js";
10
11
  import { defineKnownCompletion, defineOptionMatch, definePassThroughPriority, getOptionMatch, getPassThroughFailure, getPassThroughPriority } from "./internal/passthrough.js";
11
12
  import { hiddenCommandAliasesKey } from "./internal/command-alias.js";
@@ -246,33 +247,33 @@ function* getSuggestionsWithDependency(valueParser, prefix, dependencyRegistry,
246
247
  }
247
248
  yield* valueParser.suggest(prefix);
248
249
  }
250
+ /** Returns the joined prefix accepted by option parsing. */
251
+ function joinedOptionPrefix(names, input) {
252
+ return names.filter((name) => name.startsWith("--") || name.startsWith("/") || name.startsWith("-") && name.length > 2).map((name) => name + (name.startsWith("/") ? ":" : "=")).find((prefix) => input.startsWith(prefix));
253
+ }
249
254
  /**
250
255
  * Internal sync helper for option suggest functionality.
251
256
  * @internal
252
257
  */
253
258
  function* suggestOptionSync(optionNames$1, valueParser, hidden, description, context, prefix) {
254
259
  if (hidden || context.optionsTerminated) return;
255
- const equalsIndex = prefix.indexOf("=");
256
- if (equalsIndex >= 0) {
257
- const optionPart = prefix.slice(0, equalsIndex);
258
- const valuePart = prefix.slice(equalsIndex + 1);
260
+ const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
261
+ const joinedPrefix = joinedOptionPrefix(optionNames$1, prefix);
262
+ if (joinedPrefix !== void 0 && !expectingValue) {
263
+ const optionPart = joinedPrefix.slice(0, -1);
264
+ const valuePart = prefix.slice(joinedPrefix.length);
259
265
  if (optionNames$1.includes(optionPart)) {
260
266
  if (valueParser && valueParser.suggest) {
261
267
  const valueSuggestions = getSuggestionsWithDependency(valueParser, valuePart, context.dependencyRegistry, context.exec);
262
268
  for (const suggestion of valueSuggestions) if (suggestion.kind === "literal") yield {
263
269
  kind: "literal",
264
- text: `${optionPart}=${suggestion.text}`,
265
- description: suggestion.description
266
- };
267
- else yield {
268
- kind: "literal",
269
- text: `${optionPart}=${suggestion.pattern || ""}`,
270
+ text: `${joinedPrefix}${suggestion.text}`,
270
271
  description: suggestion.description
271
272
  };
273
+ else yield joinedFileSuggestion(joinedPrefix, suggestion, valuePart);
272
274
  }
273
275
  }
274
276
  } else {
275
- const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
276
277
  if (!expectingValue && (prefix.startsWith("--") || prefix.startsWith("-") || prefix.startsWith("/") || prefix.startsWith("+"))) {
277
278
  for (const optionName$1 of optionNames$1) if (optionName$1.startsWith(prefix)) {
278
279
  if (prefix === "-" && optionName$1.length !== 2) continue;
@@ -337,27 +338,23 @@ async function* getSuggestionsWithDependencyAsync(valueParser, prefix, dependenc
337
338
  */
338
339
  async function* suggestOptionAsync(optionNames$1, valueParser, hidden, description, context, prefix) {
339
340
  if (hidden || context.optionsTerminated) return;
340
- const equalsIndex = prefix.indexOf("=");
341
- if (equalsIndex >= 0) {
342
- const optionPart = prefix.slice(0, equalsIndex);
343
- const valuePart = prefix.slice(equalsIndex + 1);
341
+ const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
342
+ const joinedPrefix = joinedOptionPrefix(optionNames$1, prefix);
343
+ if (joinedPrefix !== void 0 && !expectingValue) {
344
+ const optionPart = joinedPrefix.slice(0, -1);
345
+ const valuePart = prefix.slice(joinedPrefix.length);
344
346
  if (optionNames$1.includes(optionPart)) {
345
347
  if (valueParser && valueParser.suggest) {
346
348
  const valueSuggestions = getSuggestionsWithDependencyAsync(valueParser, valuePart, context.dependencyRegistry, context.exec);
347
349
  for await (const suggestion of valueSuggestions) if (suggestion.kind === "literal") yield {
348
350
  kind: "literal",
349
- text: `${optionPart}=${suggestion.text}`,
350
- description: suggestion.description
351
- };
352
- else yield {
353
- kind: "literal",
354
- text: `${optionPart}=${suggestion.pattern || ""}`,
351
+ text: `${joinedPrefix}${suggestion.text}`,
355
352
  description: suggestion.description
356
353
  };
354
+ else yield joinedFileSuggestion(joinedPrefix, suggestion, valuePart);
357
355
  }
358
356
  }
359
357
  } else {
360
- const expectingValue = context.buffer.length > 0 && optionNames$1.includes(context.buffer[context.buffer.length - 1]);
361
358
  if (!expectingValue && (prefix.startsWith("--") || prefix.startsWith("-") || prefix.startsWith("/") || prefix.startsWith("+"))) {
362
359
  for (const optionName$1 of optionNames$1) if (optionName$1.startsWith(prefix)) {
363
360
  if (prefix === "-" && optionName$1.length !== 2) continue;
@@ -1,5 +1,6 @@
1
1
  const require_message = require('./message.cjs');
2
2
  const require_usage = require('./usage.cjs');
3
+ const require_joined_file_suggestion = require('./internal/joined-file-suggestion.cjs');
3
4
 
4
5
  //#region src/suggestion.ts
5
6
  /**
@@ -257,22 +258,27 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
257
258
  *
258
259
  * For literal suggestions, the text itself is used as the key.
259
260
  * For file suggestions, a composite key is created from the type,
260
- * extensions, and pattern.
261
+ * extensions, pattern, and insertion prefix.
261
262
  *
262
263
  * @param suggestion The suggestion to create a key for
263
264
  * @returns A string key that uniquely identifies this suggestion
264
265
  * @internal
265
266
  */
266
267
  function getSuggestionKey(suggestion) {
267
- if (suggestion.kind === "literal") return suggestion.text;
268
- return `__FILE__:${suggestion.type}:${suggestion.extensions?.toSorted().join(",") ?? ""}:${suggestion.pattern ?? ""}`;
268
+ if (suggestion.kind === "literal") return `literal:${suggestion.text}`;
269
+ return `file:${JSON.stringify([
270
+ suggestion.type,
271
+ suggestion.extensions?.toSorted().join(",") ?? "",
272
+ suggestion.pattern ?? "",
273
+ suggestion.prefix ?? ""
274
+ ])}`;
269
275
  }
270
276
  /**
271
277
  * Removes duplicate suggestions from an array while preserving order.
272
278
  *
273
279
  * Suggestions are considered duplicates if they have the same key:
274
280
  * - Literal suggestions: same text
275
- * - File suggestions: same type, extensions, and pattern
281
+ * - File suggestions: same type, extensions, pattern, and insertion prefix
276
282
  *
277
283
  * The first occurrence of each unique suggestion is kept. For file
278
284
  * suggestions, `includeHidden` is merged across duplicates: if any
@@ -305,7 +311,8 @@ function deduplicateSuggestions(suggestions) {
305
311
  if (existing == null) {
306
312
  entries.set(key, suggestion);
307
313
  order.push(key);
308
- } else if (suggestion.kind === "file" && existing.kind === "file" && suggestion.includeHidden === true && existing.includeHidden !== true) entries.set(key, {
314
+ } else if (suggestion.kind === "literal" && existing.kind === "literal") require_joined_file_suggestion.mergeJoinedFileSuggestions(existing, suggestion);
315
+ else if (suggestion.kind === "file" && existing.kind === "file" && suggestion.includeHidden === true && existing.includeHidden !== true) entries.set(key, {
309
316
  ...existing,
310
317
  includeHidden: true
311
318
  });
@@ -159,7 +159,7 @@ declare function createErrorWithSuggestions(baseError: Message, invalidInput: st
159
159
  *
160
160
  * Suggestions are considered duplicates if they have the same key:
161
161
  * - Literal suggestions: same text
162
- * - File suggestions: same type, extensions, and pattern
162
+ * - File suggestions: same type, extensions, pattern, and insertion prefix
163
163
  *
164
164
  * The first occurrence of each unique suggestion is kept. For file
165
165
  * suggestions, `includeHidden` is merged across duplicates: if any
@@ -159,7 +159,7 @@ declare function createErrorWithSuggestions(baseError: Message, invalidInput: st
159
159
  *
160
160
  * Suggestions are considered duplicates if they have the same key:
161
161
  * - Literal suggestions: same text
162
- * - File suggestions: same type, extensions, and pattern
162
+ * - File suggestions: same type, extensions, pattern, and insertion prefix
163
163
  *
164
164
  * The first occurrence of each unique suggestion is kept. For file
165
165
  * suggestions, `includeHidden` is merged across duplicates: if any