1import { isCommandBuilderCallback } from './command.js'; 2import { assertNotStrictEqual } from './typings/common-types.js'; 3import * as templates from './completion-templates.js'; 4import { isPromise } from './utils/is-promise.js'; 5import { parseCommand } from './parse-command.js'; 6export class Completion { 7 constructor(yargs, usage, command, shim) { 8 var _a, _b, _c; 9 this.yargs = yargs; 10 this.usage = usage; 11 this.command = command; 12 this.shim = shim; 13 this.completionKey = 'get-yargs-completions'; 14 this.aliases = null; 15 this.customCompletionFunction = null; 16 this.zshShell = 17 (_c = (((_a = this.shim.getEnv('SHELL')) === null || _a === void 0 ? void 0 : _a.includes('zsh')) || 18 ((_b = this.shim.getEnv('ZSH_NAME')) === null || _b === void 0 ? void 0 : _b.includes('zsh')))) !== null && _c !== void 0 ? _c : false; 19 } 20 defaultCompletion(args, argv, current, done) { 21 const handlers = this.command.getCommandHandlers(); 22 for (let i = 0, ii = args.length; i < ii; ++i) { 23 if (handlers[args[i]] && handlers[args[i]].builder) { 24 const builder = handlers[args[i]].builder; 25 if (isCommandBuilderCallback(builder)) { 26 const y = this.yargs.getInternalMethods().reset(); 27 builder(y, true); 28 return y.argv; 29 } 30 } 31 } 32 const completions = []; 33 this.commandCompletions(completions, args, current); 34 this.optionCompletions(completions, args, argv, current); 35 this.choicesCompletions(completions, args, argv, current); 36 done(null, completions); 37 } 38 commandCompletions(completions, args, current) { 39 const parentCommands = this.yargs 40 .getInternalMethods() 41 .getContext().commands; 42 if (!current.match(/^-/) && 43 parentCommands[parentCommands.length - 1] !== current && 44 !this.previousArgHasChoices(args)) { 45 this.usage.getCommands().forEach(usageCommand => { 46 const commandName = parseCommand(usageCommand[0]).cmd; 47 if (args.indexOf(commandName) === -1) { 48 if (!this.zshShell) { 49 completions.push(commandName); 50 } 51 else { 52 const desc = usageCommand[1] || ''; 53 completions.push(commandName.replace(/:/g, '\\:') + ':' + desc); 54 } 55 } 56 }); 57 } 58 } 59 optionCompletions(completions, args, argv, current) { 60 if ((current.match(/^-/) || (current === '' && completions.length === 0)) && 61 !this.previousArgHasChoices(args)) { 62 const options = this.yargs.getOptions(); 63 const positionalKeys = this.yargs.getGroups()[this.usage.getPositionalGroupName()] || []; 64 Object.keys(options.key).forEach(key => { 65 const negable = !!options.configuration['boolean-negation'] && 66 options.boolean.includes(key); 67 const isPositionalKey = positionalKeys.includes(key); 68 if (!isPositionalKey && 69 !this.argsContainKey(args, argv, key, negable)) { 70 this.completeOptionKey(key, completions, current); 71 if (negable && !!options.default[key]) 72 this.completeOptionKey(`no-${key}`, completions, current); 73 } 74 }); 75 } 76 } 77 choicesCompletions(completions, args, argv, current) { 78 if (this.previousArgHasChoices(args)) { 79 const choices = this.getPreviousArgChoices(args); 80 if (choices && choices.length > 0) { 81 completions.push(...choices); 82 } 83 } 84 } 85 getPreviousArgChoices(args) { 86 if (args.length < 1) 87 return; 88 let previousArg = args[args.length - 1]; 89 let filter = ''; 90 if (!previousArg.startsWith('--') && args.length > 1) { 91 filter = previousArg; 92 previousArg = args[args.length - 2]; 93 } 94 if (!previousArg.startsWith('--')) 95 return; 96 const previousArgKey = previousArg.replace(/-/g, ''); 97 const options = this.yargs.getOptions(); 98 if (Object.keys(options.key).some(key => key === previousArgKey) && 99 Array.isArray(options.choices[previousArgKey])) { 100 return options.choices[previousArgKey].filter(choice => !filter || choice.startsWith(filter)); 101 } 102 } 103 previousArgHasChoices(args) { 104 const choices = this.getPreviousArgChoices(args); 105 return choices !== undefined && choices.length > 0; 106 } 107 argsContainKey(args, argv, key, negable) { 108 if (args.indexOf(`--${key}`) !== -1) 109 return true; 110 if (negable && args.indexOf(`--no-${key}`) !== -1) 111 return true; 112 if (this.aliases) { 113 for (const alias of this.aliases[key]) { 114 if (argv[alias] !== undefined) 115 return true; 116 } 117 } 118 return false; 119 } 120 completeOptionKey(key, completions, current) { 121 const descs = this.usage.getDescriptions(); 122 const startsByTwoDashes = (s) => /^--/.test(s); 123 const isShortOption = (s) => /^[^0-9]$/.test(s); 124 const dashes = !startsByTwoDashes(current) && isShortOption(key) ? '-' : '--'; 125 if (!this.zshShell) { 126 completions.push(dashes + key); 127 } 128 else { 129 const desc = descs[key] || ''; 130 completions.push(dashes + 131 `${key.replace(/:/g, '\\:')}:${desc.replace('__yargsString__:', '')}`); 132 } 133 } 134 customCompletion(args, argv, current, done) { 135 assertNotStrictEqual(this.customCompletionFunction, null, this.shim); 136 if (isSyncCompletionFunction(this.customCompletionFunction)) { 137 const result = this.customCompletionFunction(current, argv); 138 if (isPromise(result)) { 139 return result 140 .then(list => { 141 this.shim.process.nextTick(() => { 142 done(null, list); 143 }); 144 }) 145 .catch(err => { 146 this.shim.process.nextTick(() => { 147 done(err, undefined); 148 }); 149 }); 150 } 151 return done(null, result); 152 } 153 else if (isFallbackCompletionFunction(this.customCompletionFunction)) { 154 return this.customCompletionFunction(current, argv, (onCompleted = done) => this.defaultCompletion(args, argv, current, onCompleted), completions => { 155 done(null, completions); 156 }); 157 } 158 else { 159 return this.customCompletionFunction(current, argv, completions => { 160 done(null, completions); 161 }); 162 } 163 } 164 getCompletion(args, done) { 165 const current = args.length ? args[args.length - 1] : ''; 166 const argv = this.yargs.parse(args, true); 167 const completionFunction = this.customCompletionFunction 168 ? (argv) => this.customCompletion(args, argv, current, done) 169 : (argv) => this.defaultCompletion(args, argv, current, done); 170 return isPromise(argv) 171 ? argv.then(completionFunction) 172 : completionFunction(argv); 173 } 174 generateCompletionScript($0, cmd) { 175 let script = this.zshShell 176 ? templates.completionZshTemplate 177 : templates.completionShTemplate; 178 const name = this.shim.path.basename($0); 179 if ($0.match(/\.js$/)) 180 $0 = `./${$0}`; 181 script = script.replace(/{{app_name}}/g, name); 182 script = script.replace(/{{completion_command}}/g, cmd); 183 return script.replace(/{{app_path}}/g, $0); 184 } 185 registerFunction(fn) { 186 this.customCompletionFunction = fn; 187 } 188 setParsed(parsed) { 189 this.aliases = parsed.aliases; 190 } 191} 192export function completion(yargs, usage, command, shim) { 193 return new Completion(yargs, usage, command, shim); 194} 195function isSyncCompletionFunction(completionFunction) { 196 return completionFunction.length < 3; 197} 198function isFallbackCompletionFunction(completionFunction) { 199 return completionFunction.length > 3; 200} 201