politty 0.5.1 → 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-BFOAOg95.cjs → completion-CLHO3Xaz.cjs} +1812 -337
- package/dist/completion-CLHO3Xaz.cjs.map +1 -0
- package/dist/{completion-K5LGh1hO.js → completion-DHnVx9Zk.js} +1786 -340
- 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-Cg8qstsT.d.cts → index-Csk1VFou.d.ts} +119 -4
- package/dist/index-Csk1VFou.d.ts.map +1 -0
- package/dist/{index-O3yn97Ed.d.ts → index-Ct48_myg.d.cts} +119 -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-BFOAOg95.cjs.map +0 -1
- package/dist/completion-K5LGh1hO.js.map +0 -1
- package/dist/index-Cg8qstsT.d.cts.map +0 -1
- package/dist/index-O3yn97Ed.d.ts.map +0 -1
- package/dist/schema-extractor-C50R-1re.js.map +0 -1
- package/dist/schema-extractor-SLPgBNgZ.cjs.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { a as toCamelCase, h as arg, m as resolveSubCommandMeta, n as getAllAliases, t as extractFields, u as resolveSubCommandAlias } from "./schema-extractor-
|
|
1
|
+
import { a as toCamelCase, h as arg, m as resolveSubCommandMeta, n as getAllAliases, t as extractFields, u as resolveSubCommandAlias } from "./schema-extractor-Dqe7_kyQ.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { execSync, spawn } from "node:child_process";
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { execFile, execSync, spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
6
7
|
|
|
7
8
|
//#region src/core/command.ts
|
|
8
9
|
function defineCommand(config) {
|
|
@@ -132,6 +133,33 @@ function ansiC(s) {
|
|
|
132
133
|
return out;
|
|
133
134
|
}
|
|
134
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
|
+
/**
|
|
135
163
|
* Render an alias as its CLI token form: single-char aliases become `-x`,
|
|
136
164
|
* multi-char aliases become `--long`. Mirrors the parser's accepted shapes
|
|
137
165
|
* and is the bare-token form (no quoting) used inside generated case
|
|
@@ -293,13 +321,19 @@ function resolveValueCompletion(field) {
|
|
|
293
321
|
* Parse completion context from partial command line
|
|
294
322
|
*/
|
|
295
323
|
/**
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
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.
|
|
300
328
|
*/
|
|
301
|
-
function
|
|
302
|
-
|
|
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
|
+
};
|
|
303
337
|
}
|
|
304
338
|
/**
|
|
305
339
|
* Extract options from a command
|
|
@@ -322,7 +356,7 @@ function extractOptionsFromSchema(schema) {
|
|
|
322
356
|
valueType: field.type,
|
|
323
357
|
required: field.required,
|
|
324
358
|
defaultNegationAccepted: field.type === "boolean" && (field.negation === void 0 || field.negation === true),
|
|
325
|
-
valueCompletion:
|
|
359
|
+
valueCompletion: resolveRuntimeCompletion(resolveValueCompletion(field))
|
|
326
360
|
};
|
|
327
361
|
});
|
|
328
362
|
}
|
|
@@ -453,7 +487,7 @@ function extractPositionalsForContext(command) {
|
|
|
453
487
|
description: field.description,
|
|
454
488
|
required: field.required,
|
|
455
489
|
variadic: field.type === "array",
|
|
456
|
-
valueCompletion:
|
|
490
|
+
valueCompletion: resolveRuntimeCompletion(resolveValueCompletion(field))
|
|
457
491
|
}));
|
|
458
492
|
}
|
|
459
493
|
/**
|
|
@@ -881,11 +915,15 @@ async function generateCandidates(context, options) {
|
|
|
881
915
|
return generateValueCandidates(inlinePrefix ? {
|
|
882
916
|
...context,
|
|
883
917
|
currentWord: context.currentWord.slice(inlinePrefix.length)
|
|
884
|
-
} : context, options, opt?.name, opt?.valueCompletion);
|
|
918
|
+
} : context, options, opt?.name, opt?.valueCompletion, void 0, opt?.valueType === "array");
|
|
885
919
|
}
|
|
886
920
|
case "positional": {
|
|
887
921
|
const positional = resolvePositionalTarget(context);
|
|
888
|
-
|
|
922
|
+
if (!positional) return {
|
|
923
|
+
candidates: [],
|
|
924
|
+
directive: CompletionDirective.NoFileCompletion
|
|
925
|
+
};
|
|
926
|
+
return generateValueCandidates(context, options, positional.name, positional.valueCompletion, positional.description, false);
|
|
889
927
|
}
|
|
890
928
|
}
|
|
891
929
|
}
|
|
@@ -920,6 +958,16 @@ function executeShellCommand(command) {
|
|
|
920
958
|
return [];
|
|
921
959
|
}
|
|
922
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
|
+
}
|
|
923
971
|
/**
|
|
924
972
|
* Two-stage `key=value` post-processing. Returns the transformed candidate
|
|
925
973
|
* list plus whether it contains a bare `key=` entry so the caller can flip
|
|
@@ -968,7 +1016,7 @@ function dropBareKeyEcho(candidates, currentWord) {
|
|
|
968
1016
|
/**
|
|
969
1017
|
* Resolve value completion, executing shell commands and file lookups in JS
|
|
970
1018
|
*/
|
|
971
|
-
async function resolveValueCandidates(vc, ctx, description) {
|
|
1019
|
+
async function resolveValueCandidates(vc, ctx, completionContext, description, dedupeKeyValues) {
|
|
972
1020
|
const candidates = [];
|
|
973
1021
|
let directive = CompletionDirective.FilterPrefix;
|
|
974
1022
|
let fileExtensions;
|
|
@@ -1025,9 +1073,40 @@ async function resolveValueCandidates(vc, ctx, description) {
|
|
|
1025
1073
|
case "expand":
|
|
1026
1074
|
directive |= CompletionDirective.NoFileCompletion;
|
|
1027
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
|
+
}
|
|
1028
1107
|
}
|
|
1029
|
-
if (vc.type === "dynamic" || vc.type === "expand") {
|
|
1030
|
-
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);
|
|
1031
1110
|
if (processed.hasEqSuffix) directive |= CompletionDirective.NoSpace;
|
|
1032
1111
|
return {
|
|
1033
1112
|
candidates: processed.candidates,
|
|
@@ -1043,6 +1122,19 @@ async function resolveValueCandidates(vc, ctx, description) {
|
|
|
1043
1122
|
fileMatchers
|
|
1044
1123
|
};
|
|
1045
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
|
+
}
|
|
1046
1138
|
/**
|
|
1047
1139
|
* Generate subcommand candidates
|
|
1048
1140
|
*/
|
|
@@ -1066,7 +1158,7 @@ function generateSubcommandCandidates(context) {
|
|
|
1066
1158
|
}
|
|
1067
1159
|
return {
|
|
1068
1160
|
candidates,
|
|
1069
|
-
directive: CompletionDirective.FilterPrefix
|
|
1161
|
+
directive: CompletionDirective.FilterPrefix | CompletionDirective.NoFileCompletion
|
|
1070
1162
|
};
|
|
1071
1163
|
}
|
|
1072
1164
|
/**
|
|
@@ -1100,7 +1192,7 @@ function generateOptionNameCandidates(context) {
|
|
|
1100
1192
|
});
|
|
1101
1193
|
return {
|
|
1102
1194
|
candidates,
|
|
1103
|
-
directive: CompletionDirective.FilterPrefix
|
|
1195
|
+
directive: CompletionDirective.FilterPrefix | CompletionDirective.NoFileCompletion
|
|
1104
1196
|
};
|
|
1105
1197
|
}
|
|
1106
1198
|
/**
|
|
@@ -1136,12 +1228,12 @@ function parsedArgsWithoutTarget(parsedArgs, key) {
|
|
|
1136
1228
|
* is propagated to choices candidates (positional path supplies it; option
|
|
1137
1229
|
* path does not, mirroring the prior split implementations).
|
|
1138
1230
|
*/
|
|
1139
|
-
async function generateValueCandidates(context, options, targetFieldName, vc, description) {
|
|
1231
|
+
async function generateValueCandidates(context, options, targetFieldName, vc, description, dedupeKeyValues) {
|
|
1140
1232
|
if (!vc) return {
|
|
1141
1233
|
candidates: [],
|
|
1142
1234
|
directive: CompletionDirective.FilterPrefix
|
|
1143
1235
|
};
|
|
1144
|
-
return resolveValueCandidates(vc, resolverContext(context, options, targetFieldName), description);
|
|
1236
|
+
return resolveValueCandidates(vc, resolverContext(context, options, targetFieldName), context, description, dedupeKeyValues);
|
|
1145
1237
|
}
|
|
1146
1238
|
|
|
1147
1239
|
//#endregion
|
|
@@ -1152,10 +1244,10 @@ async function generateValueCandidates(context, options, targetFieldName, vc, de
|
|
|
1152
1244
|
* sentinel via `resolveValueCompletion`) and passes them here once siblings
|
|
1153
1245
|
* are known.
|
|
1154
1246
|
*/
|
|
1155
|
-
function resolveExpandTargets(sub, targets, globalOptions = []) {
|
|
1247
|
+
function resolveExpandTargets(sub, targets, globalOptions = [], validateOnly = false) {
|
|
1156
1248
|
if (targets.length === 0) return;
|
|
1157
1249
|
const siblingIndex = buildSiblingIndex(sub, globalOptions);
|
|
1158
|
-
for (const target of targets) target.set(resolveOne(target, siblingIndex));
|
|
1250
|
+
for (const target of targets) target.set(resolveOne(target, siblingIndex, validateOnly));
|
|
1159
1251
|
}
|
|
1160
1252
|
/**
|
|
1161
1253
|
* Build a name → static-values map for siblings, using each field's already
|
|
@@ -1182,7 +1274,7 @@ function buildSiblingIndex(sub, globalOptions) {
|
|
|
1182
1274
|
visit(globalOptions, true);
|
|
1183
1275
|
return index;
|
|
1184
1276
|
}
|
|
1185
|
-
function resolveOne(target, siblings) {
|
|
1277
|
+
function resolveOne(target, siblings, validateOnly = false) {
|
|
1186
1278
|
const { spec } = target;
|
|
1187
1279
|
const deps = spec.dependsOn;
|
|
1188
1280
|
if (deps.length === 0) throw new Error(`Field "${target.describe}": completion.custom.expand.dependsOn must list at least one sibling arg.`);
|
|
@@ -1193,6 +1285,11 @@ function resolveOne(target, siblings) {
|
|
|
1193
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.`);
|
|
1194
1286
|
valueLists.push([...values]);
|
|
1195
1287
|
}
|
|
1288
|
+
if (validateOnly) return {
|
|
1289
|
+
type: "expand",
|
|
1290
|
+
dependsOn: deps,
|
|
1291
|
+
table: []
|
|
1292
|
+
};
|
|
1196
1293
|
const table = [];
|
|
1197
1294
|
for (const combo of cartesian(valueLists)) {
|
|
1198
1295
|
const depsRecord = {};
|
|
@@ -1403,11 +1500,11 @@ function fieldsToPositionals(fields, pending) {
|
|
|
1403
1500
|
/**
|
|
1404
1501
|
* Extract a completable subcommand from a command
|
|
1405
1502
|
*/
|
|
1406
|
-
function extractSubcommand(name, command, globalOptions = []) {
|
|
1503
|
+
function extractSubcommand(name, command, globalOptions = [], validateOnly = false) {
|
|
1407
1504
|
const subcommands = [];
|
|
1408
1505
|
if (command.subCommands) for (const [subName, subCommand] of Object.entries(command.subCommands)) {
|
|
1409
1506
|
const resolved = resolveSubCommandMeta(subCommand);
|
|
1410
|
-
if (resolved) subcommands.push(extractSubcommand(subName, resolved, globalOptions));
|
|
1507
|
+
if (resolved) subcommands.push(extractSubcommand(subName, resolved, globalOptions, validateOnly));
|
|
1411
1508
|
else subcommands.push({
|
|
1412
1509
|
name: subName,
|
|
1413
1510
|
description: "(lazy loaded)",
|
|
@@ -1426,7 +1523,7 @@ function extractSubcommand(name, command, globalOptions = []) {
|
|
|
1426
1523
|
options: fieldsToOptions(fields, pending),
|
|
1427
1524
|
positionals: fieldsToPositionals(fields, pending)
|
|
1428
1525
|
};
|
|
1429
|
-
resolveExpandTargets(node, pending, globalOptions);
|
|
1526
|
+
resolveExpandTargets(node, pending, globalOptions, validateOnly);
|
|
1430
1527
|
return node;
|
|
1431
1528
|
}
|
|
1432
1529
|
/** Join parent and child with a separator, omitting separator when parent is empty. */
|
|
@@ -1825,7 +1922,7 @@ function propagateGlobalOptions(sub, globalOptions) {
|
|
|
1825
1922
|
* @param globalArgsSchema - Optional global args schema. When provided, global options
|
|
1826
1923
|
* are derived from this schema instead of the root command's options.
|
|
1827
1924
|
*/
|
|
1828
|
-
function extractCompletionData(command, programName, globalArgsSchema) {
|
|
1925
|
+
function extractCompletionData(command, programName, globalArgsSchema, validateOnly = false) {
|
|
1829
1926
|
let globalOptions = [];
|
|
1830
1927
|
if (globalArgsSchema) {
|
|
1831
1928
|
const globalPending = [];
|
|
@@ -1838,9 +1935,9 @@ function extractCompletionData(command, programName, globalArgsSchema) {
|
|
|
1838
1935
|
subcommands: [],
|
|
1839
1936
|
options: globalOptions,
|
|
1840
1937
|
positionals: []
|
|
1841
|
-
}, globalPending);
|
|
1938
|
+
}, globalPending, [], validateOnly);
|
|
1842
1939
|
}
|
|
1843
|
-
const rootSubcommand = extractSubcommand(programName, command, globalOptions);
|
|
1940
|
+
const rootSubcommand = extractSubcommand(programName, command, globalOptions, validateOnly);
|
|
1844
1941
|
if (globalArgsSchema) propagateGlobalOptions(rootSubcommand, globalOptions);
|
|
1845
1942
|
else globalOptions = rootSubcommand.options;
|
|
1846
1943
|
return {
|
|
@@ -1857,8 +1954,9 @@ function extractCompletionData(command, programName, globalArgsSchema) {
|
|
|
1857
1954
|
*
|
|
1858
1955
|
* Every completion script generated by politty starts with a small
|
|
1859
1956
|
* machine-readable header. The rc loader and the runMain background
|
|
1860
|
-
* refresh path use the `# politty-bin-sig:`
|
|
1861
|
-
* 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.
|
|
1862
1960
|
*/
|
|
1863
1961
|
/** Schema version of the header itself. Bump when the header layout changes. */
|
|
1864
1962
|
const COMPLETION_VERSION = 1;
|
|
@@ -1874,6 +1972,9 @@ function computeBinSig(binPath) {
|
|
|
1874
1972
|
return "0";
|
|
1875
1973
|
}
|
|
1876
1974
|
}
|
|
1975
|
+
function headerValue(value) {
|
|
1976
|
+
return value.replace(/\r?\n/g, " ");
|
|
1977
|
+
}
|
|
1877
1978
|
/**
|
|
1878
1979
|
* Walk `$PATH` looking for an executable named `programName`. Returns
|
|
1879
1980
|
* the first match's full path, or `null` when not found. We mirror the
|
|
@@ -1904,6 +2005,8 @@ function findOnPath(programName) {
|
|
|
1904
2005
|
*/
|
|
1905
2006
|
function resolveBinPath(programName, override) {
|
|
1906
2007
|
if (override) return override;
|
|
2008
|
+
const envOverride = process.env[binEnvVarName(sanitize(programName))];
|
|
2009
|
+
if (envOverride) return envOverride;
|
|
1907
2010
|
return findOnPath(programName) ?? process.argv[1] ?? "";
|
|
1908
2011
|
}
|
|
1909
2012
|
/**
|
|
@@ -1912,10 +2015,12 @@ function resolveBinPath(programName, override) {
|
|
|
1912
2015
|
* marker.
|
|
1913
2016
|
*/
|
|
1914
2017
|
function buildHeaderLines(opts) {
|
|
1915
|
-
const
|
|
2018
|
+
const binPath = resolveBinPath(opts.programName, opts.binPath);
|
|
2019
|
+
const sig = computeBinSig(binPath);
|
|
1916
2020
|
const lines = [
|
|
1917
2021
|
`# politty-completion-version: ${1}`,
|
|
1918
2022
|
`# politty-bin-sig: ${sig}`,
|
|
2023
|
+
`# politty-bin-path: ${headerValue(binPath)}`,
|
|
1919
2024
|
`# program: ${opts.programName}`
|
|
1920
2025
|
];
|
|
1921
2026
|
if (opts.programVersion) lines.push(`# program-version: ${opts.programVersion}`);
|
|
@@ -1934,13 +2039,13 @@ function buildHeaderLines(opts) {
|
|
|
1934
2039
|
* file in place, then sources the fresh file and stops executing the
|
|
1935
2040
|
* stale body.
|
|
1936
2041
|
*/
|
|
1937
|
-
function statSigExpr() {
|
|
1938
|
-
return `$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null)`;
|
|
1939
|
-
}
|
|
1940
2042
|
function generateBashSelfRefresh(opts) {
|
|
1941
2043
|
const { programName, binPath } = opts;
|
|
1942
2044
|
const fn = sanitize(programName);
|
|
1943
|
-
const
|
|
2045
|
+
const envName = binEnvVarName(fn);
|
|
2046
|
+
const resolvedBinPath = resolveBinPath(programName, binPath);
|
|
2047
|
+
const sig = computeBinSig(resolvedBinPath);
|
|
2048
|
+
const quotedBinPath = shSingleQuote(resolvedBinPath);
|
|
1944
2049
|
const refreshFn = `__${fn}_self_refresh`;
|
|
1945
2050
|
return [
|
|
1946
2051
|
`${refreshFn}() {`,
|
|
@@ -1948,14 +2053,15 @@ function generateBashSelfRefresh(opts) {
|
|
|
1948
2053
|
` _self=\${BASH_SOURCE[0]:-}`,
|
|
1949
2054
|
` [[ -n "$_self" && -f "$_self" ]] || return 1`,
|
|
1950
2055
|
` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
|
|
1951
|
-
` head -n 8 "$_self" 2>/dev/null | grep -
|
|
1952
|
-
` head -n 8 "$_self" 2>/dev/null | grep -
|
|
1953
|
-
` _bin
|
|
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)}"`,
|
|
1954
2059
|
` [[ -n "$_bin" ]] || return 1`,
|
|
1955
|
-
` _sig=${statSigExpr()} || return 1`,
|
|
1956
|
-
` [[ "$_sig" != "${sig}" ]] || return 1`,
|
|
1957
|
-
` "$_bin" __refresh-completion bash "$_self" 2>/dev/null || return 1`,
|
|
1958
|
-
` head -n 8 "$_self" 2>/dev/null | grep -
|
|
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`,
|
|
1959
2065
|
` source "$_self" 2>/dev/null || return 1`,
|
|
1960
2066
|
` return 0`,
|
|
1961
2067
|
`}`,
|
|
@@ -1971,8 +2077,11 @@ function generateBashSelfRefresh(opts) {
|
|
|
1971
2077
|
function generateZshSelfRefresh(opts) {
|
|
1972
2078
|
const { programName, binPath } = opts;
|
|
1973
2079
|
const fn = sanitize(programName);
|
|
2080
|
+
const envName = binEnvVarName(fn);
|
|
1974
2081
|
const completionFn = `_${programName}`;
|
|
1975
|
-
const
|
|
2082
|
+
const resolvedBinPath = resolveBinPath(programName, binPath);
|
|
2083
|
+
const sig = computeBinSig(resolvedBinPath);
|
|
2084
|
+
const quotedBinPath = shSingleQuote(resolvedBinPath);
|
|
1976
2085
|
const refreshFn = `__${fn}_self_refresh`;
|
|
1977
2086
|
return [
|
|
1978
2087
|
`${refreshFn}() {`,
|
|
@@ -1982,14 +2091,15 @@ function generateZshSelfRefresh(opts) {
|
|
|
1982
2091
|
` _self="\${(%):-%x}"`,
|
|
1983
2092
|
` [[ -n "$_self" && -f "$_self" ]] || return 1`,
|
|
1984
2093
|
` head -n 8 "$_self" 2>/dev/null | grep -qF "# politty-completion-version:" || return 1`,
|
|
1985
|
-
` head -n 8 "$_self" 2>/dev/null | grep -
|
|
1986
|
-
` head -n 8 "$_self" 2>/dev/null | grep -
|
|
1987
|
-
` _bin
|
|
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)}"`,
|
|
1988
2097
|
` [[ -n "$_bin" ]] || return 1`,
|
|
1989
|
-
` _sig=${statSigExpr()} || return 1`,
|
|
1990
|
-
` [[ "$_sig" != "${sig}" ]] || return 1`,
|
|
1991
|
-
` "$_bin" __refresh-completion zsh "$_self" 2>/dev/null || return 1`,
|
|
1992
|
-
` head -n 8 "$_self" 2>/dev/null | grep -
|
|
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`,
|
|
1993
2103
|
` source "$_self" 2>/dev/null || return 1`,
|
|
1994
2104
|
` ${completionFn} "$@"`,
|
|
1995
2105
|
` return 0`,
|
|
@@ -2105,6 +2215,7 @@ function bashValueLines(vc, inline, fn, location) {
|
|
|
2105
2215
|
case "directory": return [`COMPREPLY=($(compgen -P "$_inline_prefix" -d -- "$_cur"))`, `compopt -o filenames`];
|
|
2106
2216
|
case "command": return [`COMPREPLY=($(compgen -P "$_inline_prefix" -W "$(${vc.shellCommand})" -- "$_cur"))`];
|
|
2107
2217
|
case "none": return [`compopt +o default 2>/dev/null`];
|
|
2218
|
+
case "runtime-expand": return [];
|
|
2108
2219
|
}
|
|
2109
2220
|
}
|
|
2110
2221
|
/**
|
|
@@ -2167,6 +2278,28 @@ function positionalBlock$2(positionals, fn, funcSuffix, options = []) {
|
|
|
2167
2278
|
lines.push(` esac`);
|
|
2168
2279
|
return lines;
|
|
2169
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
|
+
}
|
|
2170
2303
|
/** Generate prev/inline value completion blocks for options */
|
|
2171
2304
|
function valueCompletionBlocks(options, positionals, fn, funcSuffix) {
|
|
2172
2305
|
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
@@ -2242,8 +2375,7 @@ function generateSubHandler$2(sub, fn, path) {
|
|
|
2242
2375
|
lines.push(` fi`);
|
|
2243
2376
|
if (visibleSubs.length > 0) {
|
|
2244
2377
|
const subNames = getSubNamesWithAliases(sub.subcommands).map((s) => s.name).join(" ");
|
|
2245
|
-
lines.push(
|
|
2246
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
2378
|
+
lines.push(...subOrPositionalLines$2(subNames, sub.positionals, fn, funcSuffix, sub.options).map((l) => ` ${l}`));
|
|
2247
2379
|
} else if (sub.positionals.length > 0) lines.push(...positionalBlock$2(sub.positionals, fn, funcSuffix, sub.options));
|
|
2248
2380
|
lines.push(`}`);
|
|
2249
2381
|
lines.push(``);
|
|
@@ -2252,7 +2384,9 @@ function generateSubHandler$2(sub, fn, path) {
|
|
|
2252
2384
|
function generateBashCompletion(command, options) {
|
|
2253
2385
|
const { programName } = options;
|
|
2254
2386
|
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
2255
|
-
const
|
|
2387
|
+
const baseFn = sanitize(programName);
|
|
2388
|
+
const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
|
|
2389
|
+
const isWorker = options.staticWorker !== void 0;
|
|
2256
2390
|
const root = data.command;
|
|
2257
2391
|
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
2258
2392
|
const expandSpecs = collectExpandSpecs(root);
|
|
@@ -2264,9 +2398,11 @@ function generateBashCompletion(command, options) {
|
|
|
2264
2398
|
binPath: options.binPath,
|
|
2265
2399
|
programVersion: options.programVersion
|
|
2266
2400
|
}));
|
|
2401
|
+
lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
|
|
2402
|
+
if (isWorker) lines.push(`# politty-completion-worker: true`);
|
|
2267
2403
|
lines.push(`# Generated by politty`);
|
|
2268
2404
|
lines.push(``);
|
|
2269
|
-
lines.push(...generateBashSelfRefresh({
|
|
2405
|
+
if (!isWorker) lines.push(...generateBashSelfRefresh({
|
|
2270
2406
|
programName,
|
|
2271
2407
|
binPath: options.binPath
|
|
2272
2408
|
}));
|
|
@@ -2410,8 +2546,7 @@ function generateBashCompletion(command, options) {
|
|
|
2410
2546
|
if (visibleSubs.length > 0) {
|
|
2411
2547
|
lines.push(` else`);
|
|
2412
2548
|
const subNames = getSubNamesWithAliases(root.subcommands).map((s) => s.name).join(" ");
|
|
2413
|
-
lines.push(
|
|
2414
|
-
lines.push(` compopt +o default 2>/dev/null`);
|
|
2549
|
+
lines.push(...subOrPositionalLines$2(subNames, root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
2415
2550
|
} else if (root.positionals.length > 0) {
|
|
2416
2551
|
lines.push(` else`);
|
|
2417
2552
|
lines.push(...positionalBlock$2(root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
@@ -2509,17 +2644,17 @@ function generateBashCompletion(command, options) {
|
|
|
2509
2644
|
lines.push(` esac`);
|
|
2510
2645
|
lines.push(`}`);
|
|
2511
2646
|
lines.push(``);
|
|
2512
|
-
lines.push(`complete -o default -F _${fn}_completions ${programName}`);
|
|
2647
|
+
if (!isWorker) lines.push(`complete -o default -F _${fn}_completions ${programName}`);
|
|
2513
2648
|
lines.push(``);
|
|
2514
2649
|
return {
|
|
2515
2650
|
script: lines.join("\n"),
|
|
2516
2651
|
shell: "bash",
|
|
2517
2652
|
installInstructions: `# To enable auto-refreshing bash completions, add this to your ~/.bashrc:
|
|
2518
|
-
eval "$(${programName} completion bash)"
|
|
2653
|
+
eval "$(${programName} completion bash --static)"
|
|
2519
2654
|
|
|
2520
2655
|
# For faster shell startup, save the script instead:
|
|
2521
2656
|
mkdir -p ~/.local/share/bash-completion/completions
|
|
2522
|
-
${programName} completion bash > ~/.local/share/bash-completion/completions/${programName}
|
|
2657
|
+
${programName} completion bash --static > ~/.local/share/bash-completion/completions/${programName}
|
|
2523
2658
|
|
|
2524
2659
|
# Then reload your shell or run:
|
|
2525
2660
|
source ~/.bashrc`
|
|
@@ -2527,195 +2662,1330 @@ source ~/.bashrc`
|
|
|
2527
2662
|
}
|
|
2528
2663
|
|
|
2529
2664
|
//#endregion
|
|
2530
|
-
//#region src/completion/
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2665
|
+
//#region src/completion/bundled-worker.ts
|
|
2666
|
+
const execFileAsync = promisify(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 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 = 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;
|
|
2541
2710
|
}
|
|
2542
2711
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2712
|
+
function addBaseDirs(out, path) {
|
|
2713
|
+
if (!path) return;
|
|
2714
|
+
out.add(dirname(resolve(path)));
|
|
2715
|
+
try {
|
|
2716
|
+
out.add(dirname(realpathSync(path)));
|
|
2717
|
+
} catch {}
|
|
2718
|
+
const shimTarget = readCmdShimTarget(path);
|
|
2719
|
+
if (shimTarget) addBaseDirs(out, shimTarget);
|
|
2550
2720
|
}
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
*
|
|
2554
|
-
* - Pre-filters candidates by currentWord prefix (replaces compgen -W)
|
|
2555
|
-
* - Handles --opt=value inline values by prepending prefix
|
|
2556
|
-
* - Outputs plain values only (no descriptions - bash COMPREPLY doesn't support them)
|
|
2557
|
-
* - Last line: :directive
|
|
2558
|
-
*/
|
|
2559
|
-
function formatForBash(result, options) {
|
|
2560
|
-
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);
|
|
2561
|
-
appendMetadata(lines, result);
|
|
2562
|
-
return lines.join("\n");
|
|
2721
|
+
function workerHead(path) {
|
|
2722
|
+
return readFileSync(path, "utf8").split("\n", 24).join("\n");
|
|
2563
2723
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
*/
|
|
2571
|
-
function formatForZsh(result, _options) {
|
|
2572
|
-
const lines = result.candidates.map((c) => {
|
|
2573
|
-
const escapedValue = c.value.replace(/:/g, "\\:");
|
|
2574
|
-
if (c.description) return `${escapedValue}:${c.description.replace(/:/g, "\\:")}`;
|
|
2575
|
-
return escapedValue;
|
|
2576
|
-
});
|
|
2577
|
-
appendMetadata(lines, result);
|
|
2578
|
-
return lines.join("\n");
|
|
2724
|
+
function requiredBundledWorkerHeaders(programName, shell) {
|
|
2725
|
+
return [
|
|
2726
|
+
...REQUIRED_WORKER_HEADERS,
|
|
2727
|
+
`# program: ${programName}`,
|
|
2728
|
+
`# shell: ${shell}`
|
|
2729
|
+
];
|
|
2579
2730
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
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 (!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 (isAbsolute(rel) && isBundledWorkerFile(rel, opts.programName, opts.shell)) return rel;
|
|
2756
|
+
for (const base of bases) for (const rel of rels) {
|
|
2757
|
+
if (isAbsolute(rel)) continue;
|
|
2758
|
+
const candidate = 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 isAbsolute(path) ? 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 = 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 = 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 = 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
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
2817
|
+
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
|
|
2590
2827
|
});
|
|
2591
|
-
|
|
2592
|
-
|
|
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 = realpathSync(outputPath);
|
|
2839
|
+
const reportedReal = 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
|
+
};
|
|
2593
2848
|
}
|
|
2594
2849
|
|
|
2595
2850
|
//#endregion
|
|
2596
|
-
//#region src/completion/
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
*
|
|
2600
|
-
* This creates a hidden `__complete` command that outputs completion candidates
|
|
2601
|
-
* for shell scripts to consume. Usage:
|
|
2602
|
-
*
|
|
2603
|
-
* mycli __complete --shell bash -- build --fo
|
|
2604
|
-
* mycli __complete --shell zsh -- plugin add
|
|
2605
|
-
*
|
|
2606
|
-
* Output format depends on the target shell:
|
|
2607
|
-
* bash: plain values (pre-filtered by prefix), last line :directive
|
|
2608
|
-
* zsh: value:description pairs, last line :directive
|
|
2609
|
-
* fish: value\tdescription pairs, last line :directive
|
|
2610
|
-
*/
|
|
2611
|
-
/**
|
|
2612
|
-
* Schema for the __complete command
|
|
2613
|
-
*/
|
|
2614
|
-
const completeArgsSchema = z.object({
|
|
2615
|
-
shell: arg(z.enum([
|
|
2616
|
-
"bash",
|
|
2617
|
-
"zsh",
|
|
2618
|
-
"fish"
|
|
2619
|
-
]), { description: "Target shell for output formatting" }),
|
|
2620
|
-
args: arg(z.array(z.string()).default([]), {
|
|
2621
|
-
positional: true,
|
|
2622
|
-
description: "Arguments to complete",
|
|
2623
|
-
variadic: true
|
|
2624
|
-
})
|
|
2625
|
-
});
|
|
2626
|
-
/**
|
|
2627
|
-
* Create the dynamic completion command
|
|
2628
|
-
*
|
|
2629
|
-
* @param rootCommand - The root command to generate completions for
|
|
2630
|
-
* @param programName - The program name (optional, defaults to rootCommand.name)
|
|
2631
|
-
* @param globalArgsSchema - Global args schema. Forwarded to
|
|
2632
|
-
* `parseCompletionContext` so resolvers attached to global options remain
|
|
2633
|
-
* reachable at every subcommand level.
|
|
2634
|
-
* @returns A command that outputs completion candidates
|
|
2635
|
-
*/
|
|
2636
|
-
function createDynamicCompleteCommand(rootCommand, _programName, globalArgsSchema) {
|
|
2637
|
-
return defineCommand({
|
|
2638
|
-
name: "__complete",
|
|
2639
|
-
args: completeArgsSchema,
|
|
2640
|
-
async run(args) {
|
|
2641
|
-
const context = parseCompletionContext(args.args, rootCommand, globalArgsSchema);
|
|
2642
|
-
const inlinePrefix = context.completionType === "option-value" && context.targetOption ? detectInlineOptionPrefix(context.currentWord) : void 0;
|
|
2643
|
-
const effectiveWord = inlinePrefix ? context.currentWord.slice(inlinePrefix.length) : context.currentWord;
|
|
2644
|
-
const output = formatForShell(await generateCandidates(context, { shell: args.shell }), {
|
|
2645
|
-
shell: args.shell,
|
|
2646
|
-
currentWord: effectiveWord,
|
|
2647
|
-
inlinePrefix
|
|
2648
|
-
});
|
|
2649
|
-
console.log(output);
|
|
2650
|
-
}
|
|
2651
|
-
});
|
|
2851
|
+
//#region src/completion/dispatcher.ts
|
|
2852
|
+
function compileCacheSuffix(programName) {
|
|
2853
|
+
return shSingleQuote(`/${programName}/node-compile-cache`);
|
|
2652
2854
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
*/
|
|
2656
|
-
function hasCompleteCommand(command) {
|
|
2657
|
-
return Boolean(command.subCommands?.["__complete"]);
|
|
2855
|
+
function hardcodedCompileCacheDir(cacheDir) {
|
|
2856
|
+
return cacheDir ? shSingleQuote(`${cacheDir}/node-compile-cache`) : void 0;
|
|
2658
2857
|
}
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
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}`);
|
|
2665
2863
|
}
|
|
2666
2864
|
/**
|
|
2667
|
-
*
|
|
2668
|
-
*
|
|
2669
|
-
*
|
|
2670
|
-
* literal — otherwise a key like `prod*` would also match a runtime
|
|
2671
|
-
* value of `production`. Quote/dollar/backslash are escaped first so the
|
|
2672
|
-
* 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.
|
|
2673
2868
|
*/
|
|
2674
|
-
function
|
|
2675
|
-
return
|
|
2869
|
+
function posixCacheDefault(printCmd, hardcoded, suffix) {
|
|
2870
|
+
return hardcoded ? ` ${printCmd} ${hardcoded}` : ` ${printCmd} "\${XDG_CACHE_HOME:-$HOME/.cache}"${suffix}`;
|
|
2676
2871
|
}
|
|
2677
2872
|
/**
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
* `location` is required for the expand variant (carries fieldName +
|
|
2682
|
-
* 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.
|
|
2683
2875
|
*/
|
|
2684
|
-
function
|
|
2685
|
-
if (
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
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 = z.object({
|
|
3885
|
+
shell: arg(z.enum([
|
|
3886
|
+
"bash",
|
|
3887
|
+
"zsh",
|
|
3888
|
+
"fish"
|
|
3889
|
+
]), { description: "Target shell for output formatting" }),
|
|
3890
|
+
args: arg(z.array(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}`,
|
|
3976
|
+
` ${echoLine}`,
|
|
3977
|
+
` end`
|
|
3978
|
+
] : [` ${echoLine}`];
|
|
3979
|
+
for (const c of entry.candidates) {
|
|
3980
|
+
const eqIdx = c.value.indexOf("=");
|
|
3981
|
+
const keyPart = eqIdx > 0 ? c.value.slice(0, eqIdx) : "";
|
|
3982
|
+
const echoLine = printfLine(c.value, c.description);
|
|
3983
|
+
if (!(keyPart.length > 0 && c.value.length === eqIdx + 1)) fullLines.push(...wrapWithDedup(echoLine, keyPart));
|
|
3984
|
+
if (keyPart.length === 0) keyOnlyLines.push(` ${echoLine}`);
|
|
3985
|
+
else if (!seenKeys.has(keyPart)) {
|
|
3986
|
+
seenKeys.add(keyPart);
|
|
3987
|
+
keyOnlyLines.push(...wrapWithDedup(printfLine(`${keyPart}=`, c.description), keyPart));
|
|
3988
|
+
}
|
|
2719
3989
|
}
|
|
2720
3990
|
if (fullLines.length !== keyOnlyLines.length || fullLines.some((l, i) => l !== keyOnlyLines[i])) {
|
|
2721
3991
|
out.push(` if string match -q '*=*' -- "$_cur"`);
|
|
@@ -2741,6 +4011,7 @@ function fishValueLines(vc, fn, location) {
|
|
|
2741
4011
|
`end`
|
|
2742
4012
|
];
|
|
2743
4013
|
case "none": return [];
|
|
4014
|
+
case "runtime-expand": return [];
|
|
2744
4015
|
}
|
|
2745
4016
|
}
|
|
2746
4017
|
/** Generate fish matcher-filtered file completion */
|
|
@@ -2762,9 +4033,13 @@ function fishMatcherLines(patterns) {
|
|
|
2762
4033
|
function fishExtensionLines(extensions) {
|
|
2763
4034
|
const lines = [];
|
|
2764
4035
|
lines.push(`__fish_complete_directories "$_cur"`);
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
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`);
|
|
2768
4043
|
lines.push(`end`);
|
|
2769
4044
|
}
|
|
2770
4045
|
return lines;
|
|
@@ -2801,6 +4076,27 @@ function positionalBlock$1(positionals, fn, options = []) {
|
|
|
2801
4076
|
}
|
|
2802
4077
|
return lines;
|
|
2803
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
|
+
}
|
|
2804
4100
|
/** Generate available-option echo lines for fish */
|
|
2805
4101
|
function availableOptionLines$1(options, fn) {
|
|
2806
4102
|
const lines = [];
|
|
@@ -2856,11 +4152,10 @@ function generateSubHandler$1(sub, fn, path) {
|
|
|
2856
4152
|
lines.push(...availableOptionLines$1(sub.options, fn));
|
|
2857
4153
|
lines.push(` return`);
|
|
2858
4154
|
lines.push(` end`);
|
|
2859
|
-
if (visibleSubs.length > 0)
|
|
2860
|
-
const
|
|
2861
|
-
lines.push(`
|
|
2862
|
-
}
|
|
2863
|
-
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));
|
|
2864
4159
|
lines.push(`end`);
|
|
2865
4160
|
lines.push(``);
|
|
2866
4161
|
return lines;
|
|
@@ -2878,7 +4173,9 @@ function optTakesValueCases(sub, parentPath) {
|
|
|
2878
4173
|
function generateFishCompletion(command, options) {
|
|
2879
4174
|
const { programName } = options;
|
|
2880
4175
|
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
2881
|
-
const
|
|
4176
|
+
const baseFn = sanitize(programName);
|
|
4177
|
+
const fn = options.staticWorker ? `${baseFn}_${sanitize(options.staticWorker.functionSuffix)}` : baseFn;
|
|
4178
|
+
const isWorker = options.staticWorker !== void 0;
|
|
2882
4179
|
const root = data.command;
|
|
2883
4180
|
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
2884
4181
|
const expandSpecs = collectExpandSpecs(root);
|
|
@@ -2893,27 +4190,34 @@ function generateFishCompletion(command, options) {
|
|
|
2893
4190
|
binPath: options.binPath,
|
|
2894
4191
|
programVersion: options.programVersion
|
|
2895
4192
|
}));
|
|
4193
|
+
lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
|
|
4194
|
+
if (isWorker) lines.push(`# politty-completion-worker: true`);
|
|
2896
4195
|
lines.push(`# Generated by politty`);
|
|
2897
4196
|
lines.push(``);
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
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
|
+
}
|
|
2917
4221
|
if (hasDynamicCompletion(root)) {
|
|
2918
4222
|
lines.push(`function __${fn}_invoke_complete`);
|
|
2919
4223
|
lines.push(` set -l _shell $argv[1]`);
|
|
@@ -3060,10 +4364,8 @@ function generateFishCompletion(command, options) {
|
|
|
3060
4364
|
lines.push(...availableOptionLines$1(root.options, fn));
|
|
3061
4365
|
if (visibleSubs.length > 0) {
|
|
3062
4366
|
lines.push(` else`);
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
lines.push(` echo "${s.name}\t${desc}"`);
|
|
3066
|
-
}
|
|
4367
|
+
const subItems = getSubNamesWithAliases(root.subcommands);
|
|
4368
|
+
lines.push(...subOrPositionalLines$1(subItems, root.positionals, fn, root.options).map((l) => ` ${l}`));
|
|
3067
4369
|
} else if (root.positionals.length > 0) {
|
|
3068
4370
|
lines.push(` else`);
|
|
3069
4371
|
lines.push(...positionalBlock$1(root.positionals, fn, root.options));
|
|
@@ -3172,17 +4474,19 @@ function generateFishCompletion(command, options) {
|
|
|
3172
4474
|
lines.push(` end`);
|
|
3173
4475
|
lines.push(`end`);
|
|
3174
4476
|
lines.push(``);
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
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
|
+
}
|
|
3180
4484
|
lines.push(``);
|
|
3181
4485
|
return {
|
|
3182
4486
|
script: lines.join("\n"),
|
|
3183
4487
|
shell: "fish",
|
|
3184
4488
|
installInstructions: `# To enable auto-refreshing fish completions, run:
|
|
3185
|
-
${programName} completion fish --install`
|
|
4489
|
+
${programName} completion fish --install --static`
|
|
3186
4490
|
};
|
|
3187
4491
|
}
|
|
3188
4492
|
|
|
@@ -3197,38 +4501,30 @@ ${programName} completion fish --install`
|
|
|
3197
4501
|
* 1. Looks up the binary on $PATH.
|
|
3198
4502
|
* 2. Reads its mtime.
|
|
3199
4503
|
* 3. If the on-disk completion cache is missing or its
|
|
3200
|
-
* `# politty-bin-sig:`
|
|
3201
|
-
* spawning the binary once.
|
|
4504
|
+
* `# politty-bin-sig:` / `# politty-bin-path:` headers differ,
|
|
4505
|
+
* regenerates the cache by spawning the binary once.
|
|
3202
4506
|
* 4. Sources the cache.
|
|
3203
4507
|
*
|
|
3204
4508
|
* All failure modes are silent no-ops so a broken / missing CLI never
|
|
3205
4509
|
* blocks shell startup.
|
|
3206
4510
|
*/
|
|
3207
|
-
/**
|
|
3208
|
-
* Single-quote escape: `'` -> `'\''`. Inside single quotes the shell
|
|
3209
|
-
* performs no expansion at all, so `$`, backticks, and `$(...)` are
|
|
3210
|
-
* inert. Used for hardcoded paths because callers may sources them
|
|
3211
|
-
* from env / config — we must not let metachars in the path execute as
|
|
3212
|
-
* commands when the rc snippet is sourced.
|
|
3213
|
-
*/
|
|
3214
|
-
function shSingleQuote$1(s) {
|
|
3215
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3216
|
-
}
|
|
3217
4511
|
function bashCachePathExpr(programName, cacheDir, shell) {
|
|
3218
|
-
if (cacheDir) return shSingleQuote
|
|
4512
|
+
if (cacheDir) return shSingleQuote(`${cacheDir}/completion.${shell}`);
|
|
3219
4513
|
return `"\${XDG_CACHE_HOME:-$HOME/.cache}/${programName}/completion.${shell}"`;
|
|
3220
4514
|
}
|
|
3221
4515
|
function generateBashLoader(opts) {
|
|
3222
4516
|
const fn = sanitize(opts.programName);
|
|
4517
|
+
const envName = binEnvVarName(fn);
|
|
3223
4518
|
const cache = bashCachePathExpr(opts.programName, opts.cacheDir, "bash");
|
|
3224
4519
|
return `__${fn}_load_completion() {
|
|
3225
|
-
local _bin _cache _sig
|
|
3226
|
-
_bin
|
|
4520
|
+
local _bin _cache _sig _sig_hdr _path_hdr
|
|
4521
|
+
_bin="\${${envName}:-$(type -P ${opts.programName} 2>/dev/null)}"
|
|
3227
4522
|
[[ -n "$_bin" ]] || return 0
|
|
3228
4523
|
_cache=${cache}
|
|
3229
4524
|
_sig=$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null) || return 0
|
|
3230
|
-
|
|
3231
|
-
|
|
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
|
|
3232
4528
|
# Use the hidden __refresh-completion subcommand instead of
|
|
3233
4529
|
# \`$_bin completion bash\`: the foreground completion command
|
|
3234
4530
|
# is subject to user setup/cleanup/prompt and required
|
|
@@ -3250,17 +4546,19 @@ unset -f __${fn}_load_completion
|
|
|
3250
4546
|
}
|
|
3251
4547
|
function generateZshLoader(opts) {
|
|
3252
4548
|
const fn = sanitize(opts.programName);
|
|
4549
|
+
const envName = binEnvVarName(fn);
|
|
3253
4550
|
const cache = bashCachePathExpr(opts.programName, opts.cacheDir, "zsh");
|
|
3254
4551
|
return `__${fn}_load_completion() {
|
|
3255
4552
|
emulate -L zsh
|
|
3256
4553
|
setopt local_options no_aliases
|
|
3257
|
-
local _bin _cache _sig
|
|
3258
|
-
_bin
|
|
4554
|
+
local _bin _cache _sig _sig_hdr _path_hdr
|
|
4555
|
+
_bin="\${${envName}:-$(whence -p ${opts.programName} 2>/dev/null)}"
|
|
3259
4556
|
[[ -n "$_bin" ]] || return 0
|
|
3260
4557
|
_cache=${cache}
|
|
3261
4558
|
_sig=$(stat -L -c '%Y' "$_bin" 2>/dev/null || stat -L -f '%m' "$_bin" 2>/dev/null) || return 0
|
|
3262
|
-
|
|
3263
|
-
|
|
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
|
|
3264
4562
|
# See bash loader for why we use __refresh-completion instead
|
|
3265
4563
|
# of \`$_bin completion zsh\`.
|
|
3266
4564
|
"$_bin" __refresh-completion zsh 2>/dev/null
|
|
@@ -3332,11 +4630,14 @@ function generateScript(ctx, shell) {
|
|
|
3332
4630
|
return generateCompletion(ctx.rootCommand, {
|
|
3333
4631
|
shell,
|
|
3334
4632
|
programName: ctx.programName,
|
|
4633
|
+
mode: ctx.completionMode ?? "dispatcher",
|
|
3335
4634
|
includeDescriptions: true,
|
|
3336
4635
|
...ctx.programVersion !== void 0 && { programVersion: ctx.programVersion },
|
|
3337
4636
|
...ctx.binPath !== void 0 && { binPath: ctx.binPath },
|
|
3338
4637
|
...ctx.cacheDir !== void 0 && { cacheDir: ctx.cacheDir },
|
|
3339
|
-
...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 }
|
|
3340
4641
|
}).script;
|
|
3341
4642
|
}
|
|
3342
4643
|
/** Write the script for `shell` to its install path. Returns the path. */
|
|
@@ -3359,11 +4660,30 @@ function readCachedSig(path) {
|
|
|
3359
4660
|
return null;
|
|
3360
4661
|
}
|
|
3361
4662
|
}
|
|
4663
|
+
function readCachedMode(path) {
|
|
4664
|
+
try {
|
|
4665
|
+
if (!existsSync(path)) return void 0;
|
|
4666
|
+
const m = 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 (!existsSync(path)) return null;
|
|
4676
|
+
const m = 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
|
+
}
|
|
3362
4682
|
function isManagedTarget(path, programName, shell) {
|
|
3363
4683
|
try {
|
|
3364
4684
|
if (!existsSync(path)) return false;
|
|
3365
|
-
const
|
|
3366
|
-
return /^# politty-completion-version: \S
|
|
4685
|
+
const lines = 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}`);
|
|
3367
4687
|
} catch {
|
|
3368
4688
|
return false;
|
|
3369
4689
|
}
|
|
@@ -3383,8 +4703,9 @@ function isManagedTarget(path, programName, shell) {
|
|
|
3383
4703
|
*/
|
|
3384
4704
|
function refreshIfStale(ctx, shell) {
|
|
3385
4705
|
try {
|
|
3386
|
-
const target = ctx.targetPath ? realpathSync(ctx.targetPath) : installPath(ctx.programName, shell, ctx.cacheDir);
|
|
3387
|
-
if (ctx.targetPath && !isManagedTarget(target, ctx.programName, shell)) return;
|
|
4706
|
+
const target = ctx.targetPath ? existsSync(ctx.targetPath) ? realpathSync(ctx.targetPath) : ctx.targetPath : installPath(ctx.programName, shell, ctx.cacheDir);
|
|
4707
|
+
if (ctx.targetPath && existsSync(target) && !isManagedTarget(target, ctx.programName, shell)) return;
|
|
4708
|
+
if (ctx.targetPath && !existsSync(target) && !ctx.allowTargetCreate) return;
|
|
3388
4709
|
const binPath = resolveBinPath(ctx.programName, ctx.binPath);
|
|
3389
4710
|
if (!binPath) return;
|
|
3390
4711
|
let currentSig;
|
|
@@ -3393,8 +4714,12 @@ function refreshIfStale(ctx, shell) {
|
|
|
3393
4714
|
} catch {
|
|
3394
4715
|
return;
|
|
3395
4716
|
}
|
|
3396
|
-
if (readCachedSig(target) === currentSig) return;
|
|
3397
|
-
|
|
4717
|
+
if (readCachedSig(target) === currentSig && readCachedBinPath(target) === binPath) return;
|
|
4718
|
+
const completionMode = ctx.completionMode ?? readCachedMode(target) ?? (existsSync(target) ? "static" : "dispatcher");
|
|
4719
|
+
writeAtomic(target, generateScript({
|
|
4720
|
+
...ctx,
|
|
4721
|
+
completionMode
|
|
4722
|
+
}, shell));
|
|
3398
4723
|
} catch {}
|
|
3399
4724
|
}
|
|
3400
4725
|
/**
|
|
@@ -3450,6 +4775,49 @@ function escapeDescribeValue(s) {
|
|
|
3450
4775
|
function escapeZshDQ(s) {
|
|
3451
4776
|
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
3452
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
|
+
}
|
|
3453
4821
|
/**
|
|
3454
4822
|
* Generate zsh value completion lines for a ValueCompletion spec.
|
|
3455
4823
|
* Uses `_vals` array (must be declared in the calling function scope).
|
|
@@ -3506,12 +4874,13 @@ function zshValueLines(vc, fn, location) {
|
|
|
3506
4874
|
case "dynamic": return [`__${fn}_apply_dynamic_output "$(__${fn}_invoke_complete zsh "\${(@)words[2,CURRENT]}")"`];
|
|
3507
4875
|
case "choices": return [`_vals=(${vc.choices.map((c) => `"${escapeDesc(c)}"`).join(" ")})`, `__${fn}_cdescribe 'completions' _vals`];
|
|
3508
4876
|
case "file":
|
|
3509
|
-
if (vc.matcher?.length) return
|
|
3510
|
-
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);
|
|
3511
4879
|
return [`_files`];
|
|
3512
4880
|
case "directory": return [`_files -/`];
|
|
3513
4881
|
case "command": return [`_vals=("\${(@f)$(${vc.shellCommand})}")`, `__${fn}_cdescribe 'completions' _vals`];
|
|
3514
4882
|
case "none": return [];
|
|
4883
|
+
case "runtime-expand": return [];
|
|
3515
4884
|
}
|
|
3516
4885
|
}
|
|
3517
4886
|
/** Generate option-value case branches */
|
|
@@ -3550,6 +4919,31 @@ function positionalBlock(positionals, fn, funcSuffix, options = []) {
|
|
|
3550
4919
|
lines.push(` esac`);
|
|
3551
4920
|
return lines;
|
|
3552
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
|
+
}
|
|
3553
4947
|
/** Generate prev-word value completion case block */
|
|
3554
4948
|
function valueCompletionBlock(options, positionals, fn, funcSuffix) {
|
|
3555
4949
|
if (!options.some((o) => o.takesValue && o.valueCompletion)) return [];
|
|
@@ -3621,12 +5015,8 @@ function generateSubHandler(sub, fn, path) {
|
|
|
3621
5015
|
lines.push(` return 0`);
|
|
3622
5016
|
lines.push(` fi`);
|
|
3623
5017
|
if (visibleSubs.length > 0) {
|
|
3624
|
-
const subItems = getSubNamesWithAliases(sub.subcommands)
|
|
3625
|
-
|
|
3626
|
-
return `"${s.name}${desc}"`;
|
|
3627
|
-
}).join(" ");
|
|
3628
|
-
lines.push(` local -a _subs=(${subItems})`);
|
|
3629
|
-
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}`));
|
|
3630
5020
|
} else if (sub.positionals.length > 0) lines.push(...positionalBlock(sub.positionals, fn, funcSuffix, sub.options));
|
|
3631
5021
|
lines.push(`}`);
|
|
3632
5022
|
lines.push(``);
|
|
@@ -3635,8 +5025,10 @@ function generateSubHandler(sub, fn, path) {
|
|
|
3635
5025
|
function generateZshCompletion(command, options) {
|
|
3636
5026
|
const { programName } = options;
|
|
3637
5027
|
const data = extractCompletionData(command, programName, options.globalArgsSchema);
|
|
3638
|
-
const
|
|
3639
|
-
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}`;
|
|
3640
5032
|
const autoloadCheck = `"\${funcstack[1]:-}" == "${escapeZshDQ(completionFn)}"`;
|
|
3641
5033
|
const root = data.command;
|
|
3642
5034
|
const visibleSubs = getVisibleSubs(root.subcommands);
|
|
@@ -3646,17 +5038,21 @@ function generateZshCompletion(command, options) {
|
|
|
3646
5038
|
const arrayExpandSpecs = expandSpecs.filter((s) => s.isArrayOption);
|
|
3647
5039
|
const hasArrayExpand = arrayExpandSpecs.length > 0;
|
|
3648
5040
|
const lines = [];
|
|
3649
|
-
|
|
3650
|
-
|
|
5041
|
+
if (!isWorker) {
|
|
5042
|
+
lines.push(`#compdef ${programName}`);
|
|
5043
|
+
lines.push(``);
|
|
5044
|
+
}
|
|
3651
5045
|
lines.push(...buildHeaderLines({
|
|
3652
5046
|
programName,
|
|
3653
5047
|
shell: "zsh",
|
|
3654
5048
|
binPath: options.binPath,
|
|
3655
5049
|
programVersion: options.programVersion
|
|
3656
5050
|
}));
|
|
5051
|
+
lines.push(`# politty-completion-mode: ${isWorker ? "worker" : "static"}`);
|
|
5052
|
+
if (isWorker) lines.push(`# politty-completion-worker: true`);
|
|
3657
5053
|
lines.push(`# Generated by politty`);
|
|
3658
5054
|
lines.push(``);
|
|
3659
|
-
lines.push(...generateZshSelfRefresh({
|
|
5055
|
+
if (!isWorker) lines.push(...generateZshSelfRefresh({
|
|
3660
5056
|
programName,
|
|
3661
5057
|
binPath: options.binPath
|
|
3662
5058
|
}));
|
|
@@ -3732,6 +5128,11 @@ function generateZshCompletion(command, options) {
|
|
|
3732
5128
|
lines.push(` return 0`);
|
|
3733
5129
|
lines.push(`}`);
|
|
3734
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(``);
|
|
3735
5136
|
lines.push(`__${fn}_opt_takes_value() {`);
|
|
3736
5137
|
lines.push(` case "$1:$2" in`);
|
|
3737
5138
|
lines.push(...optTakesValueEntries(root, ""));
|
|
@@ -3788,12 +5189,8 @@ function generateZshCompletion(command, options) {
|
|
|
3788
5189
|
lines.push(` __${fn}_cdescribe 'options' _opts`);
|
|
3789
5190
|
if (visibleSubs.length > 0) {
|
|
3790
5191
|
lines.push(` else`);
|
|
3791
|
-
const subItems = getSubNamesWithAliases(root.subcommands)
|
|
3792
|
-
|
|
3793
|
-
return `"${s.name}${desc}"`;
|
|
3794
|
-
}).join(" ");
|
|
3795
|
-
lines.push(` local -a _subs=(${subItems})`);
|
|
3796
|
-
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}`));
|
|
3797
5194
|
} else if (root.positionals.length > 0) {
|
|
3798
5195
|
lines.push(` else`);
|
|
3799
5196
|
lines.push(...positionalBlock(root.positionals, fn, "root", root.options).map((l) => ` ${l}`));
|
|
@@ -3868,21 +5265,23 @@ function generateZshCompletion(command, options) {
|
|
|
3868
5265
|
lines.push(``);
|
|
3869
5266
|
lines.push(`zstyle ':completion:*:*:${programName}:*' file-patterns '%p:globbed-files *(-/):directories'`);
|
|
3870
5267
|
lines.push(``);
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
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
|
+
}
|
|
3877
5276
|
return {
|
|
3878
5277
|
script: lines.join("\n"),
|
|
3879
5278
|
shell: "zsh",
|
|
3880
5279
|
installInstructions: `# To enable auto-refreshing zsh completions, add this to your ~/.zshrc after compinit:
|
|
3881
|
-
eval "$(${programName} completion zsh)"
|
|
5280
|
+
eval "$(${programName} completion zsh --static)"
|
|
3882
5281
|
|
|
3883
5282
|
# For faster shell startup, save the script in your fpath:
|
|
3884
5283
|
mkdir -p ~/.zsh/completions
|
|
3885
|
-
${programName} completion zsh > ~/.zsh/completions/_${programName}
|
|
5284
|
+
${programName} completion zsh --static > ~/.zsh/completions/_${programName}
|
|
3886
5285
|
|
|
3887
5286
|
# Make sure your ~/.zshrc includes the fpath line before compinit:
|
|
3888
5287
|
fpath=(~/.zsh/completions $fpath)
|
|
@@ -3927,6 +5326,7 @@ source ~/.zshrc`
|
|
|
3927
5326
|
* Generate completion script for the specified shell
|
|
3928
5327
|
*/
|
|
3929
5328
|
function generateCompletion(command, options) {
|
|
5329
|
+
if (options.mode === "dispatcher") return generateDispatcherCompletion(command, options);
|
|
3930
5330
|
switch (options.shell) {
|
|
3931
5331
|
case "bash": return generateBashCompletion(command, options);
|
|
3932
5332
|
case "zsh": return generateZshCompletion(command, options);
|
|
@@ -3944,9 +5344,6 @@ function getSupportedShells() {
|
|
|
3944
5344
|
"fish"
|
|
3945
5345
|
];
|
|
3946
5346
|
}
|
|
3947
|
-
function shSingleQuote(s) {
|
|
3948
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3949
|
-
}
|
|
3950
5347
|
function printZshFpathSetup(programName, target) {
|
|
3951
5348
|
console.error("");
|
|
3952
5349
|
console.error("Configure zsh fpath with:");
|
|
@@ -3987,7 +5384,10 @@ const completionArgsSchema = z.object({
|
|
|
3987
5384
|
description: "Show installation instructions"
|
|
3988
5385
|
}),
|
|
3989
5386
|
loader: arg(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." }),
|
|
3990
|
-
install: arg(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: arg(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: arg(z.boolean().default(false), { description: "Generate the legacy static completion script with command metadata baked in." }),
|
|
5389
|
+
dispatcher: arg(z.boolean().default(false), { description: "Generate the runtime dispatcher completion script. This is the default." }),
|
|
5390
|
+
worker: arg(z.boolean().default(false), { description: "Generate an internal static worker artifact for dispatcher mode." })
|
|
3991
5391
|
});
|
|
3992
5392
|
const refreshArgsSchema = z.object({
|
|
3993
5393
|
shell: arg(z.enum([
|
|
@@ -4003,8 +5403,19 @@ const refreshArgsSchema = z.object({
|
|
|
4003
5403
|
positional: true,
|
|
4004
5404
|
description: "Existing politty-generated completion file to refresh",
|
|
4005
5405
|
placeholder: "TARGET"
|
|
4006
|
-
})
|
|
5406
|
+
}),
|
|
5407
|
+
static: arg(z.boolean().default(false), { description: "Refresh using the legacy static completion script mode." }),
|
|
5408
|
+
worker: arg(z.boolean().default(false), { description: "Refresh an internal static worker completion script." })
|
|
4007
5409
|
});
|
|
5410
|
+
const workerPathArgsSchema = z.object({ shell: arg(z.enum([
|
|
5411
|
+
"bash",
|
|
5412
|
+
"zsh",
|
|
5413
|
+
"fish"
|
|
5414
|
+
]), {
|
|
5415
|
+
positional: true,
|
|
5416
|
+
description: "Shell worker to locate",
|
|
5417
|
+
placeholder: "SHELL"
|
|
5418
|
+
}) });
|
|
4008
5419
|
/**
|
|
4009
5420
|
* Create a completion subcommand for your CLI
|
|
4010
5421
|
*
|
|
@@ -4026,7 +5437,8 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
4026
5437
|
const refreshExtra = {
|
|
4027
5438
|
...cacheDir !== void 0 && { cacheDir },
|
|
4028
5439
|
...programVersion !== void 0 && { programVersion },
|
|
4029
|
-
...globalArgsSchema !== void 0 && { globalArgsSchema }
|
|
5440
|
+
...globalArgsSchema !== void 0 && { globalArgsSchema },
|
|
5441
|
+
...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker }
|
|
4030
5442
|
};
|
|
4031
5443
|
const installCtxBase = {
|
|
4032
5444
|
programName: resolvedProgramName,
|
|
@@ -4044,6 +5456,10 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
4044
5456
|
...rootCommand.subCommands,
|
|
4045
5457
|
"__refresh-completion": createRefreshCompletionCommand(rootCommand, resolvedProgramName, refreshExtra)
|
|
4046
5458
|
};
|
|
5459
|
+
if (!rootCommand.subCommands?.["__completion-worker-path"]) rootCommand.subCommands = {
|
|
5460
|
+
...rootCommand.subCommands,
|
|
5461
|
+
"__completion-worker-path": createCompletionWorkerPathCommand(resolvedProgramName, refreshExtra)
|
|
5462
|
+
};
|
|
4047
5463
|
return defineCommand({
|
|
4048
5464
|
name: "completion",
|
|
4049
5465
|
description: "Generate shell completion script",
|
|
@@ -4055,12 +5471,17 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
4055
5471
|
process.exitCode = 1;
|
|
4056
5472
|
return;
|
|
4057
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";
|
|
4058
5478
|
if (args.install) {
|
|
4059
5479
|
let target;
|
|
4060
5480
|
try {
|
|
4061
5481
|
target = install({
|
|
4062
5482
|
rootCommand,
|
|
4063
|
-
...installCtxBase
|
|
5483
|
+
...installCtxBase,
|
|
5484
|
+
completionMode
|
|
4064
5485
|
}, shellType);
|
|
4065
5486
|
} catch (e) {
|
|
4066
5487
|
throw new Error(`install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -4088,10 +5509,13 @@ function createCompletionCommand(rootCommand, programName, globalArgsSchema, ext
|
|
|
4088
5509
|
const result = generateCompletion(rootCommand, {
|
|
4089
5510
|
shell: shellType,
|
|
4090
5511
|
programName: resolvedProgramName,
|
|
5512
|
+
mode: completionMode,
|
|
4091
5513
|
includeDescriptions: true,
|
|
4092
5514
|
...globalArgsSchema !== void 0 && { globalArgsSchema },
|
|
4093
5515
|
...programVersion !== void 0 && { programVersion },
|
|
4094
|
-
...cacheDir !== void 0 && { cacheDir }
|
|
5516
|
+
...cacheDir !== void 0 && { cacheDir },
|
|
5517
|
+
...extra.bundledWorker !== void 0 && { bundledWorker: extra.bundledWorker },
|
|
5518
|
+
...args.worker && { staticWorker: { functionSuffix: "worker" } }
|
|
4095
5519
|
});
|
|
4096
5520
|
if (args.instructions) console.log(result.installInstructions);
|
|
4097
5521
|
else console.log(result.script);
|
|
@@ -4113,11 +5537,31 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
|
|
|
4113
5537
|
rootCommand,
|
|
4114
5538
|
programName,
|
|
4115
5539
|
...extra,
|
|
5540
|
+
completionMode: args.static || args.worker ? "static" : void 0,
|
|
5541
|
+
...args.worker && { staticWorker: { functionSuffix: "worker" } },
|
|
5542
|
+
...args.worker && { allowTargetCreate: true },
|
|
4116
5543
|
...args.target !== void 0 && { targetPath: args.target }
|
|
4117
5544
|
}, args.shell);
|
|
4118
5545
|
}
|
|
4119
5546
|
});
|
|
4120
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
|
+
}
|
|
4121
5565
|
/**
|
|
4122
5566
|
* Wrap a command with a completion subcommand
|
|
4123
5567
|
*
|
|
@@ -4139,19 +5583,21 @@ function createRefreshCompletionCommand(rootCommand, programName, extra = {}) {
|
|
|
4139
5583
|
* ```
|
|
4140
5584
|
*/
|
|
4141
5585
|
function withCompletionCommand(command, options) {
|
|
4142
|
-
const { programName, globalArgsSchema, cacheDir, programVersion } = typeof options === "string" ? { programName: options } : options ?? {};
|
|
5586
|
+
const { programName, globalArgsSchema, cacheDir, programVersion, bundledWorker } = typeof options === "string" ? { programName: options } : options ?? {};
|
|
4143
5587
|
const resolvedProgramName = programName ?? command.name;
|
|
4144
5588
|
const extra = {
|
|
4145
5589
|
...cacheDir !== void 0 && { cacheDir },
|
|
4146
5590
|
...programVersion !== void 0 && { programVersion },
|
|
4147
|
-
...globalArgsSchema !== void 0 && { globalArgsSchema }
|
|
5591
|
+
...globalArgsSchema !== void 0 && { globalArgsSchema },
|
|
5592
|
+
...bundledWorker !== void 0 && { bundledWorker }
|
|
4148
5593
|
};
|
|
4149
5594
|
const wrappedCommand = { ...command };
|
|
4150
5595
|
wrappedCommand.subCommands = {
|
|
4151
5596
|
...command.subCommands,
|
|
4152
5597
|
completion: createCompletionCommand(wrappedCommand, programName, globalArgsSchema, extra),
|
|
4153
5598
|
__complete: createDynamicCompleteCommand(wrappedCommand, programName, globalArgsSchema),
|
|
4154
|
-
"__refresh-completion": createRefreshCompletionCommand(wrappedCommand, resolvedProgramName, extra)
|
|
5599
|
+
"__refresh-completion": createRefreshCompletionCommand(wrappedCommand, resolvedProgramName, extra),
|
|
5600
|
+
"__completion-worker-path": createCompletionWorkerPathCommand(resolvedProgramName, extra)
|
|
4155
5601
|
};
|
|
4156
5602
|
wrappedCommand.runMainHook = (argv) => {
|
|
4157
5603
|
maybeSpawnRefresh(argv, {
|
|
@@ -4178,7 +5624,7 @@ function withCompletionCommand(command, options) {
|
|
|
4178
5624
|
function maybeSpawnRefresh(argv, ctx) {
|
|
4179
5625
|
if (process.env.POLITTY_NO_COMPLETION_REFRESH) return;
|
|
4180
5626
|
const firstPositional = argv.find((a) => !a.startsWith("-"));
|
|
4181
|
-
if (firstPositional === "__complete" || firstPositional === "__refresh-completion" || firstPositional === "completion") return;
|
|
5627
|
+
if (firstPositional === "__complete" || firstPositional === "__refresh-completion" || firstPositional === "__completion-worker-path" || firstPositional === "completion") return;
|
|
4182
5628
|
const shell = detectShell();
|
|
4183
5629
|
if (!shell) return;
|
|
4184
5630
|
const argv0 = process.argv[1];
|
|
@@ -4188,5 +5634,5 @@ function maybeSpawnRefresh(argv, ctx) {
|
|
|
4188
5634
|
}
|
|
4189
5635
|
|
|
4190
5636
|
//#endregion
|
|
4191
|
-
export { defineCommand as _,
|
|
4192
|
-
//# sourceMappingURL=completion-
|
|
5637
|
+
export { defineCommand as S, CompletionDirective as _, generateCompletion as a, resolveValueCompletion as b, createDynamicCompleteCommand as c, bundledWorkerShellExtension as d, defaultBundledWorkerOutputPath as f, extractPositionals as g, extractCompletionData as h, detectShell as i, hasCompleteCommand as l, validateBundledWorkerFile as m, createCompletionWorkerPathCommand as n, getSupportedShells as o, generateBundledCompletionWorker as p, createRefreshCompletionCommand as r, withCompletionCommand as s, createCompletionCommand as t, formatForShell as u, generateCandidates as v, createDefineCommand as x, parseCompletionContext as y };
|
|
5638
|
+
//# sourceMappingURL=completion-DHnVx9Zk.js.map
|