Skip to content

Commit

Permalink
add basic implementation of tab completion (#663)
Browse files Browse the repository at this point in the history
* add basic implementation of bash completion

* update cobra to 1.0.0 because of spf13/cobra#1048

* add support for zsh and fish, improved help messages

* add flag ´--no-descriptions´ to disable automatic command description (for shells that supports it)

* fix fish not supporting env variables with dash in the name

* fixed zsh completion not working

* revert "#compdef" patch

* add check on --no-description flag
add comments regarding "hacks"
change Replacer with ReplaceAll (for readability)

* add docs

* Apply suggestions from code review

fix typos and corrections

Co-authored-by: per1234 <accounts@perglass.com>

* forgot a space

* fix fish docs

* add test for completion

Co-authored-by: per1234 <accounts@perglass.com>
  • Loading branch information
umbynos and per1234 committed Jun 12, 2020
1 parent f7a6d63 commit 15d35de
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 2 deletions.
2 changes: 2 additions & 0 deletions cli/cli.go
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/arduino/arduino-cli/cli/board"
"github.com/arduino/arduino-cli/cli/cache"
"github.com/arduino/arduino-cli/cli/compile"
"github.com/arduino/arduino-cli/cli/completion"
"github.com/arduino/arduino-cli/cli/config"
"github.com/arduino/arduino-cli/cli/core"
"github.com/arduino/arduino-cli/cli/daemon"
Expand Down Expand Up @@ -77,6 +78,7 @@ func createCliCommandTree(cmd *cobra.Command) {
cmd.AddCommand(board.NewCommand())
cmd.AddCommand(cache.NewCommand())
cmd.AddCommand(compile.NewCommand())
cmd.AddCommand(completion.NewCommand())
cmd.AddCommand(config.NewCommand())
cmd.AddCommand(core.NewCommand())
cmd.AddCommand(daemon.NewCommand())
Expand Down
76 changes: 76 additions & 0 deletions cli/completion/completion.go
@@ -0,0 +1,76 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package completion

import (
"bytes"
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/spf13/cobra"
)

var (
completionNoDesc bool //Disable completion description for shells that support it
)

// NewCommand created a new `version` command
func NewCommand() *cobra.Command {
command := &cobra.Command{
Use: "completion [bash|zsh|fish] [--no-descriptions]",
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.ExactArgs(1),
Short: "Generates completion scripts",
Long: "Generates completion scripts for various shells",
Example: " " + os.Args[0] + " completion bash > completion.sh\n" +
" " + "source completion.sh",
Run: run,
}
command.Flags().BoolVar(&completionNoDesc, "no-descriptions", false, "Disable completion description for shells that support it")

return command
}

func run(cmd *cobra.Command, args []string) {
if completionNoDesc && (args[0] == "bash" || args[0] == "zsh") {
feedback.Errorf("Error: command description is not supported by %v", args[0])
os.Exit(errorcodes.ErrGeneric)
}
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
break
case "zsh":
buf := new(bytes.Buffer)
cmd.Root().GenZshCompletion(buf)
// Next 3 lines are Hack, we'll wait new version of cobra with ZshV2Completion https://github.com/spf13/cobra/pull/1070
//insert escaping before [ and ]
s := strings.ReplaceAll(buf.String(), "[", "\\[")
s = strings.ReplaceAll(s, "]", "\\]")
s = strings.ReplaceAll(s, "\\[1\\]", "[1]") // revert the case
os.Stdout.WriteString(s)
break
case "fish":
buf := new(bytes.Buffer)
cmd.Root().GenFishCompletion(buf, !completionNoDesc)
// Next 2 lines are Hack, fixed here https://github.com/spf13/cobra/pull/1122
s := strings.ReplaceAll(buf.String(), "arduino-cli_comp", "arduino_cli_comp") //required because fish does not support env variables with "-" in the name
os.Stdout.WriteString(s)
break
}
}
45 changes: 45 additions & 0 deletions docs/command-line-completion.md
@@ -0,0 +1,45 @@
`arduino-cli` supports command-line completion (also known as *tab completion*) for basic commands.
Currently only `bash`, `zsh`, `fish` shells are supported

### Before you start
In order to generate the file required to make the completion work you have to [install](installation.md) Arduino CLI first.

### Generate the completion file
To generate the completion file you can use `arduino-cli completion [bash|zsh|fish] [--no-descriptions]`.
By default this command will print on the standard output (the shell window) the content of the completion file. To save to an actual file use the `>` redirect symbol.

### Bash
Use `arduino-cli completion bash > arduino-cli.sh` to generate the completion file.
At this point you can move that file in `/etc/bash_completion.d/` (root access is required) with `sudo mv arduino-cli.sh /etc/bash_completion.d/`.

A not recommended alternative is to source the completion file in `.bashrc`.

Remember to open a new shell to test the functionality

### Zsh
Use `arduino-cli completion zsh > _arduino-cli` to generate the completion file.
At this point you can place the file in a directory listed in your `fpath` if you have already created a directory to store your completion.

Or if you want you can create a directory, add it to your `fpath` and copy the file in it:

1. `mkdir ~/completion_zsh`
2. add `fpath=($HOME/completion_zsh $fpath)` at the beginning of your `.zshrc` file
3. `mv _arduino-cli ~/completion_zsh/`

Remember to open a new shell to test the functionality

*N.B.*
The Zsh completion is working with [Oh-My-Zsh](https://ohmyz.sh/) but not with [Prezto](https://github.com/sorin-ionescu/prezto) (the zsh completion system is working in a different way than classic zsh). But hopefully it will be fixed in the future

### Fish
Use `arduino-cli completion fish > arduino-cli.fish` to generate the completion file.
At this point you can place the file in `~/.config/fish/completions` as stated in the [official documentation](http://fishshell.com/docs/current/index.html#where-to-put-completions).
Remember to create the directory if it's not already there `mkdir -p ~/.config/fish/completions/` and then place the completion file in there with `mv arduino-cli.fish ~/.config/fish/completions/`

Remember to open a new shell to test the functionality

#### Disabling command and flag descriptions
By default fish completion has command and flag description enabled by default. If you want to disable this behaviour you can simply pass the `--no-descriptions` flag when calling `completion` command and the generated file will not have descriptions

*N.B.*
This flag is not compatible with bash or zsh
2 changes: 1 addition & 1 deletion docsgen/go.mod
Expand Up @@ -6,5 +6,5 @@ replace github.com/arduino/arduino-cli => ../

require (
github.com/arduino/arduino-cli v0.0.0
github.com/spf13/cobra v0.0.6
github.com/spf13/cobra v1.0.0
)
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -36,7 +36,7 @@ require (
github.com/schollz/closestmatch v2.1.0+incompatible
github.com/segmentio/stats/v4 v4.5.3
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.4.0
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Expand Up @@ -44,6 +44,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
Expand Down Expand Up @@ -175,6 +177,8 @@ github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5H
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ=
Expand All @@ -183,6 +187,8 @@ github.com/segmentio/objconv v1.0.1 h1:QjfLzwriJj40JibCV3MGSEiAoXixbp4ybhwfTB8RX
github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys=
github.com/segmentio/stats/v4 v4.5.3 h1:Y/DSUWZ4c8ICgqJ9rQohzKvGqGWbLPWad5zmxVoKN+Y=
github.com/segmentio/stats/v4 v4.5.3/go.mod h1:LsaahUJR7iiSs8mnkvQvdQ/RLHAS5adGLxuntg0ydGo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
Expand All @@ -198,12 +204,15 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Expand Up @@ -56,6 +56,7 @@ nav:
- Documentation Home: index.md
- installation.md
- getting-started.md
- command-line-completion.md
- CONTRIBUTING.md
- FAQ.md
- Command reference:
Expand All @@ -68,6 +69,7 @@ nav:
- cache: commands/arduino-cli_cache.md
- cache clean: commands/arduino-cli_cache_clean.md
- compile: commands/arduino-cli_compile.md
- completion: commands/arduino-cli_completion.md
- config: commands/arduino-cli_config.md
- config dump: commands/arduino-cli_config_dump.md
- config init: commands/arduino-cli_config_init.md
Expand Down
63 changes: 63 additions & 0 deletions test/test_completion.py
@@ -0,0 +1,63 @@
# This file is part of arduino-cli.
#
# Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
#
# This software is released under the GNU General Public License version 3,
# which covers the main part of arduino-cli.
# The terms of this license can be found at:
# https://www.gnu.org/licenses/gpl-3.0.en.html
#
# You can be released from the requirements of the above licenses by purchasing
# a commercial license. Buying such a license is mandatory if you want to modify or
# otherwise use the software for commercial activities involving the Arduino
# software without disclosing the source code of your own applications. To purchase
# a commercial license, send an email to license@arduino.cc.

import pytest

def test_completion_no_args(run_command):
result = run_command("completion")
assert not result.ok
assert "Error: accepts 1 arg(s), received 0" in result.stderr
assert result.stdout == ""

def test_completion_bash(run_command):
result = run_command("completion bash")
assert result.ok
assert result.stderr == ""
assert "_arduino-cli_root_command()" in result.stdout
assert "__start_arduino-cli()" in result.stdout

def test_completion_zsh(run_command):
result = run_command("completion zsh")
assert result.ok
assert result.stderr == ""
assert "#compdef _arduino-cli arduino-cli" in result.stdout
assert "function _arduino-cli" in result.stdout

def test_completion_fish(run_command):
result = run_command("completion fish")
assert result.ok
assert result.stderr == ""
assert "# fish completion for arduino-cli" in result.stdout
assert "function __arduino-cli_perform_completion" in result.stdout

def test_completion_bash_no_desc(run_command):
result = run_command("completion bash --no-descriptions")
assert not result.ok
assert result.stdout == ""
assert "Error: command description is not supported by bash" in result.stderr

def test_completion_zsh_no_desc(run_command):
result = run_command("completion zsh --no-descriptions")
assert not result.ok
assert result.stdout == ""
assert "Error: command description is not supported by zsh" in result.stderr

def test_completion_fish_no_desc(run_command):
result = run_command("completion fish --no-descriptions")
assert result.ok
assert result.stderr == ""
assert "# fish completion for arduino-cli" in result.stdout
assert "function __arduino-cli_perform_completion" in result.stdout
assert "__completeNoDesc" in result.stdout

0 comments on commit 15d35de

Please sign in to comment.