Skip to content

Commit 10beac7

Browse files
authored
add dynamic completion (#1509)
* add completion for `-b` or `--fqbn` for every command * add completion for `-l` or `--protocol` But does only work for connected boards and do not list every protocol installed * the previous implementation was working only with a board connected to the pc RPC was not exposing that functionality * add static completion for `--log-level, `--log-format` and `--format` * add completion for `-P` or `--programmer` & fix typo * add completion for `core uninstall` maybe could be done better (filter and remove the manually installed ones) * add completion for `core install` and `core download` * add completion for `lib uninstall` * add completion for `lib install`, `lib download` * add completion for `lib examples` * add completion for `config add`, `config remove`, `config delete` and `config set` * add completion for `lib deps` * add tests * enhance the completion for `config add` and `config remove` * add description completion suggestion for core, lib, fqbn, programmer * add completion also for `-p` or `--port` flag * remove the `toComplete` parameter from all the completion functions as of now this parameter is useless because if a string is typed in the terminal it cannot be swapped with a different one. For example if I write `arduino-cli compile -b avr<TAB><TAB>` the completion function returns all elements starting with `arduino:avr...`. So the completions are not showed because they cannot be swapped with a string that starts differently. The only shell which seems to support this seems to be zsh * fixes after rebase * update docs * add `-b` or `--fqbn` completion for the monitor command and tests * apply suggestions from code review * fixes after rebase
1 parent 6c3c864 commit 10beac7

25 files changed

+609
-217
lines changed

Diff for: cli/arguments/completion.go

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package arguments
2+
3+
import (
4+
"context"
5+
6+
"github.com/arduino/arduino-cli/arduino/cores"
7+
"github.com/arduino/arduino-cli/cli/instance"
8+
"github.com/arduino/arduino-cli/commands"
9+
"github.com/arduino/arduino-cli/commands/board"
10+
"github.com/arduino/arduino-cli/commands/core"
11+
"github.com/arduino/arduino-cli/commands/lib"
12+
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
13+
)
14+
15+
// GetInstalledBoards is an helper function useful to autocomplete.
16+
// It returns a list of fqbn
17+
// it's taken from cli/board/listall.go
18+
func GetInstalledBoards() []string {
19+
inst := instance.CreateAndInit()
20+
21+
list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
22+
Instance: inst,
23+
SearchArgs: nil,
24+
IncludeHiddenBoards: false,
25+
})
26+
var res []string
27+
// transform the data structure for the completion
28+
for _, i := range list.Boards {
29+
res = append(res, i.Fqbn+"\t"+i.Name)
30+
}
31+
return res
32+
}
33+
34+
// GetInstalledProtocols is an helper function useful to autocomplete.
35+
// It returns a list of protocols available based on the installed boards
36+
func GetInstalledProtocols() []string {
37+
inst := instance.CreateAndInit()
38+
pm := commands.GetPackageManager(inst.Id)
39+
boards := pm.InstalledBoards()
40+
41+
installedProtocols := make(map[string]struct{})
42+
for _, board := range boards {
43+
for _, protocol := range board.Properties.SubTree("upload.tool").FirstLevelKeys() {
44+
if protocol == "default" {
45+
// default is used as fallback when trying to upload to a board
46+
// using a protocol not defined for it, it's useless showing it
47+
// in autocompletion
48+
continue
49+
}
50+
installedProtocols[protocol] = struct{}{}
51+
}
52+
}
53+
res := make([]string, len(installedProtocols))
54+
i := 0
55+
for k := range installedProtocols {
56+
res[i] = k
57+
i++
58+
}
59+
return res
60+
}
61+
62+
// GetInstalledProgrammers is an helper function useful to autocomplete.
63+
// It returns a list of programmers available based on the installed boards
64+
func GetInstalledProgrammers() []string {
65+
inst := instance.CreateAndInit()
66+
pm := commands.GetPackageManager(inst.Id)
67+
68+
// we need the list of the available fqbn in order to get the list of the programmers
69+
list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
70+
Instance: inst,
71+
SearchArgs: nil,
72+
IncludeHiddenBoards: false,
73+
})
74+
75+
installedProgrammers := make(map[string]string)
76+
for _, board := range list.Boards {
77+
fqbn, _ := cores.ParseFQBN(board.Fqbn)
78+
_, boardPlatform, _, _, _, _ := pm.ResolveFQBN(fqbn)
79+
for programmerID, programmer := range boardPlatform.Programmers {
80+
installedProgrammers[programmerID] = programmer.Name
81+
}
82+
}
83+
84+
res := make([]string, len(installedProgrammers))
85+
i := 0
86+
for programmerID := range installedProgrammers {
87+
res[i] = programmerID + "\t" + installedProgrammers[programmerID]
88+
i++
89+
}
90+
return res
91+
}
92+
93+
// GetUninstallableCores is an helper function useful to autocomplete.
94+
// It returns a list of cores which can be uninstalled
95+
func GetUninstallableCores() []string {
96+
inst := instance.CreateAndInit()
97+
98+
platforms, _ := core.GetPlatforms(&rpc.PlatformListRequest{
99+
Instance: inst,
100+
UpdatableOnly: false,
101+
All: false,
102+
})
103+
var res []string
104+
// transform the data structure for the completion
105+
for _, i := range platforms {
106+
res = append(res, i.Id+"\t"+i.Name)
107+
}
108+
return res
109+
}
110+
111+
// GetInstallableCores is an helper function useful to autocomplete.
112+
// It returns a list of cores which can be installed/downloaded
113+
func GetInstallableCores() []string {
114+
inst := instance.CreateAndInit()
115+
116+
platforms, _ := core.PlatformSearch(&rpc.PlatformSearchRequest{
117+
Instance: inst,
118+
SearchArgs: "",
119+
AllVersions: false,
120+
})
121+
var res []string
122+
// transform the data structure for the completion
123+
for _, i := range platforms.SearchOutput {
124+
res = append(res, i.Id+"\t"+i.Name)
125+
}
126+
return res
127+
}
128+
129+
// GetInstalledLibraries is an helper function useful to autocomplete.
130+
// It returns a list of libs which are currently installed, including the builtin ones
131+
func GetInstalledLibraries() []string {
132+
return getLibraries(true)
133+
}
134+
135+
// GetUninstallableLibraries is an helper function useful to autocomplete.
136+
// It returns a list of libs which can be uninstalled
137+
func GetUninstallableLibraries() []string {
138+
return getLibraries(false)
139+
}
140+
141+
func getLibraries(all bool) []string {
142+
inst := instance.CreateAndInit()
143+
libs, _ := lib.LibraryList(context.Background(), &rpc.LibraryListRequest{
144+
Instance: inst,
145+
All: all,
146+
Updatable: false,
147+
Name: "",
148+
Fqbn: "",
149+
})
150+
var res []string
151+
// transform the data structure for the completion
152+
for _, i := range libs.InstalledLibraries {
153+
res = append(res, i.Library.Name+"\t"+i.Library.Sentence)
154+
}
155+
return res
156+
}
157+
158+
// GetInstallableLibs is an helper function useful to autocomplete.
159+
// It returns a list of libs which can be installed/downloaded
160+
func GetInstallableLibs() []string {
161+
inst := instance.CreateAndInit()
162+
163+
libs, _ := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchRequest{
164+
Instance: inst,
165+
Query: "", // if no query is specified all the libs are returned
166+
})
167+
var res []string
168+
// transform the data structure for the completion
169+
for _, i := range libs.Libraries {
170+
res = append(res, i.Name+"\t"+i.Latest.Sentence)
171+
}
172+
return res
173+
}
174+
175+
// GetConnectedBoards is an helper function useful to autocomplete.
176+
// It returns a list of boards which are currently connected
177+
// Obviously it does not suggests network ports because of the timeout
178+
func GetConnectedBoards() []string {
179+
inst := instance.CreateAndInit()
180+
181+
list, _ := board.List(&rpc.BoardListRequest{
182+
Instance: inst,
183+
})
184+
var res []string
185+
// transform the data structure for the completion
186+
for _, i := range list {
187+
res = append(res, i.Port.Address)
188+
}
189+
return res
190+
}

Diff for: cli/arguments/port.go

+6
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ type Port struct {
4242
// AddToCommand adds the flags used to set port and protocol to the specified Command
4343
func (p *Port) AddToCommand(cmd *cobra.Command) {
4444
cmd.Flags().StringVarP(&p.address, "port", "p", "", tr("Upload port address, e.g.: COM3 or /dev/ttyACM2"))
45+
cmd.RegisterFlagCompletionFunc("port", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
46+
return GetConnectedBoards(), cobra.ShellCompDirectiveDefault
47+
})
4548
cmd.Flags().StringVarP(&p.protocol, "protocol", "l", "", tr("Upload port protocol, e.g: serial"))
49+
cmd.RegisterFlagCompletionFunc("protocol", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
50+
return GetInstalledProtocols(), cobra.ShellCompDirectiveDefault
51+
})
4652
cmd.Flags().DurationVar(&p.timeout, "discovery-timeout", 5*time.Second, tr("Max time to wait for port discovery, e.g.: 30s, 1m"))
4753
}
4854

Diff for: cli/board/details.go

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"os"
2222

23+
"github.com/arduino/arduino-cli/cli/arguments"
2324
"github.com/arduino/arduino-cli/cli/errorcodes"
2425
"github.com/arduino/arduino-cli/cli/feedback"
2526
"github.com/arduino/arduino-cli/cli/instance"
@@ -48,6 +49,9 @@ func initDetailsCommand() *cobra.Command {
4849

4950
detailsCommand.Flags().BoolVarP(&showFullDetails, "full", "f", false, tr("Show full board details"))
5051
detailsCommand.Flags().StringVarP(&fqbn, "fqbn", "b", "", tr("Fully Qualified Board Name, e.g.: arduino:avr:uno"))
52+
detailsCommand.RegisterFlagCompletionFunc("fqbn", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
53+
return arguments.GetInstalledBoards(), cobra.ShellCompDirectiveDefault
54+
})
5155
detailsCommand.Flags().BoolVarP(&listProgrammers, "list-programmers", "", false, tr("Show list of available programmers"))
5256
// detailsCommand.MarkFlagRequired("fqbn") // enable once `board details <fqbn>` is removed
5357

Diff for: cli/burnbootloader/burnbootloader.go

+6
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,16 @@ func NewCommand() *cobra.Command {
5151
}
5252

5353
burnBootloaderCommand.Flags().StringVarP(&fqbn, "fqbn", "b", "", tr("Fully Qualified Board Name, e.g.: arduino:avr:uno"))
54+
burnBootloaderCommand.RegisterFlagCompletionFunc("fqbn", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
55+
return arguments.GetInstalledBoards(), cobra.ShellCompDirectiveDefault
56+
})
5457
port.AddToCommand(burnBootloaderCommand)
5558
burnBootloaderCommand.Flags().BoolVarP(&verify, "verify", "t", false, tr("Verify uploaded binary after the upload."))
5659
burnBootloaderCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, tr("Turns on verbose mode."))
5760
burnBootloaderCommand.Flags().StringVarP(&programmer, "programmer", "P", "", tr("Use the specified programmer to upload."))
61+
burnBootloaderCommand.RegisterFlagCompletionFunc("programmer", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
62+
return arguments.GetInstalledProgrammers(), cobra.ShellCompDirectiveDefault
63+
})
5864
burnBootloaderCommand.Flags().BoolVar(&dryRun, "dry-run", false, tr("Do not perform the actual upload, just log out actions"))
5965
burnBootloaderCommand.Flags().MarkHidden("dry-run")
6066

Diff for: cli/cli.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,21 @@ func createCliCommandTree(cmd *cobra.Command) {
105105
cmd.AddCommand(version.NewCommand())
106106

107107
cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, tr("Print the logs on the standard output."))
108-
cmd.PersistentFlags().String("log-level", "", tr("Messages with this level and above will be logged. Valid levels are: %s", "trace, debug, info, warn, error, fatal, panic"))
108+
validLogLevels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}
109+
cmd.PersistentFlags().String("log-level", "", tr("Messages with this level and above will be logged. Valid levels are: %s", strings.Join(validLogLevels, ", ")))
110+
cmd.RegisterFlagCompletionFunc("log-level", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
111+
return validLogLevels, cobra.ShellCompDirectiveDefault
112+
})
109113
cmd.PersistentFlags().String("log-file", "", tr("Path to the file where logs will be written."))
110-
cmd.PersistentFlags().String("log-format", "", tr("The output format for the logs, can be: %s", "text, json"))
111-
cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", tr("The output format for the logs, can be: %s", "text, json"))
114+
validFormats := []string{"text", "json"}
115+
cmd.PersistentFlags().String("log-format", "", tr("The output format for the logs, can be: %s", strings.Join(validFormats, ", ")))
116+
cmd.RegisterFlagCompletionFunc("log-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
117+
return validFormats, cobra.ShellCompDirectiveDefault
118+
})
119+
cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", tr("The output format for the logs, can be: %s", strings.Join(validFormats, ", ")))
120+
cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
121+
return validFormats, cobra.ShellCompDirectiveDefault
122+
})
112123
cmd.PersistentFlags().StringVar(&configFile, "config-file", "", tr("The custom config file (if not specified the default will be used)."))
113124
cmd.PersistentFlags().StringSlice("additional-urls", []string{}, tr("Comma-separated list of additional URLs for the Boards Manager."))
114125
cmd.PersistentFlags().Bool("no-color", false, "Disable colored output.")

Diff for: cli/compile/compile.go

+4-18
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131

3232
"github.com/arduino/arduino-cli/cli/errorcodes"
3333
"github.com/arduino/arduino-cli/cli/instance"
34-
"github.com/arduino/arduino-cli/commands/board"
3534
"github.com/arduino/arduino-cli/commands/compile"
3635
"github.com/arduino/arduino-cli/commands/upload"
3736
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -84,7 +83,7 @@ func NewCommand() *cobra.Command {
8483

8584
command.Flags().StringVarP(&fqbn, "fqbn", "b", "", tr("Fully Qualified Board Name, e.g.: arduino:avr:uno"))
8685
command.RegisterFlagCompletionFunc("fqbn", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
87-
return getBoards(toComplete), cobra.ShellCompDirectiveDefault
86+
return arguments.GetInstalledBoards(), cobra.ShellCompDirectiveDefault
8887
})
8988
command.Flags().BoolVar(&showProperties, "show-properties", false, tr("Show all build properties used instead of compiling."))
9089
command.Flags().BoolVar(&preprocess, "preprocess", false, tr("Print preprocessed code to stdout instead of compiling."))
@@ -110,6 +109,9 @@ func NewCommand() *cobra.Command {
110109
tr("List of custom libraries dir paths separated by commas. Or can be used multiple times for multiple libraries dir paths."))
111110
command.Flags().BoolVar(&optimizeForDebug, "optimize-for-debug", false, tr("Optional, optimize compile output for debugging, rather than for release."))
112111
command.Flags().StringVarP(&programmer, "programmer", "P", "", tr("Optional, use the specified programmer to upload."))
112+
command.RegisterFlagCompletionFunc("programmer", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
113+
return arguments.GetInstalledProgrammers(), cobra.ShellCompDirectiveDefault
114+
})
113115
command.Flags().BoolVar(&compilationDatabaseOnly, "only-compilation-database", false, tr("Just produce the compilation database, without actually compiling."))
114116
command.Flags().BoolVar(&clean, "clean", false, tr("Optional, cleanup the build folder and do not use any cached build."))
115117
// We must use the following syntax for this flag since it's also bound to settings.
@@ -276,19 +278,3 @@ func (r *compileResult) String() string {
276278
// The output is already printed via os.Stdout/os.Stdin
277279
return ""
278280
}
279-
280-
func getBoards(toComplete string) []string {
281-
// from listall.go TODO optimize
282-
inst := instance.CreateAndInit()
283-
284-
list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
285-
Instance: inst,
286-
SearchArgs: nil,
287-
IncludeHiddenBoards: false,
288-
})
289-
var res []string
290-
for _, i := range list.GetBoards() {
291-
res = append(res, i.Fqbn)
292-
}
293-
return res
294-
}

Diff for: cli/config/add.go

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ func initAddCommand() *cobra.Command {
3535
" " + os.Args[0] + " config add board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json\n",
3636
Args: cobra.MinimumNArgs(2),
3737
Run: runAddCommand,
38+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
39+
return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
40+
},
3841
}
3942
return addCommand
4043
}

Diff for: cli/config/config.go

+16
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ package config
1717

1818
import (
1919
"os"
20+
"reflect"
2021

22+
"github.com/arduino/arduino-cli/configuration"
2123
"github.com/arduino/arduino-cli/i18n"
2224
"github.com/spf13/cobra"
2325
)
@@ -41,3 +43,17 @@ func NewCommand() *cobra.Command {
4143

4244
return configCommand
4345
}
46+
47+
// GetConfigurationKeys is an helper function useful to autocomplete.
48+
// It returns a list of configuration keys which can be changed
49+
func GetConfigurationKeys() []string {
50+
var res []string
51+
keys := configuration.Settings.AllKeys()
52+
for _, key := range keys {
53+
kind, _ := typeOf(key)
54+
if kind == reflect.Slice {
55+
res = append(res, key)
56+
}
57+
}
58+
return res
59+
}

Diff for: cli/config/delete.go

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func initDeleteCommand() *cobra.Command {
3636
" " + os.Args[0] + " config delete board_manager.additional_urls",
3737
Args: cobra.ExactArgs(1),
3838
Run: runDeleteCommand,
39+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
40+
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
41+
},
3942
}
4043
return addCommand
4144
}

Diff for: cli/config/remove.go

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ func initRemoveCommand() *cobra.Command {
3535
" " + os.Args[0] + " config remove board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json\n",
3636
Args: cobra.MinimumNArgs(2),
3737
Run: runRemoveCommand,
38+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
39+
return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
40+
},
3841
}
3942
return addCommand
4043
}

Diff for: cli/config/set.go

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ func initSetCommand() *cobra.Command {
3838
" " + os.Args[0] + " config set board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json",
3939
Args: cobra.MinimumNArgs(2),
4040
Run: runSetCommand,
41+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
42+
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
43+
},
4144
}
4245
return addCommand
4346
}

Diff for: cli/core/download.go

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ func initDownloadCommand() *cobra.Command {
4141
" " + os.Args[0] + " core download arduino:[email protected] # " + tr("download a specific version (in this case 1.6.9)."),
4242
Args: cobra.MinimumNArgs(1),
4343
Run: runDownloadCommand,
44+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
45+
return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault
46+
},
4447
}
4548
return downloadCommand
4649
}

Diff for: cli/core/install.go

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ func initInstallCommand() *cobra.Command {
4343
" " + os.Args[0] + " core install arduino:[email protected]",
4444
Args: cobra.MinimumNArgs(1),
4545
Run: runInstallCommand,
46+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
47+
return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault
48+
},
4649
}
4750
AddPostInstallFlagsToCommand(installCommand)
4851
return installCommand

0 commit comments

Comments
 (0)