Skip to content

Commit

Permalink
add dynamic completion (#1509)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
umbynos authored Oct 18, 2021
1 parent 6c3c864 commit 10beac7
Show file tree
Hide file tree
Showing 25 changed files with 609 additions and 217 deletions.
190 changes: 190 additions & 0 deletions cli/arguments/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package arguments

import (
"context"

"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/commands/board"
"github.com/arduino/arduino-cli/commands/core"
"github.com/arduino/arduino-cli/commands/lib"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)

// GetInstalledBoards is an helper function useful to autocomplete.
// It returns a list of fqbn
// it's taken from cli/board/listall.go
func GetInstalledBoards() []string {
inst := instance.CreateAndInit()

list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
Instance: inst,
SearchArgs: nil,
IncludeHiddenBoards: false,
})
var res []string
// transform the data structure for the completion
for _, i := range list.Boards {
res = append(res, i.Fqbn+"\t"+i.Name)
}
return res
}

// GetInstalledProtocols is an helper function useful to autocomplete.
// It returns a list of protocols available based on the installed boards
func GetInstalledProtocols() []string {
inst := instance.CreateAndInit()
pm := commands.GetPackageManager(inst.Id)
boards := pm.InstalledBoards()

installedProtocols := make(map[string]struct{})
for _, board := range boards {
for _, protocol := range board.Properties.SubTree("upload.tool").FirstLevelKeys() {
if protocol == "default" {
// default is used as fallback when trying to upload to a board
// using a protocol not defined for it, it's useless showing it
// in autocompletion
continue
}
installedProtocols[protocol] = struct{}{}
}
}
res := make([]string, len(installedProtocols))
i := 0
for k := range installedProtocols {
res[i] = k
i++
}
return res
}

// GetInstalledProgrammers is an helper function useful to autocomplete.
// It returns a list of programmers available based on the installed boards
func GetInstalledProgrammers() []string {
inst := instance.CreateAndInit()
pm := commands.GetPackageManager(inst.Id)

// we need the list of the available fqbn in order to get the list of the programmers
list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
Instance: inst,
SearchArgs: nil,
IncludeHiddenBoards: false,
})

installedProgrammers := make(map[string]string)
for _, board := range list.Boards {
fqbn, _ := cores.ParseFQBN(board.Fqbn)
_, boardPlatform, _, _, _, _ := pm.ResolveFQBN(fqbn)
for programmerID, programmer := range boardPlatform.Programmers {
installedProgrammers[programmerID] = programmer.Name
}
}

res := make([]string, len(installedProgrammers))
i := 0
for programmerID := range installedProgrammers {
res[i] = programmerID + "\t" + installedProgrammers[programmerID]
i++
}
return res
}

// GetUninstallableCores is an helper function useful to autocomplete.
// It returns a list of cores which can be uninstalled
func GetUninstallableCores() []string {
inst := instance.CreateAndInit()

platforms, _ := core.GetPlatforms(&rpc.PlatformListRequest{
Instance: inst,
UpdatableOnly: false,
All: false,
})
var res []string
// transform the data structure for the completion
for _, i := range platforms {
res = append(res, i.Id+"\t"+i.Name)
}
return res
}

// GetInstallableCores is an helper function useful to autocomplete.
// It returns a list of cores which can be installed/downloaded
func GetInstallableCores() []string {
inst := instance.CreateAndInit()

platforms, _ := core.PlatformSearch(&rpc.PlatformSearchRequest{
Instance: inst,
SearchArgs: "",
AllVersions: false,
})
var res []string
// transform the data structure for the completion
for _, i := range platforms.SearchOutput {
res = append(res, i.Id+"\t"+i.Name)
}
return res
}

// GetInstalledLibraries is an helper function useful to autocomplete.
// It returns a list of libs which are currently installed, including the builtin ones
func GetInstalledLibraries() []string {
return getLibraries(true)
}

// GetUninstallableLibraries is an helper function useful to autocomplete.
// It returns a list of libs which can be uninstalled
func GetUninstallableLibraries() []string {
return getLibraries(false)
}

func getLibraries(all bool) []string {
inst := instance.CreateAndInit()
libs, _ := lib.LibraryList(context.Background(), &rpc.LibraryListRequest{
Instance: inst,
All: all,
Updatable: false,
Name: "",
Fqbn: "",
})
var res []string
// transform the data structure for the completion
for _, i := range libs.InstalledLibraries {
res = append(res, i.Library.Name+"\t"+i.Library.Sentence)
}
return res
}

// GetInstallableLibs is an helper function useful to autocomplete.
// It returns a list of libs which can be installed/downloaded
func GetInstallableLibs() []string {
inst := instance.CreateAndInit()

libs, _ := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchRequest{
Instance: inst,
Query: "", // if no query is specified all the libs are returned
})
var res []string
// transform the data structure for the completion
for _, i := range libs.Libraries {
res = append(res, i.Name+"\t"+i.Latest.Sentence)
}
return res
}

// GetConnectedBoards is an helper function useful to autocomplete.
// It returns a list of boards which are currently connected
// Obviously it does not suggests network ports because of the timeout
func GetConnectedBoards() []string {
inst := instance.CreateAndInit()

list, _ := board.List(&rpc.BoardListRequest{
Instance: inst,
})
var res []string
// transform the data structure for the completion
for _, i := range list {
res = append(res, i.Port.Address)
}
return res
}
6 changes: 6 additions & 0 deletions cli/arguments/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ type Port struct {
// AddToCommand adds the flags used to set port and protocol to the specified Command
func (p *Port) AddToCommand(cmd *cobra.Command) {
cmd.Flags().StringVarP(&p.address, "port", "p", "", tr("Upload port address, e.g.: COM3 or /dev/ttyACM2"))
cmd.RegisterFlagCompletionFunc("port", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetConnectedBoards(), cobra.ShellCompDirectiveDefault
})
cmd.Flags().StringVarP(&p.protocol, "protocol", "l", "", tr("Upload port protocol, e.g: serial"))
cmd.RegisterFlagCompletionFunc("protocol", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetInstalledProtocols(), cobra.ShellCompDirectiveDefault
})
cmd.Flags().DurationVar(&p.timeout, "discovery-timeout", 5*time.Second, tr("Max time to wait for port discovery, e.g.: 30s, 1m"))
}

Expand Down
4 changes: 4 additions & 0 deletions cli/board/details.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"os"

"github.com/arduino/arduino-cli/cli/arguments"
"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/instance"
Expand Down Expand Up @@ -48,6 +49,9 @@ func initDetailsCommand() *cobra.Command {

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

Expand Down
6 changes: 6 additions & 0 deletions cli/burnbootloader/burnbootloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,16 @@ func NewCommand() *cobra.Command {
}

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

Expand Down
17 changes: 14 additions & 3 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,21 @@ func createCliCommandTree(cmd *cobra.Command) {
cmd.AddCommand(version.NewCommand())

cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, tr("Print the logs on the standard output."))
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"))
validLogLevels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}
cmd.PersistentFlags().String("log-level", "", tr("Messages with this level and above will be logged. Valid levels are: %s", strings.Join(validLogLevels, ", ")))
cmd.RegisterFlagCompletionFunc("log-level", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return validLogLevels, cobra.ShellCompDirectiveDefault
})
cmd.PersistentFlags().String("log-file", "", tr("Path to the file where logs will be written."))
cmd.PersistentFlags().String("log-format", "", tr("The output format for the logs, can be: %s", "text, json"))
cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", tr("The output format for the logs, can be: %s", "text, json"))
validFormats := []string{"text", "json"}
cmd.PersistentFlags().String("log-format", "", tr("The output format for the logs, can be: %s", strings.Join(validFormats, ", ")))
cmd.RegisterFlagCompletionFunc("log-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return validFormats, cobra.ShellCompDirectiveDefault
})
cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", tr("The output format for the logs, can be: %s", strings.Join(validFormats, ", ")))
cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return validFormats, cobra.ShellCompDirectiveDefault
})
cmd.PersistentFlags().StringVar(&configFile, "config-file", "", tr("The custom config file (if not specified the default will be used)."))
cmd.PersistentFlags().StringSlice("additional-urls", []string{}, tr("Comma-separated list of additional URLs for the Boards Manager."))
cmd.PersistentFlags().Bool("no-color", false, "Disable colored output.")
Expand Down
22 changes: 4 additions & 18 deletions cli/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/instance"
"github.com/arduino/arduino-cli/commands/board"
"github.com/arduino/arduino-cli/commands/compile"
"github.com/arduino/arduino-cli/commands/upload"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
Expand Down Expand Up @@ -84,7 +83,7 @@ func NewCommand() *cobra.Command {

command.Flags().StringVarP(&fqbn, "fqbn", "b", "", tr("Fully Qualified Board Name, e.g.: arduino:avr:uno"))
command.RegisterFlagCompletionFunc("fqbn", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return getBoards(toComplete), cobra.ShellCompDirectiveDefault
return arguments.GetInstalledBoards(), cobra.ShellCompDirectiveDefault
})
command.Flags().BoolVar(&showProperties, "show-properties", false, tr("Show all build properties used instead of compiling."))
command.Flags().BoolVar(&preprocess, "preprocess", false, tr("Print preprocessed code to stdout instead of compiling."))
Expand All @@ -110,6 +109,9 @@ func NewCommand() *cobra.Command {
tr("List of custom libraries dir paths separated by commas. Or can be used multiple times for multiple libraries dir paths."))
command.Flags().BoolVar(&optimizeForDebug, "optimize-for-debug", false, tr("Optional, optimize compile output for debugging, rather than for release."))
command.Flags().StringVarP(&programmer, "programmer", "P", "", tr("Optional, use the specified programmer to upload."))
command.RegisterFlagCompletionFunc("programmer", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return arguments.GetInstalledProgrammers(), cobra.ShellCompDirectiveDefault
})
command.Flags().BoolVar(&compilationDatabaseOnly, "only-compilation-database", false, tr("Just produce the compilation database, without actually compiling."))
command.Flags().BoolVar(&clean, "clean", false, tr("Optional, cleanup the build folder and do not use any cached build."))
// We must use the following syntax for this flag since it's also bound to settings.
Expand Down Expand Up @@ -276,19 +278,3 @@ func (r *compileResult) String() string {
// The output is already printed via os.Stdout/os.Stdin
return ""
}

func getBoards(toComplete string) []string {
// from listall.go TODO optimize
inst := instance.CreateAndInit()

list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
Instance: inst,
SearchArgs: nil,
IncludeHiddenBoards: false,
})
var res []string
for _, i := range list.GetBoards() {
res = append(res, i.Fqbn)
}
return res
}
3 changes: 3 additions & 0 deletions cli/config/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func initAddCommand() *cobra.Command {
" " + 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",
Args: cobra.MinimumNArgs(2),
Run: runAddCommand,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
},
}
return addCommand
}
Expand Down
16 changes: 16 additions & 0 deletions cli/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ package config

import (
"os"
"reflect"

"github.com/arduino/arduino-cli/configuration"
"github.com/arduino/arduino-cli/i18n"
"github.com/spf13/cobra"
)
Expand All @@ -41,3 +43,17 @@ func NewCommand() *cobra.Command {

return configCommand
}

// GetConfigurationKeys is an helper function useful to autocomplete.
// It returns a list of configuration keys which can be changed
func GetConfigurationKeys() []string {
var res []string
keys := configuration.Settings.AllKeys()
for _, key := range keys {
kind, _ := typeOf(key)
if kind == reflect.Slice {
res = append(res, key)
}
}
return res
}
3 changes: 3 additions & 0 deletions cli/config/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ func initDeleteCommand() *cobra.Command {
" " + os.Args[0] + " config delete board_manager.additional_urls",
Args: cobra.ExactArgs(1),
Run: runDeleteCommand,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
},
}
return addCommand
}
Expand Down
3 changes: 3 additions & 0 deletions cli/config/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func initRemoveCommand() *cobra.Command {
" " + 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",
Args: cobra.MinimumNArgs(2),
Run: runRemoveCommand,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
},
}
return addCommand
}
Expand Down
3 changes: 3 additions & 0 deletions cli/config/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ func initSetCommand() *cobra.Command {
" " + os.Args[0] + " config set board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json",
Args: cobra.MinimumNArgs(2),
Run: runSetCommand,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
},
}
return addCommand
}
Expand Down
3 changes: 3 additions & 0 deletions cli/core/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func initDownloadCommand() *cobra.Command {
" " + os.Args[0] + " core download arduino:samd@1.6.9 # " + tr("download a specific version (in this case 1.6.9)."),
Args: cobra.MinimumNArgs(1),
Run: runDownloadCommand,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault
},
}
return downloadCommand
}
Expand Down
3 changes: 3 additions & 0 deletions cli/core/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func initInstallCommand() *cobra.Command {
" " + os.Args[0] + " core install arduino:samd@1.6.9",
Args: cobra.MinimumNArgs(1),
Run: runInstallCommand,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault
},
}
AddPostInstallFlagsToCommand(installCommand)
return installCommand
Expand Down
Loading

0 comments on commit 10beac7

Please sign in to comment.