diff --git a/src/cac.ts b/src/cac.ts index 58ac7fc..077ca52 100644 --- a/src/cac.ts +++ b/src/cac.ts @@ -3,7 +3,8 @@ import * as bash from './bash'; import * as fish from './fish'; import * as powershell from './powershell'; import type { CAC } from 'cac'; -import { Completion } from './'; +import { Completion } from './index'; +import { noopHandler, TabFunction } from './shared'; const execPath = process.execPath; const processArgs = process.argv.slice(1); @@ -17,7 +18,7 @@ function quoteIfNeeded(path: string): string { return path.includes(' ') ? `'${path}'` : path; } -export default function tab(instance: CAC): Completion { +const tab: TabFunction = async (instance, completionConfig) => { const completion = new Completion(); // Add all commands and their options @@ -29,24 +30,30 @@ export default function tab(instance: CAC): Completion { arg.startsWith('[') ); // true if optional (wrapped in []) + const isRootCommand = cmd.name === '@@global@@'; + const commandCompletionConfig = isRootCommand + ? completionConfig + : completionConfig?.subCommands?.[cmd.name]; + // Add command to completion const commandName = completion.addCommand( - cmd.name === '@@global@@' ? '' : cmd.name, + isRootCommand ? '' : cmd.name, cmd.description || '', args, - async () => [] + commandCompletionConfig?.handler ?? noopHandler ); // Add command options for (const option of [...instance.globalCommand.options, ...cmd.options]) { // Extract short flag from the name if it exists (e.g., "-c, --config" -> "c") const shortFlag = option.name.match(/^-([a-zA-Z]), --/)?.[1]; + const argName = option.name.replace(/^-[a-zA-Z], --/, ''); completion.addOption( commandName, - `--${option.name.replace(/^-[a-zA-Z], --/, '')}`, // Remove the short flag part if it exists + `--${argName}`, // Remove the short flag part if it exists option.description || '', - async () => [], + commandCompletionConfig?.options?.[argName]?.handler ?? noopHandler, shortFlag ); } @@ -93,4 +100,6 @@ export default function tab(instance: CAC): Completion { }); return completion; -} +}; + +export default tab; diff --git a/src/citty.ts b/src/citty.ts index e435b3e..ad90a43 100644 --- a/src/citty.ts +++ b/src/citty.ts @@ -3,7 +3,7 @@ import * as zsh from './zsh'; import * as bash from './bash'; import * as fish from './fish'; import * as powershell from './powershell'; -import { Completion, type Handler } from '.'; +import { Completion } from './index'; import type { ArgsDef, CommandDef, @@ -11,6 +11,7 @@ import type { SubCommandsDef, } from 'citty'; import { generateFigSpec } from './fig'; +import { CompletionConfig, noopHandler, TabFunction } from './shared'; function quoteIfNeeded(path: string) { return path.includes(' ') ? `'${path}'` : path; @@ -30,23 +31,6 @@ function isConfigPositional(config: CommandDef) { ); } -// TODO (43081j): use type inference some day, so we can type-check -// that the sub commands exist, the options exist, etc. -interface CompletionConfig { - handler?: Handler; - subCommands?: Record; - options?: Record< - string, - { - handler: Handler; - } - >; -} - -const noopHandler: Handler = () => { - return []; -}; - async function handleSubCommands( completion: Completion, subCommands: SubCommandsDef, @@ -108,10 +92,10 @@ async function handleSubCommands( } } -export default async function tab( - instance: CommandDef, - completionConfig?: CompletionConfig -) { +const tab: TabFunction> = async ( + instance, + completionConfig +) => { const completion = new Completion(); const meta = await resolve(instance.meta); @@ -220,7 +204,9 @@ export default async function tab( subCommands.complete = completeCommand; return completion; -} +}; + +export default tab; type Resolvable = T | Promise | (() => T) | (() => Promise); diff --git a/src/shared.ts b/src/shared.ts index e69de29..5f6f0ae 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -0,0 +1,23 @@ +import { Handler, Completion } from './index'; + +export const noopHandler: Handler = () => { + return []; +}; + +// TODO (43081j): use type inference some day, so we can type-check +// that the sub commands exist, the options exist, etc. +export interface CompletionConfig { + handler?: Handler; + subCommands?: Record; + options?: Record< + string, + { + handler: Handler; + } + >; +} + +export type TabFunction = ( + instance: T, + completionConfig?: CompletionConfig +) => Promise;