politty 0.5.0 → 0.6.0
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/README.md +47 -0
- package/dist/{arg-registry-6E0WHOh_.d.ts → arg-registry-BoqVZRFO.d.cts} +24 -22
- package/dist/arg-registry-BoqVZRFO.d.cts.map +1 -0
- package/dist/{arg-registry--NRaNFJM.d.cts → arg-registry-BoqVZRFO.d.ts} +24 -22
- package/dist/arg-registry-BoqVZRFO.d.ts.map +1 -0
- package/dist/augment.d.cts +1 -1
- package/dist/augment.d.ts +1 -1
- package/dist/cli.cjs +54 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +55 -0
- package/dist/cli.js.map +1 -0
- package/dist/completion/index.cjs +6 -1
- package/dist/completion/index.d.cts +3 -3
- package/dist/completion/index.d.ts +3 -3
- package/dist/completion/index.js +2 -2
- package/dist/{completion-Cqs1Ja7C.cjs → completion-CLHO3Xaz.cjs} +1915 -315
- package/dist/completion-CLHO3Xaz.cjs.map +1 -0
- package/dist/{completion-BA5JMvVG.js → completion-DHnVx9Zk.js} +1889 -318
- package/dist/completion-DHnVx9Zk.js.map +1 -0
- package/dist/docs/index.cjs +3 -3
- package/dist/docs/index.cjs.map +1 -1
- package/dist/docs/index.d.cts +1 -1
- package/dist/docs/index.d.cts.map +1 -1
- package/dist/docs/index.d.ts +1 -1
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +3 -3
- package/dist/docs/index.js.map +1 -1
- package/dist/{index-DBMfKZ34.d.ts → index-Csk1VFou.d.ts} +120 -4
- package/dist/index-Csk1VFou.d.ts.map +1 -0
- package/dist/{index-DJp8k5Bq.d.cts → index-Ct48_myg.d.cts} +120 -4
- package/dist/index-Ct48_myg.d.cts.map +1 -0
- package/dist/index.cjs +4 -3
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{log-collector-Cu6MCtAx.js → log-collector-DK32-73m.js} +1 -1
- package/dist/{log-collector-Cu6MCtAx.js.map → log-collector-DK32-73m.js.map} +1 -1
- package/dist/{log-collector-Cd2_mv87.cjs → log-collector-DUqC427m.cjs} +1 -1
- package/dist/{log-collector-Cd2_mv87.cjs.map → log-collector-DUqC427m.cjs.map} +1 -1
- package/dist/prompt/clack/index.cjs +1 -2
- package/dist/prompt/clack/index.cjs.map +1 -1
- package/dist/prompt/clack/index.d.cts +1 -1
- package/dist/prompt/clack/index.d.ts +1 -1
- package/dist/prompt/clack/index.js +1 -1
- package/dist/prompt/index.cjs +1 -1
- package/dist/prompt/index.d.cts +1 -1
- package/dist/prompt/index.d.cts.map +1 -1
- package/dist/prompt/index.d.ts +1 -1
- package/dist/prompt/index.d.ts.map +1 -1
- package/dist/prompt/index.js +1 -1
- package/dist/prompt/inquirer/index.cjs +1 -2
- package/dist/prompt/inquirer/index.cjs.map +1 -1
- package/dist/prompt/inquirer/index.d.cts +1 -1
- package/dist/prompt/inquirer/index.d.ts +1 -1
- package/dist/prompt/inquirer/index.js +1 -1
- package/dist/{prompt-aXfSf27y.cjs → prompt-Bs9e-Em3.cjs} +1 -1
- package/dist/{prompt-aXfSf27y.cjs.map → prompt-Bs9e-Em3.cjs.map} +1 -1
- package/dist/{prompt-BKHqGrFw.js → prompt-Cc8Tfmdv.js} +1 -1
- package/dist/{prompt-BKHqGrFw.js.map → prompt-Cc8Tfmdv.js.map} +1 -1
- package/dist/{runner-BmSEiD9A.js → runner--Zn4KN9B.js} +3 -3
- package/dist/{runner-BmSEiD9A.js.map → runner--Zn4KN9B.js.map} +1 -1
- package/dist/{runner-CRZ_7Y9i.cjs → runner-BloFWJEB.cjs} +3 -3
- package/dist/{runner-CRZ_7Y9i.cjs.map → runner-BloFWJEB.cjs.map} +1 -1
- package/dist/{schema-extractor-SLPgBNgZ.cjs → schema-extractor-BxSRwLrx.cjs} +1 -2
- package/dist/schema-extractor-BxSRwLrx.cjs.map +1 -0
- package/dist/{schema-extractor-C50R-1re.js → schema-extractor-Dqe7_kyQ.js} +1 -1
- package/dist/schema-extractor-Dqe7_kyQ.js.map +1 -0
- package/package.json +14 -11
- package/dist/arg-registry--NRaNFJM.d.cts.map +0 -1
- package/dist/arg-registry-6E0WHOh_.d.ts.map +0 -1
- package/dist/completion-BA5JMvVG.js.map +0 -1
- package/dist/completion-Cqs1Ja7C.cjs.map +0 -1
- package/dist/index-DBMfKZ34.d.ts.map +0 -1
- package/dist/index-DJp8k5Bq.d.cts.map +0 -1
- package/dist/schema-extractor-C50R-1re.js.map +0 -1
- package/dist/schema-extractor-SLPgBNgZ.cjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
const require_schema_extractor = require('./schema-extractor-SLPgBNgZ.cjs');
|
|
1
|
+
const require_schema_extractor = require('./schema-extractor-BxSRwLrx.cjs');
|
|
3
2
|
let zod = require("zod");
|
|
4
3
|
let node_child_process = require("node:child_process");
|
|
5
4
|
let node_fs = require("node:fs");
|
|
6
5
|
let node_path = require("node:path");
|
|
6
|
+
let node_util = require("node:util");
|
|
7
7
|
|
|
8
8
|
//#region src/core/command.ts
|
|
9
9
|
function defineCommand(config) {
|
|
@@ -133,6 +133,33 @@ function ansiC(s) {
|
|
|
133
133
|
return out;
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
136
|
+
* Single-quote escape for a POSIX shell literal: `'` -> `'\''`. Inside single
|
|
137
|
+
* quotes the shell performs no expansion at all, so `$`, backticks, and
|
|
138
|
+
* `$(...)` stay inert. Used for hardcoded paths that may originate from
|
|
139
|
+
* env/config so path metachars never execute as commands when the generated
|
|
140
|
+
* snippet is sourced. Safe for bash, zsh, and fish.
|
|
141
|
+
*/
|
|
142
|
+
function shSingleQuote(s) {
|
|
143
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Shell sub-expression that prints a file's stat signature, trying GNU
|
|
147
|
+
* `stat -c` first (BSD `-f` is filesystem mode there) then falling back to
|
|
148
|
+
* BSD `stat -f`. `-L` follows symlinks so the shell-side mtime matches Node's
|
|
149
|
+
* `fs.statSync`. `posix` (bash/zsh) wraps it as `$(… || …)`; fish has no
|
|
150
|
+
* `$(…)` capture inside `(…)`, so it uses `(…; or …)`. With `withSize`, the
|
|
151
|
+
* signature is `mtime:size` so a worker cache rewritten within the same
|
|
152
|
+
* second still reads as stale; otherwise it is the bare mtime in whole
|
|
153
|
+
* seconds.
|
|
154
|
+
*/
|
|
155
|
+
function statSigExpr(fileVar, opts) {
|
|
156
|
+
const gnuFmt = opts.withSize ? "%Y:%s" : "%Y";
|
|
157
|
+
const bsdFmt = opts.withSize ? "%m:%z" : "%m";
|
|
158
|
+
const gnu = `stat -L -c '${gnuFmt}' "${fileVar}" 2>/dev/null`;
|
|
159
|
+
const bsd = `stat -L -f '${bsdFmt}' "${fileVar}" 2>/dev/null`;
|
|
160
|
+
return opts.shell === "fish" ? `(${gnu}; or ${bsd})` : `$(${gnu} || ${bsd})`;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
136
163
|
* Render an alias as its CLI token form: single-char aliases become `-x`,
|
|
137
164
|
* multi-char aliases become `--long`. Mirrors the parser's accepted shapes
|
|
138
165
|
* and is the bare-token form (no quoting) used inside generated case
|
|
@@ -294,13 +321,19 @@ function resolveValueCompletion(field) {
|
|
|
294
321
|
* Parse completion context from partial command line
|
|
295
322
|
*/
|
|
296
323
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
324
|
+
* `completion.custom.expand` is baked into static scripts, but dispatcher
|
|
325
|
+
* scripts call `__complete` at TAB time. Convert the pending sentinel into a
|
|
326
|
+
* runtime form that can call `enumerate` with the dependency values already
|
|
327
|
+
* typed on the command line.
|
|
301
328
|
*/
|
|
302
|
-
function
|
|
303
|
-
|
|
329
|
+
function resolveRuntimeCompletion(vc) {
|
|
330
|
+
if (!vc) return void 0;
|
|
331
|
+
if (vc.type !== "pending-expand") return vc;
|
|
332
|
+
return {
|
|
333
|
+
type: "runtime-expand",
|
|
334
|
+
dependsOn: vc.spec.dependsOn,
|
|
335
|
+
enumerate: vc.spec.enumerate
|
|
336
|
+
};
|
|
304
337
|
}
|
|
305
338
|
/**
|
|
306
339
|
* Extract options from a command
|
|
@@ -323,7 +356,7 @@ function extractOptionsFromSchema(schema) {
|
|
|
323
356
|
valueType: field.type,
|
|
324
357
|
required: field.required,
|
|
325
358
|
defaultNegationAccepted: field.type === "boolean" && (field.negation === void 0 || field.negation === true),
|
|
326
|
-
valueCompletion:
|
|
359
|
+
valueCompletion: resolveRuntimeCompletion(resolveValueCompletion(field))
|
|
327
360
|
};
|
|
328
361
|
});
|
|
329
362
|
}
|
|
@@ -454,7 +487,7 @@ function extractPositionalsForContext(command) {
|
|
|
454
487
|
description: field.description,
|
|
455
488
|
required: field.required,
|
|
456
489
|
variadic: field.type === "array",
|
|
457
|
-
valueCompletion:
|
|
490
|
+
valueCompletion: resolveRuntimeCompletion(resolveValueCompletion(field))
|
|
458
491
|
}));
|
|
459
492
|
}
|
|
460
493
|
/**
|
|
@@ -882,11 +915,15 @@ async function generateCandidates(context, options) {
|
|
|
882
915
|
return generateValueCandidates(inlinePrefix ? {
|
|
883
916
|
...context,
|
|
884
917
|
currentWord: context.currentWord.slice(inlinePrefix.length)
|
|
885
|
-
} : context, options, opt?.name, opt?.valueCompletion);
|
|
918
|
+
} : context, options, opt?.name, opt?.valueCompletion, void 0, opt?.valueType === "array");
|
|
886
919
|
}
|
|
887
920
|
case "positional": {
|
|
888
921
|
const positional = resolvePositionalTarget(context);
|
|
889
|
-
|
|
922
|
+
if (!positional) return {
|
|
923
|
+
candidates: [],
|
|
924
|
+
directive: CompletionDirective.NoFileCompletion
|
|
925
|
+
};
|
|
926
|
+
return generateValueCandidates(context, options, positional.name, positional.valueCompletion, positional.description, false);
|
|
890
927
|
}
|
|
891
928
|
}
|
|
892
929
|
}
|
|
@@ -921,6 +958,16 @@ function executeShellCommand(command) {
|
|
|
921
958
|
return [];
|
|
922
959
|
}
|
|
923
960
|
}
|
|
961
|
+
function staticChoices(vc) {
|
|
962
|
+
return vc?.type === "choices" ? vc.choices : void 0;
|
|
963
|
+
}
|
|
964
|
+
function runtimeExpandDepChoices(context, dep) {
|
|
965
|
+
const localOption = context.options.find((opt) => opt.name === dep && opt.isGlobal !== true);
|
|
966
|
+
if (localOption) return staticChoices(localOption.valueCompletion);
|
|
967
|
+
const positional = context.positionals.find((pos) => pos.name === dep);
|
|
968
|
+
if (positional) return staticChoices(positional.valueCompletion);
|
|
969
|
+
return staticChoices(context.options.find((opt) => opt.name === dep && opt.isGlobal === true)?.valueCompletion);
|
|
970
|
+
}
|
|
924
971
|
/**
|
|
925
972
|
* Two-stage `key=value` post-processing. Returns the transformed candidate
|
|
926
973
|
* list plus whether it contains a bare `key=` entry so the caller can flip
|
|
@@ -969,7 +1016,7 @@ function dropBareKeyEcho(candidates, currentWord) {
|
|
|
969
1016
|
/**
|
|
970
1017
|
* Resolve value completion, executing shell commands and file lookups in JS
|
|
971
1018
|
*/
|
|
972
|
-
async function resolveValueCandidates(vc, ctx, description) {
|
|
1019
|
+
async function resolveValueCandidates(vc, ctx, completionContext, description, dedupeKeyValues) {
|
|
973
1020
|
const candidates = [];
|
|
974
1021
|
let directive = CompletionDirective.FilterPrefix;
|
|
975
1022
|
let fileExtensions;
|
|
@@ -1026,9 +1073,40 @@ async function resolveValueCandidates(vc, ctx, description) {
|
|
|
1026
1073
|
case "expand":
|
|
1027
1074
|
directive |= CompletionDirective.NoFileCompletion;
|
|
1028
1075
|
break;
|
|
1076
|
+
case "runtime-expand": {
|
|
1077
|
+
const deps = {};
|
|
1078
|
+
let missingDep = false;
|
|
1079
|
+
for (const dep of vc.dependsOn) {
|
|
1080
|
+
const raw = ctx.parsedArgs[dep];
|
|
1081
|
+
const value = Array.isArray(raw) ? raw[raw.length - 1] : raw;
|
|
1082
|
+
const allowedValues = runtimeExpandDepChoices(completionContext, dep);
|
|
1083
|
+
if (typeof value !== "string" || allowedValues === void 0 || !allowedValues.includes(value)) {
|
|
1084
|
+
missingDep = true;
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
deps[dep] = value;
|
|
1088
|
+
}
|
|
1089
|
+
if (!missingDep) try {
|
|
1090
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1091
|
+
for (const item of vc.enumerate(deps)) {
|
|
1092
|
+
const normalized = typeof item === "string" ? { value: item } : item;
|
|
1093
|
+
if (seen.has(normalized.value)) continue;
|
|
1094
|
+
seen.add(normalized.value);
|
|
1095
|
+
candidates.push({
|
|
1096
|
+
...normalized,
|
|
1097
|
+
type: "value"
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
} catch {
|
|
1101
|
+
directive = CompletionDirective.NoFileCompletion | CompletionDirective.Error;
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
directive |= CompletionDirective.NoFileCompletion;
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1029
1107
|
}
|
|
1030
|
-
if (vc.type === "dynamic" || vc.type === "expand") {
|
|
1031
|
-
const processed = applyKeyValuePostProcessing(candidates, ctx.currentWord);
|
|
1108
|
+
if (vc.type === "dynamic" || vc.type === "expand" || vc.type === "runtime-expand") {
|
|
1109
|
+
const processed = applyKeyValuePostProcessing(vc.type === "runtime-expand" && dedupeKeyValues === true ? dropAlreadyUsedKeyCandidates(candidates, ctx.previousValues) : candidates, ctx.currentWord);
|
|
1032
1110
|
if (processed.hasEqSuffix) directive |= CompletionDirective.NoSpace;
|
|
1033
1111
|
return {
|
|
1034
1112
|
candidates: processed.candidates,
|
|
@@ -1044,6 +1122,19 @@ async function resolveValueCandidates(vc, ctx, description) {
|
|
|
1044
1122
|
fileMatchers
|
|
1045
1123
|
};
|
|
1046
1124
|
}
|
|
1125
|
+
function dropAlreadyUsedKeyCandidates(candidates, previousValues) {
|
|
1126
|
+
const used = /* @__PURE__ */ new Set();
|
|
1127
|
+
for (const value of previousValues) {
|
|
1128
|
+
const eqIdx = value.indexOf("=");
|
|
1129
|
+
if (eqIdx > 0) used.add(value.slice(0, eqIdx));
|
|
1130
|
+
}
|
|
1131
|
+
if (used.size === 0) return [...candidates];
|
|
1132
|
+
return candidates.filter((candidate) => {
|
|
1133
|
+
const eqIdx = candidate.value.indexOf("=");
|
|
1134
|
+
if (eqIdx <= 0) return true;
|
|
1135
|
+
return !used.has(candidate.value.slice(0, eqIdx));
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1047
1138
|
/**
|
|
1048
1139
|
* Generate subcommand candidates
|
|
1049
1140
|
*/
|
|
@@ -1067,7 +1158,7 @@ function generateSubcommandCandidates(context) {
|
|
|
1067
1158
|
}
|
|
1068
1159
|
return {
|
|
1069
1160
|
candidates,
|
|
1070
|
-
directive: CompletionDirective.FilterPrefix
|
|
1161
|
+
directive: CompletionDirective.FilterPrefix | CompletionDirective.NoFileCompletion
|
|
1071
1162
|
};
|
|
1072
1163
|
}
|
|
1073
1164
|
/**
|
|
@@ -1101,7 +1192,7 @@ function generateOptionNameCandidates(context) {
|
|
|
1101
1192
|
});
|
|
1102
1193
|
return {
|
|
1103
1194
|
candidates,
|
|
1104
|
-
directive: CompletionDirective.FilterPrefix
|
|
1195
|
+
directive: CompletionDirective.FilterPrefix | CompletionDirective.NoFileCompletion
|
|
1105
1196
|
};
|
|
1106
1197
|
}
|
|
1107
1198
|
/**
|
|
@@ -1137,12 +1228,12 @@ function parsedArgsWithoutTarget(parsedArgs, key) {
|
|
|
1137
1228
|
* is propagated to choices candidates (positional path supplies it; option
|
|
1138
1229
|
* path does not, mirroring the prior split implementations).
|
|
1139
1230
|
*/
|
|
1140
|
-
async function generateValueCandidates(context, options, targetFieldName, vc, description) {
|
|
1231
|
+
async function generateValueCandidates(context, options, targetFieldName, vc, description, dedupeKeyValues) {
|
|
1141
1232
|
if (!vc) return {
|
|
1142
1233
|
candidates: [],
|
|
1143
1234
|
directive: CompletionDirective.FilterPrefix
|
|
1144
1235
|
};
|
|
1145
|
-
return resolveValueCandidates(vc, resolverContext(context, options, targetFieldName), description);
|
|
1236
|
+
return resolveValueCandidates(vc, resolverContext(context, options, targetFieldName), context, description, dedupeKeyValues);
|
|
1146
1237
|
}
|
|
1147
1238
|
|
|
1148
1239
|
//#endregion
|
|
@@ -1153,10 +1244,10 @@ async function generateValueCandidates(context, options, targetFieldName, vc, de
|
|
|
1153
1244
|
* sentinel via `resolveValueCompletion`) and passes them here once siblings
|
|
1154
1245
|
* are known.
|
|
1155
1246
|
*/
|
|
1156
|
-
function resolveExpandTargets(sub, targets, globalOptions = []) {
|
|
1247
|
+
function resolveExpandTargets(sub, targets, globalOptions = [], validateOnly = false) {
|
|
1157
1248
|
if (targets.length === 0) return;
|
|
1158
1249
|
const siblingIndex = buildSiblingIndex(sub, globalOptions);
|
|
1159
|
-
for (const target of targets) target.set(resolveOne(target, siblingIndex));
|
|
1250
|
+
for (const target of targets) target.set(resolveOne(target, siblingIndex, validateOnly));
|
|
1160
1251
|
}
|
|
1161
1252
|
/**
|
|
1162
1253
|
* Build a name → static-values map for siblings, using each field's already
|
|
@@ -1183,7 +1274,7 @@ function buildSiblingIndex(sub, globalOptions) {
|
|
|
1183
1274
|
visit(globalOptions, true);
|
|
1184
1275
|
return index;
|
|
1185
1276
|
}
|
|
1186
|
-
function resolveOne(target, siblings) {
|
|
1277
|
+
function resolveOne(target, siblings, validateOnly = false) {
|
|
1187
1278
|
const { spec } = target;
|
|
1188
1279
|
const deps = spec.dependsOn;
|
|
1189
1280
|
if (deps.length === 0) throw new Error(`Field "${target.describe}": completion.custom.expand.dependsOn must list at least one sibling arg.`);
|
|
@@ -1194,6 +1285,11 @@ function resolveOne(target, siblings) {
|
|
|
1194
1285
|
if (!values) throw new Error(`Field "${target.describe}": completion.custom.expand.dependsOn references "${dep}", which is not a sibling arg with a static \`choices\`/enum schema on the same command. Chaining expand specs is not supported.`);
|
|
1195
1286
|
valueLists.push([...values]);
|
|
1196
1287
|
}
|
|
1288
|
+
if (validateOnly) return {
|
|
1289
|
+
type: "expand",
|
|
1290
|
+
dependsOn: deps,
|
|
1291
|
+
table: []
|
|
1292
|
+
};
|
|
1197
1293
|
const table = [];
|
|
1198
1294
|
for (const combo of cartesian(valueLists)) {
|
|
1199
1295
|
const depsRecord = {};
|
|
@@ -1404,11 +1500,11 @@ function fieldsToPositionals(fields, pending) {
|
|
|
1404
1500
|
/**
|
|
1405
1501
|
* Extract a completable subcommand from a command
|
|
1406
1502
|
*/
|
|
1407
|
-
function extractSubcommand(name, command, globalOptions = []) {
|
|
1503
|
+
function extractSubcommand(name, command, globalOptions = [], validateOnly = false) {
|
|
1408
1504
|
const subcommands = [];
|
|
1409
1505
|
if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) {
|
|
1410
1506
|
const resolved = require_schema_extractor.resolveSubCommandMeta(subCommand);
|
|
1411
|
-
if (resolved) subcommands.push(extractSubcommand(subName, resolved, globalOptions));
|
|
1507
|
+
if (resolved) subcommands.push(extractSubcommand(subName, resolved, globalOptions, validateOnly));
|
|
1412
1508
|
else subcommands.push({
|
|
1413
1509
|
name: subName,
|
|
1414
1510
|
description: "(lazy loaded)",
|
|
@@ -1427,7 +1523,7 @@ function extractSubcommand(name, command, globalOptions = []) {
|
|
|
1427
1523
|
options: fieldsToOptions(fields, pending),
|
|
1428
1524
|
positionals: fieldsToPositionals(fields, pending)
|
|
1429
1525
|
};
|
|
1430
|
-
resolveExpandTargets(node, pending, globalOptions);
|
|
1526
|
+
resolveExpandTargets(node, pending, globalOptions, validateOnly);
|
|
1431
1527
|
return node;
|
|
1432
1528
|
}
|
|
1433
1529
|
/** Join parent and child with a separator, omitting separator when parent is empty. */
|
|
@@ -1826,7 +1922,7 @@ function propagateGlobalOptions(sub, globalOptions) {
|
|
|
1826
1922
|
* @param globalArgsSchema - Optional global args schema. When provided, global options
|
|
1827
1923
|
* are derived from this schema instead of the root command's options.
|
|
1828
1924
|
*/
|
|
1829
|
-
function extractCompletionData(command, programName, globalArgsSchema) {
|
|
1925
|
+
function extractCompletionData(command, programName, globalArgsSchema, validateOnly = false) {
|
|
1830
1926
|
let globalOptions = [];
|
|
1831
1927
|
if (globalArgsSchema) {
|
|
1832
1928
|
const globalPending = [];
|
|
@@ -1839,9 +1935,9 @@ function extractCompletionData(command, programName, globalArgsSchema) {
|
|
|
1839
1935
|
subcommands: [],
|
|
1840
1936
|
options: globalOptions,
|
|
1841
1937
|
positionals: []
|
|
1842
|
-
}, globalPending);
|
|
1938
|
+
}, globalPending, [], validateOnly);
|
|
1843
1939
|
}
|
|
1844
|
-
const rootSubcommand = extractSubcommand(programName, command, globalOptions);
|
|
1940
|
+
const rootSubcommand = extractSubcommand(programName, command, globalOptions, validateOnly);
|
|
1845
1941
|
if (globalArgsSchema) propagateGlobalOptions(rootSubcommand, globalOptions);
|
|
1846
1942
|
else globalOptions = rootSubcommand.options;
|
|
1847
1943
|
return {
|
|
@@ -1858,8 +1954,9 @@ function extractCompletionData(command, programName, globalArgsSchema) {
|
|
|
1858
1954
|
*
|
|
1859
1955
|
* Every completion script generated by politty starts with a small
|
|
1860
1956
|
* machine-readable header. The rc loader and the runMain background
|
|
1861
|
-
* refresh path use the `# politty-bin-sig:`
|
|
1862
|
-
* cached script is stale relative to the
|
|
1957
|
+
* refresh path use the `# politty-bin-sig:` and `# politty-bin-path:`
|
|
1958
|
+
* lines to detect when the cached script is stale relative to the
|
|
1959
|
+
* binary on disk.
|
|
1863
1960
|
*/
|
|
1864
1961
|
/** Schema version of the header itself. Bump when the header layout changes. */
|
|
1865
1962
|
const COMPLETION_VERSION = 1;
|
|
@@ -1875,6 +1972,9 @@ function computeBinSig(binPath) {
|
|
|
1875
1972
|
return "0";
|
|
1876
1973
|
}
|
|
1877
1974
|
}
|
|
1975
|
+
function headerValue(value) {
|
|
1976
|
+
return value.replace(/\r?\n/g, " ");
|
|
1977
|
+
}
|
|
1878
1978
|
/**
|
|
1879
1979
|
* Walk `$PATH` looking for an executable named `programName`. Returns
|
|
1880
1980
|
* the first match's full path, or `null` when not found. We mirror the
|
|
@@ -1905,6 +2005,8 @@ function findOnPath(programName) {
|
|
|
1905
2005
|
*/
|
|
1906
2006
|
function resolveBinPath(programName, override) {
|
|
1907
2007
|
if (override) return override;
|
|
2008
|
+
const envOverride = process.env[binEnvVarName(sanitize(programName))];
|
|
2009
|
+
if (envOverride) return envOverride;
|
|
1908
2010
|
return findOnPath(programName) ?? process.argv[1] ?? "";
|
|
1909
2011
|
}
|
|
1910
2012
|
/**
|
|
@@ -1913,10 +2015,12 @@ function resolveBinPath(programName, override) {
|
|
|
1913
2015
|
* marker.
|
|
1914
2016
|
*/
|
|
1915
2017
|
function buildHeaderLines(opts) {
|
|
1916
|
-
const
|
|
2018
|
+
const binPath = resolveBinPath(opts.programName, opts.binPath);
|
|
2019
|
+
const sig = computeBinSig(binPath);
|
|
1917
2020
|
const lines = [
|
|
1918
2021
|
`# politty-completion-version: ${1}`,
|
|
1919
2022
|
`# politty-bin-sig: ${sig}`,
|
|
2023
|
+
`# politty-bin-path: ${headerValue(binPath)}`,
|
|
1920
2024
|
`# program: ${opts.programName}`
|
|
1921
2025
|
];
|
|
1922
2026
|
if (opts.programVersion) lines.push(`# program-version: ${opts.programVersion}`);
|
|
@@ -1924,6 +2028,92 @@ function buildHeaderLines(opts) {
|
|
|
1924
2028
|
return lines;
|
|
1925
2029
|
}
|
|
1926
2030
|
|
|
2031
|
+
//#endregion
|
|
2032
|
+
//#region src/completion/self-refresh.ts
|
|
2033
|
+
/**
|
|
2034
|
+
* Self-refresh guards embedded in generated bash/zsh scripts.
|
|
2035
|
+
*
|
|
2036
|
+
* These guards make the default `completion <shell>` output safe to
|
|
2037
|
+
* save as a static completion file: when the CLI binary changes, the
|
|
2038
|
+
* script asks the hidden refresh subcommand to rewrite the sourced
|
|
2039
|
+
* file in place, then sources the fresh file and stops executing the
|
|
2040
|
+
* stale body.
|
|
2041
|
+
*/
|
|
2042
|
+
function generateBashSelfRefresh(opts) {
|
|
2043
|
+
const { programName, binPath } = opts;
|
|
2044
|
+
const fn = sanitize(programName);
|
|
2045
|
+
const envName = binEnvVarName(fn);
|
|
2046
|
+
const resolvedBinPath = resolveBinPath(programName, binPath);
|
|
2047
|
+
const sig = computeBinSig(resolvedBinPath);
|
|
2048
|
+
const quotedBinPath = shSingleQuote(resolvedBinPath);
|
|
2049
|
+
const refreshFn = `__${fn}_self_refresh`;
|
|
2050
|
+
return [
|
|
2051
|
+
`${refreshFn}() {`,
|
|
2052
|
+
` local _self _bin _sig`,
|
|
2053
|
+
` _self=\${BASH_SOURCE[0]:-}`,
|
|
2054
|
+
` [[ -n "$_self" && -f "$_self" ]] || return 1`,
|
|
2055
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
|
|
2056
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# program: ${programName}" || return 1`,
|
|
2057
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# shell: bash" || return 1`,
|
|
2058
|
+
` _bin="\${${envName}:-$(type -P ${programName} 2>/dev/null)}"`,
|
|
2059
|
+
` [[ -n "$_bin" ]] || return 1`,
|
|
2060
|
+
` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`,
|
|
2061
|
+
` [[ "$_sig" != "${sig}" || "$_bin" != ${quotedBinPath} ]] || return 1`,
|
|
2062
|
+
` "$_bin" __refresh-completion bash "$_self" --static 2>/dev/null || return 1`,
|
|
2063
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-sig: $_sig" || return 1`,
|
|
2064
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-path: $_bin" || return 1`,
|
|
2065
|
+
` source "$_self" 2>/dev/null || return 1`,
|
|
2066
|
+
` return 0`,
|
|
2067
|
+
`}`,
|
|
2068
|
+
`if ${refreshFn}; then`,
|
|
2069
|
+
` unset -f ${refreshFn}`,
|
|
2070
|
+
` return 0 2>/dev/null || true`,
|
|
2071
|
+
`else`,
|
|
2072
|
+
` unset -f ${refreshFn}`,
|
|
2073
|
+
`fi`,
|
|
2074
|
+
``
|
|
2075
|
+
];
|
|
2076
|
+
}
|
|
2077
|
+
function generateZshSelfRefresh(opts) {
|
|
2078
|
+
const { programName, binPath } = opts;
|
|
2079
|
+
const fn = sanitize(programName);
|
|
2080
|
+
const envName = binEnvVarName(fn);
|
|
2081
|
+
const completionFn = `_${programName}`;
|
|
2082
|
+
const resolvedBinPath = resolveBinPath(programName, binPath);
|
|
2083
|
+
const sig = computeBinSig(resolvedBinPath);
|
|
2084
|
+
const quotedBinPath = shSingleQuote(resolvedBinPath);
|
|
2085
|
+
const refreshFn = `__${fn}_self_refresh`;
|
|
2086
|
+
return [
|
|
2087
|
+
`${refreshFn}() {`,
|
|
2088
|
+
` emulate -L zsh`,
|
|
2089
|
+
` setopt local_options no_aliases`,
|
|
2090
|
+
` local _self _bin _sig`,
|
|
2091
|
+
` _self="\${(%):-%x}"`,
|
|
2092
|
+
` [[ -n "$_self" && -f "$_self" ]] || return 1`,
|
|
2093
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
|
|
2094
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# program: ${programName}" || return 1`,
|
|
2095
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# shell: zsh" || return 1`,
|
|
2096
|
+
` _bin="\${${envName}:-$(whence -p ${programName} 2>/dev/null)}"`,
|
|
2097
|
+
` [[ -n "$_bin" ]] || return 1`,
|
|
2098
|
+
` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`,
|
|
2099
|
+
` [[ "$_sig" != "${sig}" || "$_bin" != ${quotedBinPath} ]] || return 1`,
|
|
2100
|
+
` "$_bin" __refresh-completion zsh "$_self" --static 2>/dev/null || return 1`,
|
|
2101
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-sig: $_sig" || return 1`,
|
|
2102
|
+
` head -n 8 "$_self" 2>/dev/null | grep -qxF "# politty-bin-path: $_bin" || return 1`,
|
|
2103
|
+
` source "$_self" 2>/dev/null || return 1`,
|
|
2104
|
+
` ${completionFn} "$@"`,
|
|
2105
|
+
` return 0`,
|
|
2106
|
+
`}`,
|
|
2107
|
+
`if ${refreshFn} "$@"; then`,
|
|
2108
|
+
` unfunction ${refreshFn} 2>/dev/null`,
|
|
2109
|
+
` return 0 2>/dev/null || true`,
|
|
2110
|
+
`else`,
|
|
2111
|
+
` unfunction ${refreshFn} 2>/dev/null`,
|
|
2112
|
+
`fi`,
|
|
2113
|
+
``
|
|
2114
|
+
];
|
|
2115
|
+
}
|
|
2116
|
+
|
|
1927
2117
|
//#endregion
|
|
1928
2118
|
//#region src/completion/bash.ts
|
|
1929
2119
|
/** Escape a string for use inside bash double-quotes */
|
|
@@ -2025,6 +2215,7 @@ function bashValueLines(vc, inline, fn, location) {
|
|
|
2025
2215
|
case "directory": return [`COMPREPLY=($(compgen -P "$_inline_prefix" -d -- "$_cur"))`, `compopt -o filenames`];
|
|
2026
2216
|
case "command": return [`COMPREPLY=($(compgen -P "$_inline_prefix" -W "$(${vc.shellCommand})" -- "$_cur"))`];
|
|
2027
2217
|
case "none": return [`compopt +o default 2>/dev/null`];
|
|
2218
|
+
case "runtime-expand": return [];
|
|
2028
2219
|
}
|
|
2029
2220
|
}
|
|
2030
2221
|
/**
|
|
@@ -2087,6 +2278,28 @@ function positionalBlock$2(positionals, fn, funcSuffix, options = []) {
|
|
|
2087
2278
|
lines.push(` esac`);
|
|
2088
2279
|
return lines;
|
|
2089
2280
|
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Subcommand-name completion. When the same node also has positionals, emit a
|
|
2283
|
+
* runtime check that completes subcommand names while the cursor still prefixes
|
|
2284
|
+
* one and falls through to positional completion otherwise. Returns lines at
|
|
2285
|
+
* base indentation; callers re-indent for their handler depth.
|
|
2286
|
+
*/
|
|
2287
|
+
function subOrPositionalLines$2(subNames, positionals, fn, funcSuffix, options) {
|
|
2288
|
+
const subReply = [`COMPREPLY=($(compgen -W "${subNames}" -- "$_cur"))`, `compopt +o default 2>/dev/null`];
|
|
2289
|
+
if (positionals.length === 0) return subReply;
|
|
2290
|
+
return [
|
|
2291
|
+
`local -a _sub_names=(${subNames})`,
|
|
2292
|
+
`local _sub_name _sub_match=0`,
|
|
2293
|
+
`for _sub_name in "\${_sub_names[@]}"; do`,
|
|
2294
|
+
` [[ "$_sub_name" == "$_cur"* ]] && _sub_match=1 && break`,
|
|
2295
|
+
`done`,
|
|
2296
|
+
`if (( _sub_match )); then`,
|
|
2297
|
+
...subReply.map((l) => ` ${l}`),
|
|
2298
|
+
`else`,
|
|
2299
|
+
...positionalBlock$2(positionals, fn, funcSuffix, options),
|
|
2300
|
+
`fi`
|
|
2301
|
+
];
|
|
2302
|
+
}
|
|
2090
2303
|
/** Generate prev/inline value completion blocks for options */
|
|
2091
2304
|
function valueCompletionBlocks(options, positionals, fn, funcSuffix) {
|
|
2092
2305
|
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
@@ -2162,8 +2375,7 @@ function generateSubHandler$2(sub, fn, path) {
|
|
|
2162
2375
|
lines.push(` fi`);
|
|
2163
2376
|
if (visibleSubs.length > 0) {
|
|
2164
2377
|
const subNames = getSubNamesWithAliases(sub.subcommands).map((s) => s.name).join(" ");
|
|
2165
|
-
lines.push(
|
|
2166
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
2378
|
+
lines.push(...subOrPositionalLines$2(subNames, sub.positionals, fn, funcSuffix, sub.options).map((l) => ` ${l}`));
|
|
2167
2379
|
} else if (sub.positionals.length > 0) lines.push(...positionalBlock$2(sub.positionals, fn, funcSuffix, sub.options));
|
|
2168
2380
|
lines.push(`}`);
|
|
2169
2381
|
lines.push(``);
|
|
@@ -2172,7 +2384,9 @@ function generateSubHandler$2(sub, fn, path) {
|
|
|
2172
2384
|
function generateBashCompletion(command, options) {
|
|
2173
2385
|
const { programName } = options;
|
|
2174
2386
|
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
2175
|
-
const
|
|
2387
|
+
const baseFn = sanitize(programName);
|
|
2388
|
+
const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
|
|
2389
|
+
const isWorker = options.staticWorker !== void 0;
|
|
2176
2390
|
const root = data.command;
|
|
2177
2391
|
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
2178
2392
|
const expandSpecs = collectExpandSpecs(root);
|
|
@@ -2184,8 +2398,14 @@ function generateBashCompletion(command, options) {
|
|
|
2184
2398
|
binPath: options.binPath,
|
|
2185
2399
|
programVersion: options.programVersion
|
|
2186
2400
|
}));
|
|
2401
|
+
lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
|
|
2402
|
+
if (isWorker) lines.push(`# politty-completion-worker: true`);
|
|
2187
2403
|
lines.push(`# Generated by politty`);
|
|
2188
2404
|
lines.push(``);
|
|
2405
|
+
if (!isWorker) lines.push(...generateBashSelfRefresh({
|
|
2406
|
+
programName,
|
|
2407
|
+
binPath: options.binPath
|
|
2408
|
+
}));
|
|
2189
2409
|
const hasExpand = expandSpecs.length > 0;
|
|
2190
2410
|
const arrayExpandSpecs = expandSpecs.filter((s) => s.isArrayOption);
|
|
2191
2411
|
const hasArrayExpand = arrayExpandSpecs.length > 0;
|
|
@@ -2326,8 +2546,7 @@ function generateBashCompletion(command, options) {
|
|
|
2326
2546
|
if (visibleSubs.length > 0) {
|
|
2327
2547
|
lines.push(` else`);
|
|
2328
2548
|
const subNames = getSubNamesWithAliases(root.subcommands).map((s) => s.name).join(" ");
|
|
2329
|
-
lines.push(
|
|
2330
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
2549
|
+
lines.push(...subOrPositionalLines$2(subNames, root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
2331
2550
|
} else if (root.positionals.length > 0) {
|
|
2332
2551
|
lines.push(` else`);
|
|
2333
2552
|
lines.push(...positionalBlock$2(root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
@@ -2425,18 +2644,17 @@ function generateBashCompletion(command, options) {
|
|
|
2425
2644
|
lines.push(` esac`);
|
|
2426
2645
|
lines.push(`}`);
|
|
2427
2646
|
lines.push(``);
|
|
2428
|
-
lines.push(`complete -o default -F _${fn}_completions ${programName}`);
|
|
2647
|
+
if (!isWorker) lines.push(`complete -o default -F _${fn}_completions ${programName}`);
|
|
2429
2648
|
lines.push(``);
|
|
2430
2649
|
return {
|
|
2431
2650
|
script: lines.join("\n"),
|
|
2432
2651
|
shell: "bash",
|
|
2433
|
-
installInstructions: `# To enable completions, add
|
|
2434
|
-
|
|
2435
|
-
# Option 1: Source directly
|
|
2436
|
-
eval "$(${programName} completion bash)"
|
|
2652
|
+
installInstructions: `# To enable auto-refreshing bash completions, add this to your ~/.bashrc:
|
|
2653
|
+
eval "$(${programName} completion bash --static)"
|
|
2437
2654
|
|
|
2438
|
-
#
|
|
2439
|
-
|
|
2655
|
+
# For faster shell startup, save the script instead:
|
|
2656
|
+
mkdir -p ~/.local/share/bash-completion/completions
|
|
2657
|
+
${programName} completion bash --static > ~/.local/share/bash-completion/completions/${programName}
|
|
2440
2658
|
|
|
2441
2659
|
# Then reload your shell or run:
|
|
2442
2660
|
source ~/.bashrc`
|
|
@@ -2444,182 +2662,1317 @@ source ~/.bashrc`
|
|
|
2444
2662
|
}
|
|
2445
2663
|
|
|
2446
2664
|
//#endregion
|
|
2447
|
-
//#region src/completion/
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2665
|
+
//#region src/completion/bundled-worker.ts
|
|
2666
|
+
const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
|
|
2667
|
+
const SHELL_EXT = {
|
|
2668
|
+
bash: "bash",
|
|
2669
|
+
zsh: "zsh",
|
|
2670
|
+
fish: "fish"
|
|
2671
|
+
};
|
|
2672
|
+
const REQUIRED_WORKER_HEADERS = [
|
|
2673
|
+
"# politty-completion-version: 1",
|
|
2674
|
+
"# politty-completion-mode: worker",
|
|
2675
|
+
"# politty-completion-worker: true"
|
|
2676
|
+
];
|
|
2677
|
+
function bundledWorkerShellExtension(shell) {
|
|
2678
|
+
return SHELL_EXT[shell];
|
|
2679
|
+
}
|
|
2680
|
+
function defaultBundledWorkerOutputPath(shell) {
|
|
2681
|
+
return (0, node_path.join)("dist", "completion", `${shell}-worker.${bundledWorkerShellExtension(shell)}`);
|
|
2682
|
+
}
|
|
2683
|
+
function defaultBundledWorkerRelativePaths(shell) {
|
|
2684
|
+
const ext = SHELL_EXT[shell];
|
|
2685
|
+
return [
|
|
2686
|
+
`completion/${shell}-worker.${ext}`,
|
|
2687
|
+
`../completion/${shell}-worker.${ext}`,
|
|
2688
|
+
`dist/completion/${shell}-worker.${ext}`,
|
|
2689
|
+
`../dist/completion/${shell}-worker.${ext}`,
|
|
2690
|
+
`completion-worker.${shell}`,
|
|
2691
|
+
`../completion-worker.${shell}`
|
|
2692
|
+
];
|
|
2693
|
+
}
|
|
2694
|
+
function bundledWorkerRelativePaths(programName, shell, options) {
|
|
2695
|
+
if (options?.disabled) return [];
|
|
2696
|
+
const configured = options?.relativePaths?.[shell];
|
|
2697
|
+
return (configured && configured.length > 0 ? configured : defaultBundledWorkerRelativePaths(shell)).map((p) => p.replaceAll("{shell}", shell).replaceAll("{ext}", SHELL_EXT[shell]).replaceAll("{program}", programName));
|
|
2698
|
+
}
|
|
2699
|
+
function readCmdShimTarget(path) {
|
|
2700
|
+
try {
|
|
2701
|
+
const content = (0, node_fs.readFileSync)(path, "utf8");
|
|
2702
|
+
let target = null;
|
|
2703
|
+
for (const line of content.split("\n")) {
|
|
2704
|
+
const match = line.match(/^# cmd-shim-target=(.+)$/);
|
|
2705
|
+
if (match) target = match[1];
|
|
2706
|
+
}
|
|
2707
|
+
return target;
|
|
2708
|
+
} catch {
|
|
2709
|
+
return null;
|
|
2458
2710
|
}
|
|
2459
2711
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2712
|
+
function addBaseDirs(out, path) {
|
|
2713
|
+
if (!path) return;
|
|
2714
|
+
out.add((0, node_path.dirname)((0, node_path.resolve)(path)));
|
|
2715
|
+
try {
|
|
2716
|
+
out.add((0, node_path.dirname)((0, node_fs.realpathSync)(path)));
|
|
2717
|
+
} catch {}
|
|
2718
|
+
const shimTarget = readCmdShimTarget(path);
|
|
2719
|
+
if (shimTarget) addBaseDirs(out, shimTarget);
|
|
2467
2720
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
*
|
|
2471
|
-
* - Pre-filters candidates by currentWord prefix (replaces compgen -W)
|
|
2472
|
-
* - Handles --opt=value inline values by prepending prefix
|
|
2473
|
-
* - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
|
|
2474
|
-
* - Last line: :directive
|
|
2475
|
-
*/
|
|
2476
|
-
function formatForBash(result, options) {
|
|
2477
|
-
const lines = ((result.directive & CompletionDirective.FilterPrefix) !== 0 && options.currentWord ? result.candidates.filter((c) => c.value.startsWith(options.currentWord)) : result.candidates).map((c) => options.inlinePrefix ? `${options.inlinePrefix}${c.value}` : c.value);
|
|
2478
|
-
appendMetadata(lines, result);
|
|
2479
|
-
return lines.join("\n");
|
|
2721
|
+
function workerHead(path) {
|
|
2722
|
+
return (0, node_fs.readFileSync)(path, "utf8").split("\n", 24).join("\n");
|
|
2480
2723
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
*/
|
|
2488
|
-
function formatForZsh(result, _options) {
|
|
2489
|
-
const lines = result.candidates.map((c) => {
|
|
2490
|
-
const escapedValue = c.value.replace(/:/g, "\\:");
|
|
2491
|
-
if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
|
|
2492
|
-
return escapedValue;
|
|
2493
|
-
});
|
|
2494
|
-
appendMetadata(lines, result);
|
|
2495
|
-
return lines.join("\n");
|
|
2724
|
+
function requiredBundledWorkerHeaders(programName, shell) {
|
|
2725
|
+
return [
|
|
2726
|
+
...REQUIRED_WORKER_HEADERS,
|
|
2727
|
+
`# program: ${programName}`,
|
|
2728
|
+
`# shell: ${shell}`
|
|
2729
|
+
];
|
|
2496
2730
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2731
|
+
function missingBundledWorkerHeaders(head, programName, shell) {
|
|
2732
|
+
const lines = head.split("\n").map((line) => line.trimEnd());
|
|
2733
|
+
return requiredBundledWorkerHeaders(programName, shell).filter((header) => !lines.includes(header));
|
|
2734
|
+
}
|
|
2735
|
+
function validateBundledWorkerFile(path, programName, shell) {
|
|
2736
|
+
if (!(0, node_fs.existsSync)(path)) throw new Error(`Bundled completion worker does not exist: ${path}`);
|
|
2737
|
+
const missing = missingBundledWorkerHeaders(workerHead(path), programName, shell);
|
|
2738
|
+
if (missing.length > 0) throw new Error(`Invalid bundled completion worker ${path}: missing ${missing.map((h) => JSON.stringify(h)).join(", ")}`);
|
|
2739
|
+
}
|
|
2740
|
+
function isBundledWorkerFile(path, programName, shell) {
|
|
2741
|
+
try {
|
|
2742
|
+
validateBundledWorkerFile(path, programName, shell);
|
|
2743
|
+
return true;
|
|
2744
|
+
} catch {
|
|
2745
|
+
return false;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
function resolveBundledWorkerPath(opts) {
|
|
2749
|
+
const rels = bundledWorkerRelativePaths(opts.programName, opts.shell, opts.bundledWorker);
|
|
2750
|
+
if (rels.length === 0) return null;
|
|
2751
|
+
const bases = /* @__PURE__ */ new Set();
|
|
2752
|
+
if (opts.binPath !== void 0) addBaseDirs(bases, opts.binPath);
|
|
2753
|
+
if (process.argv[1]) addBaseDirs(bases, process.argv[1]);
|
|
2754
|
+
addBaseDirs(bases, resolveBinPath(opts.programName, opts.binPath));
|
|
2755
|
+
for (const rel of rels) if ((0, node_path.isAbsolute)(rel) && isBundledWorkerFile(rel, opts.programName, opts.shell)) return rel;
|
|
2756
|
+
for (const base of bases) for (const rel of rels) {
|
|
2757
|
+
if ((0, node_path.isAbsolute)(rel)) continue;
|
|
2758
|
+
const candidate = (0, node_path.join)(base, rel);
|
|
2759
|
+
if (isBundledWorkerFile(candidate, opts.programName, opts.shell)) return candidate;
|
|
2760
|
+
}
|
|
2761
|
+
return null;
|
|
2762
|
+
}
|
|
2763
|
+
function resolvePathFromCwd(path, cwd) {
|
|
2764
|
+
return (0, node_path.isAbsolute)(path) ? path : (0, node_path.resolve)(cwd, path);
|
|
2765
|
+
}
|
|
2766
|
+
function executableCommand(bin, args, cwd) {
|
|
2767
|
+
const binPath = bin.startsWith(".") || bin.includes("/") || bin.includes("\\") ? resolvePathFromCwd(bin, cwd) : bin;
|
|
2768
|
+
const ext = (0, node_path.extname)(binPath).toLowerCase();
|
|
2769
|
+
if (ext === ".js" || ext === ".mjs" || ext === ".cjs") return {
|
|
2770
|
+
command: process.execPath,
|
|
2771
|
+
args: [binPath, ...args]
|
|
2772
|
+
};
|
|
2773
|
+
return {
|
|
2774
|
+
command: binPath,
|
|
2775
|
+
args: [...args]
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
async function runTargetBin(bin, args, opts) {
|
|
2779
|
+
const command = executableCommand(bin, args, opts.cwd);
|
|
2780
|
+
try {
|
|
2781
|
+
const { stdout, stderr } = await execFileAsync(command.command, command.args, {
|
|
2782
|
+
cwd: opts.cwd,
|
|
2783
|
+
env: {
|
|
2784
|
+
...process.env,
|
|
2785
|
+
...opts.env
|
|
2786
|
+
},
|
|
2787
|
+
encoding: "utf8",
|
|
2788
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2789
|
+
});
|
|
2790
|
+
return {
|
|
2791
|
+
stdout,
|
|
2792
|
+
stderr
|
|
2793
|
+
};
|
|
2794
|
+
} catch (error) {
|
|
2795
|
+
const stderr = typeof error === "object" && error !== null && "stderr" in error ? String(error.stderr ?? "").trim() : "";
|
|
2796
|
+
const detail = stderr ? `\n${stderr}` : "";
|
|
2797
|
+
throw new Error(`Command failed: ${command.command} ${command.args.join(" ")}${detail}`, error instanceof Error ? { cause: error } : void 0);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
function assertNonEmptyFile(path) {
|
|
2801
|
+
const stat = (0, node_fs.statSync)(path);
|
|
2802
|
+
if (!stat.isFile() || stat.size === 0) throw new Error(`Generated bundled completion worker is empty: ${path}`);
|
|
2803
|
+
return stat.size;
|
|
2804
|
+
}
|
|
2805
|
+
function formatSize(size) {
|
|
2806
|
+
if (size < 1024) return `${size} B`;
|
|
2807
|
+
return `${(size / 1024).toFixed(1)} KiB`;
|
|
2808
|
+
}
|
|
2809
|
+
function printSuccess(path, size, cwd) {
|
|
2810
|
+
const rel = (0, node_path.relative)(cwd, path);
|
|
2811
|
+
console.log(`Generated bundled completion worker: ${rel && !rel.startsWith("..") ? rel : path} (${formatSize(size)})`);
|
|
2812
|
+
}
|
|
2813
|
+
async function generateBundledCompletionWorker(options) {
|
|
2814
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2815
|
+
const outputPath = resolvePathFromCwd(options.outputPath ?? defaultBundledWorkerOutputPath(options.shell), cwd);
|
|
2816
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(outputPath), { recursive: true });
|
|
2817
|
+
(0, node_fs.rmSync)(outputPath, { force: true });
|
|
2818
|
+
await runTargetBin(options.bin, [
|
|
2819
|
+
"__refresh-completion",
|
|
2820
|
+
options.shell,
|
|
2821
|
+
outputPath,
|
|
2822
|
+
"--static",
|
|
2823
|
+
"--worker"
|
|
2824
|
+
], {
|
|
2825
|
+
cwd,
|
|
2826
|
+
env: options.env
|
|
2507
2827
|
});
|
|
2508
|
-
|
|
2509
|
-
|
|
2828
|
+
const size = assertNonEmptyFile(outputPath);
|
|
2829
|
+
validateBundledWorkerFile(outputPath, options.programName, options.shell);
|
|
2830
|
+
let reportedPath;
|
|
2831
|
+
if (options.verify) {
|
|
2832
|
+
const lines = (await runTargetBin(options.bin, ["__completion-worker-path", options.shell], {
|
|
2833
|
+
cwd,
|
|
2834
|
+
env: options.env
|
|
2835
|
+
})).stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
2836
|
+
if (lines.length !== 1) throw new Error(`Expected __completion-worker-path ${options.shell} to print exactly one path, got ${lines.length}.`);
|
|
2837
|
+
reportedPath = resolvePathFromCwd(lines[0], cwd);
|
|
2838
|
+
const generatedReal = (0, node_fs.realpathSync)(outputPath);
|
|
2839
|
+
const reportedReal = (0, node_fs.realpathSync)(reportedPath);
|
|
2840
|
+
if (reportedReal !== generatedReal) throw new Error(`Bundled completion worker path mismatch: generated ${generatedReal}, reported ${reportedReal}`);
|
|
2841
|
+
}
|
|
2842
|
+
if (!options.quiet) printSuccess(outputPath, size, cwd);
|
|
2843
|
+
return {
|
|
2844
|
+
outputPath,
|
|
2845
|
+
size,
|
|
2846
|
+
...reportedPath !== void 0 && { reportedPath }
|
|
2847
|
+
};
|
|
2510
2848
|
}
|
|
2511
2849
|
|
|
2512
2850
|
//#endregion
|
|
2513
|
-
//#region src/completion/
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
*
|
|
2517
|
-
* This creates a hidden `__complete` command that outputs completion candidates
|
|
2518
|
-
* for shell scripts to consume. Usage:
|
|
2519
|
-
*
|
|
2520
|
-
* mycli __complete --shell bash -- build --fo
|
|
2521
|
-
* mycli __complete --shell zsh -- plugin add
|
|
2522
|
-
*
|
|
2523
|
-
* Output format depends on the target shell:
|
|
2524
|
-
* bash: plain values (pre-filtered by prefix), last line :directive
|
|
2525
|
-
* zsh: value:description pairs, last line :directive
|
|
2526
|
-
* fish: value\tdescription pairs, last line :directive
|
|
2527
|
-
*/
|
|
2528
|
-
/**
|
|
2529
|
-
* Schema for the __complete command
|
|
2530
|
-
*/
|
|
2531
|
-
const completeArgsSchema = zod.z.object({
|
|
2532
|
-
shell: require_schema_extractor.arg(zod.z.enum([
|
|
2533
|
-
"bash",
|
|
2534
|
-
"zsh",
|
|
2535
|
-
"fish"
|
|
2536
|
-
]), { description: "Target shell for output formatting" }),
|
|
2537
|
-
args: require_schema_extractor.arg(zod.z.array(zod.z.string()).default([]), {
|
|
2538
|
-
positional: true,
|
|
2539
|
-
description: "Arguments to complete",
|
|
2540
|
-
variadic: true
|
|
2541
|
-
})
|
|
2542
|
-
});
|
|
2543
|
-
/**
|
|
2544
|
-
* Create the dynamic completion command
|
|
2545
|
-
*
|
|
2546
|
-
* @param rootCommand - The root command to generate completions for
|
|
2547
|
-
* @param programName - The program name (optional, defaults to rootCommand.name)
|
|
2548
|
-
* @param globalArgsSchema - Global args schema. Forwarded to
|
|
2549
|
-
* `parseCompletionContext` so resolvers attached to global options remain
|
|
2550
|
-
* reachable at every subcommand level.
|
|
2551
|
-
* @returns A command that outputs completion candidates
|
|
2552
|
-
*/
|
|
2553
|
-
function createDynamicCompleteCommand(rootCommand, _programName, globalArgsSchema) {
|
|
2554
|
-
return defineCommand({
|
|
2555
|
-
name: "__complete",
|
|
2556
|
-
args: completeArgsSchema,
|
|
2557
|
-
async run(args) {
|
|
2558
|
-
const context = parseCompletionContext(args.args, rootCommand, globalArgsSchema);
|
|
2559
|
-
const inlinePrefix = context.completionType === "option-value" && context.targetOption ? detectInlineOptionPrefix(context.currentWord) : void 0;
|
|
2560
|
-
const effectiveWord = inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord;
|
|
2561
|
-
const output = formatForShell(await generateCandidates(context, { shell: args.shell }), {
|
|
2562
|
-
shell: args.shell,
|
|
2563
|
-
currentWord: effectiveWord,
|
|
2564
|
-
inlinePrefix
|
|
2565
|
-
});
|
|
2566
|
-
console.log(output);
|
|
2567
|
-
}
|
|
2568
|
-
});
|
|
2851
|
+
//#region src/completion/dispatcher.ts
|
|
2852
|
+
function compileCacheSuffix(programName) {
|
|
2853
|
+
return shSingleQuote(`/${programName}/node-compile-cache`);
|
|
2569
2854
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
*/
|
|
2573
|
-
function hasCompleteCommand(command) {
|
|
2574
|
-
return Boolean(command.subCommands?.["__complete"]);
|
|
2855
|
+
function hardcodedCompileCacheDir(cacheDir) {
|
|
2856
|
+
return cacheDir ? shSingleQuote(`${cacheDir}/node-compile-cache`) : void 0;
|
|
2575
2857
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
|
|
2858
|
+
function hardcodedWorkerPath(cacheDir, shell) {
|
|
2859
|
+
return cacheDir ? shSingleQuote(`${cacheDir}/completion-worker.${shell}`) : void 0;
|
|
2860
|
+
}
|
|
2861
|
+
function workerPathSuffix(programName, shell) {
|
|
2862
|
+
return shSingleQuote(`/${programName}/completion-worker.${shell}`);
|
|
2582
2863
|
}
|
|
2583
2864
|
/**
|
|
2584
|
-
*
|
|
2585
|
-
*
|
|
2586
|
-
*
|
|
2587
|
-
* literal — otherwise a key like `prod*` would also match a runtime
|
|
2588
|
-
* value of `production`. Quote/dollar/backslash are escaped first so the
|
|
2589
|
-
* resulting string remains valid inside a double-quoted literal.
|
|
2865
|
+
* `<printCmd> <dir>` line resolving a cache path: the hardcoded `cacheDir` when
|
|
2866
|
+
* configured, else `${XDG_CACHE_HOME:-$HOME/.cache}` joined with `suffix`. Used
|
|
2867
|
+
* by bash and zsh, which both expand the XDG default inline.
|
|
2590
2868
|
*/
|
|
2591
|
-
function
|
|
2592
|
-
return
|
|
2869
|
+
function posixCacheDefault(printCmd, hardcoded, suffix) {
|
|
2870
|
+
return hardcoded ? ` ${printCmd} ${hardcoded}` : ` ${printCmd} "\${XDG_CACHE_HOME:-$HOME/.cache}"${suffix}`;
|
|
2593
2871
|
}
|
|
2594
2872
|
/**
|
|
2595
|
-
*
|
|
2596
|
-
*
|
|
2597
|
-
*
|
|
2598
|
-
* `location` is required for the expand variant (carries fieldName +
|
|
2599
|
-
* isArrayOption); other variants ignore it.
|
|
2873
|
+
* fish equivalent of {@link posixCacheDefault}. fish has no `${VAR:-default}`
|
|
2874
|
+
* form, so the XDG fallback is materialized over three lines.
|
|
2600
2875
|
*/
|
|
2601
|
-
function
|
|
2602
|
-
if (
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2876
|
+
function fishCacheDefault(hardcoded, suffix) {
|
|
2877
|
+
if (hardcoded) return ` printf '%s\\n' ${hardcoded}`;
|
|
2878
|
+
return [
|
|
2879
|
+
` set -l _cache_root "$XDG_CACHE_HOME"`,
|
|
2880
|
+
` test -n "$_cache_root"; or set _cache_root "$HOME/.cache"`,
|
|
2881
|
+
` printf '%s\\n' "$_cache_root"${suffix}`
|
|
2882
|
+
].join("\n");
|
|
2883
|
+
}
|
|
2884
|
+
function shellWorkerRelList(options, shell) {
|
|
2885
|
+
return bundledWorkerRelativePaths(options.programName, shell, options.bundledWorker).map(shSingleQuote).join(" ");
|
|
2886
|
+
}
|
|
2887
|
+
function fishDQ(s) {
|
|
2888
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$")}"`;
|
|
2889
|
+
}
|
|
2890
|
+
function fishWorkerRelList(options) {
|
|
2891
|
+
return bundledWorkerRelativePaths(options.programName, "fish", options.bundledWorker).map(fishDQ).join(" ");
|
|
2892
|
+
}
|
|
2893
|
+
function bundledWorkerPathCommandEnabled(options) {
|
|
2894
|
+
return options.bundledWorker?.queryCommand === true && !options.bundledWorker.disabled;
|
|
2895
|
+
}
|
|
2896
|
+
function bashDispatcher(_command, options) {
|
|
2897
|
+
const { programName } = options;
|
|
2898
|
+
const fn = sanitize(programName);
|
|
2899
|
+
const workerFn = `${fn}_worker`;
|
|
2900
|
+
const workerBinEnvName = binEnvVarName(workerFn);
|
|
2901
|
+
const envName = binEnvVarName(fn);
|
|
2902
|
+
const programLookup = shSingleQuote(programName);
|
|
2903
|
+
const compileCacheDefault = posixCacheDefault("printf '%s\\n'", hardcodedCompileCacheDir(options.cacheDir), compileCacheSuffix(programName));
|
|
2904
|
+
const workerPathDefault = posixCacheDefault("printf '%s\\n'", hardcodedWorkerPath(options.cacheDir, "bash"), workerPathSuffix(programName, "bash"));
|
|
2905
|
+
const workerRelList = shellWorkerRelList(options, "bash");
|
|
2906
|
+
const canQueryBundledWorkerPath = bundledWorkerPathCommandEnabled(options);
|
|
2907
|
+
const lines = [];
|
|
2908
|
+
lines.push(...buildHeaderLines({
|
|
2909
|
+
programName,
|
|
2910
|
+
shell: "bash",
|
|
2911
|
+
binPath: options.binPath,
|
|
2912
|
+
programVersion: options.programVersion
|
|
2913
|
+
}));
|
|
2914
|
+
lines.push(`# Generated by politty`);
|
|
2915
|
+
lines.push(`# politty-completion-mode: dispatcher`);
|
|
2916
|
+
lines.push(``);
|
|
2917
|
+
lines.push(`__${fn}_resolve_bin() {`);
|
|
2918
|
+
lines.push(` if [[ -n "\${${envName}:-}" ]]; then`);
|
|
2919
|
+
lines.push(` printf '%s\\n' "\${${envName}}"`);
|
|
2920
|
+
lines.push(` return 0`);
|
|
2921
|
+
lines.push(` fi`);
|
|
2922
|
+
lines.push(` type -P ${programLookup} 2>/dev/null`);
|
|
2923
|
+
lines.push(`}`);
|
|
2924
|
+
lines.push(``);
|
|
2925
|
+
lines.push(`__${fn}_node_compile_cache_dir() {`);
|
|
2926
|
+
lines.push(` if [[ -n "\${NODE_COMPILE_CACHE:-}" ]]; then`);
|
|
2927
|
+
lines.push(` printf '%s\\n' "$NODE_COMPILE_CACHE"`);
|
|
2928
|
+
lines.push(` return 0`);
|
|
2929
|
+
lines.push(` fi`);
|
|
2930
|
+
lines.push(compileCacheDefault);
|
|
2931
|
+
lines.push(`}`);
|
|
2932
|
+
lines.push(``);
|
|
2933
|
+
lines.push(`__${fn}_static_worker_path() {`);
|
|
2934
|
+
lines.push(workerPathDefault);
|
|
2935
|
+
lines.push(`}`);
|
|
2936
|
+
lines.push(``);
|
|
2937
|
+
lines.push(`__${fn}_worker_file_sig() {`);
|
|
2938
|
+
lines.push(` local _worker="$1" _sig`);
|
|
2939
|
+
lines.push(` _sig=${statSigExpr("$_worker", {
|
|
2940
|
+
shell: "posix",
|
|
2941
|
+
withSize: true
|
|
2942
|
+
})} || return 1`);
|
|
2943
|
+
lines.push(` printf '%s\\n' "$_sig"`);
|
|
2944
|
+
lines.push(`}`);
|
|
2945
|
+
lines.push(``);
|
|
2946
|
+
lines.push(`__${fn}_is_worker_file() {`);
|
|
2947
|
+
lines.push(` local _worker="$1" _head`);
|
|
2948
|
+
lines.push(` [[ -f "$_worker" ]] || return 1`);
|
|
2949
|
+
lines.push(` _head="$(head -n 24 "$_worker" 2>/dev/null)" || return 1`);
|
|
2950
|
+
lines.push(` _head=$'\\n'"$_head"$'\\n'`);
|
|
2951
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-version: 1"$'\\n'* ]] || return 1`);
|
|
2952
|
+
lines.push(` [[ "$_head" == *$'\\n'"# program: ${programName}"$'\\n'* ]] || return 1`);
|
|
2953
|
+
lines.push(` [[ "$_head" == *$'\\n'"# shell: bash"$'\\n'* ]] || return 1`);
|
|
2954
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-mode: worker"$'\\n'* ]] || return 1`);
|
|
2955
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-worker: true"$'\\n'* ]] || return 1`);
|
|
2956
|
+
lines.push(`}`);
|
|
2957
|
+
lines.push(``);
|
|
2958
|
+
lines.push(`__${fn}_load_worker() {`);
|
|
2959
|
+
lines.push(` local _worker="$1" _sig _key`);
|
|
2960
|
+
lines.push(` __${fn}_is_worker_file "$_worker" || return 1`);
|
|
2961
|
+
lines.push(` _sig="$(__${fn}_worker_file_sig "$_worker")" || return 1`);
|
|
2962
|
+
lines.push(` _key="$_worker:$_sig"`);
|
|
2963
|
+
lines.push(` if [[ "\${__${fn}_loaded_worker_key:-}" != "$_key" ]]; then`);
|
|
2964
|
+
lines.push(` source "$_worker" 2>/dev/null || return 1`);
|
|
2965
|
+
lines.push(` __${fn}_loaded_worker_key="$_key"`);
|
|
2966
|
+
lines.push(` fi`);
|
|
2967
|
+
lines.push(` declare -F _${workerFn}_completions >/dev/null 2>&1`);
|
|
2968
|
+
lines.push(`}`);
|
|
2969
|
+
lines.push(``);
|
|
2970
|
+
lines.push(`__${fn}_realpath() {`);
|
|
2971
|
+
lines.push(` local _path="$1" _dir _target _limit=0`);
|
|
2972
|
+
lines.push(` while [[ -L "$_path" && $_limit -lt 40 ]]; do`);
|
|
2973
|
+
lines.push(` _dir="$(cd -P "$(dirname "$_path")" 2>/dev/null && pwd)" || return 1`);
|
|
2974
|
+
lines.push(` _target="$(readlink "$_path")" || return 1`);
|
|
2975
|
+
lines.push(` if [[ "$_target" == /* ]]; then _path="$_target"; else _path="$_dir/$_target"; fi`);
|
|
2976
|
+
lines.push(` (( _limit++ ))`);
|
|
2977
|
+
lines.push(` done`);
|
|
2978
|
+
lines.push(` _dir="$(cd -P "$(dirname "$_path")" 2>/dev/null && pwd)" || return 1`);
|
|
2979
|
+
lines.push(` printf '%s\\n' "$_dir/$(basename "$_path")"`);
|
|
2980
|
+
lines.push(`}`);
|
|
2981
|
+
lines.push(``);
|
|
2982
|
+
lines.push(`__${fn}_worker_from_dir() {`);
|
|
2983
|
+
lines.push(` local _dir="$1" _rel _candidate`);
|
|
2984
|
+
if (workerRelList.length > 0) {
|
|
2985
|
+
lines.push(` for _rel in ${workerRelList}; do`);
|
|
2986
|
+
lines.push(` [[ "$_rel" == /* ]] && _candidate="$_rel" || _candidate="$_dir/$_rel"`);
|
|
2987
|
+
lines.push(` if __${fn}_is_worker_file "$_candidate"; then`);
|
|
2988
|
+
lines.push(` printf '%s\\n' "$_candidate"`);
|
|
2989
|
+
lines.push(` return 0`);
|
|
2990
|
+
lines.push(` fi`);
|
|
2991
|
+
lines.push(` done`);
|
|
2992
|
+
}
|
|
2993
|
+
lines.push(` return 1`);
|
|
2994
|
+
lines.push(`}`);
|
|
2995
|
+
lines.push(``);
|
|
2996
|
+
lines.push(`__${fn}_cmd_shim_target() {`);
|
|
2997
|
+
lines.push(` sed -n 's/^# cmd-shim-target=//p' "$1" 2>/dev/null | tail -n 1`);
|
|
2998
|
+
lines.push(`}`);
|
|
2999
|
+
lines.push(``);
|
|
3000
|
+
lines.push(`__${fn}_bundled_worker_path() {`);
|
|
3001
|
+
lines.push(` local _bin="$1" _node_compile_cache="\${2:-}" _real _dir _target _reported`);
|
|
3002
|
+
lines.push(` _real="$(__${fn}_realpath "$_bin" 2>/dev/null)" || _real=""`);
|
|
3003
|
+
lines.push(` if [[ -n "$_real" ]]; then`);
|
|
3004
|
+
lines.push(` _dir="$(dirname "$_real")"`);
|
|
3005
|
+
lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
|
|
3006
|
+
lines.push(` fi`);
|
|
3007
|
+
lines.push(` _dir="$(dirname "$_bin")"`);
|
|
3008
|
+
lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
|
|
3009
|
+
lines.push(` _target="$(__${fn}_cmd_shim_target "$_bin")"`);
|
|
3010
|
+
lines.push(` if [[ -n "$_target" ]]; then`);
|
|
3011
|
+
lines.push(` _real="$(__${fn}_realpath "$_target" 2>/dev/null)" || _real="$_target"`);
|
|
3012
|
+
lines.push(` _dir="$(dirname "$_real")"`);
|
|
3013
|
+
lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
|
|
3014
|
+
lines.push(` fi`);
|
|
3015
|
+
if (canQueryBundledWorkerPath) {
|
|
3016
|
+
lines.push(` if [[ "\${__${fn}_queried_bin:-}" == "$_bin" && -n "\${__${fn}_queried_worker:-}" ]] && __${fn}_is_worker_file "\${__${fn}_queried_worker}"; then`);
|
|
3017
|
+
lines.push(` printf '%s\\n' "\${__${fn}_queried_worker}"`);
|
|
3018
|
+
lines.push(` return 0`);
|
|
3019
|
+
lines.push(` fi`);
|
|
3020
|
+
lines.push(` _reported="$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __completion-worker-path bash 2>/dev/null)" || _reported=""`);
|
|
3021
|
+
lines.push(` if [[ -n "$_reported" ]] && __${fn}_is_worker_file "$_reported"; then`);
|
|
3022
|
+
lines.push(` __${fn}_queried_bin="$_bin"`);
|
|
3023
|
+
lines.push(` __${fn}_queried_worker="$_reported"`);
|
|
3024
|
+
lines.push(` printf '%s\\n' "$_reported"`);
|
|
3025
|
+
lines.push(` return 0`);
|
|
3026
|
+
lines.push(` fi`);
|
|
3027
|
+
}
|
|
3028
|
+
lines.push(` return 1`);
|
|
3029
|
+
lines.push(`}`);
|
|
3030
|
+
lines.push(``);
|
|
3031
|
+
lines.push(`__${fn}_bin_sig() {`);
|
|
3032
|
+
lines.push(` local _bin="$1" _sig`);
|
|
3033
|
+
lines.push(` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`);
|
|
3034
|
+
lines.push(` printf '%s\\n' "$_sig"`);
|
|
3035
|
+
lines.push(`}`);
|
|
3036
|
+
lines.push(``);
|
|
3037
|
+
lines.push(`__${fn}_worker_matches_bin() {`);
|
|
3038
|
+
lines.push(` local _worker="$1" _sig="$2" _bin="$3" _head`);
|
|
3039
|
+
lines.push(` [[ -f "$_worker" ]] || return 1`);
|
|
3040
|
+
lines.push(` _head="$(head -n 12 "$_worker" 2>/dev/null)" || return 1`);
|
|
3041
|
+
lines.push(` grep -qxF "# politty-bin-sig: $_sig" <<< "$_head" || return 1`);
|
|
3042
|
+
lines.push(` grep -qxF "# politty-bin-path: $_bin" <<< "$_head" || return 1`);
|
|
3043
|
+
lines.push(`}`);
|
|
3044
|
+
lines.push(``);
|
|
3045
|
+
lines.push(`__${fn}_apply_dynamic_output() {`);
|
|
3046
|
+
lines.push(` local _raw="$1" _cur="$2" _inline_prefix="$3"`);
|
|
3047
|
+
lines.push(` COMPREPLY=()`);
|
|
3048
|
+
lines.push(` local _directive=0`);
|
|
3049
|
+
lines.push(` local -a _lines=() _exts=() _matchers=()`);
|
|
3050
|
+
lines.push(` local _line`);
|
|
3051
|
+
lines.push(` while IFS= read -r _line; do _lines+=("$_line"); done <<< "$_raw"`);
|
|
3052
|
+
lines.push(` local _last=$((\${#_lines[@]} - 1))`);
|
|
3053
|
+
lines.push(` if (( _last >= 0 )) && [[ "\${_lines[$_last]}" == :[0-9]* ]]; then`);
|
|
3054
|
+
lines.push(` local -a _meta=()`);
|
|
3055
|
+
lines.push(` IFS=$'\\t' read -r -a _meta <<< "\${_lines[$_last]}"`);
|
|
3056
|
+
lines.push(` unset '_lines[_last]'`);
|
|
3057
|
+
lines.push(` _directive="\${_meta[0]#:}"`);
|
|
3058
|
+
lines.push(` local _m`);
|
|
3059
|
+
lines.push(` for _m in "\${_meta[@]:1}"; do`);
|
|
3060
|
+
lines.push(` if [[ "$_m" == @ext:* ]]; then`);
|
|
3061
|
+
lines.push(` IFS=',' read -r -a _exts <<< "\${_m#@ext:}"`);
|
|
3062
|
+
lines.push(` elif [[ "$_m" == @matcher:* ]]; then`);
|
|
3063
|
+
lines.push(` IFS=',' read -r -a _matchers <<< "\${_m#@matcher:}"`);
|
|
3064
|
+
lines.push(` fi`);
|
|
3065
|
+
lines.push(` done`);
|
|
3066
|
+
lines.push(` fi`);
|
|
3067
|
+
lines.push(` for _line in "\${_lines[@]}"; do`);
|
|
3068
|
+
lines.push(` [[ -z "$_line" ]] && continue`);
|
|
3069
|
+
lines.push(` COMPREPLY+=("$_line")`);
|
|
3070
|
+
lines.push(` done`);
|
|
3071
|
+
lines.push(` local _ip="\${_inline_prefix:-}"`);
|
|
3072
|
+
lines.push(` if (( \${#_exts[@]} > 0 || \${#_matchers[@]} > 0 )); then`);
|
|
3073
|
+
lines.push(` local _need_empty=0`);
|
|
3074
|
+
lines.push(` compopt +o default 2>/dev/null || _need_empty=1`);
|
|
3075
|
+
lines.push(` compopt -o filenames 2>/dev/null`);
|
|
3076
|
+
lines.push(` local _f _ext _pat _base _ok`);
|
|
3077
|
+
lines.push(` while IFS= read -r _f; do`);
|
|
3078
|
+
lines.push(` if [[ -d "$_f" ]]; then`);
|
|
3079
|
+
lines.push(` COMPREPLY+=("\${_ip}\${_f}")`);
|
|
3080
|
+
lines.push(` continue`);
|
|
3081
|
+
lines.push(` fi`);
|
|
3082
|
+
lines.push(` _ok=0`);
|
|
3083
|
+
lines.push(` if (( \${#_exts[@]} > 0 )); then`);
|
|
3084
|
+
lines.push(` for _ext in "\${_exts[@]}"; do`);
|
|
3085
|
+
lines.push(` [[ -n "$_ext" && "$_f" == *."$_ext" ]] && _ok=1`);
|
|
3086
|
+
lines.push(` done`);
|
|
3087
|
+
lines.push(` fi`);
|
|
3088
|
+
lines.push(` if (( \${#_matchers[@]} > 0 )); then`);
|
|
3089
|
+
lines.push(` _base="\${_f##*/}"`);
|
|
3090
|
+
lines.push(` for _pat in "\${_matchers[@]}"; do`);
|
|
3091
|
+
lines.push(` [[ -n "$_pat" && "$_base" == $_pat ]] && _ok=1`);
|
|
3092
|
+
lines.push(` done`);
|
|
3093
|
+
lines.push(` fi`);
|
|
3094
|
+
lines.push(` (( _ok )) && COMPREPLY+=("\${_ip}\${_f}")`);
|
|
3095
|
+
lines.push(` done < <(compgen -f -- "$_cur")`);
|
|
3096
|
+
lines.push(` if (( _need_empty && \${#COMPREPLY[@]} == 0 )); then COMPREPLY=( "" ); fi`);
|
|
3097
|
+
lines.push(` elif (( _directive & ${CompletionDirective.DirectoryCompletion} )); then`);
|
|
3098
|
+
lines.push(` local _need_empty=0`);
|
|
3099
|
+
lines.push(` compopt +o default 2>/dev/null || _need_empty=1`);
|
|
3100
|
+
lines.push(` compopt -o filenames 2>/dev/null`);
|
|
3101
|
+
lines.push(` local _d`);
|
|
3102
|
+
lines.push(` while IFS= read -r _d; do COMPREPLY+=("\${_ip}\${_d}"); done < <(compgen -d -- "$_cur")`);
|
|
3103
|
+
lines.push(` if (( _need_empty && \${#COMPREPLY[@]} == 0 )); then COMPREPLY=( "" ); fi`);
|
|
3104
|
+
lines.push(` elif (( _directive & ${CompletionDirective.FileCompletion} )); then`);
|
|
3105
|
+
lines.push(` compopt -o filenames 2>/dev/null`);
|
|
3106
|
+
lines.push(` local _p`);
|
|
3107
|
+
lines.push(` while IFS= read -r _p; do COMPREPLY+=("\${_ip}\${_p}"); done < <(compgen -f -- "$_cur")`);
|
|
3108
|
+
lines.push(` elif (( _directive & ${CompletionDirective.NoFileCompletion} )); then`);
|
|
3109
|
+
lines.push(` local _need_empty=0`);
|
|
3110
|
+
lines.push(` compopt +o default 2>/dev/null || _need_empty=1`);
|
|
3111
|
+
lines.push(` if (( _need_empty && \${#COMPREPLY[@]} == 0 )); then COMPREPLY=( "" ); fi`);
|
|
3112
|
+
lines.push(` fi`);
|
|
3113
|
+
lines.push(` if (( _directive & ${CompletionDirective.NoSpace} )); then`);
|
|
3114
|
+
lines.push(` compopt -o nospace 2>/dev/null`);
|
|
3115
|
+
lines.push(` fi`);
|
|
3116
|
+
lines.push(`}`);
|
|
3117
|
+
lines.push(``);
|
|
3118
|
+
lines.push(`_${fn}_completions() {`);
|
|
3119
|
+
lines.push(` COMPREPLY=()`);
|
|
3120
|
+
lines.push(` local -a _words=()`);
|
|
3121
|
+
lines.push(` local _i=1`);
|
|
3122
|
+
lines.push(` while (( _i <= COMP_CWORD )); do`);
|
|
3123
|
+
lines.push(` if [[ "\${COMP_WORDS[_i]}" == "=" && \${#_words[@]} -gt 0 ]]; then`);
|
|
3124
|
+
lines.push(` _words[\${#_words[@]}-1]+="=\${COMP_WORDS[_i+1]:-}"`);
|
|
3125
|
+
lines.push(` (( _i += 2 ))`);
|
|
3126
|
+
lines.push(` else`);
|
|
3127
|
+
lines.push(` _words+=("\${COMP_WORDS[_i]}")`);
|
|
3128
|
+
lines.push(` (( _i++ ))`);
|
|
3129
|
+
lines.push(` fi`);
|
|
3130
|
+
lines.push(` done`);
|
|
3131
|
+
lines.push(` local _cur="" _inline_prefix=""`);
|
|
3132
|
+
lines.push(` (( \${#_words[@]} > 0 )) && _cur="\${_words[\${#_words[@]}-1]}"`);
|
|
3133
|
+
lines.push(` local _after_dd=0 _di=0`);
|
|
3134
|
+
lines.push(` while (( _di < \${#_words[@]} - 1 )); do`);
|
|
3135
|
+
lines.push(` [[ "\${_words[_di]}" == "--" ]] && { _after_dd=1; break; }`);
|
|
3136
|
+
lines.push(` (( _di++ ))`);
|
|
3137
|
+
lines.push(` done`);
|
|
3138
|
+
lines.push(` if (( ! _after_dd )) && [[ "$_cur" == -*=* ]]; then`);
|
|
3139
|
+
lines.push(` _inline_prefix="\${_cur%%=*}="`);
|
|
3140
|
+
lines.push(` _cur="\${_cur#*=}"`);
|
|
3141
|
+
lines.push(` fi`);
|
|
3142
|
+
lines.push(` local _bin _out _node_compile_cache _worker _bundled_worker _sig`);
|
|
3143
|
+
lines.push(` _bin="$(__${fn}_resolve_bin)" || _bin=""`);
|
|
3144
|
+
lines.push(` [[ -n "$_bin" ]] || return 0`);
|
|
3145
|
+
lines.push(` _node_compile_cache="$(__${fn}_node_compile_cache_dir)" || _node_compile_cache=""`);
|
|
3146
|
+
lines.push(` _bundled_worker="$(__${fn}_bundled_worker_path "$_bin" "$_node_compile_cache")" || _bundled_worker=""`);
|
|
3147
|
+
lines.push(` if [[ -n "$_bundled_worker" ]] && __${fn}_load_worker "$_bundled_worker"; then`);
|
|
3148
|
+
lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions`);
|
|
3149
|
+
lines.push(` return 0`);
|
|
3150
|
+
lines.push(` fi`);
|
|
3151
|
+
lines.push(` _worker="$(__${fn}_static_worker_path)" || _worker=""`);
|
|
3152
|
+
lines.push(` _sig="$(__${fn}_bin_sig "$_bin")" || _sig=""`);
|
|
3153
|
+
lines.push(` if [[ -n "$_worker" && -n "$_sig" ]]; then`);
|
|
3154
|
+
lines.push(` if ! __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"; then`);
|
|
3155
|
+
lines.push(` mkdir -p "\${_worker%/*}" 2>/dev/null`);
|
|
3156
|
+
lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${envName}="$_bin" "$_bin" __refresh-completion bash "$_worker" --static --worker 2>/dev/null`);
|
|
3157
|
+
lines.push(` fi`);
|
|
3158
|
+
lines.push(` if __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin" && __${fn}_load_worker "$_worker"; then`);
|
|
3159
|
+
lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions`);
|
|
3160
|
+
lines.push(` return 0`);
|
|
3161
|
+
lines.push(` fi`);
|
|
3162
|
+
lines.push(` fi`);
|
|
3163
|
+
lines.push(` _out=$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __complete --shell bash -- "\${_words[@]}" 2>/dev/null) || return 0`);
|
|
3164
|
+
lines.push(` __${fn}_apply_dynamic_output "$_out" "$_cur" "$_inline_prefix"`);
|
|
3165
|
+
lines.push(`}`);
|
|
3166
|
+
lines.push(``);
|
|
3167
|
+
lines.push(`complete -o default -F _${fn}_completions ${programName}`);
|
|
3168
|
+
lines.push(``);
|
|
3169
|
+
return {
|
|
3170
|
+
script: lines.join("\n"),
|
|
3171
|
+
shell: "bash",
|
|
3172
|
+
installInstructions: `# To enable dispatcher bash completions, add this to your ~/.bashrc:
|
|
3173
|
+
eval "$(${programName} completion bash)"
|
|
3174
|
+
|
|
3175
|
+
# To save the dispatcher script instead:
|
|
3176
|
+
mkdir -p ~/.local/share/bash-completion/completions
|
|
3177
|
+
${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
|
|
3178
|
+
|
|
3179
|
+
# To generate the older static script:
|
|
3180
|
+
${programName} completion bash --static`
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
function zshDispatcher(_command, options) {
|
|
3184
|
+
const { programName } = options;
|
|
3185
|
+
const fn = sanitize(programName);
|
|
3186
|
+
const workerFn = `${fn}_worker`;
|
|
3187
|
+
const workerBinEnvName = binEnvVarName(workerFn);
|
|
3188
|
+
const envName = binEnvVarName(fn);
|
|
3189
|
+
const programLookup = shSingleQuote(programName);
|
|
3190
|
+
const completionFn = `_${programName}`;
|
|
3191
|
+
const autoloadCheck = `"\${funcstack[1]:-}" == "${completionFn}"`;
|
|
3192
|
+
const compileCacheDefault = posixCacheDefault("print -r --", hardcodedCompileCacheDir(options.cacheDir), compileCacheSuffix(programName));
|
|
3193
|
+
const workerPathDefault = posixCacheDefault("print -r --", hardcodedWorkerPath(options.cacheDir, "zsh"), workerPathSuffix(programName, "zsh"));
|
|
3194
|
+
const workerRelList = shellWorkerRelList(options, "zsh");
|
|
3195
|
+
const canQueryBundledWorkerPath = bundledWorkerPathCommandEnabled(options);
|
|
3196
|
+
const lines = [];
|
|
3197
|
+
lines.push(`#compdef ${programName}`);
|
|
3198
|
+
lines.push(...buildHeaderLines({
|
|
3199
|
+
programName,
|
|
3200
|
+
shell: "zsh",
|
|
3201
|
+
binPath: options.binPath,
|
|
3202
|
+
programVersion: options.programVersion
|
|
3203
|
+
}));
|
|
3204
|
+
lines.push(`# Generated by politty`);
|
|
3205
|
+
lines.push(`# politty-completion-mode: dispatcher`);
|
|
3206
|
+
lines.push(``);
|
|
3207
|
+
lines.push(`__${fn}_resolve_bin() {`);
|
|
3208
|
+
lines.push(` emulate -L zsh`);
|
|
3209
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3210
|
+
lines.push(` if [[ -n "\${${envName}:-}" ]]; then`);
|
|
3211
|
+
lines.push(` print -r -- "\${${envName}}"`);
|
|
3212
|
+
lines.push(` return 0`);
|
|
3213
|
+
lines.push(` fi`);
|
|
3214
|
+
lines.push(` whence -p ${programLookup} 2>/dev/null`);
|
|
3215
|
+
lines.push(`}`);
|
|
3216
|
+
lines.push(``);
|
|
3217
|
+
lines.push(`__${fn}_node_compile_cache_dir() {`);
|
|
3218
|
+
lines.push(` emulate -L zsh`);
|
|
3219
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3220
|
+
lines.push(` if [[ -n "\${NODE_COMPILE_CACHE:-}" ]]; then`);
|
|
3221
|
+
lines.push(` print -r -- "$NODE_COMPILE_CACHE"`);
|
|
3222
|
+
lines.push(` return 0`);
|
|
3223
|
+
lines.push(` fi`);
|
|
3224
|
+
lines.push(compileCacheDefault);
|
|
3225
|
+
lines.push(`}`);
|
|
3226
|
+
lines.push(``);
|
|
3227
|
+
lines.push(`__${fn}_static_worker_path() {`);
|
|
3228
|
+
lines.push(` emulate -L zsh`);
|
|
3229
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3230
|
+
lines.push(workerPathDefault);
|
|
3231
|
+
lines.push(`}`);
|
|
3232
|
+
lines.push(``);
|
|
3233
|
+
lines.push(`__${fn}_worker_file_sig() {`);
|
|
3234
|
+
lines.push(` emulate -L zsh`);
|
|
3235
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3236
|
+
lines.push(` local _worker="$1" _sig`);
|
|
3237
|
+
lines.push(` _sig=${statSigExpr("$_worker", {
|
|
3238
|
+
shell: "posix",
|
|
3239
|
+
withSize: true
|
|
3240
|
+
})} || return 1`);
|
|
3241
|
+
lines.push(` print -r -- "$_sig"`);
|
|
3242
|
+
lines.push(`}`);
|
|
3243
|
+
lines.push(``);
|
|
3244
|
+
lines.push(`__${fn}_is_worker_file() {`);
|
|
3245
|
+
lines.push(` emulate -L zsh`);
|
|
3246
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3247
|
+
lines.push(` local _worker="$1" _head`);
|
|
3248
|
+
lines.push(` [[ -f "$_worker" ]] || return 1`);
|
|
3249
|
+
lines.push(` _head="$(<$_worker)" || return 1`);
|
|
3250
|
+
lines.push(` _head=$'\\n'"$_head"$'\\n'`);
|
|
3251
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-version: 1"$'\\n'* ]] || return 1`);
|
|
3252
|
+
lines.push(` [[ "$_head" == *$'\\n'"# program: ${programName}"$'\\n'* ]] || return 1`);
|
|
3253
|
+
lines.push(` [[ "$_head" == *$'\\n'"# shell: zsh"$'\\n'* ]] || return 1`);
|
|
3254
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-mode: worker"$'\\n'* ]] || return 1`);
|
|
3255
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-completion-worker: true"$'\\n'* ]] || return 1`);
|
|
3256
|
+
lines.push(`}`);
|
|
3257
|
+
lines.push(``);
|
|
3258
|
+
lines.push(`__${fn}_load_worker() {`);
|
|
3259
|
+
lines.push(` emulate -L zsh`);
|
|
3260
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3261
|
+
lines.push(` local _worker="$1" _sig _key`);
|
|
3262
|
+
lines.push(` __${fn}_is_worker_file "$_worker" || return 1`);
|
|
3263
|
+
lines.push(` _sig="$(__${fn}_worker_file_sig "$_worker")" || return 1`);
|
|
3264
|
+
lines.push(` _key="$_worker:$_sig"`);
|
|
3265
|
+
lines.push(` if [[ "\${__${fn}_loaded_worker_key:-}" != "$_key" ]]; then`);
|
|
3266
|
+
lines.push(` source "$_worker" 2>/dev/null || return 1`);
|
|
3267
|
+
lines.push(` typeset -g __${fn}_loaded_worker_key="$_key"`);
|
|
3268
|
+
lines.push(` fi`);
|
|
3269
|
+
lines.push(` (( $+functions[_${workerFn}_completions] ))`);
|
|
3270
|
+
lines.push(`}`);
|
|
3271
|
+
lines.push(``);
|
|
3272
|
+
lines.push(`__${fn}_realpath() {`);
|
|
3273
|
+
lines.push(` emulate -L zsh`);
|
|
3274
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3275
|
+
lines.push(` local _path="$1"`);
|
|
3276
|
+
lines.push(` [[ -n "$_path" ]] || return 1`);
|
|
3277
|
+
lines.push(` print -r -- "\${_path:A}"`);
|
|
3278
|
+
lines.push(`}`);
|
|
3279
|
+
lines.push(``);
|
|
3280
|
+
lines.push(`__${fn}_worker_from_dir() {`);
|
|
3281
|
+
lines.push(` emulate -L zsh`);
|
|
3282
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3283
|
+
lines.push(` local _dir="$1" _rel _candidate`);
|
|
3284
|
+
if (workerRelList.length > 0) {
|
|
3285
|
+
lines.push(` for _rel in ${workerRelList}; do`);
|
|
3286
|
+
lines.push(` [[ "$_rel" == /* ]] && _candidate="$_rel" || _candidate="$_dir/$_rel"`);
|
|
3287
|
+
lines.push(` if __${fn}_is_worker_file "$_candidate"; then`);
|
|
3288
|
+
lines.push(` print -r -- "$_candidate"`);
|
|
3289
|
+
lines.push(` return 0`);
|
|
3290
|
+
lines.push(` fi`);
|
|
3291
|
+
lines.push(` done`);
|
|
3292
|
+
}
|
|
3293
|
+
lines.push(` return 1`);
|
|
3294
|
+
lines.push(`}`);
|
|
3295
|
+
lines.push(``);
|
|
3296
|
+
lines.push(`__${fn}_cmd_shim_target() {`);
|
|
3297
|
+
lines.push(` emulate -L zsh`);
|
|
3298
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3299
|
+
lines.push(` [[ -f "$1" ]] || return 0`);
|
|
3300
|
+
lines.push(` local _l _t=""`);
|
|
3301
|
+
lines.push(` for _l in "\${(@f)$(<$1)}"; do`);
|
|
3302
|
+
lines.push(` [[ "$_l" == "# cmd-shim-target="* ]] && _t="\${_l#\\# cmd-shim-target=}"`);
|
|
3303
|
+
lines.push(` done`);
|
|
3304
|
+
lines.push(` [[ -n "$_t" ]] && print -r -- "$_t"`);
|
|
3305
|
+
lines.push(`}`);
|
|
3306
|
+
lines.push(``);
|
|
3307
|
+
lines.push(`__${fn}_bundled_worker_path() {`);
|
|
3308
|
+
lines.push(` emulate -L zsh`);
|
|
3309
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3310
|
+
lines.push(` local _bin="$1" _node_compile_cache="\${2:-}" _real _dir _target _reported`);
|
|
3311
|
+
lines.push(` _real="$(__${fn}_realpath "$_bin" 2>/dev/null)" || _real=""`);
|
|
3312
|
+
lines.push(` if [[ -n "$_real" ]]; then`);
|
|
3313
|
+
lines.push(` _dir="\${_real:h}"`);
|
|
3314
|
+
lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
|
|
3315
|
+
lines.push(` fi`);
|
|
3316
|
+
lines.push(` _dir="\${_bin:h}"`);
|
|
3317
|
+
lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
|
|
3318
|
+
lines.push(` _target="$(__${fn}_cmd_shim_target "$_bin")"`);
|
|
3319
|
+
lines.push(` if [[ -n "$_target" ]]; then`);
|
|
3320
|
+
lines.push(` _real="$(__${fn}_realpath "$_target" 2>/dev/null)" || _real="$_target"`);
|
|
3321
|
+
lines.push(` _dir="\${_real:h}"`);
|
|
3322
|
+
lines.push(` __${fn}_worker_from_dir "$_dir" && return 0`);
|
|
3323
|
+
lines.push(` fi`);
|
|
3324
|
+
if (canQueryBundledWorkerPath) {
|
|
3325
|
+
lines.push(` if [[ "\${__${fn}_queried_bin:-}" == "$_bin" && -n "\${__${fn}_queried_worker:-}" ]] && __${fn}_is_worker_file "\${__${fn}_queried_worker}"; then`);
|
|
3326
|
+
lines.push(` print -r -- "\${__${fn}_queried_worker}"`);
|
|
3327
|
+
lines.push(` return 0`);
|
|
3328
|
+
lines.push(` fi`);
|
|
3329
|
+
lines.push(` _reported="$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __completion-worker-path zsh 2>/dev/null)" || _reported=""`);
|
|
3330
|
+
lines.push(` if [[ -n "$_reported" ]] && __${fn}_is_worker_file "$_reported"; then`);
|
|
3331
|
+
lines.push(` typeset -g __${fn}_queried_bin="$_bin"`);
|
|
3332
|
+
lines.push(` typeset -g __${fn}_queried_worker="$_reported"`);
|
|
3333
|
+
lines.push(` print -r -- "$_reported"`);
|
|
3334
|
+
lines.push(` return 0`);
|
|
3335
|
+
lines.push(` fi`);
|
|
3336
|
+
}
|
|
3337
|
+
lines.push(` return 1`);
|
|
3338
|
+
lines.push(`}`);
|
|
3339
|
+
lines.push(``);
|
|
3340
|
+
lines.push(`__${fn}_bin_sig() {`);
|
|
3341
|
+
lines.push(` emulate -L zsh`);
|
|
3342
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3343
|
+
lines.push(` local _bin="$1" _sig`);
|
|
3344
|
+
lines.push(` _sig=${statSigExpr("$_bin", { shell: "posix" })} || return 1`);
|
|
3345
|
+
lines.push(` print -r -- "$_sig"`);
|
|
3346
|
+
lines.push(`}`);
|
|
3347
|
+
lines.push(``);
|
|
3348
|
+
lines.push(`__${fn}_worker_matches_bin() {`);
|
|
3349
|
+
lines.push(` emulate -L zsh`);
|
|
3350
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3351
|
+
lines.push(` local _worker="$1" _sig="$2" _bin="$3" _head`);
|
|
3352
|
+
lines.push(` [[ -f "$_worker" ]] || return 1`);
|
|
3353
|
+
lines.push(` _head=$'\\n'"$(<$_worker)"$'\\n'`);
|
|
3354
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-bin-sig: $_sig"$'\\n'* ]] || return 1`);
|
|
3355
|
+
lines.push(` [[ "$_head" == *$'\\n'"# politty-bin-path: $_bin"$'\\n'* ]] || return 1`);
|
|
3356
|
+
lines.push(`}`);
|
|
3357
|
+
lines.push(``);
|
|
3358
|
+
lines.push(`__${fn}_cdescribe() {`);
|
|
3359
|
+
lines.push(` _describe "$@" 2>/dev/null && return 0`);
|
|
3360
|
+
lines.push(` shift`);
|
|
3361
|
+
lines.push(` local _cd_arr="$1"`);
|
|
3362
|
+
lines.push(` shift`);
|
|
3363
|
+
lines.push(` local -a _cd_vals=("\${(@)\${(P)_cd_arr}%%:*}")`);
|
|
3364
|
+
lines.push(` compadd "$@" -a _cd_vals 2>/dev/null`);
|
|
3365
|
+
lines.push(` return 0`);
|
|
3366
|
+
lines.push(`}`);
|
|
3367
|
+
lines.push(``);
|
|
3368
|
+
lines.push(`__${fn}_add_path_candidate() {`);
|
|
3369
|
+
lines.push(` compadd -f -- "$1" 2>/dev/null && return 0`);
|
|
3370
|
+
lines.push(` print -r -- "$1"`);
|
|
3371
|
+
lines.push(`}`);
|
|
3372
|
+
lines.push(``);
|
|
3373
|
+
lines.push(`__${fn}_apply_dynamic_output() {`);
|
|
3374
|
+
lines.push(` local _raw="$1"`);
|
|
3375
|
+
lines.push(` local _directive=0`);
|
|
3376
|
+
lines.push(` local -a _vals _lines _exts _matchers`);
|
|
3377
|
+
lines.push(` local _meta`);
|
|
3378
|
+
lines.push(` _lines=("\${(@f)_raw}")`);
|
|
3379
|
+
lines.push(` local _last=$#_lines`);
|
|
3380
|
+
lines.push(` if (( _last >= 1 )) && [[ "\${_lines[$_last]}" == :<->* ]]; then`);
|
|
3381
|
+
lines.push(` local _dline="\${_lines[$_last]}"`);
|
|
3382
|
+
lines.push(` _lines[$_last]=()`);
|
|
3383
|
+
lines.push(` local -a _dfields`);
|
|
3384
|
+
lines.push(` _dfields=("\${(@ps:\\t:)_dline}")`);
|
|
3385
|
+
lines.push(` _directive="\${_dfields[1]#:}"`);
|
|
3386
|
+
lines.push(` local _m`);
|
|
3387
|
+
lines.push(` for _m in "\${(@)_dfields[2,-1]}"; do`);
|
|
3388
|
+
lines.push(` if [[ "$_m" == @ext:* ]]; then`);
|
|
3389
|
+
lines.push(` _meta="\${_m#@ext:}"`);
|
|
3390
|
+
lines.push(` _exts=("\${(@s:,:)_meta}")`);
|
|
3391
|
+
lines.push(` elif [[ "$_m" == @matcher:* ]]; then`);
|
|
3392
|
+
lines.push(` _meta="\${_m#@matcher:}"`);
|
|
3393
|
+
lines.push(` _matchers=("\${(@s:,:)_meta}")`);
|
|
3394
|
+
lines.push(` fi`);
|
|
3395
|
+
lines.push(` done`);
|
|
3396
|
+
lines.push(` fi`);
|
|
3397
|
+
lines.push(` local _l`);
|
|
3398
|
+
lines.push(` for _l in "\${_lines[@]}"; do`);
|
|
3399
|
+
lines.push(` [[ -z "$_l" ]] && continue`);
|
|
3400
|
+
lines.push(` _vals+=("$_l")`);
|
|
3401
|
+
lines.push(` done`);
|
|
3402
|
+
lines.push(` if (( \${#_vals[@]} > 0 )); then`);
|
|
3403
|
+
lines.push(` if (( _directive & ${CompletionDirective.NoSpace} )); then`);
|
|
3404
|
+
lines.push(` __${fn}_cdescribe 'completions' _vals -S ''`);
|
|
3405
|
+
lines.push(` else`);
|
|
3406
|
+
lines.push(` __${fn}_cdescribe 'completions' _vals`);
|
|
3407
|
+
lines.push(` fi`);
|
|
3408
|
+
lines.push(` fi`);
|
|
3409
|
+
lines.push(` if (( \${#_exts[@]} > 0 || \${#_matchers[@]} > 0 )); then`);
|
|
3410
|
+
lines.push(` setopt local_options null_glob`);
|
|
3411
|
+
lines.push(` local _cur="\${words[CURRENT]:-}" _dir _prefix _f _out _ext _pat`);
|
|
3412
|
+
lines.push(` if [[ -z "$_cur" || "$_cur" != */* ]]; then`);
|
|
3413
|
+
lines.push(` _dir="."`);
|
|
3414
|
+
lines.push(` _prefix="$_cur"`);
|
|
3415
|
+
lines.push(` elif [[ "$_cur" == */ ]]; then`);
|
|
3416
|
+
lines.push(` _dir="\${_cur%/}"`);
|
|
3417
|
+
lines.push(` _prefix=""`);
|
|
3418
|
+
lines.push(` else`);
|
|
3419
|
+
lines.push(` _dir="\${_cur%/*}"`);
|
|
3420
|
+
lines.push(` _prefix="\${_cur##*/}"`);
|
|
3421
|
+
lines.push(` fi`);
|
|
3422
|
+
lines.push(` for _f in "$_dir"/"$_prefix"*(N/); do`);
|
|
3423
|
+
lines.push(` _out="\${_f#./}"`);
|
|
3424
|
+
lines.push(` __${fn}_add_path_candidate "$_out"`);
|
|
3425
|
+
lines.push(` done`);
|
|
3426
|
+
lines.push(` for _f in "$_dir"/"$_prefix"*(N.); do`);
|
|
3427
|
+
lines.push(` _out="\${_f#./}"`);
|
|
3428
|
+
lines.push(` for _ext in "\${_exts[@]}"; do`);
|
|
3429
|
+
lines.push(` [[ -n "$_ext" && "$_out" == *."$_ext" ]] && { __${fn}_add_path_candidate "$_out"; break; }`);
|
|
3430
|
+
lines.push(` done`);
|
|
3431
|
+
lines.push(` done`);
|
|
3432
|
+
lines.push(` for _pat in "\${_matchers[@]}"; do`);
|
|
3433
|
+
lines.push(` [[ -n "$_pat" ]] || continue`);
|
|
3434
|
+
lines.push(` for _f in "$_dir"/\${~_pat}(N.); do`);
|
|
3435
|
+
lines.push(` _out="\${_f#./}"`);
|
|
3436
|
+
lines.push(` [[ "$_out" == "$_cur"* ]] && __${fn}_add_path_candidate "$_out"`);
|
|
3437
|
+
lines.push(` done`);
|
|
3438
|
+
lines.push(` done`);
|
|
3439
|
+
lines.push(` elif (( _directive & ${CompletionDirective.DirectoryCompletion} )); then`);
|
|
3440
|
+
lines.push(` _files -/`);
|
|
3441
|
+
lines.push(` elif (( _directive & ${CompletionDirective.FileCompletion} )); then`);
|
|
3442
|
+
lines.push(` _files`);
|
|
3443
|
+
lines.push(` elif (( \${#_vals[@]} == 0 )) && ! (( _directive & ${CompletionDirective.NoFileCompletion} )); then`);
|
|
3444
|
+
lines.push(` _files`);
|
|
3445
|
+
lines.push(` fi`);
|
|
3446
|
+
lines.push(`}`);
|
|
3447
|
+
lines.push(``);
|
|
3448
|
+
lines.push(`${completionFn}() {`);
|
|
3449
|
+
lines.push(` emulate -L zsh`);
|
|
3450
|
+
lines.push(` setopt local_options no_aliases`);
|
|
3451
|
+
lines.push(` local _bin _out _node_compile_cache _worker _bundled_worker _sig`);
|
|
3452
|
+
lines.push(` _bin="$(__${fn}_resolve_bin)"`);
|
|
3453
|
+
lines.push(` [[ -n "$_bin" ]] || return 1`);
|
|
3454
|
+
lines.push(` _node_compile_cache="$(__${fn}_node_compile_cache_dir)"`);
|
|
3455
|
+
lines.push(` _bundled_worker="$(__${fn}_bundled_worker_path "$_bin" "$_node_compile_cache")"`);
|
|
3456
|
+
lines.push(` if [[ -n "$_bundled_worker" ]] && __${fn}_load_worker "$_bundled_worker"; then`);
|
|
3457
|
+
lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions "$@"`);
|
|
3458
|
+
lines.push(` return 0`);
|
|
3459
|
+
lines.push(` fi`);
|
|
3460
|
+
lines.push(` _worker="$(__${fn}_static_worker_path)"`);
|
|
3461
|
+
lines.push(` _sig="$(__${fn}_bin_sig "$_bin")"`);
|
|
3462
|
+
lines.push(` if [[ -n "$_worker" && -n "$_sig" ]]; then`);
|
|
3463
|
+
lines.push(` if ! __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"; then`);
|
|
3464
|
+
lines.push(` mkdir -p "\${_worker%/*}" 2>/dev/null`);
|
|
3465
|
+
lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${envName}="$_bin" "$_bin" __refresh-completion zsh "$_worker" --static --worker 2>/dev/null`);
|
|
3466
|
+
lines.push(` fi`);
|
|
3467
|
+
lines.push(` if __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin" && __${fn}_load_worker "$_worker"; then`);
|
|
3468
|
+
lines.push(` NODE_COMPILE_CACHE="$_node_compile_cache" ${workerBinEnvName}="$_bin" _${workerFn}_completions "$@"`);
|
|
3469
|
+
lines.push(` return 0`);
|
|
3470
|
+
lines.push(` fi`);
|
|
3471
|
+
lines.push(` fi`);
|
|
3472
|
+
lines.push(` _out=$(NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __complete --shell zsh -- "\${(@)words[2,CURRENT]}" 2>/dev/null) || return 1`);
|
|
3473
|
+
lines.push(` __${fn}_apply_dynamic_output "$_out"`);
|
|
3474
|
+
lines.push(`}`);
|
|
3475
|
+
lines.push(``);
|
|
3476
|
+
lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
|
|
3477
|
+
lines.push(``);
|
|
3478
|
+
lines.push(`if [[ ${autoloadCheck} ]]; then`);
|
|
3479
|
+
lines.push(` ${completionFn} "$@"`);
|
|
3480
|
+
lines.push(`else`);
|
|
3481
|
+
lines.push(` compdef ${completionFn} ${programName}`);
|
|
3482
|
+
lines.push(`fi`);
|
|
3483
|
+
lines.push(``);
|
|
3484
|
+
return {
|
|
3485
|
+
script: lines.join("\n"),
|
|
3486
|
+
shell: "zsh",
|
|
3487
|
+
installInstructions: `# To enable dispatcher zsh completions, add this to your ~/.zshrc after compinit:
|
|
3488
|
+
eval "$(${programName} completion zsh)"
|
|
3489
|
+
|
|
3490
|
+
# To save the dispatcher script in your fpath:
|
|
3491
|
+
mkdir -p ~/.zsh/completions
|
|
3492
|
+
${programName} completion zsh > ~/.zsh/completions/_${programName}
|
|
3493
|
+
|
|
3494
|
+
# Make sure your ~/.zshrc includes the fpath line before compinit:
|
|
3495
|
+
fpath=(~/.zsh/completions $fpath)
|
|
3496
|
+
autoload -Uz compinit && compinit
|
|
3497
|
+
|
|
3498
|
+
# To generate the older static script:
|
|
3499
|
+
${programName} completion zsh --static`
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
function fishDispatcher(_command, options) {
|
|
3503
|
+
const { programName } = options;
|
|
3504
|
+
const fn = sanitize(programName);
|
|
3505
|
+
const workerFn = `${fn}_worker`;
|
|
3506
|
+
const workerBinEnvName = binEnvVarName(workerFn);
|
|
3507
|
+
const envName = binEnvVarName(fn);
|
|
3508
|
+
const programLookup = shSingleQuote(programName);
|
|
3509
|
+
const compileCacheDefault = fishCacheDefault(hardcodedCompileCacheDir(options.cacheDir), compileCacheSuffix(programName));
|
|
3510
|
+
const workerPathDefault = fishCacheDefault(hardcodedWorkerPath(options.cacheDir, "fish"), workerPathSuffix(programName, "fish"));
|
|
3511
|
+
const workerRelList = fishWorkerRelList(options);
|
|
3512
|
+
const canQueryBundledWorkerPath = bundledWorkerPathCommandEnabled(options);
|
|
3513
|
+
const lines = [];
|
|
3514
|
+
lines.push(...buildHeaderLines({
|
|
3515
|
+
programName,
|
|
3516
|
+
shell: "fish",
|
|
3517
|
+
binPath: options.binPath,
|
|
3518
|
+
programVersion: options.programVersion
|
|
3519
|
+
}));
|
|
3520
|
+
lines.push(`# Generated by politty`);
|
|
3521
|
+
lines.push(`# politty-completion-mode: dispatcher`);
|
|
3522
|
+
lines.push(``);
|
|
3523
|
+
lines.push(`function __${fn}_resolve_bin`);
|
|
3524
|
+
lines.push(` if set -q ${envName}; and test -n "$${envName}"`);
|
|
3525
|
+
lines.push(` printf '%s\\n' "$${envName}"`);
|
|
3526
|
+
lines.push(` return 0`);
|
|
3527
|
+
lines.push(` end`);
|
|
3528
|
+
lines.push(` set -l _bin (command -v ${programLookup})`);
|
|
3529
|
+
lines.push(` test -n "$_bin"; and test -x "$_bin"; and not test -d "$_bin"; and printf '%s\\n' "$_bin"`);
|
|
3530
|
+
lines.push(`end`);
|
|
3531
|
+
lines.push(``);
|
|
3532
|
+
lines.push(`function __${fn}_node_compile_cache_dir`);
|
|
3533
|
+
lines.push(` if set -q NODE_COMPILE_CACHE; and test -n "$NODE_COMPILE_CACHE"`);
|
|
3534
|
+
lines.push(` printf '%s\\n' "$NODE_COMPILE_CACHE"`);
|
|
3535
|
+
lines.push(` return 0`);
|
|
3536
|
+
lines.push(` end`);
|
|
3537
|
+
lines.push(compileCacheDefault);
|
|
3538
|
+
lines.push(`end`);
|
|
3539
|
+
lines.push(``);
|
|
3540
|
+
lines.push(`function __${fn}_static_worker_path`);
|
|
3541
|
+
lines.push(workerPathDefault);
|
|
3542
|
+
lines.push(`end`);
|
|
3543
|
+
lines.push(``);
|
|
3544
|
+
lines.push(`function __${fn}_worker_file_sig`);
|
|
3545
|
+
lines.push(` set -l _worker $argv[1]`);
|
|
3546
|
+
lines.push(` set -l _sig ${statSigExpr("$_worker", {
|
|
3547
|
+
shell: "fish",
|
|
3548
|
+
withSize: true
|
|
3549
|
+
})}`);
|
|
3550
|
+
lines.push(` test -n "$_sig"; and printf '%s\\n' "$_sig"`);
|
|
3551
|
+
lines.push(`end`);
|
|
3552
|
+
lines.push(``);
|
|
3553
|
+
lines.push(`function __${fn}_is_worker_file`);
|
|
3554
|
+
lines.push(` set -l _worker $argv[1]`);
|
|
3555
|
+
lines.push(` test -f "$_worker"; or return 1`);
|
|
3556
|
+
lines.push(` set -l _head (head -n 24 "$_worker" 2>/dev/null)`);
|
|
3557
|
+
lines.push(` string match -q -- "# politty-completion-version: 1" $_head; or return 1`);
|
|
3558
|
+
lines.push(` string match -q -- "# program: ${programName}" $_head; or return 1`);
|
|
3559
|
+
lines.push(` string match -q -- "# shell: fish" $_head; or return 1`);
|
|
3560
|
+
lines.push(` string match -q -- "# politty-completion-mode: worker" $_head; or return 1`);
|
|
3561
|
+
lines.push(` string match -q -- "# politty-completion-worker: true" $_head; or return 1`);
|
|
3562
|
+
lines.push(`end`);
|
|
3563
|
+
lines.push(``);
|
|
3564
|
+
lines.push(`function __${fn}_load_worker`);
|
|
3565
|
+
lines.push(` set -l _worker $argv[1]`);
|
|
3566
|
+
lines.push(` __${fn}_is_worker_file "$_worker"; or return 1`);
|
|
3567
|
+
lines.push(` set -l _sig (__${fn}_worker_file_sig "$_worker")`);
|
|
3568
|
+
lines.push(` test -n "$_sig"; or return 1`);
|
|
3569
|
+
lines.push(` set -l _key "$_worker:$_sig"`);
|
|
3570
|
+
lines.push(` if not set -q __${fn}_loaded_worker_key; or test "$__${fn}_loaded_worker_key" != "$_key"`);
|
|
3571
|
+
lines.push(` source "$_worker" 2>/dev/null; or return 1`);
|
|
3572
|
+
lines.push(` set -g __${fn}_loaded_worker_key "$_key"`);
|
|
3573
|
+
lines.push(` end`);
|
|
3574
|
+
lines.push(` functions -q __fish_${workerFn}_complete`);
|
|
3575
|
+
lines.push(`end`);
|
|
3576
|
+
lines.push(``);
|
|
3577
|
+
lines.push(`function __${fn}_realpath`);
|
|
3578
|
+
lines.push(` set -l _path $argv[1]`);
|
|
3579
|
+
lines.push(` set -l _old_pwd (pwd)`);
|
|
3580
|
+
lines.push(` set -l _limit 0`);
|
|
3581
|
+
lines.push(` while test -L "$_path"; and test $_limit -lt 40`);
|
|
3582
|
+
lines.push(` set -l _dir (dirname "$_path")`);
|
|
3583
|
+
lines.push(` if not cd "$_dir" 2>/dev/null`);
|
|
3584
|
+
lines.push(` cd "$_old_pwd" 2>/dev/null`);
|
|
3585
|
+
lines.push(` return 1`);
|
|
3586
|
+
lines.push(` end`);
|
|
3587
|
+
lines.push(` set -l _absdir (pwd -P)`);
|
|
3588
|
+
lines.push(` cd "$_old_pwd" 2>/dev/null`);
|
|
3589
|
+
lines.push(` test -n "$_absdir"; or return 1`);
|
|
3590
|
+
lines.push(` set -l _target (readlink "$_path")`);
|
|
3591
|
+
lines.push(` test -n "$_target"; or return 1`);
|
|
3592
|
+
lines.push(` if string match -q '/*' -- "$_target"`);
|
|
3593
|
+
lines.push(` set _path "$_target"`);
|
|
3594
|
+
lines.push(` else`);
|
|
3595
|
+
lines.push(` set _path "$_absdir/$_target"`);
|
|
3596
|
+
lines.push(` end`);
|
|
3597
|
+
lines.push(` set _limit (math $_limit + 1)`);
|
|
3598
|
+
lines.push(` end`);
|
|
3599
|
+
lines.push(` set -l _dir (dirname "$_path")`);
|
|
3600
|
+
lines.push(` if not cd "$_dir" 2>/dev/null`);
|
|
3601
|
+
lines.push(` cd "$_old_pwd" 2>/dev/null`);
|
|
3602
|
+
lines.push(` return 1`);
|
|
3603
|
+
lines.push(` end`);
|
|
3604
|
+
lines.push(` set -l _absdir (pwd -P)`);
|
|
3605
|
+
lines.push(` cd "$_old_pwd" 2>/dev/null`);
|
|
3606
|
+
lines.push(` test -n "$_absdir"; or return 1`);
|
|
3607
|
+
lines.push(` set -l _base (basename "$_path")`);
|
|
3608
|
+
lines.push(` printf '%s\\n' "$_absdir/$_base"`);
|
|
3609
|
+
lines.push(`end`);
|
|
3610
|
+
lines.push(``);
|
|
3611
|
+
lines.push(`function __${fn}_worker_from_dir`);
|
|
3612
|
+
lines.push(` set -l _dir $argv[1]`);
|
|
3613
|
+
if (workerRelList.length > 0) {
|
|
3614
|
+
lines.push(` for _rel in ${workerRelList}`);
|
|
3615
|
+
lines.push(` if string match -q '/*' -- "$_rel"`);
|
|
3616
|
+
lines.push(` set _candidate "$_rel"`);
|
|
3617
|
+
lines.push(` else`);
|
|
3618
|
+
lines.push(` set _candidate "$_dir/$_rel"`);
|
|
3619
|
+
lines.push(` end`);
|
|
3620
|
+
lines.push(` if __${fn}_is_worker_file "$_candidate"`);
|
|
3621
|
+
lines.push(` printf '%s\\n' "$_candidate"`);
|
|
3622
|
+
lines.push(` return 0`);
|
|
3623
|
+
lines.push(` end`);
|
|
3624
|
+
lines.push(` end`);
|
|
3625
|
+
}
|
|
3626
|
+
lines.push(` return 1`);
|
|
3627
|
+
lines.push(`end`);
|
|
3628
|
+
lines.push(``);
|
|
3629
|
+
lines.push(`function __${fn}_cmd_shim_target`);
|
|
3630
|
+
lines.push(` sed -n 's/^# cmd-shim-target=//p' "$argv[1]" 2>/dev/null | tail -n 1`);
|
|
3631
|
+
lines.push(`end`);
|
|
3632
|
+
lines.push(``);
|
|
3633
|
+
lines.push(`function __${fn}_bundled_worker_path`);
|
|
3634
|
+
lines.push(` set -l _bin $argv[1]`);
|
|
3635
|
+
lines.push(` set -l _node_compile_cache $argv[2]`);
|
|
3636
|
+
lines.push(` set -l _real (__${fn}_realpath "$_bin" 2>/dev/null)`);
|
|
3637
|
+
lines.push(` if test -n "$_real"`);
|
|
3638
|
+
lines.push(` set -l _dir (dirname "$_real")`);
|
|
3639
|
+
lines.push(` __${fn}_worker_from_dir "$_dir"; and return 0`);
|
|
3640
|
+
lines.push(` end`);
|
|
3641
|
+
lines.push(` set -l _dir (dirname "$_bin")`);
|
|
3642
|
+
lines.push(` __${fn}_worker_from_dir "$_dir"; and return 0`);
|
|
3643
|
+
lines.push(` set -l _target (__${fn}_cmd_shim_target "$_bin")`);
|
|
3644
|
+
lines.push(` if test -n "$_target"`);
|
|
3645
|
+
lines.push(` set _real (__${fn}_realpath "$_target" 2>/dev/null)`);
|
|
3646
|
+
lines.push(` test -n "$_real"; or set _real "$_target"`);
|
|
3647
|
+
lines.push(` set _dir (dirname "$_real")`);
|
|
3648
|
+
lines.push(` __${fn}_worker_from_dir "$_dir"; and return 0`);
|
|
3649
|
+
lines.push(` end`);
|
|
3650
|
+
if (canQueryBundledWorkerPath) {
|
|
3651
|
+
lines.push(` if set -q __${fn}_queried_bin; and test "$__${fn}_queried_bin" = "$_bin"; and test -n "$__${fn}_queried_worker"; and __${fn}_is_worker_file "$__${fn}_queried_worker"`);
|
|
3652
|
+
lines.push(` printf '%s\\n' "$__${fn}_queried_worker"`);
|
|
3653
|
+
lines.push(` return 0`);
|
|
3654
|
+
lines.push(` end`);
|
|
3655
|
+
lines.push(` set -l _reported (env NODE_COMPILE_CACHE="$_node_compile_cache" "$_bin" __completion-worker-path fish 2>/dev/null)`);
|
|
3656
|
+
lines.push(` if test -n "$_reported"; and __${fn}_is_worker_file "$_reported"`);
|
|
3657
|
+
lines.push(` set -g __${fn}_queried_bin "$_bin"`);
|
|
3658
|
+
lines.push(` set -g __${fn}_queried_worker "$_reported"`);
|
|
3659
|
+
lines.push(` printf '%s\\n' "$_reported"`);
|
|
3660
|
+
lines.push(` return 0`);
|
|
3661
|
+
lines.push(` end`);
|
|
3662
|
+
}
|
|
3663
|
+
lines.push(` return 1`);
|
|
3664
|
+
lines.push(`end`);
|
|
3665
|
+
lines.push(``);
|
|
3666
|
+
lines.push(`function __${fn}_bin_sig`);
|
|
3667
|
+
lines.push(` set -l _bin $argv[1]`);
|
|
3668
|
+
lines.push(` set -l _sig ${statSigExpr("$_bin", { shell: "fish" })}`);
|
|
3669
|
+
lines.push(` test -n "$_sig"; and printf '%s\\n' "$_sig"`);
|
|
3670
|
+
lines.push(`end`);
|
|
3671
|
+
lines.push(``);
|
|
3672
|
+
lines.push(`function __${fn}_worker_matches_bin`);
|
|
3673
|
+
lines.push(` set -l _worker $argv[1]`);
|
|
3674
|
+
lines.push(` set -l _sig $argv[2]`);
|
|
3675
|
+
lines.push(` set -l _bin $argv[3]`);
|
|
3676
|
+
lines.push(` test -f "$_worker"; or return 1`);
|
|
3677
|
+
lines.push(` set -l _head (head -n 12 "$_worker" 2>/dev/null)`);
|
|
3678
|
+
lines.push(` contains -- "# politty-bin-sig: $_sig" $_head; or return 1`);
|
|
3679
|
+
lines.push(` contains -- "# politty-bin-path: $_bin" $_head; or return 1`);
|
|
3680
|
+
lines.push(`end`);
|
|
3681
|
+
lines.push(``);
|
|
3682
|
+
lines.push(`function __${fn}_apply_dynamic_output`);
|
|
3683
|
+
lines.push(` set -l _cur $argv[1]`);
|
|
3684
|
+
lines.push(` set -l _directive 0`);
|
|
3685
|
+
lines.push(` set -l _emitted 0`);
|
|
3686
|
+
lines.push(` set -l _exts`);
|
|
3687
|
+
lines.push(` set -l _matchers`);
|
|
3688
|
+
lines.push(` set -l _lines`);
|
|
3689
|
+
lines.push(` while read -l _l`);
|
|
3690
|
+
lines.push(` set -a _lines $_l`);
|
|
3691
|
+
lines.push(` end`);
|
|
3692
|
+
lines.push(` set -l _n (count $_lines)`);
|
|
3693
|
+
lines.push(` if test $_n -ge 1; and string match -qr '^:[0-9]+' -- $_lines[$_n]`);
|
|
3694
|
+
lines.push(` set -l _fields (string split \\t -- $_lines[$_n])`);
|
|
3695
|
+
lines.push(` set -e _lines[$_n]`);
|
|
3696
|
+
lines.push(` set _directive (string sub -s 2 -- $_fields[1])`);
|
|
3697
|
+
lines.push(` for _f in $_fields[2..-1]`);
|
|
3698
|
+
lines.push(` if string match -q '@ext:*' -- $_f`);
|
|
3699
|
+
lines.push(` set _exts (string split , -- (string sub -s 6 -- $_f))`);
|
|
3700
|
+
lines.push(` else if string match -q '@matcher:*' -- $_f`);
|
|
3701
|
+
lines.push(` set _matchers (string split , -- (string sub -s 10 -- $_f))`);
|
|
3702
|
+
lines.push(` end`);
|
|
3703
|
+
lines.push(` end`);
|
|
3704
|
+
lines.push(` end`);
|
|
3705
|
+
lines.push(` for _l in $_lines`);
|
|
3706
|
+
lines.push(` if test -n "$_l"`);
|
|
3707
|
+
lines.push(` printf '%s\\n' $_l`);
|
|
3708
|
+
lines.push(` set _emitted 1`);
|
|
3709
|
+
lines.push(` end`);
|
|
3710
|
+
lines.push(` end`);
|
|
3711
|
+
lines.push(` if test (count $_exts) -gt 0; or test (count $_matchers) -gt 0`);
|
|
3712
|
+
lines.push(` __fish_complete_directories "$_cur"`);
|
|
3713
|
+
lines.push(` set -l _dir ""`);
|
|
3714
|
+
lines.push(` if string match -q '*/*' "$_cur"`);
|
|
3715
|
+
lines.push(` set _dir (string replace -r '[^/]*$' '' "$_cur")`);
|
|
3716
|
+
lines.push(` end`);
|
|
3717
|
+
lines.push(` for _f in "$_cur"*`);
|
|
3718
|
+
lines.push(` test -f "$_f"; or continue`);
|
|
3719
|
+
lines.push(` for _ext in $_exts`);
|
|
3720
|
+
lines.push(` if string match -q -- "*.$_ext" "$_f"`);
|
|
3721
|
+
lines.push(` printf '%s\\n' "$_f"`);
|
|
3722
|
+
lines.push(` break`);
|
|
3723
|
+
lines.push(` end`);
|
|
3724
|
+
lines.push(` end`);
|
|
3725
|
+
lines.push(` end`);
|
|
3726
|
+
lines.push(` for _pat in $_matchers`);
|
|
3727
|
+
lines.push(` for _f in "$_dir"$_pat`);
|
|
3728
|
+
lines.push(` test -f "$_f"; and string match -q "$_cur*" "$_f"; and printf '%s\\n' "$_f"`);
|
|
3729
|
+
lines.push(` end`);
|
|
3730
|
+
lines.push(` end`);
|
|
3731
|
+
lines.push(` else if test (math "bitand($_directive, ${CompletionDirective.DirectoryCompletion})") -ne 0`);
|
|
3732
|
+
lines.push(` __fish_complete_directories "$_cur"`);
|
|
3733
|
+
lines.push(` else if test (math "bitand($_directive, ${CompletionDirective.FileCompletion})") -ne 0`);
|
|
3734
|
+
lines.push(` __fish_complete_path "$_cur"`);
|
|
3735
|
+
lines.push(` else if test $_emitted -eq 0; and test (math "bitand($_directive, ${CompletionDirective.NoFileCompletion})") -eq 0`);
|
|
3736
|
+
lines.push(` __fish_complete_path "$_cur"`);
|
|
3737
|
+
lines.push(` end`);
|
|
3738
|
+
lines.push(`end`);
|
|
3739
|
+
lines.push(``);
|
|
3740
|
+
lines.push(`function __fish_${fn}_complete`);
|
|
3741
|
+
lines.push(` set -l _bin (__${fn}_resolve_bin)`);
|
|
3742
|
+
lines.push(` test -n "$_bin"; or return 0`);
|
|
3743
|
+
lines.push(` set -l _node_compile_cache (__${fn}_node_compile_cache_dir)`);
|
|
3744
|
+
lines.push(` set -l _bundled_worker (__${fn}_bundled_worker_path "$_bin" "$_node_compile_cache")`);
|
|
3745
|
+
lines.push(` if test -n "$_bundled_worker"; and __${fn}_load_worker "$_bundled_worker"`);
|
|
3746
|
+
lines.push(` set -lx ${workerBinEnvName} "$_bin"`);
|
|
3747
|
+
lines.push(` set -lx NODE_COMPILE_CACHE "$_node_compile_cache"`);
|
|
3748
|
+
lines.push(` __fish_${workerFn}_complete`);
|
|
3749
|
+
lines.push(` return 0`);
|
|
3750
|
+
lines.push(` end`);
|
|
3751
|
+
lines.push(` set -l _worker (__${fn}_static_worker_path)`);
|
|
3752
|
+
lines.push(` set -l _sig (__${fn}_bin_sig "$_bin")`);
|
|
3753
|
+
lines.push(` if test -n "$_worker"; and test -n "$_sig"`);
|
|
3754
|
+
lines.push(` if not __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"`);
|
|
3755
|
+
lines.push(` mkdir -p (dirname "$_worker") 2>/dev/null`);
|
|
3756
|
+
lines.push(` env NODE_COMPILE_CACHE="$_node_compile_cache" ${envName}="$_bin" $_bin __refresh-completion fish "$_worker" --static --worker 2>/dev/null`);
|
|
3757
|
+
lines.push(` end`);
|
|
3758
|
+
lines.push(` if __${fn}_worker_matches_bin "$_worker" "$_sig" "$_bin"`);
|
|
3759
|
+
lines.push(` if __${fn}_load_worker "$_worker"`);
|
|
3760
|
+
lines.push(` set -lx ${workerBinEnvName} "$_bin"`);
|
|
3761
|
+
lines.push(` set -lx NODE_COMPILE_CACHE "$_node_compile_cache"`);
|
|
3762
|
+
lines.push(` __fish_${workerFn}_complete`);
|
|
3763
|
+
lines.push(` return 0`);
|
|
3764
|
+
lines.push(` end`);
|
|
3765
|
+
lines.push(` end`);
|
|
3766
|
+
lines.push(` end`);
|
|
3767
|
+
lines.push(` set -l _args (commandline -opc)`);
|
|
3768
|
+
lines.push(` set -l _cur (commandline -ct)`);
|
|
3769
|
+
lines.push(` if test (count $_args) -gt 0`);
|
|
3770
|
+
lines.push(` set _args $_args[2..]`);
|
|
3771
|
+
lines.push(` end`);
|
|
3772
|
+
lines.push(` env NODE_COMPILE_CACHE="$_node_compile_cache" $_bin __complete --shell fish -- $_args "$_cur" 2>/dev/null | __${fn}_apply_dynamic_output "$_cur"`);
|
|
3773
|
+
lines.push(`end`);
|
|
3774
|
+
lines.push(``);
|
|
3775
|
+
lines.push(`complete -e -c ${programName}`);
|
|
3776
|
+
lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
|
|
3777
|
+
lines.push(``);
|
|
3778
|
+
return {
|
|
3779
|
+
script: lines.join("\n"),
|
|
3780
|
+
shell: "fish",
|
|
3781
|
+
installInstructions: `# To enable dispatcher fish completions, run:
|
|
3782
|
+
${programName} completion fish --install
|
|
3783
|
+
|
|
3784
|
+
# To generate the older static script:
|
|
3785
|
+
${programName} completion fish --static`
|
|
3786
|
+
};
|
|
3787
|
+
}
|
|
3788
|
+
function generateDispatcherCompletion(command, options) {
|
|
3789
|
+
extractCompletionData(command, options.programName, options.globalArgsSchema, true);
|
|
3790
|
+
switch (options.shell) {
|
|
3791
|
+
case "bash": return bashDispatcher(command, options);
|
|
3792
|
+
case "zsh": return zshDispatcher(command, options);
|
|
3793
|
+
case "fish": return fishDispatcher(command, options);
|
|
3794
|
+
default: throw new Error(`Unsupported shell: ${options.shell}`);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
//#endregion
|
|
3799
|
+
//#region src/completion/dynamic/shell-formatter.ts
|
|
3800
|
+
/**
|
|
3801
|
+
* Format completion candidates for the specified shell
|
|
3802
|
+
*
|
|
3803
|
+
* @returns Shell-ready output string (lines separated by newline, last line is :directive)
|
|
3804
|
+
*/
|
|
3805
|
+
function formatForShell(result, options) {
|
|
3806
|
+
switch (options.shell) {
|
|
3807
|
+
case "bash": return formatForBash(result, options);
|
|
3808
|
+
case "zsh": return formatForZsh(result, options);
|
|
3809
|
+
case "fish": return formatForFish(result, options);
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
/**
|
|
3813
|
+
* Append extension metadata and directive to output lines
|
|
3814
|
+
*/
|
|
3815
|
+
function appendMetadata(lines, result) {
|
|
3816
|
+
let directiveLine = `:${result.directive}`;
|
|
3817
|
+
if (result.fileExtensions && result.fileExtensions.length > 0) directiveLine += `\t@ext:${result.fileExtensions.join(",")}`;
|
|
3818
|
+
if (result.fileMatchers && result.fileMatchers.length > 0) directiveLine += `\t@matcher:${result.fileMatchers.join(",")}`;
|
|
3819
|
+
lines.push(directiveLine);
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Format for bash
|
|
3823
|
+
*
|
|
3824
|
+
* - Pre-filters candidates by currentWord prefix (replaces compgen -W)
|
|
3825
|
+
* - Handles --opt=value inline values by prepending prefix
|
|
3826
|
+
* - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
|
|
3827
|
+
* - Last line: :directive
|
|
3828
|
+
*/
|
|
3829
|
+
function formatForBash(result, options) {
|
|
3830
|
+
const lines = ((result.directive & CompletionDirective.FilterPrefix) !== 0 && options.currentWord ? result.candidates.filter((c) => c.value.startsWith(options.currentWord)) : result.candidates).map((c) => options.inlinePrefix ? `${options.inlinePrefix}${c.value}` : c.value);
|
|
3831
|
+
appendMetadata(lines, result);
|
|
3832
|
+
return lines.join("\n");
|
|
3833
|
+
}
|
|
3834
|
+
/**
|
|
3835
|
+
* Format for zsh
|
|
3836
|
+
*
|
|
3837
|
+
* - Outputs value:description pairs for _describe
|
|
3838
|
+
* - Colons in values/descriptions are escaped with backslash
|
|
3839
|
+
* - Last line: :directive
|
|
3840
|
+
*/
|
|
3841
|
+
function formatForZsh(result, _options) {
|
|
3842
|
+
const lines = result.candidates.map((c) => {
|
|
3843
|
+
const escapedValue = c.value.replace(/:/g, "\\:");
|
|
3844
|
+
if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
|
|
3845
|
+
return escapedValue;
|
|
3846
|
+
});
|
|
3847
|
+
appendMetadata(lines, result);
|
|
3848
|
+
return lines.join("\n");
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Format for fish
|
|
3852
|
+
*
|
|
3853
|
+
* - Outputs value\tdescription pairs
|
|
3854
|
+
* - Last line: :directive
|
|
3855
|
+
*/
|
|
3856
|
+
function formatForFish(result, _options) {
|
|
3857
|
+
const lines = result.candidates.map((c) => {
|
|
3858
|
+
if (c.description) return `${c.value}\t${c.description}`;
|
|
3859
|
+
return c.value;
|
|
3860
|
+
});
|
|
3861
|
+
appendMetadata(lines, result);
|
|
3862
|
+
return lines.join("\n");
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
//#endregion
|
|
3866
|
+
//#region src/completion/dynamic/complete-command.ts
|
|
3867
|
+
/**
|
|
3868
|
+
* Dynamic completion command implementation
|
|
3869
|
+
*
|
|
3870
|
+
* This creates a hidden `__complete` command that outputs completion candidates
|
|
3871
|
+
* for shell scripts to consume. Usage:
|
|
3872
|
+
*
|
|
3873
|
+
* mycli __complete --shell bash -- build --fo
|
|
3874
|
+
* mycli __complete --shell zsh -- plugin add
|
|
3875
|
+
*
|
|
3876
|
+
* Output format depends on the target shell:
|
|
3877
|
+
* bash: plain values (pre-filtered by prefix), last line :directive
|
|
3878
|
+
* zsh: value:description pairs, last line :directive
|
|
3879
|
+
* fish: value\tdescription pairs, last line :directive
|
|
3880
|
+
*/
|
|
3881
|
+
/**
|
|
3882
|
+
* Schema for the __complete command
|
|
3883
|
+
*/
|
|
3884
|
+
const completeArgsSchema = zod.z.object({
|
|
3885
|
+
shell: require_schema_extractor.arg(zod.z.enum([
|
|
3886
|
+
"bash",
|
|
3887
|
+
"zsh",
|
|
3888
|
+
"fish"
|
|
3889
|
+
]), { description: "Target shell for output formatting" }),
|
|
3890
|
+
args: require_schema_extractor.arg(zod.z.array(zod.z.string()).default([]), {
|
|
3891
|
+
positional: true,
|
|
3892
|
+
description: "Arguments to complete",
|
|
3893
|
+
variadic: true
|
|
3894
|
+
})
|
|
3895
|
+
});
|
|
3896
|
+
/**
|
|
3897
|
+
* Create the dynamic completion command
|
|
3898
|
+
*
|
|
3899
|
+
* @param rootCommand - The root command to generate completions for
|
|
3900
|
+
* @param programName - The program name (optional, defaults to rootCommand.name)
|
|
3901
|
+
* @param globalArgsSchema - Global args schema. Forwarded to
|
|
3902
|
+
* `parseCompletionContext` so resolvers attached to global options remain
|
|
3903
|
+
* reachable at every subcommand level.
|
|
3904
|
+
* @returns A command that outputs completion candidates
|
|
3905
|
+
*/
|
|
3906
|
+
function createDynamicCompleteCommand(rootCommand, _programName, globalArgsSchema) {
|
|
3907
|
+
return defineCommand({
|
|
3908
|
+
name: "__complete",
|
|
3909
|
+
args: completeArgsSchema,
|
|
3910
|
+
async run(args) {
|
|
3911
|
+
const context = parseCompletionContext(args.args, rootCommand, globalArgsSchema);
|
|
3912
|
+
const inlinePrefix = context.completionType === "option-value" && context.targetOption ? detectInlineOptionPrefix(context.currentWord) : void 0;
|
|
3913
|
+
const effectiveWord = inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord;
|
|
3914
|
+
const output = formatForShell(await generateCandidates(context, { shell: args.shell }), {
|
|
3915
|
+
shell: args.shell,
|
|
3916
|
+
currentWord: effectiveWord,
|
|
3917
|
+
inlinePrefix
|
|
3918
|
+
});
|
|
3919
|
+
console.log(output);
|
|
3920
|
+
}
|
|
3921
|
+
});
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Check if a command tree contains the __complete command
|
|
3925
|
+
*/
|
|
3926
|
+
function hasCompleteCommand(command) {
|
|
3927
|
+
return Boolean(command.subCommands?.["__complete"]);
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
//#endregion
|
|
3931
|
+
//#region src/completion/fish.ts
|
|
3932
|
+
/** Escape shell-special characters for fish double-quoted strings */
|
|
3933
|
+
function escapeDesc$1(s) {
|
|
3934
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$");
|
|
3935
|
+
}
|
|
3936
|
+
/**
|
|
3937
|
+
* Escape a fish `switch` case pattern. Fish's `case` interprets its
|
|
3938
|
+
* arguments as globs even when double-quoted, so glob metacharacters
|
|
3939
|
+
* (`*`, `?`, `[`, `]`) must be backslash-escaped to keep the comparison
|
|
3940
|
+
* literal — otherwise a key like `prod*` would also match a runtime
|
|
3941
|
+
* value of `production`. Quote/dollar/backslash are escaped first so the
|
|
3942
|
+
* resulting string remains valid inside a double-quoted literal.
|
|
3943
|
+
*/
|
|
3944
|
+
function fishCaseEscape(s) {
|
|
3945
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/\*/g, "\\*").replace(/\?/g, "\\?").replace(/\[/g, "\\[").replace(/]/g, "\\]");
|
|
3946
|
+
}
|
|
3947
|
+
/**
|
|
3948
|
+
* Generate fish value completion lines for a ValueCompletion spec.
|
|
3949
|
+
* Each line outputs candidates via echo (tab-separated value\tdescription).
|
|
3950
|
+
*
|
|
3951
|
+
* `location` is required for the expand variant (carries fieldName +
|
|
3952
|
+
* isArrayOption); other variants ignore it.
|
|
3953
|
+
*/
|
|
3954
|
+
function fishValueLines(vc, fn, location) {
|
|
3955
|
+
if (!vc) return [];
|
|
3956
|
+
switch (vc.type) {
|
|
3957
|
+
case "expand": {
|
|
3958
|
+
if (!location) throw new Error("fishValueLines: expand variant requires a location");
|
|
3959
|
+
const depExpr = (d) => {
|
|
3960
|
+
const safe = sanitize(d.name);
|
|
3961
|
+
return d.isGlobal ? `$_global_arg_values_${safe}` : `$_arg_values_${safe}`;
|
|
3962
|
+
};
|
|
3963
|
+
const depKey = location.resolvedDeps.map((d) => `"${depExpr(d)}"`).join(`\\x1f`);
|
|
3964
|
+
const bucket = sanitize(location.fieldName);
|
|
3965
|
+
const bucketList = location.isGlobal ? `$_global_used_field_keys_${bucket}` : `$_used_field_keys_${bucket}`;
|
|
3966
|
+
const out = [`switch ${depKey}`];
|
|
3967
|
+
for (const entry of vc.table) {
|
|
3968
|
+
const casePattern = entry.key.map((k) => `"${fishCaseEscape(k)}"`).join(`\\x1f`);
|
|
3969
|
+
out.push(` case ${casePattern}`);
|
|
3970
|
+
const keyOnlyLines = [];
|
|
3971
|
+
const fullLines = [];
|
|
3972
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
3973
|
+
const printfLine = (value, description) => description ? `printf '%s\\t%s\\n' "${escapeDesc$1(value)}" "${escapeDesc$1(description)}"` : `printf '%s\\n' "${escapeDesc$1(value)}"`;
|
|
3974
|
+
const wrapWithDedup = (echoLine, keyPart) => location.isArrayOption && keyPart.length > 0 ? [
|
|
3975
|
+
` if not contains -- "${escapeDesc$1(keyPart)}" ${bucketList}`,
|
|
2623
3976
|
` ${echoLine}`,
|
|
2624
3977
|
` end`
|
|
2625
3978
|
] : [` ${echoLine}`];
|
|
@@ -2658,6 +4011,7 @@ function fishValueLines(vc, fn, location) {
|
|
|
2658
4011
|
`end`
|
|
2659
4012
|
];
|
|
2660
4013
|
case "none": return [];
|
|
4014
|
+
case "runtime-expand": return [];
|
|
2661
4015
|
}
|
|
2662
4016
|
}
|
|
2663
4017
|
/** Generate fish matcher-filtered file completion */
|
|
@@ -2679,9 +4033,13 @@ function fishMatcherLines(patterns) {
|
|
|
2679
4033
|
function fishExtensionLines(extensions) {
|
|
2680
4034
|
const lines = [];
|
|
2681
4035
|
lines.push(`__fish_complete_directories "$_cur"`);
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
lines.push(`
|
|
4036
|
+
if (extensions.length > 0) {
|
|
4037
|
+
const extMatch = extensions.map((ext) => `string match -q -- "*.${ext}" "$_f"`).join("; or ");
|
|
4038
|
+
lines.push(`for _f in "$_cur"*`);
|
|
4039
|
+
lines.push(` test -f "$_f"; or continue`);
|
|
4040
|
+
lines.push(` if ${extMatch}`);
|
|
4041
|
+
lines.push(` echo "$_f"`);
|
|
4042
|
+
lines.push(` end`);
|
|
2685
4043
|
lines.push(`end`);
|
|
2686
4044
|
}
|
|
2687
4045
|
return lines;
|
|
@@ -2718,6 +4076,27 @@ function positionalBlock$1(positionals, fn, options = []) {
|
|
|
2718
4076
|
}
|
|
2719
4077
|
return lines;
|
|
2720
4078
|
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Subcommand-name echoes. When the same node also has positionals, complete
|
|
4081
|
+
* subcommand names only while the cursor still prefixes one and fall through to
|
|
4082
|
+
* positional completion otherwise. Returns lines at base indentation; callers
|
|
4083
|
+
* re-indent for their handler depth.
|
|
4084
|
+
*/
|
|
4085
|
+
function subOrPositionalLines$1(subItems, positionals, fn, options) {
|
|
4086
|
+
const echoSubs = subItems.map((s) => `echo "${s.name}\t${escapeDesc$1(s.description ?? "")}"`);
|
|
4087
|
+
if (positionals.length === 0) return echoSubs;
|
|
4088
|
+
return [
|
|
4089
|
+
`set -l _sub_match 0`,
|
|
4090
|
+
`for _sub_name in ${subItems.map((s) => `"${escapeDesc$1(s.name)}"`).join(" ")}`,
|
|
4091
|
+
` test (string sub -l (string length -- "$_cur") -- "$_sub_name") = "$_cur"; and set _sub_match 1; and break`,
|
|
4092
|
+
`end`,
|
|
4093
|
+
`if test $_sub_match -eq 1`,
|
|
4094
|
+
...echoSubs.map((l) => ` ${l}`),
|
|
4095
|
+
`else`,
|
|
4096
|
+
...positionalBlock$1(positionals, fn, options),
|
|
4097
|
+
`end`
|
|
4098
|
+
];
|
|
4099
|
+
}
|
|
2721
4100
|
/** Generate available-option echo lines for fish */
|
|
2722
4101
|
function availableOptionLines$1(options, fn) {
|
|
2723
4102
|
const lines = [];
|
|
@@ -2773,11 +4152,10 @@ function generateSubHandler$1(sub, fn, path) {
|
|
|
2773
4152
|
lines.push(...availableOptionLines$1(sub.options, fn));
|
|
2774
4153
|
lines.push(` return`);
|
|
2775
4154
|
lines.push(` end`);
|
|
2776
|
-
if (visibleSubs.length > 0)
|
|
2777
|
-
const
|
|
2778
|
-
lines.push(`
|
|
2779
|
-
}
|
|
2780
|
-
else if (sub.positionals.length > 0) lines.push(...positionalBlock$1(sub.positionals, fn, sub.options));
|
|
4155
|
+
if (visibleSubs.length > 0) {
|
|
4156
|
+
const subItems = getSubNamesWithAliases(sub.subcommands);
|
|
4157
|
+
lines.push(...subOrPositionalLines$1(subItems, sub.positionals, fn, sub.options).map((l) => ` ${l}`));
|
|
4158
|
+
} else if (sub.positionals.length > 0) lines.push(...positionalBlock$1(sub.positionals, fn, sub.options));
|
|
2781
4159
|
lines.push(`end`);
|
|
2782
4160
|
lines.push(``);
|
|
2783
4161
|
return lines;
|
|
@@ -2795,7 +4173,9 @@ function optTakesValueCases(sub, parentPath) {
|
|
|
2795
4173
|
function generateFishCompletion(command, options) {
|
|
2796
4174
|
const { programName } = options;
|
|
2797
4175
|
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
2798
|
-
const
|
|
4176
|
+
const baseFn = sanitize(programName);
|
|
4177
|
+
const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
|
|
4178
|
+
const isWorker = options.staticWorker !== void 0;
|
|
2799
4179
|
const root = data.command;
|
|
2800
4180
|
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
2801
4181
|
const expandSpecs = collectExpandSpecs(root);
|
|
@@ -2810,26 +4190,34 @@ function generateFishCompletion(command, options) {
|
|
|
2810
4190
|
binPath: options.binPath,
|
|
2811
4191
|
programVersion: options.programVersion
|
|
2812
4192
|
}));
|
|
4193
|
+
lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
|
|
4194
|
+
if (isWorker) lines.push(`# politty-completion-worker: true`);
|
|
2813
4195
|
lines.push(`# Generated by politty`);
|
|
2814
4196
|
lines.push(``);
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
4197
|
+
if (!isWorker) {
|
|
4198
|
+
const resolvedBinPath = resolveBinPath(programName, options.binPath);
|
|
4199
|
+
const sig = computeBinSig(resolvedBinPath);
|
|
4200
|
+
const refreshEnvName = binEnvVarName(baseFn);
|
|
4201
|
+
const refreshFn = `__${fn}_refresh_completion`;
|
|
4202
|
+
lines.push(`function ${refreshFn} --no-scope-shadowing`);
|
|
4203
|
+
lines.push(` set -l _bin $${refreshEnvName}`);
|
|
4204
|
+
lines.push(` test -z "$_bin"; and set _bin (command -v ${programName})`);
|
|
4205
|
+
lines.push(` test -z "$_bin"; and return 1`);
|
|
4206
|
+
lines.push(` set -l _sig ${statSigExpr("$_bin", { shell: "fish" })}`);
|
|
4207
|
+
lines.push(` test "$_sig" = "${sig}"; and test "$_bin" = "${escapeDesc$1(resolvedBinPath)}"; and return 1`);
|
|
4208
|
+
lines.push(` set -l _target (status current-filename)`);
|
|
4209
|
+
lines.push(` test -n "$_target"; and test -f "$_target"; or return 1`);
|
|
4210
|
+
lines.push(` "$_bin" __refresh-completion fish "$_target" --static 2>/dev/null`);
|
|
4211
|
+
lines.push(` and source "$_target" 2>/dev/null`);
|
|
4212
|
+
lines.push(` and return 0`);
|
|
4213
|
+
lines.push(` return 1`);
|
|
4214
|
+
lines.push(`end`);
|
|
4215
|
+
lines.push(`${refreshFn}`);
|
|
4216
|
+
lines.push(`set -l _politty_refreshed $status`);
|
|
4217
|
+
lines.push(`functions -e ${refreshFn}`);
|
|
4218
|
+
lines.push(`test $_politty_refreshed -eq 0; and return`);
|
|
4219
|
+
lines.push(``);
|
|
4220
|
+
}
|
|
2833
4221
|
if (hasDynamicCompletion(root)) {
|
|
2834
4222
|
lines.push(`function __${fn}_invoke_complete`);
|
|
2835
4223
|
lines.push(` set -l _shell $argv[1]`);
|
|
@@ -2976,10 +4364,8 @@ function generateFishCompletion(command, options) {
|
|
|
2976
4364
|
lines.push(...availableOptionLines$1(root.options, fn));
|
|
2977
4365
|
if (visibleSubs.length > 0) {
|
|
2978
4366
|
lines.push(` else`);
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
lines.push(` echo "${s.name}\t${desc}"`);
|
|
2982
|
-
}
|
|
4367
|
+
const subItems = getSubNamesWithAliases(root.subcommands);
|
|
4368
|
+
lines.push(...subOrPositionalLines$1(subItems, root.positionals, fn, root.options).map((l) => ` ${l}`));
|
|
2983
4369
|
} else if (root.positionals.length > 0) {
|
|
2984
4370
|
lines.push(` else`);
|
|
2985
4371
|
lines.push(...positionalBlock$1(root.positionals, fn, root.options));
|
|
@@ -3088,26 +4474,19 @@ function generateFishCompletion(command, options) {
|
|
|
3088
4474
|
lines.push(` end`);
|
|
3089
4475
|
lines.push(`end`);
|
|
3090
4476
|
lines.push(``);
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
4477
|
+
if (!isWorker) {
|
|
4478
|
+
lines.push(`# Clear existing completions`);
|
|
4479
|
+
lines.push(`complete -e -c ${programName}`);
|
|
4480
|
+
lines.push(``);
|
|
4481
|
+
lines.push(`# Register completion`);
|
|
4482
|
+
lines.push(`complete -c ${programName} -f -a '(__fish_${fn}_complete)'`);
|
|
4483
|
+
}
|
|
3096
4484
|
lines.push(``);
|
|
3097
4485
|
return {
|
|
3098
4486
|
script: lines.join("\n"),
|
|
3099
4487
|
shell: "fish",
|
|
3100
|
-
installInstructions: `# To enable completions, run
|
|
3101
|
-
|
|
3102
|
-
# Option 1: Source directly
|
|
3103
|
-
${programName} completion fish | source
|
|
3104
|
-
|
|
3105
|
-
# Option 2: Save to the fish completions directory
|
|
3106
|
-
${programName} completion fish > ~/.config/fish/completions/${programName}.fish
|
|
3107
|
-
|
|
3108
|
-
# The completion will be available immediately in new shell sessions.
|
|
3109
|
-
# To use in the current session, run:
|
|
3110
|
-
source ~/.config/fish/completions/${programName}.fish`
|
|
4488
|
+
installInstructions: `# To enable auto-refreshing fish completions, run:
|
|
4489
|
+
${programName} completion fish --install --static`
|
|
3111
4490
|
};
|
|
3112
4491
|
}
|
|
3113
4492
|
|
|
@@ -3122,38 +4501,30 @@ source ~/.config/fish/completions/${programName}.fish`
|
|
|
3122
4501
|
* 1. Looks up the binary on $PATH.
|
|
3123
4502
|
* 2. Reads its mtime.
|
|
3124
4503
|
* 3. If the on-disk completion cache is missing or its
|
|
3125
|
-
* `# politty-bin-sig:`
|
|
3126
|
-
* spawning the binary once.
|
|
4504
|
+
* `# politty-bin-sig:` / `# politty-bin-path:` headers differ,
|
|
4505
|
+
* regenerates the cache by spawning the binary once.
|
|
3127
4506
|
* 4. Sources the cache.
|
|
3128
4507
|
*
|
|
3129
4508
|
* All failure modes are silent no-ops so a broken / missing CLI never
|
|
3130
4509
|
* blocks shell startup.
|
|
3131
4510
|
*/
|
|
3132
|
-
/**
|
|
3133
|
-
* Single-quote escape: `'` -> `'\''`. Inside single quotes the shell
|
|
3134
|
-
* performs no expansion at all, so `$`, backticks, and `$(...)` are
|
|
3135
|
-
* inert. Used for hardcoded paths because callers may sources them
|
|
3136
|
-
* from env / config — we must not let metachars in the path execute as
|
|
3137
|
-
* commands when the rc snippet is sourced.
|
|
3138
|
-
*/
|
|
3139
|
-
function shSingleQuote(s) {
|
|
3140
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3141
|
-
}
|
|
3142
4511
|
function bashCachePathExpr(programName, cacheDir, shell) {
|
|
3143
4512
|
if (cacheDir) return shSingleQuote(`${cacheDir}/completion.${shell}`);
|
|
3144
4513
|
return `"\${XDG_CACHE_HOME:-$HOME/.cache}/${programName}/completion.${shell}"`;
|
|
3145
4514
|
}
|
|
3146
4515
|
function generateBashLoader(opts) {
|
|
3147
4516
|
const fn = sanitize(opts.programName);
|
|
4517
|
+
const envName = binEnvVarName(fn);
|
|
3148
4518
|
const cache = bashCachePathExpr(opts.programName, opts.cacheDir, "bash");
|
|
3149
4519
|
return `__${fn}_load_completion() {
|
|
3150
|
-
local _bin _cache _sig
|
|
3151
|
-
_bin
|
|
4520
|
+
local _bin _cache _sig _sig_hdr _path_hdr
|
|
4521
|
+
_bin="\${${envName}:-$(type -P ${opts.programName} 2>/dev/null)}"
|
|
3152
4522
|
[[ -n "$_bin" ]] || return 0
|
|
3153
4523
|
_cache=${cache}
|
|
3154
4524
|
_sig=$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null) || return 0
|
|
3155
|
-
|
|
3156
|
-
|
|
4525
|
+
_sig_hdr="# politty-bin-sig: $_sig"
|
|
4526
|
+
_path_hdr="# politty-bin-path: $_bin"
|
|
4527
|
+
if [[ ! -f "$_cache" ]] || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_sig_hdr" || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_path_hdr"; then
|
|
3157
4528
|
# Use the hidden __refresh-completion subcommand instead of
|
|
3158
4529
|
# \`$_bin completion bash\`: the foreground completion command
|
|
3159
4530
|
# is subject to user setup/cleanup/prompt and required
|
|
@@ -3175,17 +4546,19 @@ unset -f __${fn}_load_completion
|
|
|
3175
4546
|
}
|
|
3176
4547
|
function generateZshLoader(opts) {
|
|
3177
4548
|
const fn = sanitize(opts.programName);
|
|
4549
|
+
const envName = binEnvVarName(fn);
|
|
3178
4550
|
const cache = bashCachePathExpr(opts.programName, opts.cacheDir, "zsh");
|
|
3179
4551
|
return `__${fn}_load_completion() {
|
|
3180
4552
|
emulate -L zsh
|
|
3181
4553
|
setopt local_options no_aliases
|
|
3182
|
-
local _bin _cache _sig
|
|
3183
|
-
_bin
|
|
4554
|
+
local _bin _cache _sig _sig_hdr _path_hdr
|
|
4555
|
+
_bin="\${${envName}:-$(whence -p ${opts.programName} 2>/dev/null)}"
|
|
3184
4556
|
[[ -n "$_bin" ]] || return 0
|
|
3185
4557
|
_cache=${cache}
|
|
3186
4558
|
_sig=$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null) || return 0
|
|
3187
|
-
|
|
3188
|
-
|
|
4559
|
+
_sig_hdr="# politty-bin-sig: $_sig"
|
|
4560
|
+
_path_hdr="# politty-bin-path: $_bin"
|
|
4561
|
+
if [[ ! -f "$_cache" ]] || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_sig_hdr" || ! head -8 "$_cache" 2>/dev/null | grep -qxF "$_path_hdr"; then
|
|
3189
4562
|
# See bash loader for why we use __refresh-completion instead
|
|
3190
4563
|
# of \`$_bin completion zsh\`.
|
|
3191
4564
|
"$_bin" __refresh-completion zsh 2>/dev/null
|
|
@@ -3257,11 +4630,14 @@ function generateScript(ctx, shell) {
|
|
|
3257
4630
|
return generateCompletion(ctx.rootCommand, {
|
|
3258
4631
|
shell,
|
|
3259
4632
|
programName: ctx.programName,
|
|
4633
|
+
mode: ctx.completionMode ?? "dispatcher",
|
|
3260
4634
|
includeDescriptions: true,
|
|
3261
4635
|
...ctx.programVersion !== void 0 && { programVersion: ctx.programVersion },
|
|
3262
4636
|
...ctx.binPath !== void 0 && { binPath: ctx.binPath },
|
|
3263
4637
|
...ctx.cacheDir !== void 0 && { cacheDir: ctx.cacheDir },
|
|
3264
|
-
...ctx.globalArgsSchema !== void 0 && { globalArgsSchema: ctx.globalArgsSchema }
|
|
4638
|
+
...ctx.globalArgsSchema !== void 0 && { globalArgsSchema: ctx.globalArgsSchema },
|
|
4639
|
+
...ctx.bundledWorker !== void 0 && { bundledWorker: ctx.bundledWorker },
|
|
4640
|
+
...ctx.staticWorker !== void 0 && { staticWorker: ctx.staticWorker }
|
|
3265
4641
|
}).script;
|
|
3266
4642
|
}
|
|
3267
4643
|
/** Write the script for `shell` to its install path. Returns the path. */
|
|
@@ -3284,6 +4660,34 @@ function readCachedSig(path) {
|
|
|
3284
4660
|
return null;
|
|
3285
4661
|
}
|
|
3286
4662
|
}
|
|
4663
|
+
function readCachedMode(path) {
|
|
4664
|
+
try {
|
|
4665
|
+
if (!(0, node_fs.existsSync)(path)) return void 0;
|
|
4666
|
+
const m = (0, node_fs.readFileSync)(path, "utf8").split("\n", 10).join("\n").match(/^# politty-completion-mode: (dispatcher|static)$/m);
|
|
4667
|
+
if (m) return m[1];
|
|
4668
|
+
return;
|
|
4669
|
+
} catch {
|
|
4670
|
+
return;
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
function readCachedBinPath(path) {
|
|
4674
|
+
try {
|
|
4675
|
+
if (!(0, node_fs.existsSync)(path)) return null;
|
|
4676
|
+
const m = (0, node_fs.readFileSync)(path, "utf8").split("\n", 10).join("\n").match(/^# politty-bin-path: (.*)$/m);
|
|
4677
|
+
return m ? m[1] : null;
|
|
4678
|
+
} catch {
|
|
4679
|
+
return null;
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
function isManagedTarget(path, programName, shell) {
|
|
4683
|
+
try {
|
|
4684
|
+
if (!(0, node_fs.existsSync)(path)) return false;
|
|
4685
|
+
const lines = (0, node_fs.readFileSync)(path, "utf8").split("\n", 8).map((line) => line.trimEnd());
|
|
4686
|
+
return lines.some((line) => /^# politty-completion-version: \S+$/.test(line)) && lines.includes(`# program: ${programName}`) && lines.includes(`# shell: ${shell}`);
|
|
4687
|
+
} catch {
|
|
4688
|
+
return false;
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
3287
4691
|
/**
|
|
3288
4692
|
* Rewrite the cache only when stale. Used by:
|
|
3289
4693
|
* - `<program> __refresh-completion <shell>` (the hidden subcommand
|
|
@@ -3299,7 +4703,9 @@ function readCachedSig(path) {
|
|
|
3299
4703
|
*/
|
|
3300
4704
|
function refreshIfStale(ctx, shell) {
|
|
3301
4705
|
try {
|
|
3302
|
-
const target = installPath(ctx.programName, shell, ctx.cacheDir);
|
|
4706
|
+
const target = ctx.targetPath ? (0, node_fs.existsSync)(ctx.targetPath) ? (0, node_fs.realpathSync)(ctx.targetPath) : ctx.targetPath : installPath(ctx.programName, shell, ctx.cacheDir);
|
|
4707
|
+
if (ctx.targetPath && (0, node_fs.existsSync)(target) && !isManagedTarget(target, ctx.programName, shell)) return;
|
|
4708
|
+
if (ctx.targetPath && !(0, node_fs.existsSync)(target) && !ctx.allowTargetCreate) return;
|
|
3303
4709
|
const binPath = resolveBinPath(ctx.programName, ctx.binPath);
|
|
3304
4710
|
if (!binPath) return;
|
|
3305
4711
|
let currentSig;
|
|
@@ -3308,8 +4714,12 @@ function refreshIfStale(ctx, shell) {
|
|
|
3308
4714
|
} catch {
|
|
3309
4715
|
return;
|
|
3310
4716
|
}
|
|
3311
|
-
if (readCachedSig(target) === currentSig) return;
|
|
3312
|
-
|
|
4717
|
+
if (readCachedSig(target) === currentSig && readCachedBinPath(target) === binPath) return;
|
|
4718
|
+
const completionMode = ctx.completionMode ?? readCachedMode(target) ?? ((0, node_fs.existsSync)(target) ? "static" : "dispatcher");
|
|
4719
|
+
writeAtomic(target, generateScript({
|
|
4720
|
+
...ctx,
|
|
4721
|
+
completionMode
|
|
4722
|
+
}, shell));
|
|
3313
4723
|
} catch {}
|
|
3314
4724
|
}
|
|
3315
4725
|
/**
|
|
@@ -3361,6 +4771,53 @@ function escapeDesc(s) {
|
|
|
3361
4771
|
function escapeDescribeValue(s) {
|
|
3362
4772
|
return s.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
|
|
3363
4773
|
}
|
|
4774
|
+
/** Escape a string for use inside zsh double-quotes. */
|
|
4775
|
+
function escapeZshDQ(s) {
|
|
4776
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
4777
|
+
}
|
|
4778
|
+
function zshArrayLiteral(values) {
|
|
4779
|
+
return values.map((v) => `"${escapeZshDQ(v)}"`).join(" ");
|
|
4780
|
+
}
|
|
4781
|
+
function zshFilteredFileLines(fn, kind, values) {
|
|
4782
|
+
const lines = [
|
|
4783
|
+
`setopt local_options null_glob`,
|
|
4784
|
+
`local _cur="\${words[CURRENT]:-}" _dir _prefix _f _out`,
|
|
4785
|
+
`local -a ${kind === "extensions" ? "_exts" : "_matchers"}=(${zshArrayLiteral(values)})`,
|
|
4786
|
+
`if [[ -z "$_cur" || "$_cur" != */* ]]; then`,
|
|
4787
|
+
` _dir="."`,
|
|
4788
|
+
` _prefix="$_cur"`,
|
|
4789
|
+
`elif [[ "$_cur" == */ ]]; then`,
|
|
4790
|
+
` _dir="\${_cur%/}"`,
|
|
4791
|
+
` _prefix=""`,
|
|
4792
|
+
`else`,
|
|
4793
|
+
` _dir="\${_cur%/*}"`,
|
|
4794
|
+
` _prefix="\${_cur##*/}"`,
|
|
4795
|
+
`fi`,
|
|
4796
|
+
`for _f in "$_dir"/"$_prefix"*(N/); do`,
|
|
4797
|
+
` _out="\${_f#./}"`,
|
|
4798
|
+
` __${fn}_add_path_candidate "$_out"`,
|
|
4799
|
+
`done`
|
|
4800
|
+
];
|
|
4801
|
+
if (kind === "extensions") {
|
|
4802
|
+
lines.push(`local _ext`);
|
|
4803
|
+
lines.push(`for _f in "$_dir"/"$_prefix"*(N.); do`);
|
|
4804
|
+
lines.push(` _out="\${_f#./}"`);
|
|
4805
|
+
lines.push(` for _ext in "\${_exts[@]}"; do`);
|
|
4806
|
+
lines.push(` [[ -n "$_ext" && "$_out" == *."$_ext" ]] && { __${fn}_add_path_candidate "$_out"; break; }`);
|
|
4807
|
+
lines.push(` done`);
|
|
4808
|
+
lines.push(`done`);
|
|
4809
|
+
} else {
|
|
4810
|
+
lines.push(`local _pat`);
|
|
4811
|
+
lines.push(`for _pat in "\${_matchers[@]}"; do`);
|
|
4812
|
+
lines.push(` [[ -n "$_pat" ]] || continue`);
|
|
4813
|
+
lines.push(` for _f in "$_dir"/\${~_pat}(N.); do`);
|
|
4814
|
+
lines.push(` _out="\${_f#./}"`);
|
|
4815
|
+
lines.push(` [[ "$_out" == "$_cur"* ]] && __${fn}_add_path_candidate "$_out"`);
|
|
4816
|
+
lines.push(` done`);
|
|
4817
|
+
lines.push(`done`);
|
|
4818
|
+
}
|
|
4819
|
+
return lines;
|
|
4820
|
+
}
|
|
3364
4821
|
/**
|
|
3365
4822
|
* Generate zsh value completion lines for a ValueCompletion spec.
|
|
3366
4823
|
* Uses `_vals` array (must be declared in the calling function scope).
|
|
@@ -3417,12 +4874,13 @@ function zshValueLines(vc, fn, location) {
|
|
|
3417
4874
|
case "dynamic": return [`__${fn}_apply_dynamic_output "$(__${fn}_invoke_complete zsh "\${(@)words[2,CURRENT]}")"`];
|
|
3418
4875
|
case "choices": return [`_vals=(${vc.choices.map((c) => `"${escapeDesc(c)}"`).join(" ")})`, `__${fn}_cdescribe 'completions' _vals`];
|
|
3419
4876
|
case "file":
|
|
3420
|
-
if (vc.matcher?.length) return
|
|
3421
|
-
if (vc.extensions?.length) return
|
|
4877
|
+
if (vc.matcher?.length) return zshFilteredFileLines(fn, "matchers", vc.matcher);
|
|
4878
|
+
if (vc.extensions?.length) return zshFilteredFileLines(fn, "extensions", vc.extensions);
|
|
3422
4879
|
return [`_files`];
|
|
3423
4880
|
case "directory": return [`_files -/`];
|
|
3424
4881
|
case "command": return [`_vals=("\${(@f)$(${vc.shellCommand})}")`, `__${fn}_cdescribe 'completions' _vals`];
|
|
3425
4882
|
case "none": return [];
|
|
4883
|
+
case "runtime-expand": return [];
|
|
3426
4884
|
}
|
|
3427
4885
|
}
|
|
3428
4886
|
/** Generate option-value case branches */
|
|
@@ -3461,6 +4919,31 @@ function positionalBlock(positionals, fn, funcSuffix, options = []) {
|
|
|
3461
4919
|
lines.push(` esac`);
|
|
3462
4920
|
return lines;
|
|
3463
4921
|
}
|
|
4922
|
+
/**
|
|
4923
|
+
* Subcommand completion via `_describe`. When the same node also has
|
|
4924
|
+
* positionals, complete subcommand names only while the cursor still prefixes
|
|
4925
|
+
* one and fall through to positional completion otherwise. Returns lines at
|
|
4926
|
+
* base indentation; callers re-indent for their handler depth.
|
|
4927
|
+
*/
|
|
4928
|
+
function subOrPositionalLines(subItems, positionals, fn, funcSuffix, options) {
|
|
4929
|
+
const describe = [`local -a _subs=(${subItems.map((s) => {
|
|
4930
|
+
const desc = s.description ? `:${escapeDesc(s.description)}` : "";
|
|
4931
|
+
return `"${s.name}${desc}"`;
|
|
4932
|
+
}).join(" ")})`, `__${fn}_cdescribe 'subcommands' _subs`];
|
|
4933
|
+
if (positionals.length === 0) return describe;
|
|
4934
|
+
return [
|
|
4935
|
+
`local -a _sub_names=(${subItems.map((s) => `"${escapeZshDQ(s.name)}"`).join(" ")})`,
|
|
4936
|
+
`local _cur_word="\${words[CURRENT]:-}" _sub_name _sub_match=0`,
|
|
4937
|
+
`for _sub_name in "\${_sub_names[@]}"; do`,
|
|
4938
|
+
` [[ "$_sub_name" == "$_cur_word"* ]] && _sub_match=1 && break`,
|
|
4939
|
+
`done`,
|
|
4940
|
+
`if (( _sub_match )); then`,
|
|
4941
|
+
...describe.map((l) => ` ${l}`),
|
|
4942
|
+
`else`,
|
|
4943
|
+
...positionalBlock(positionals, fn, funcSuffix, options),
|
|
4944
|
+
`fi`
|
|
4945
|
+
];
|
|
4946
|
+
}
|
|
3464
4947
|
/** Generate prev-word value completion case block */
|
|
3465
4948
|
function valueCompletionBlock(options, positionals, fn, funcSuffix) {
|
|
3466
4949
|
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
@@ -3532,12 +5015,8 @@ function generateSubHandler(sub, fn, path) {
|
|
|
3532
5015
|
lines.push(` return 0`);
|
|
3533
5016
|
lines.push(` fi`);
|
|
3534
5017
|
if (visibleSubs.length > 0) {
|
|
3535
|
-
const subItems = getSubNamesWithAliases(sub.subcommands)
|
|
3536
|
-
|
|
3537
|
-
return `"${s.name}${desc}"`;
|
|
3538
|
-
}).join(" ");
|
|
3539
|
-
lines.push(` local -a _subs=(${subItems})`);
|
|
3540
|
-
lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
|
|
5018
|
+
const subItems = getSubNamesWithAliases(sub.subcommands);
|
|
5019
|
+
lines.push(...subOrPositionalLines(subItems, sub.positionals, fn, funcSuffix, sub.options).map((l) => ` ${l}`));
|
|
3541
5020
|
} else if (sub.positionals.length > 0) lines.push(...positionalBlock(sub.positionals, fn, funcSuffix, sub.options));
|
|
3542
5021
|
lines.push(`}`);
|
|
3543
5022
|
lines.push(``);
|
|
@@ -3546,7 +5025,11 @@ function generateSubHandler(sub, fn, path) {
|
|
|
3546
5025
|
function generateZshCompletion(command, options) {
|
|
3547
5026
|
const { programName } = options;
|
|
3548
5027
|
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
3549
|
-
const
|
|
5028
|
+
const baseFn = sanitize(programName);
|
|
5029
|
+
const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
|
|
5030
|
+
const isWorker = options.staticWorker !== void 0;
|
|
5031
|
+
const completionFn = isWorker ? `_${fn}_completions` : `_${programName}`;
|
|
5032
|
+
const autoloadCheck = `"\${funcstack[1]:-}" == "${escapeZshDQ(completionFn)}"`;
|
|
3550
5033
|
const root = data.command;
|
|
3551
5034
|
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
3552
5035
|
const expandSpecs = collectExpandSpecs(root);
|
|
@@ -3555,16 +5038,24 @@ function generateZshCompletion(command, options) {
|
|
|
3555
5038
|
const arrayExpandSpecs = expandSpecs.filter((s) => s.isArrayOption);
|
|
3556
5039
|
const hasArrayExpand = arrayExpandSpecs.length > 0;
|
|
3557
5040
|
const lines = [];
|
|
3558
|
-
|
|
3559
|
-
|
|
5041
|
+
if (!isWorker) {
|
|
5042
|
+
lines.push(`#compdef ${programName}`);
|
|
5043
|
+
lines.push(``);
|
|
5044
|
+
}
|
|
3560
5045
|
lines.push(...buildHeaderLines({
|
|
3561
5046
|
programName,
|
|
3562
5047
|
shell: "zsh",
|
|
3563
5048
|
binPath: options.binPath,
|
|
3564
5049
|
programVersion: options.programVersion
|
|
3565
5050
|
}));
|
|
5051
|
+
lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
|
|
5052
|
+
if (isWorker) lines.push(`# politty-completion-worker: true`);
|
|
3566
5053
|
lines.push(`# Generated by politty`);
|
|
3567
5054
|
lines.push(``);
|
|
5055
|
+
if (!isWorker) lines.push(...generateZshSelfRefresh({
|
|
5056
|
+
programName,
|
|
5057
|
+
binPath: options.binPath
|
|
5058
|
+
}));
|
|
3568
5059
|
for (const spec of expandSpecs) {
|
|
3569
5060
|
const varName = expandTableVarName(fn, spec.funcSuffix, spec.fieldName);
|
|
3570
5061
|
if (spec.vc.table.length === 0) lines.push(`typeset -gA ${varName}=()`);
|
|
@@ -3637,6 +5128,11 @@ function generateZshCompletion(command, options) {
|
|
|
3637
5128
|
lines.push(` return 0`);
|
|
3638
5129
|
lines.push(`}`);
|
|
3639
5130
|
lines.push(``);
|
|
5131
|
+
lines.push(`__${fn}_add_path_candidate() {`);
|
|
5132
|
+
lines.push(` compadd -f -- "$1" 2>/dev/null && return 0`);
|
|
5133
|
+
lines.push(` print -r -- "$1"`);
|
|
5134
|
+
lines.push(`}`);
|
|
5135
|
+
lines.push(``);
|
|
3640
5136
|
lines.push(`__${fn}_opt_takes_value() {`);
|
|
3641
5137
|
lines.push(` case "$1:$2" in`);
|
|
3642
5138
|
lines.push(...optTakesValueEntries(root, ""));
|
|
@@ -3693,12 +5189,8 @@ function generateZshCompletion(command, options) {
|
|
|
3693
5189
|
lines.push(` __${fn}_cdescribe 'options' _opts`);
|
|
3694
5190
|
if (visibleSubs.length > 0) {
|
|
3695
5191
|
lines.push(` else`);
|
|
3696
|
-
const subItems = getSubNamesWithAliases(root.subcommands)
|
|
3697
|
-
|
|
3698
|
-
return `"${s.name}${desc}"`;
|
|
3699
|
-
}).join(" ");
|
|
3700
|
-
lines.push(` local -a _subs=(${subItems})`);
|
|
3701
|
-
lines.push(` __${fn}_cdescribe 'subcommands' _subs`);
|
|
5192
|
+
const subItems = getSubNamesWithAliases(root.subcommands);
|
|
5193
|
+
lines.push(...subOrPositionalLines(subItems, root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
3702
5194
|
} else if (root.positionals.length > 0) {
|
|
3703
5195
|
lines.push(` else`);
|
|
3704
5196
|
lines.push(...positionalBlock(root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
@@ -3707,7 +5199,7 @@ function generateZshCompletion(command, options) {
|
|
|
3707
5199
|
lines.push(`}`);
|
|
3708
5200
|
lines.push(``);
|
|
3709
5201
|
const subRouting = subDispatchCaseLines(routeEntries, fn).join("\n");
|
|
3710
|
-
lines.push(
|
|
5202
|
+
lines.push(`${completionFn}() {`);
|
|
3711
5203
|
lines.push(` (( CURRENT )) || CURRENT=\${#words}`);
|
|
3712
5204
|
lines.push(``);
|
|
3713
5205
|
lines.push(` local _subcmd="" _after_dd=0 _pos_count=0 _skip_next=0`);
|
|
@@ -3773,22 +5265,30 @@ function generateZshCompletion(command, options) {
|
|
|
3773
5265
|
lines.push(``);
|
|
3774
5266
|
lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
|
|
3775
5267
|
lines.push(``);
|
|
3776
|
-
|
|
3777
|
-
|
|
5268
|
+
if (!isWorker) {
|
|
5269
|
+
lines.push(`if [[ ${autoloadCheck} ]]; then`);
|
|
5270
|
+
lines.push(` ${completionFn} "$@"`);
|
|
5271
|
+
lines.push(`else`);
|
|
5272
|
+
lines.push(` compdef ${completionFn} ${programName}`);
|
|
5273
|
+
lines.push(`fi`);
|
|
5274
|
+
lines.push(``);
|
|
5275
|
+
}
|
|
3778
5276
|
return {
|
|
3779
5277
|
script: lines.join("\n"),
|
|
3780
5278
|
shell: "zsh",
|
|
3781
|
-
installInstructions: `# To enable completions, add
|
|
5279
|
+
installInstructions: `# To enable auto-refreshing zsh completions, add this to your ~/.zshrc after compinit:
|
|
5280
|
+
eval "$(${programName} completion zsh --static)"
|
|
3782
5281
|
|
|
3783
|
-
#
|
|
3784
|
-
|
|
5282
|
+
# For faster shell startup, save the script in your fpath:
|
|
5283
|
+
mkdir -p ~/.zsh/completions
|
|
5284
|
+
${programName} completion zsh --static > ~/.zsh/completions/_${programName}
|
|
3785
5285
|
|
|
3786
|
-
#
|
|
3787
|
-
|
|
5286
|
+
# Make sure your ~/.zshrc includes the fpath line before compinit:
|
|
5287
|
+
fpath=(~/.zsh/completions $fpath)
|
|
5288
|
+
autoload -Uz compinit && compinit
|
|
3788
5289
|
|
|
3789
|
-
#
|
|
3790
|
-
#
|
|
3791
|
-
# autoload -Uz compinit && compinit
|
|
5290
|
+
# If ~/.zshrc already calls compinit, add only the fpath line before
|
|
5291
|
+
# the existing compinit call.
|
|
3792
5292
|
|
|
3793
5293
|
# Then reload your shell or run:
|
|
3794
5294
|
source ~/.zshrc`
|
|
@@ -3826,6 +5326,7 @@ source ~/.zshrc`
|
|
|
3826
5326
|
* Generate completion script for the specified shell
|
|
3827
5327
|
*/
|
|
3828
5328
|
function generateCompletion(command, options) {
|
|
5329
|
+
if (options.mode === "dispatcher") return generateDispatcherCompletion(command, options);
|
|
3829
5330
|
switch (options.shell) {
|
|
3830
5331
|
case "bash": return generateBashCompletion(command, options);
|
|
3831
5332
|
case "zsh": return generateZshCompletion(command, options);
|
|
@@ -3843,6 +5344,18 @@ function getSupportedShells() {
|
|
|
3843
5344
|
"fish"
|
|
3844
5345
|
];
|
|
3845
5346
|
}
|
|
5347
|
+
function printZshFpathSetup(programName, target) {
|
|
5348
|
+
console.error("");
|
|
5349
|
+
console.error("Configure zsh fpath with:");
|
|
5350
|
+
console.error("");
|
|
5351
|
+
console.error(" mkdir -p ~/.zsh/completions");
|
|
5352
|
+
console.error(` ln -sf ${shSingleQuote(target)} ~/.zsh/completions/_${programName}`);
|
|
5353
|
+
console.error("");
|
|
5354
|
+
console.error("Add only this block to your ~/.zshrc before compinit:");
|
|
5355
|
+
console.error("");
|
|
5356
|
+
console.error(" fpath=(~/.zsh/completions $fpath)");
|
|
5357
|
+
console.error(" autoload -Uz compinit && compinit");
|
|
5358
|
+
}
|
|
3846
5359
|
/**
|
|
3847
5360
|
* Detect the current shell from environment
|
|
3848
5361
|
*/
|
|
@@ -3871,15 +5384,36 @@ const completionArgsSchema = zod.z.object({
|
|
|
3871
5384
|
description: "Show installation instructions"
|
|
3872
5385
|
}),
|
|
3873
5386
|
loader: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Print just the rc loader snippet (bash/zsh). Add it to ~/.bashrc or ~/.zshrc; it auto-regenerates the cache when the binary changes." }),
|
|
3874
|
-
install: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Write the completion script to its on-disk cache (bash/zsh) or autoload location (fish) instead of printing it." })
|
|
5387
|
+
install: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Write the completion script to its on-disk cache (bash/zsh) or autoload location (fish) instead of printing it." }),
|
|
5388
|
+
static: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Generate the legacy static completion script with command metadata baked in." }),
|
|
5389
|
+
dispatcher: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Generate the runtime dispatcher completion script. This is the default." }),
|
|
5390
|
+
worker: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Generate an internal static worker artifact for dispatcher mode." })
|
|
5391
|
+
});
|
|
5392
|
+
const refreshArgsSchema = zod.z.object({
|
|
5393
|
+
shell: require_schema_extractor.arg(zod.z.enum([
|
|
5394
|
+
"bash",
|
|
5395
|
+
"zsh",
|
|
5396
|
+
"fish"
|
|
5397
|
+
]), {
|
|
5398
|
+
positional: true,
|
|
5399
|
+
description: "Shell to refresh",
|
|
5400
|
+
placeholder: "SHELL"
|
|
5401
|
+
}),
|
|
5402
|
+
target: require_schema_extractor.arg(zod.z.string().optional(), {
|
|
5403
|
+
positional: true,
|
|
5404
|
+
description: "Existing politty-generated completion file to refresh",
|
|
5405
|
+
placeholder: "TARGET"
|
|
5406
|
+
}),
|
|
5407
|
+
static: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Refresh using the legacy static completion script mode." }),
|
|
5408
|
+
worker: require_schema_extractor.arg(zod.z.boolean().default(false), { description: "Refresh an internal static worker completion script." })
|
|
3875
5409
|
});
|
|
3876
|
-
const
|
|
5410
|
+
const workerPathArgsSchema = zod.z.object({ shell: require_schema_extractor.arg(zod.z.enum([
|
|
3877
5411
|
"bash",
|
|
3878
5412
|
"zsh",
|
|
3879
5413
|
"fish"
|
|
3880
5414
|
]), {
|
|
3881
5415
|
positional: true,
|
|
3882
|
-
description: "Shell to
|
|
5416
|
+
description: "Shell worker to locate",
|
|
3883
5417
|
placeholder: "SHELL"
|
|
3884
5418
|
}) });
|
|
3885
5419
|
/**
|
|
@@ -3903,7 +5437,8 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
3903
5437
|
const refreshExtra = {
|
|
3904
5438
|
...cacheDir !== void 0 && { cacheDir },
|
|
3905
5439
|
...programVersion !== void 0 && { programVersion },
|
|
3906
|
-
...globalArgsSchema !== void 0 && { globalArgsSchema }
|
|
5440
|
+
...globalArgsSchema !== void 0 && { globalArgsSchema },
|
|
5441
|
+
...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker }
|
|
3907
5442
|
};
|
|
3908
5443
|
const installCtxBase = {
|
|
3909
5444
|
programName: resolvedProgramName,
|
|
@@ -3921,6 +5456,10 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
3921
5456
|
...rootCommand.subCommands,
|
|
3922
5457
|
"__refresh-completion": createRefreshCompletionCommand(rootCommand, resolvedProgramName, refreshExtra)
|
|
3923
5458
|
};
|
|
5459
|
+
if (!rootCommand.subCommands?.["__completion-worker-path"]) rootCommand.subCommands = {
|
|
5460
|
+
...rootCommand.subCommands,
|
|
5461
|
+
"__completion-worker-path": createCompletionWorkerPathCommand(resolvedProgramName, refreshExtra)
|
|
5462
|
+
};
|
|
3924
5463
|
return defineCommand({
|
|
3925
5464
|
name: "completion",
|
|
3926
5465
|
description: "Generate shell completion script",
|
|
@@ -3932,18 +5471,23 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
3932
5471
|
process.exitCode = 1;
|
|
3933
5472
|
return;
|
|
3934
5473
|
}
|
|
5474
|
+
if (args.static && args.dispatcher) throw new Error("Choose only one completion mode: --dispatcher or --static.");
|
|
5475
|
+
if (args.worker && !args.static) throw new Error("`--worker` requires `--static`.");
|
|
5476
|
+
if (args.worker && (args.install || args.loader || args.instructions)) throw new Error("`--worker` can only print a worker artifact.");
|
|
5477
|
+
const completionMode = args.static ? "static" : "dispatcher";
|
|
3935
5478
|
if (args.install) {
|
|
3936
5479
|
let target;
|
|
3937
5480
|
try {
|
|
3938
5481
|
target = install({
|
|
3939
5482
|
rootCommand,
|
|
3940
|
-
...installCtxBase
|
|
5483
|
+
...installCtxBase,
|
|
5484
|
+
completionMode
|
|
3941
5485
|
}, shellType);
|
|
3942
5486
|
} catch (e) {
|
|
3943
5487
|
throw new Error(`install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3944
5488
|
}
|
|
3945
5489
|
console.error(`installed: ${target}`);
|
|
3946
|
-
if (shellType
|
|
5490
|
+
if (shellType === "bash") {
|
|
3947
5491
|
console.error("");
|
|
3948
5492
|
console.error(`Add to your ~/.${shellType}rc:`);
|
|
3949
5493
|
console.error("");
|
|
@@ -3951,7 +5495,7 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
3951
5495
|
...loaderOptsBase,
|
|
3952
5496
|
shell: shellType
|
|
3953
5497
|
}).trim().replace(/^/gm, " "));
|
|
3954
|
-
}
|
|
5498
|
+
} else if (shellType === "zsh") printZshFpathSetup(resolvedProgramName, target);
|
|
3955
5499
|
return;
|
|
3956
5500
|
}
|
|
3957
5501
|
if (args.loader) {
|
|
@@ -3965,10 +5509,13 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
3965
5509
|
const result = generateCompletion(rootCommand, {
|
|
3966
5510
|
shell: shellType,
|
|
3967
5511
|
programName: resolvedProgramName,
|
|
5512
|
+
mode: completionMode,
|
|
3968
5513
|
includeDescriptions: true,
|
|
3969
5514
|
...globalArgsSchema !== void 0 && { globalArgsSchema },
|
|
3970
5515
|
...programVersion !== void 0 && { programVersion },
|
|
3971
|
-
...cacheDir !== void 0 && { cacheDir }
|
|
5516
|
+
...cacheDir !== void 0 && { cacheDir },
|
|
5517
|
+
...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker },
|
|
5518
|
+
...args.worker && { staticWorker: { functionSuffix: "worker" } }
|
|
3972
5519
|
});
|
|
3973
5520
|
if (args.instructions) console.log(result.installInstructions);
|
|
3974
5521
|
else console.log(result.script);
|
|
@@ -3989,11 +5536,32 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
|
|
|
3989
5536
|
refreshIfStale({
|
|
3990
5537
|
rootCommand,
|
|
3991
5538
|
programName,
|
|
3992
|
-
...extra
|
|
5539
|
+
...extra,
|
|
5540
|
+
completionMode: args.static || args.worker ? "static" : void 0,
|
|
5541
|
+
...args.worker && { staticWorker: { functionSuffix: "worker" } },
|
|
5542
|
+
...args.worker && { allowTargetCreate: true },
|
|
5543
|
+
...args.target !== void 0 && { targetPath: args.target }
|
|
3993
5544
|
}, args.shell);
|
|
3994
5545
|
}
|
|
3995
5546
|
});
|
|
3996
5547
|
}
|
|
5548
|
+
function createCompletionWorkerPathCommand(programName, extra = {}) {
|
|
5549
|
+
return defineCommand({
|
|
5550
|
+
name: "__completion-worker-path",
|
|
5551
|
+
description: "(internal) Print the bundled completion worker path when available.",
|
|
5552
|
+
args: workerPathArgsSchema,
|
|
5553
|
+
run(args) {
|
|
5554
|
+
const path = resolveBundledWorkerPath({
|
|
5555
|
+
programName,
|
|
5556
|
+
shell: args.shell,
|
|
5557
|
+
...extra.binPath !== void 0 && { binPath: extra.binPath },
|
|
5558
|
+
...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker }
|
|
5559
|
+
});
|
|
5560
|
+
if (!path) throw new Error(`No bundled completion worker found for ${programName} (${args.shell}).`);
|
|
5561
|
+
console.log(path);
|
|
5562
|
+
}
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
3997
5565
|
/**
|
|
3998
5566
|
* Wrap a command with a completion subcommand
|
|
3999
5567
|
*
|
|
@@ -4015,19 +5583,21 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
|
|
|
4015
5583
|
* ```
|
|
4016
5584
|
*/
|
|
4017
5585
|
function withCompletionCommand(command, options) {
|
|
4018
|
-
const { programName, globalArgsSchema, cacheDir, programVersion } = typeof options === "string" ? { programName: options } : options ?? {};
|
|
5586
|
+
const { programName, globalArgsSchema, cacheDir, programVersion, bundledWorker } = typeof options === "string" ? { programName: options } : options ?? {};
|
|
4019
5587
|
const resolvedProgramName = programName ?? command.name;
|
|
4020
5588
|
const extra = {
|
|
4021
5589
|
...cacheDir !== void 0 && { cacheDir },
|
|
4022
5590
|
...programVersion !== void 0 && { programVersion },
|
|
4023
|
-
...globalArgsSchema !== void 0 && { globalArgsSchema }
|
|
5591
|
+
...globalArgsSchema !== void 0 && { globalArgsSchema },
|
|
5592
|
+
...bundledWorker !== void 0 && { bundledWorker }
|
|
4024
5593
|
};
|
|
4025
5594
|
const wrappedCommand = { ...command };
|
|
4026
5595
|
wrappedCommand.subCommands = {
|
|
4027
5596
|
...command.subCommands,
|
|
4028
5597
|
completion: createCompletionCommand(wrappedCommand, programName, globalArgsSchema, extra),
|
|
4029
5598
|
__complete: createDynamicCompleteCommand(wrappedCommand, programName, globalArgsSchema),
|
|
4030
|
-
"__refresh-completion": createRefreshCompletionCommand(wrappedCommand, resolvedProgramName, extra)
|
|
5599
|
+
"__refresh-completion": createRefreshCompletionCommand(wrappedCommand, resolvedProgramName, extra),
|
|
5600
|
+
"__completion-worker-path": createCompletionWorkerPathCommand(resolvedProgramName, extra)
|
|
4031
5601
|
};
|
|
4032
5602
|
wrappedCommand.runMainHook = (argv) => {
|
|
4033
5603
|
maybeSpawnRefresh(argv, {
|
|
@@ -4054,7 +5624,7 @@ function withCompletionCommand(command, options) {
|
|
|
4054
5624
|
function maybeSpawnRefresh(argv, ctx) {
|
|
4055
5625
|
if (process.env.POLITTY_NO_COMPLETION_REFRESH) return;
|
|
4056
5626
|
const firstPositional = argv.find((a) => !a.startsWith("-"));
|
|
4057
|
-
if (firstPositional === "__complete" || firstPositional === "__refresh-completion" || firstPositional === "completion") return;
|
|
5627
|
+
if (firstPositional === "__complete" || firstPositional === "__refresh-completion" || firstPositional === "__completion-worker-path" || firstPositional === "completion") return;
|
|
4058
5628
|
const shell = detectShell();
|
|
4059
5629
|
if (!shell) return;
|
|
4060
5630
|
const argv0 = process.argv[1];
|
|
@@ -4070,12 +5640,24 @@ Object.defineProperty(exports, 'CompletionDirective', {
|
|
|
4070
5640
|
return CompletionDirective;
|
|
4071
5641
|
}
|
|
4072
5642
|
});
|
|
5643
|
+
Object.defineProperty(exports, 'bundledWorkerShellExtension', {
|
|
5644
|
+
enumerable: true,
|
|
5645
|
+
get: function () {
|
|
5646
|
+
return bundledWorkerShellExtension;
|
|
5647
|
+
}
|
|
5648
|
+
});
|
|
4073
5649
|
Object.defineProperty(exports, 'createCompletionCommand', {
|
|
4074
5650
|
enumerable: true,
|
|
4075
5651
|
get: function () {
|
|
4076
5652
|
return createCompletionCommand;
|
|
4077
5653
|
}
|
|
4078
5654
|
});
|
|
5655
|
+
Object.defineProperty(exports, 'createCompletionWorkerPathCommand', {
|
|
5656
|
+
enumerable: true,
|
|
5657
|
+
get: function () {
|
|
5658
|
+
return createCompletionWorkerPathCommand;
|
|
5659
|
+
}
|
|
5660
|
+
});
|
|
4079
5661
|
Object.defineProperty(exports, 'createDefineCommand', {
|
|
4080
5662
|
enumerable: true,
|
|
4081
5663
|
get: function () {
|
|
@@ -4094,6 +5676,12 @@ Object.defineProperty(exports, 'createRefreshCompletionCommand', {
|
|
|
4094
5676
|
return createRefreshCompletionCommand;
|
|
4095
5677
|
}
|
|
4096
5678
|
});
|
|
5679
|
+
Object.defineProperty(exports, 'defaultBundledWorkerOutputPath', {
|
|
5680
|
+
enumerable: true,
|
|
5681
|
+
get: function () {
|
|
5682
|
+
return defaultBundledWorkerOutputPath;
|
|
5683
|
+
}
|
|
5684
|
+
});
|
|
4097
5685
|
Object.defineProperty(exports, 'defineCommand', {
|
|
4098
5686
|
enumerable: true,
|
|
4099
5687
|
get: function () {
|
|
@@ -4124,6 +5712,12 @@ Object.defineProperty(exports, 'formatForShell', {
|
|
|
4124
5712
|
return formatForShell;
|
|
4125
5713
|
}
|
|
4126
5714
|
});
|
|
5715
|
+
Object.defineProperty(exports, 'generateBundledCompletionWorker', {
|
|
5716
|
+
enumerable: true,
|
|
5717
|
+
get: function () {
|
|
5718
|
+
return generateBundledCompletionWorker;
|
|
5719
|
+
}
|
|
5720
|
+
});
|
|
4127
5721
|
Object.defineProperty(exports, 'generateCandidates', {
|
|
4128
5722
|
enumerable: true,
|
|
4129
5723
|
get: function () {
|
|
@@ -4160,10 +5754,16 @@ Object.defineProperty(exports, 'resolveValueCompletion', {
|
|
|
4160
5754
|
return resolveValueCompletion;
|
|
4161
5755
|
}
|
|
4162
5756
|
});
|
|
5757
|
+
Object.defineProperty(exports, 'validateBundledWorkerFile', {
|
|
5758
|
+
enumerable: true,
|
|
5759
|
+
get: function () {
|
|
5760
|
+
return validateBundledWorkerFile;
|
|
5761
|
+
}
|
|
5762
|
+
});
|
|
4163
5763
|
Object.defineProperty(exports, 'withCompletionCommand', {
|
|
4164
5764
|
enumerable: true,
|
|
4165
5765
|
get: function () {
|
|
4166
5766
|
return withCompletionCommand;
|
|
4167
5767
|
}
|
|
4168
5768
|
});
|
|
4169
|
-
//# sourceMappingURL=completion-
|
|
5769
|
+
//# sourceMappingURL=completion-CLHO3Xaz.cjs.map
|