[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

feat: add completion config support to cac

Adds completion config support and moves some things to the shared
module.

There will be a better way to share logic between these two even more
but it hasn't yet clicked in my head.

James Garbutt (Mar 1, 2025, 8:30 PM +0100) 1d0beede 11277dee

+36 -24
+16 -6
src/cac.ts
··· 3 3 import * as fish from './fish'; 4 4 import * as powershell from './powershell'; 5 5 import type { CAC } from 'cac'; 6 - import { Completion } from './'; 6 + import { Completion } from './index'; 7 + import { CompletionConfig, noopHandler } from './shared'; 7 8 8 9 const execPath = process.execPath; 9 10 const processArgs = process.argv.slice(1); ··· 17 18 return path.includes(' ') ? `'${path}'` : path; 18 19 } 19 20 20 - export default function tab(instance: CAC): Completion { 21 + export default async function tab( 22 + instance: CAC, 23 + completionConfig?: CompletionConfig 24 + ) { 21 25 const completion = new Completion(); 22 26 23 27 // Add all commands and their options ··· 29 33 arg.startsWith('[') 30 34 ); // true if optional (wrapped in []) 31 35 36 + const isRootCommand = cmd.name === '@@global@@'; 37 + const commandCompletionConfig = isRootCommand 38 + ? completionConfig 39 + : completionConfig?.subCommands?.[cmd.name]; 40 + 32 41 // Add command to completion 33 42 const commandName = completion.addCommand( 34 - cmd.name === '@@global@@' ? '' : cmd.name, 43 + isRootCommand ? '' : cmd.name, 35 44 cmd.description || '', 36 45 args, 37 - async () => [] 46 + commandCompletionConfig?.handler ?? noopHandler 38 47 ); 39 48 40 49 // Add command options 41 50 for (const option of [...instance.globalCommand.options, ...cmd.options]) { 42 51 // Extract short flag from the name if it exists (e.g., "-c, --config" -> "c") 43 52 const shortFlag = option.name.match(/^-([a-zA-Z]), --/)?.[1]; 53 + const argName = option.name.replace(/^-[a-zA-Z], --/, ''); 44 54 45 55 completion.addOption( 46 56 commandName, 47 - `--${option.name.replace(/^-[a-zA-Z], --/, '')}`, // Remove the short flag part if it exists 57 + `--${argName}`, // Remove the short flag part if it exists 48 58 option.description || '', 49 - async () => [], 59 + commandCompletionConfig?.options?.[argName]?.handler ?? noopHandler, 50 60 shortFlag 51 61 ); 52 62 }
+2 -18
src/citty.ts
··· 3 3 import * as bash from './bash'; 4 4 import * as fish from './fish'; 5 5 import * as powershell from './powershell'; 6 - import { Completion, type Handler } from '.'; 6 + import { Completion } from './index'; 7 7 import type { 8 8 ArgsDef, 9 9 CommandDef, ··· 11 11 SubCommandsDef, 12 12 } from 'citty'; 13 13 import { generateFigSpec } from './fig'; 14 + import { CompletionConfig, noopHandler } from './shared'; 14 15 15 16 function quoteIfNeeded(path: string) { 16 17 return path.includes(' ') ? `'${path}'` : path; ··· 29 30 Object.values(config.args).some((arg) => arg.type === 'positional') 30 31 ); 31 32 } 32 - 33 - // TODO (43081j): use type inference some day, so we can type-check 34 - // that the sub commands exist, the options exist, etc. 35 - interface CompletionConfig { 36 - handler?: Handler; 37 - subCommands?: Record<string, CompletionConfig>; 38 - options?: Record< 39 - string, 40 - { 41 - handler: Handler; 42 - } 43 - >; 44 - } 45 - 46 - const noopHandler: Handler = () => { 47 - return []; 48 - }; 49 33 50 34 async function handleSubCommands( 51 35 completion: Completion,
+18
src/shared.ts
··· 1 + import { Handler } from './index'; 2 + 3 + export const noopHandler: Handler = () => { 4 + return []; 5 + }; 6 + 7 + // TODO (43081j): use type inference some day, so we can type-check 8 + // that the sub commands exist, the options exist, etc. 9 + export interface CompletionConfig { 10 + handler?: Handler; 11 + subCommands?: Record<string, CompletionConfig>; 12 + options?: Record< 13 + string, 14 + { 15 + handler: Handler; 16 + } 17 + >; 18 + }