Skip to content

Commit ee404b2

Browse files
authored
Add completion (#86)
1 parent e75c163 commit ee404b2

15 files changed

Lines changed: 1792 additions & 45 deletions

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ jobs:
3030
with:
3131
toit-version: ${{ matrix.toit-version }}
3232

33+
- name: Install shell completion test dependencies (Linux)
34+
if: runner.os == 'Linux'
35+
run: |
36+
sudo apt-get update
37+
sudo apt-get install -y fish
38+
39+
- name: Install shell completion test dependencies (macOS)
40+
if: runner.os == 'macOS'
41+
run: |
42+
brew install tmux fish
43+
3344
- name: Test
3445
run: |
3546
make test

README.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ It provides:
88
* Composable subcommands: `myapp subcommand`
99
* Type options/flags that parse arguments: `myapp --int-flag=49 enum_rest_arg`
1010
* Automatic help generation
11+
* Shell completion for bash, zsh, and fish
1112
* Command aliases
1213
* Functionality to cache data between runs
1314
* Functionality to store configurations
@@ -316,7 +317,7 @@ main args:
316317
317318
run invocation/Invocation:
318319
ui := invocation.cli.ui
319-
ui.emit
320+
ui.emit --result
320321
// Block that is invoked if structured data is needed.
321322
--structured=: {
322323
"result": "Computed result"
@@ -338,6 +339,51 @@ The shorthands `ui.info`, `ui.debug`, also dispatch to these methods if they rec
338339

339340
See the documentation of the `ui` library for more details.
340341

342+
## Shell Completion
343+
344+
Programs built with this package automatically get a `completion` subcommand that
345+
generates shell completion scripts for bash, zsh, and fish. Users enable completions
346+
by sourcing the output:
347+
348+
``` sh
349+
# Bash (~/.bashrc):
350+
source <(myapp completion bash)
351+
352+
# Zsh (~/.zshrc):
353+
source <(myapp completion zsh)
354+
355+
# Fish (~/.config/fish/config.fish):
356+
myapp completion fish | source
357+
```
358+
359+
Enum options and subcommands are completed automatically. For custom completions
360+
(e.g., completing device IDs from a database), pass a `--completion` callback when
361+
creating an option:
362+
363+
``` toit
364+
import cli show *
365+
366+
create-command -> Command:
367+
return Command "deploy"
368+
--options=[
369+
Option "device"
370+
--help="The device to deploy to."
371+
--completion=:: | context/CompletionContext |
372+
[
373+
CompletionCandidate "device-001" --description="Living Room Sensor",
374+
CompletionCandidate "device-002" --description="Garden Monitor",
375+
]
376+
]
377+
--run=:: | invocation/Invocation |
378+
print "Deploying to $(invocation["device"])"
379+
```
380+
381+
The callback receives a `CompletionContext` with the current prefix, option,
382+
command, and already-provided options. It returns a list of `CompletionCandidate`
383+
objects, which can include descriptions shown by shells that support them (zsh, fish).
384+
385+
See `examples/completion.toit` for a complete example.
386+
341387
## Features and bugs
342388

343389
Please file feature requests and bugs at the [issue tracker][tracker].

examples/completion.toit

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (C) 2026 Toit contributors.
2+
// Use of this source code is governed by an MIT-style license that can be
3+
// found in the package's LICENSE file.
4+
5+
import cli show *
6+
7+
/**
8+
Demonstrates shell completion support.
9+
10+
To try it out, compile to a binary and source the completion script:
11+
12+
```
13+
# Compile:
14+
toit compile -o /tmp/fleet examples/completion.toit
15+
16+
# Enable completions (bash):
17+
source <(/tmp/fleet completion bash)
18+
19+
# Enable completions (zsh):
20+
source <(/tmp/fleet completion zsh)
21+
22+
# Enable completions (fish):
23+
/tmp/fleet completion fish | source
24+
```
25+
26+
Then type `/tmp/fleet ` and press Tab to see suggestions.
27+
*/
28+
29+
main arguments:
30+
root := Command "fleet"
31+
--help="""
32+
An imaginary fleet manager for Toit devices.
33+
34+
Manages a fleet of devices, allowing you to deploy firmware,
35+
monitor status, and configure devices.
36+
"""
37+
38+
root.add create-deploy-command
39+
root.add create-status-command
40+
41+
root.run arguments
42+
43+
KNOWN-DEVICES ::= {
44+
"d3b07384-d113-4ec6-a7d2-8c6b2ab3e8f5": "Living Room Sensor",
45+
"6f1ed002-ab5d-42e0-868f-9e0c30e5a295": "Garden Monitor",
46+
"1f3870be-2748-4c9a-81e4-1b3b5e5a5c7f": "Front Door Lock",
47+
}
48+
49+
/**
50+
Completion callback that returns device UUIDs with human-readable descriptions.
51+
*/
52+
complete-device context/CompletionContext -> List:
53+
result := []
54+
KNOWN-DEVICES.do: | uuid/string name/string |
55+
if uuid.starts-with context.prefix:
56+
result.add (CompletionCandidate uuid --description=name)
57+
return result
58+
59+
create-deploy-command -> Command:
60+
return Command "deploy"
61+
--help="""
62+
Deploys firmware to a device.
63+
64+
Uploads and installs the specified firmware file on the target device.
65+
"""
66+
--options=[
67+
Option "device" --short-name="d"
68+
--help="The device to deploy to."
69+
--completion=:: complete-device it
70+
--required,
71+
OptionEnum "channel" ["stable", "beta", "dev"]
72+
--help="The release channel."
73+
--default="stable",
74+
]
75+
--rest=[
76+
Option "firmware"
77+
--type="file"
78+
--help="Path to the firmware file."
79+
--required,
80+
]
81+
--run=:: run-deploy it
82+
83+
create-status-command -> Command:
84+
return Command "status"
85+
--help="Shows live status of devices."
86+
--options=[
87+
Option "device" --short-name="d"
88+
--help="The device to show. Shows all if omitted."
89+
--completion=:: complete-device it,
90+
OptionEnum "format" ["table", "json", "sparkline"]
91+
--help="Output format."
92+
--default="table",
93+
]
94+
--run=:: run-status it
95+
96+
run-deploy invocation/Invocation:
97+
device := invocation["device"]
98+
channel := invocation["channel"]
99+
firmware := invocation["firmware"]
100+
name := KNOWN-DEVICES.get device --if-absent=: "unknown"
101+
print "Deploying '$firmware' to '$name' on $channel channel."
102+
103+
run-status invocation/Invocation:
104+
device := invocation["device"]
105+
format := invocation["format"]
106+
if device:
107+
name := KNOWN-DEVICES.get device --if-absent=: "unknown"
108+
print "Status of '$name' (format: $format)."
109+
else:
110+
print "Status of all devices (format: $format)."

0 commit comments

Comments
 (0)