1+ import * as zsh from './zsh' ;
2+ import * as bash from './bash' ;
3+ import * as fish from './fish' ;
4+ import * as powershell from './powershell' ;
5+ import type { Command as CommanderCommand } from 'commander' ;
6+ import { Completion } from './' ;
7+
8+ const execPath = process . execPath ;
9+ const processArgs = process . argv . slice ( 1 ) ;
10+ const quotedExecPath = quoteIfNeeded ( execPath ) ;
11+ const quotedProcessArgs = processArgs . map ( quoteIfNeeded ) ;
12+ const quotedProcessExecArgs = process . execArgv . map ( quoteIfNeeded ) ;
13+
14+ const x = `${ quotedExecPath } ${ quotedProcessExecArgs . join ( ' ' ) } ${ quotedProcessArgs [ 0 ] } ` ;
15+
16+ function quoteIfNeeded ( path : string ) : string {
17+ return path . includes ( ' ' ) ? `'${ path } '` : path ;
18+ }
19+
20+ export default function tab ( instance : CommanderCommand ) : Completion {
21+ const completion = new Completion ( ) ;
22+ const programName = instance . name ( ) ;
23+
24+ // Process the root command
25+ processRootCommand ( completion , instance , programName ) ;
26+
27+ // Process all subcommands
28+ processSubcommands ( completion , instance , programName ) ;
29+
30+ // Add the complete command
31+ instance
32+ . command ( 'complete [shell]' )
33+ . allowUnknownOption ( true )
34+ . description ( 'Generate shell completion scripts' )
35+ . action ( async ( shell , options ) => {
36+ // Check if there are arguments after --
37+ const dashDashIndex = process . argv . indexOf ( '--' ) ;
38+ let extra : string [ ] = [ ] ;
39+
40+ if ( dashDashIndex !== - 1 ) {
41+ extra = process . argv . slice ( dashDashIndex + 1 ) ;
42+ // If shell is actually part of the extra args, adjust accordingly
43+ if ( shell && extra . length > 0 && shell === '--' ) {
44+ shell = undefined ;
45+ }
46+ }
47+
48+ switch ( shell ) {
49+ case 'zsh' : {
50+ const script = zsh . generate ( programName , x ) ;
51+ console . log ( script ) ;
52+ break ;
53+ }
54+ case 'bash' : {
55+ const script = bash . generate ( programName , x ) ;
56+ console . log ( script ) ;
57+ break ;
58+ }
59+ case 'fish' : {
60+ const script = fish . generate ( programName , x ) ;
61+ console . log ( script ) ;
62+ break ;
63+ }
64+ case 'powershell' : {
65+ const script = powershell . generate ( programName , x ) ;
66+ console . log ( script ) ;
67+ break ;
68+ }
69+ case 'debug' : {
70+ // Debug mode to print all collected commands
71+ const commandMap = new Map < string , CommanderCommand > ( ) ;
72+ collectCommands ( instance , '' , commandMap ) ;
73+ console . log ( 'Collected commands:' ) ;
74+ for ( const [ path , cmd ] of commandMap . entries ( ) ) {
75+ console . log ( `- ${ path || '<root>' } : ${ cmd . description ( ) || 'No description' } ` ) ;
76+ }
77+ break ;
78+ }
79+ default : {
80+ // Parse current command context for autocompletion
81+ return completion . parse ( extra ) ;
82+ }
83+ }
84+ } ) ;
85+
86+ return completion ;
87+ }
88+
89+ function processRootCommand (
90+ completion : Completion ,
91+ command : CommanderCommand ,
92+ programName : string
93+ ) : void {
94+ // Add the root command
95+ completion . addCommand (
96+ '' ,
97+ command . description ( ) || '' ,
98+ [ ] ,
99+ async ( ) => [ ]
100+ ) ;
101+
102+ // Add root command options
103+ for ( const option of command . options ) {
104+ // Extract short flag from the name if it exists (e.g., "-c, --config" -> "c")
105+ const flags = option . flags ;
106+ const shortFlag = flags . match ( / ^ - ( [ a - z A - Z ] ) , - - / ) ?. [ 1 ] ;
107+ const longFlag = flags . match ( / - - ( [ a - z A - Z 0 - 9 - ] + ) / ) ?. [ 1 ] ;
108+
109+ if ( longFlag ) {
110+ completion . addOption (
111+ '' ,
112+ `--${ longFlag } ` ,
113+ option . description || '' ,
114+ async ( ) => [ ] ,
115+ shortFlag
116+ ) ;
117+ }
118+ }
119+ }
120+
121+ function processSubcommands (
122+ completion : Completion ,
123+ rootCommand : CommanderCommand ,
124+ programName : string
125+ ) : void {
126+ // Build a map of command paths
127+ const commandMap = new Map < string , CommanderCommand > ( ) ;
128+
129+ // Collect all commands with their full paths
130+ collectCommands ( rootCommand , '' , commandMap ) ;
131+
132+ // Process each command
133+ for ( const [ path , cmd ] of commandMap . entries ( ) ) {
134+ if ( path === '' ) continue ; // Skip root command, already processed
135+
136+ // Extract positional arguments from usage
137+ const usage = cmd . usage ( ) ;
138+ const args = ( usage ?. match ( / \[ .* ?\] | < .* ?> / g) || [ ] ) . map ( ( arg ) =>
139+ arg . startsWith ( '[' )
140+ ) ; // true if optional (wrapped in [])
141+
142+ // Add command to completion
143+ completion . addCommand (
144+ path ,
145+ cmd . description ( ) || '' ,
146+ args ,
147+ async ( ) => [ ]
148+ ) ;
149+
150+ // Add command options
151+ for ( const option of cmd . options ) {
152+ // Extract short flag from the name if it exists (e.g., "-c, --config" -> "c")
153+ const flags = option . flags ;
154+ const shortFlag = flags . match ( / ^ - ( [ a - z A - Z ] ) , - - / ) ?. [ 1 ] ;
155+ const longFlag = flags . match ( / - - ( [ a - z A - Z 0 - 9 - ] + ) / ) ?. [ 1 ] ;
156+
157+ if ( longFlag ) {
158+ completion . addOption (
159+ path ,
160+ `--${ longFlag } ` ,
161+ option . description || '' ,
162+ async ( ) => [ ] ,
163+ shortFlag
164+ ) ;
165+ }
166+ }
167+
168+ // For commands with subcommands, add a special handler
169+ if ( cmd . commands . length > 0 ) {
170+ const subcommandNames = cmd . commands
171+ . filter ( subcmd => subcmd . name ( ) !== 'complete' )
172+ . map ( subcmd => ( {
173+ value : subcmd . name ( ) ,
174+ description : subcmd . description ( ) || ''
175+ } ) ) ;
176+
177+ if ( subcommandNames . length > 0 ) {
178+ const cmdObj = completion . commands . get ( path ) ;
179+ if ( cmdObj ) {
180+ cmdObj . handler = async ( ) => subcommandNames ;
181+ }
182+ }
183+ }
184+ }
185+ }
186+
187+ function collectCommands (
188+ command : CommanderCommand ,
189+ parentPath : string ,
190+ commandMap : Map < string , CommanderCommand >
191+ ) : void {
192+ // Add this command to the map
193+ commandMap . set ( parentPath , command ) ;
194+
195+ // Process subcommands
196+ for ( const subcommand of command . commands ) {
197+ // Skip the completion command
198+ if ( subcommand . name ( ) === 'complete' ) continue ;
199+
200+ // Build the full path for this subcommand
201+ const subcommandPath = parentPath
202+ ? `${ parentPath } ${ subcommand . name ( ) } `
203+ : subcommand . name ( ) ;
204+
205+ // Recursively collect subcommands
206+ collectCommands ( subcommand , subcommandPath , commandMap ) ;
207+ }
208+ }
0 commit comments