completion.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { isCommandBuilderCallback } from './command.js';
  2. import { assertNotStrictEqual } from './typings/common-types.js';
  3. import * as templates from './completion-templates.js';
  4. import { isPromise } from './utils/is-promise.js';
  5. import { parseCommand } from './parse-command.js';
  6. export 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. done(null, completions);
  36. }
  37. commandCompletions(completions, args, current) {
  38. const parentCommands = this.yargs
  39. .getInternalMethods()
  40. .getContext().commands;
  41. if (!current.match(/^-/) &&
  42. parentCommands[parentCommands.length - 1] !== current) {
  43. this.usage.getCommands().forEach(usageCommand => {
  44. const commandName = parseCommand(usageCommand[0]).cmd;
  45. if (args.indexOf(commandName) === -1) {
  46. if (!this.zshShell) {
  47. completions.push(commandName);
  48. }
  49. else {
  50. const desc = usageCommand[1] || '';
  51. completions.push(commandName.replace(/:/g, '\\:') + ':' + desc);
  52. }
  53. }
  54. });
  55. }
  56. }
  57. optionCompletions(completions, args, argv, current) {
  58. if (current.match(/^-/) || (current === '' && completions.length === 0)) {
  59. const options = this.yargs.getOptions();
  60. const positionalKeys = this.yargs.getGroups()[this.usage.getPositionalGroupName()] || [];
  61. Object.keys(options.key).forEach(key => {
  62. const negable = !!options.configuration['boolean-negation'] &&
  63. options.boolean.includes(key);
  64. const isPositionalKey = positionalKeys.includes(key);
  65. if (!isPositionalKey &&
  66. !this.argsContainKey(args, argv, key, negable)) {
  67. this.completeOptionKey(key, completions, current);
  68. if (negable && !!options.default[key])
  69. this.completeOptionKey(`no-${key}`, completions, current);
  70. }
  71. });
  72. }
  73. }
  74. argsContainKey(args, argv, key, negable) {
  75. if (args.indexOf(`--${key}`) !== -1)
  76. return true;
  77. if (negable && args.indexOf(`--no-${key}`) !== -1)
  78. return true;
  79. if (this.aliases) {
  80. for (const alias of this.aliases[key]) {
  81. if (argv[alias] !== undefined)
  82. return true;
  83. }
  84. }
  85. return false;
  86. }
  87. completeOptionKey(key, completions, current) {
  88. const descs = this.usage.getDescriptions();
  89. const startsByTwoDashes = (s) => /^--/.test(s);
  90. const isShortOption = (s) => /^[^0-9]$/.test(s);
  91. const dashes = !startsByTwoDashes(current) && isShortOption(key) ? '-' : '--';
  92. if (!this.zshShell) {
  93. completions.push(dashes + key);
  94. }
  95. else {
  96. const desc = descs[key] || '';
  97. completions.push(dashes +
  98. `${key.replace(/:/g, '\\:')}:${desc.replace('__yargsString__:', '')}`);
  99. }
  100. }
  101. customCompletion(args, argv, current, done) {
  102. assertNotStrictEqual(this.customCompletionFunction, null, this.shim);
  103. if (isSyncCompletionFunction(this.customCompletionFunction)) {
  104. const result = this.customCompletionFunction(current, argv);
  105. if (isPromise(result)) {
  106. return result
  107. .then(list => {
  108. this.shim.process.nextTick(() => {
  109. done(null, list);
  110. });
  111. })
  112. .catch(err => {
  113. this.shim.process.nextTick(() => {
  114. done(err, undefined);
  115. });
  116. });
  117. }
  118. return done(null, result);
  119. }
  120. else if (isFallbackCompletionFunction(this.customCompletionFunction)) {
  121. return this.customCompletionFunction(current, argv, (onCompleted = done) => this.defaultCompletion(args, argv, current, onCompleted), completions => {
  122. done(null, completions);
  123. });
  124. }
  125. else {
  126. return this.customCompletionFunction(current, argv, completions => {
  127. done(null, completions);
  128. });
  129. }
  130. }
  131. getCompletion(args, done) {
  132. const current = args.length ? args[args.length - 1] : '';
  133. const argv = this.yargs.parse(args, true);
  134. const completionFunction = this.customCompletionFunction
  135. ? (argv) => this.customCompletion(args, argv, current, done)
  136. : (argv) => this.defaultCompletion(args, argv, current, done);
  137. return isPromise(argv)
  138. ? argv.then(completionFunction)
  139. : completionFunction(argv);
  140. }
  141. generateCompletionScript($0, cmd) {
  142. let script = this.zshShell
  143. ? templates.completionZshTemplate
  144. : templates.completionShTemplate;
  145. const name = this.shim.path.basename($0);
  146. if ($0.match(/\.js$/))
  147. $0 = `./${$0}`;
  148. script = script.replace(/{{app_name}}/g, name);
  149. script = script.replace(/{{completion_command}}/g, cmd);
  150. return script.replace(/{{app_path}}/g, $0);
  151. }
  152. registerFunction(fn) {
  153. this.customCompletionFunction = fn;
  154. }
  155. setParsed(parsed) {
  156. this.aliases = parsed.aliases;
  157. }
  158. }
  159. export function completion(yargs, usage, command, shim) {
  160. return new Completion(yargs, usage, command, shim);
  161. }
  162. function isSyncCompletionFunction(completionFunction) {
  163. return completionFunction.length < 3;
  164. }
  165. function isFallbackCompletionFunction(completionFunction) {
  166. return completionFunction.length > 3;
  167. }