0
# Shell Completion
1
2
Generate shell completion scripts for Bash and Fish, with support for dynamic completion candidates including files, hostnames, and custom completions.
3
4
## Capabilities
5
6
### Completion Candidates
7
8
Define what values should be suggested for parameters during shell completion.
9
10
```kotlin { .api }
11
/**
12
* Sealed class representing different types of completion candidates
13
*/
14
sealed class CompletionCandidates {
15
/** No completion candidates */
16
object None : CompletionCandidates()
17
18
/** File and directory path completion */
19
object Path : CompletionCandidates()
20
21
/** Hostname completion */
22
object Hostname : CompletionCandidates()
23
24
/** Username completion */
25
object Username : CompletionCandidates()
26
27
/** Fixed set of completion candidates */
28
data class Fixed(val candidates: Set<String>) : CompletionCandidates()
29
30
/** Custom completion generator */
31
data class Custom(val generator: (ShellType) -> String?) : CompletionCandidates()
32
}
33
34
/**
35
* Shell types supported for completion
36
*/
37
enum class ShellType { BASH, FISH, ZSH }
38
```
39
40
**Usage Examples:**
41
42
```kotlin
43
class MyCommand : CliktCommand() {
44
// File path completion
45
private val configFile by option("--config", help = "Configuration file",
46
completionCandidates = CompletionCandidates.Path)
47
48
// Hostname completion
49
private val server by option("--server", help = "Server hostname",
50
completionCandidates = CompletionCandidates.Hostname)
51
52
// Fixed choices completion
53
private val format by option("--format", help = "Output format",
54
completionCandidates = CompletionCandidates.Fixed(setOf("json", "xml", "yaml")))
55
56
// Custom completion
57
private val service by option("--service", help = "Service name",
58
completionCandidates = CompletionCandidates.Custom { shell ->
59
when (shell) {
60
ShellType.BASH -> "_custom_services_bash"
61
ShellType.FISH -> "__custom_services_fish"
62
ShellType.ZSH -> "_custom_services_zsh"
63
}
64
})
65
66
// Argument with path completion
67
private val inputFile by argument(name = "FILE", help = "Input file",
68
completionCandidates = CompletionCandidates.Path)
69
70
override fun run() {
71
echo("Config: $configFile")
72
echo("Server: $server")
73
echo("Format: $format")
74
echo("Service: $service")
75
echo("Input: $inputFile")
76
}
77
}
78
```
79
80
### Completion Generators
81
82
Generate shell completion scripts for different shell types.
83
84
```kotlin { .api }
85
/**
86
* Base interface for completion script generators
87
*/
88
interface CompletionGenerator {
89
/**
90
* Generate completion script for the given command
91
* @param commandName Name of the command
92
* @param command Root command
93
* @return Generated completion script
94
*/
95
fun generateScript(commandName: String, command: CliktCommand): String
96
}
97
98
/**
99
* Bash completion script generator
100
*/
101
class BashCompletionGenerator : CompletionGenerator {
102
override fun generateScript(commandName: String, command: CliktCommand): String
103
}
104
105
/**
106
* Fish completion script generator
107
*/
108
class FishCompletionGenerator : CompletionGenerator {
109
override fun generateScript(commandName: String, command: CliktCommand): String
110
}
111
```
112
113
**Usage Examples:**
114
115
```kotlin
116
class MyCommand : CliktCommand() {
117
private val generateCompletion by option("--generate-completion",
118
help = "Generate shell completion script")
119
.choice("bash", "fish")
120
121
override fun run() {
122
if (generateCompletion != null) {
123
val generator = when (generateCompletion) {
124
"bash" -> BashCompletionGenerator()
125
"fish" -> FishCompletionGenerator()
126
else -> error("Unsupported shell: $generateCompletion")
127
}
128
129
val script = generator.generateScript("mycommand", this)
130
echo(script)
131
return
132
}
133
134
// Normal command execution
135
echo("Running normal command...")
136
}
137
}
138
139
// Standalone completion script generation
140
fun main() {
141
val command = MyComplexCommand()
142
143
// Generate Bash completion
144
val bashScript = BashCompletionGenerator().generateScript("myapp", command)
145
File("completion/myapp-completion.bash").writeText(bashScript)
146
147
// Generate Fish completion
148
val fishScript = FishCompletionGenerator().generateScript("myapp", command)
149
File("completion/myapp.fish").writeText(fishScript)
150
151
// Run the command normally
152
command.main()
153
}
154
```
155
156
### Built-in Completion Options
157
158
Add completion generation options to your command.
159
160
```kotlin { .api }
161
/**
162
* Add completion generation option to command
163
* @param option Option names for completion generation
164
* @param help Help text for the option
165
*/
166
fun CliktCommand.completionOption(
167
vararg option: String = arrayOf("--generate-completion"),
168
help: String = "Generate shell completion script and exit"
169
): Unit
170
```
171
172
**Usage Examples:**
173
174
```kotlin
175
class MyCommand : CliktCommand() {
176
init {
177
// Add standard completion option
178
completionOption()
179
}
180
181
// Your regular options and arguments
182
private val input by option("--input", help = "Input file",
183
completionCandidates = CompletionCandidates.Path)
184
private val verbose by option("-v", "--verbose", help = "Verbose output").flag()
185
186
override fun run() {
187
echo("Input: $input")
188
if (verbose) echo("Verbose mode enabled")
189
}
190
}
191
192
// Custom completion option names
193
class MyCommand2 : CliktCommand() {
194
init {
195
completionOption("--completion", "--comp", help = "Generate completion script")
196
}
197
198
override fun run() {
199
echo("Running command...")
200
}
201
}
202
```
203
204
### Dynamic Completion
205
206
Create dynamic completion that depends on runtime context or previous arguments.
207
208
```kotlin { .api }
209
/**
210
* Custom completion function type
211
*/
212
typealias CompletionFunction = (context: CompletionContext) -> List<String>
213
214
/**
215
* Context provided to custom completion functions
216
*/
217
data class CompletionContext(
218
val command: CliktCommand,
219
val currentWord: String,
220
val previousWords: List<String>
221
)
222
```
223
224
**Usage Examples:**
225
226
```kotlin
227
class DatabaseCommand : CliktCommand() {
228
// Database names depend on the connection
229
private val database by option("--database", help = "Database name",
230
completionCandidates = CompletionCandidates.Custom { shell ->
231
when (shell) {
232
ShellType.BASH -> """
233
COMPREPLY=($(compgen -W "$(myapp list-databases 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))
234
""".trimIndent()
235
ShellType.FISH -> """
236
myapp list-databases 2>/dev/null
237
""".trimIndent()
238
else -> null
239
}
240
})
241
242
// Table names depend on selected database
243
private val table by option("--table", help = "Table name",
244
completionCandidates = CompletionCandidates.Custom { shell ->
245
when (shell) {
246
ShellType.BASH -> """
247
local db_option=""
248
for ((i=1; i<COMP_CWORD; i++)); do
249
if [[ ${"$"}{COMP_WORDS[i]} == "--database" ]]; then
250
db_option="--database ${"$"}{COMP_WORDS[i+1]}"
251
break
252
fi
253
done
254
COMPREPLY=($(compgen -W "$(myapp list-tables ${"$"}db_option 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))
255
""".trimIndent()
256
ShellType.FISH -> """
257
set -l db_option ""
258
set -l prev_was_db_flag false
259
for word in (commandline -opc)[2..-1]
260
if test "$prev_was_db_flag" = true
261
set db_option "--database $word"
262
set prev_was_db_flag false
263
else if test "$word" = "--database"
264
set prev_was_db_flag true
265
end
266
end
267
myapp list-tables $db_option 2>/dev/null
268
""".trimIndent()
269
else -> null
270
}
271
})
272
273
override fun run() {
274
echo("Database: $database")
275
echo("Table: $table")
276
}
277
}
278
279
// Environment-aware completion
280
class DeployCommand : CliktCommand() {
281
private val environment by option("--env", help = "Environment",
282
completionCandidates = CompletionCandidates.Custom { shell ->
283
when (shell) {
284
ShellType.BASH -> """
285
COMPREPLY=($(compgen -W "dev staging prod" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))
286
""".trimIndent()
287
ShellType.FISH -> "echo -e 'dev\nstaging\nprod'"
288
else -> null
289
}
290
})
291
292
private val service by option("--service", help = "Service name",
293
completionCandidates = CompletionCandidates.Custom { shell ->
294
when (shell) {
295
ShellType.BASH -> """
296
local env=""
297
for ((i=1; i<COMP_CWORD; i++)); do
298
if [[ ${"$"}{COMP_WORDS[i]} == "--env" ]]; then
299
env=${"$"}{COMP_WORDS[i+1]}
300
break
301
fi
302
done
303
if [[ -n "${"$"}env" ]]; then
304
COMPREPLY=($(compgen -W "$(kubectl get services -n ${"$"}env --no-headers -o custom-columns=':metadata.name' 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))
305
fi
306
""".trimIndent()
307
ShellType.FISH -> """
308
set -l env ""
309
set -l prev_was_env_flag false
310
for word in (commandline -opc)[2..-1]
311
if test "$prev_was_env_flag" = true
312
set env $word
313
set prev_was_env_flag false
314
else if test "$word" = "--env"
315
set prev_was_env_flag true
316
end
317
end
318
if test -n "$env"
319
kubectl get services -n $env --no-headers -o custom-columns=':metadata.name' 2>/dev/null
320
end
321
""".trimIndent()
322
else -> null
323
}
324
})
325
326
override fun run() {
327
echo("Deploying service $service to $environment")
328
}
329
}
330
```
331
332
### Installation and Usage
333
334
Instructions for installing and using completion scripts.
335
336
**Bash Completion Installation:**
337
338
```bash
339
# Generate completion script
340
mycommand --generate-completion bash > mycommand-completion.bash
341
342
# Install system-wide (Linux)
343
sudo cp mycommand-completion.bash /etc/bash_completion.d/
344
345
# Install user-specific
346
mkdir -p ~/.local/share/bash-completion/completions
347
cp mycommand-completion.bash ~/.local/share/bash-completion/completions/mycommand
348
349
# Or source directly in ~/.bashrc
350
echo "source /path/to/mycommand-completion.bash" >> ~/.bashrc
351
```
352
353
**Fish Completion Installation:**
354
355
```fish
356
# Generate completion script
357
mycommand --generate-completion fish > mycommand.fish
358
359
# Install user-specific
360
mkdir -p ~/.config/fish/completions
361
cp mycommand.fish ~/.config/fish/completions/
362
363
# Fish will automatically load completions from this directory
364
```
365
366
**Advanced Completion Patterns:**
367
368
```kotlin
369
class GitLikeCommand : CliktCommand() {
370
// Subcommand completion
371
init {
372
subcommands(
373
CommitCommand(),
374
PushCommand(),
375
PullCommand(),
376
BranchCommand()
377
)
378
}
379
380
override fun run() = Unit
381
}
382
383
class CommitCommand : CliktCommand(name = "commit") {
384
private val files by argument(help = "Files to commit",
385
completionCandidates = CompletionCandidates.Custom { shell ->
386
when (shell) {
387
ShellType.BASH -> """
388
COMPREPLY=($(compgen -W "$(git diff --name-only --cached 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))
389
""".trimIndent()
390
ShellType.FISH -> "git diff --name-only --cached 2>/dev/null"
391
else -> null
392
}
393
}).multiple()
394
395
override fun run() {
396
echo("Committing files: ${files.joinToString(", ")}")
397
}
398
}
399
400
class BranchCommand : CliktCommand(name = "branch") {
401
private val branchName by argument(help = "Branch name",
402
completionCandidates = CompletionCandidates.Custom { shell ->
403
when (shell) {
404
ShellType.BASH -> """
405
COMPREPLY=($(compgen -W "$(git branch --format='%(refname:short)' 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))
406
""".trimIndent()
407
ShellType.FISH -> "git branch --format='%(refname:short)' 2>/dev/null"
408
else -> null
409
}
410
}).optional()
411
412
override fun run() {
413
if (branchName != null) {
414
echo("Switching to branch: $branchName")
415
} else {
416
echo("Listing branches...")
417
}
418
}
419
}
420
```
421
422
## Debugging Completion
423
424
```kotlin
425
class MyCommand : CliktCommand() {
426
private val debugCompletion by option("--debug-completion",
427
help = "Debug completion generation").flag(default = false)
428
429
override fun run() {
430
if (debugCompletion) {
431
echo("Completion debugging enabled")
432
// Add debug logging for completion
433
}
434
}
435
}
436
```