import { isCommandBuilderCallback } from './command.js'; import { assertNotStrictEqual } from './typings/common-types.js'; import * as templates from './completion-templates.js'; import { isPromise } from './utils/is-promise.js'; import { parseCommand } from './parse-command.js'; export class Completion { constructor(yargs, usage, command, shim) { var _a, _b, _c; this.yargs = yargs; this.usage = usage; this.command = command; this.shim = shim; this.completionKey = 'get-yargs-completions'; this.aliases = null; this.customCompletionFunction = null; this.indexAfterLastReset = 0; this.zshShell = (_c = (((_a = this.shim.getEnv('SHELL')) === null || _a === void 0 ? void 0 : _a.includes('zsh')) || ((_b = this.shim.getEnv('ZSH_NAME')) === null || _b === void 0 ? void 0 : _b.includes('zsh')))) !== null && _c !== void 0 ? _c : false; } defaultCompletion(args, argv, current, done) { const handlers = this.command.getCommandHandlers(); for (let i = 0, ii = args.length; i < ii; ++i) { if (handlers[args[i]] && handlers[args[i]].builder) { const builder = handlers[args[i]].builder; if (isCommandBuilderCallback(builder)) { this.indexAfterLastReset = i + 1; const y = this.yargs.getInternalMethods().reset(); builder(y, true); return y.argv; } } } const completions = []; this.commandCompletions(completions, args, current); this.optionCompletions(completions, args, argv, current); this.choicesFromOptionsCompletions(completions, args, argv, current); this.choicesFromPositionalsCompletions(completions, args, argv, current); done(null, completions); } commandCompletions(completions, args, current) { const parentCommands = this.yargs .getInternalMethods() .getContext().commands; if (!current.match(/^-/) && parentCommands[parentCommands.length - 1] !== current && !this.previousArgHasChoices(args)) { this.usage.getCommands().forEach(usageCommand => { const commandName = parseCommand(usageCommand[0]).cmd; if (args.indexOf(commandName) === -1) { if (!this.zshShell) { completions.push(commandName); } else { const desc = usageCommand[1] || ''; completions.push(commandName.replace(/:/g, '\\:') + ':' + desc); } } }); } } optionCompletions(completions, args, argv, current) { if ((current.match(/^-/) || (current === '' && completions.length === 0)) && !this.previousArgHasChoices(args)) { const options = this.yargs.getOptions(); const positionalKeys = this.yargs.getGroups()[this.usage.getPositionalGroupName()] || []; Object.keys(options.key).forEach(key => { const negable = !!options.configuration['boolean-negation'] && options.boolean.includes(key); const isPositionalKey = positionalKeys.includes(key); if (!isPositionalKey && !options.hiddenOptions.includes(key) && !this.argsContainKey(args, key, negable)) { this.completeOptionKey(key, completions, current, negable && !!options.default[key]); } }); } } choicesFromOptionsCompletions(completions, args, argv, current) { if (this.previousArgHasChoices(args)) { const choices = this.getPreviousArgChoices(args); if (choices && choices.length > 0) { completions.push(...choices.map(c => c.replace(/:/g, '\\:'))); } } } choicesFromPositionalsCompletions(completions, args, argv, current) { if (current === '' && completions.length > 0 && this.previousArgHasChoices(args)) { return; } const positionalKeys = this.yargs.getGroups()[this.usage.getPositionalGroupName()] || []; const offset = Math.max(this.indexAfterLastReset, this.yargs.getInternalMethods().getContext().commands.length + 1); const positionalKey = positionalKeys[argv._.length - offset - 1]; if (!positionalKey) { return; } const choices = this.yargs.getOptions().choices[positionalKey] || []; for (const choice of choices) { if (choice.startsWith(current)) { completions.push(choice.replace(/:/g, '\\:')); } } } getPreviousArgChoices(args) { if (args.length < 1) return; let previousArg = args[args.length - 1]; let filter = ''; if (!previousArg.startsWith('-') && args.length > 1) { filter = previousArg; previousArg = args[args.length - 2]; } if (!previousArg.startsWith('-')) return; const previousArgKey = previousArg.replace(/^-+/, ''); const options = this.yargs.getOptions(); const possibleAliases = [ previousArgKey, ...(this.yargs.getAliases()[previousArgKey] || []), ]; let choices; for (const possibleAlias of possibleAliases) { if (Object.prototype.hasOwnProperty.call(options.key, possibleAlias) && Array.isArray(options.choices[possibleAlias])) { choices = options.choices[possibleAlias]; break; } } if (choices) { return choices.filter(choice => !filter || choice.startsWith(filter)); } } previousArgHasChoices(args) { const choices = this.getPreviousArgChoices(args); return choices !== undefined && choices.length > 0; } argsContainKey(args, key, negable) { const argsContains = (s) => args.indexOf((/^[^0-9]$/.test(s) ? '-' : '--') + s) !== -1; if (argsContains(key)) return true; if (negable && argsContains(`no-${key}`)) return true; if (this.aliases) { for (const alias of this.aliases[key]) { if (argsContains(alias)) return true; } } return false; } completeOptionKey(key, completions, current, negable) { var _a, _b, _c, _d; let keyWithDesc = key; if (this.zshShell) { const descs = this.usage.getDescriptions(); const aliasKey = (_b = (_a = this === null || this === void 0 ? void 0 : this.aliases) === null || _a === void 0 ? void 0 : _a[key]) === null || _b === void 0 ? void 0 : _b.find(alias => { const desc = descs[alias]; return typeof desc === 'string' && desc.length > 0; }); const descFromAlias = aliasKey ? descs[aliasKey] : undefined; const desc = (_d = (_c = descs[key]) !== null && _c !== void 0 ? _c : descFromAlias) !== null && _d !== void 0 ? _d : ''; keyWithDesc = `${key.replace(/:/g, '\\:')}:${desc .replace('__yargsString__:', '') .replace(/(\r\n|\n|\r)/gm, ' ')}`; } const startsByTwoDashes = (s) => /^--/.test(s); const isShortOption = (s) => /^[^0-9]$/.test(s); const dashes = !startsByTwoDashes(current) && isShortOption(key) ? '-' : '--'; completions.push(dashes + keyWithDesc); if (negable) { completions.push(dashes + 'no-' + keyWithDesc); } } customCompletion(args, argv, current, done) { assertNotStrictEqual(this.customCompletionFunction, null, this.shim); if (isSyncCompletionFunction(this.customCompletionFunction)) { const result = this.customCompletionFunction(current, argv); if (isPromise(result)) { return result .then(list => { this.shim.process.nextTick(() => { done(null, list); }); }) .catch(err => { this.shim.process.nextTick(() => { done(err, undefined); }); }); } return done(null, result); } else if (isFallbackCompletionFunction(this.customCompletionFunction)) { return this.customCompletionFunction(current, argv, (onCompleted = done) => this.defaultCompletion(args, argv, current, onCompleted), completions => { done(null, completions); }); } else { return this.customCompletionFunction(current, argv, completions => { done(null, completions); }); } } getCompletion(args, done) { const current = args.length ? args[args.length - 1] : ''; const argv = this.yargs.parse(args, true); const completionFunction = this.customCompletionFunction ? (argv) => this.customCompletion(args, argv, current, done) : (argv) => this.defaultCompletion(args, argv, current, done); return isPromise(argv) ? argv.then(completionFunction) : completionFunction(argv); } generateCompletionScript($0, cmd) { let script = this.zshShell ? templates.completionZshTemplate : templates.completionShTemplate; const name = this.shim.path.basename($0); if ($0.match(/\.js$/)) $0 = `./${$0}`; script = script.replace(/{{app_name}}/g, name); script = script.replace(/{{completion_command}}/g, cmd); return script.replace(/{{app_path}}/g, $0); } registerFunction(fn) { this.customCompletionFunction = fn; } setParsed(parsed) { this.aliases = parsed.aliases; } } export function completion(yargs, usage, command, shim) { return new Completion(yargs, usage, command, shim); } function isSyncCompletionFunction(completionFunction) { return completionFunction.length < 3; } function isFallbackCompletionFunction(completionFunction) { return completionFunction.length > 3; }