Skip to content

Commit 1d0beed

Browse files
committed
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.
1 parent 11277de commit 1d0beed

File tree

3 files changed

+36
-24
lines changed

3 files changed

+36
-24
lines changed

src/cac.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import * as bash from './bash';
33
import * as fish from './fish';
44
import * as powershell from './powershell';
55
import type { CAC } from 'cac';
6-
import { Completion } from './';
6+
import { Completion } from './index';
7+
import { CompletionConfig, noopHandler } from './shared';
78

89
const execPath = process.execPath;
910
const processArgs = process.argv.slice(1);
@@ -17,7 +18,10 @@ function quoteIfNeeded(path: string): string {
1718
return path.includes(' ') ? `'${path}'` : path;
1819
}
1920

20-
export default function tab(instance: CAC): Completion {
21+
export default async function tab(
22+
instance: CAC,
23+
completionConfig?: CompletionConfig
24+
) {
2125
const completion = new Completion();
2226

2327
// Add all commands and their options
@@ -29,24 +33,30 @@ export default function tab(instance: CAC): Completion {
2933
arg.startsWith('[')
3034
); // true if optional (wrapped in [])
3135

36+
const isRootCommand = cmd.name === '@@global@@';
37+
const commandCompletionConfig = isRootCommand
38+
? completionConfig
39+
: completionConfig?.subCommands?.[cmd.name];
40+
3241
// Add command to completion
3342
const commandName = completion.addCommand(
34-
cmd.name === '@@global@@' ? '' : cmd.name,
43+
isRootCommand ? '' : cmd.name,
3544
cmd.description || '',
3645
args,
37-
async () => []
46+
commandCompletionConfig?.handler ?? noopHandler
3847
);
3948

4049
// Add command options
4150
for (const option of [...instance.globalCommand.options, ...cmd.options]) {
4251
// Extract short flag from the name if it exists (e.g., "-c, --config" -> "c")
4352
const shortFlag = option.name.match(/^-([a-zA-Z]), --/)?.[1];
53+
const argName = option.name.replace(/^-[a-zA-Z], --/, '');
4454

4555
completion.addOption(
4656
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
4858
option.description || '',
49-
async () => [],
59+
commandCompletionConfig?.options?.[argName]?.handler ?? noopHandler,
5060
shortFlag
5161
);
5262
}

src/citty.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import * as zsh from './zsh';
33
import * as bash from './bash';
44
import * as fish from './fish';
55
import * as powershell from './powershell';
6-
import { Completion, type Handler } from '.';
6+
import { Completion } from './index';
77
import type {
88
ArgsDef,
99
CommandDef,
1010
PositionalArgDef,
1111
SubCommandsDef,
1212
} from 'citty';
1313
import { generateFigSpec } from './fig';
14+
import { CompletionConfig, noopHandler } from './shared';
1415

1516
function quoteIfNeeded(path: string) {
1617
return path.includes(' ') ? `'${path}'` : path;
@@ -30,23 +31,6 @@ function isConfigPositional<T extends ArgsDef>(config: CommandDef<T>) {
3031
);
3132
}
3233

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-
5034
async function handleSubCommands(
5135
completion: Completion,
5236
subCommands: SubCommandsDef,

src/shared.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
}

0 commit comments

Comments
 (0)