8000 Natively autocomplete `composer` and `console` · symfony-cli/symfony-cli@db7d613 · GitHub
[go: up one dir, main page]

Skip to content

Commit db7d613

Browse files
committed
Natively autocomplete composer and console
1 parent bf40c88 commit db7d613

11 files changed

+177
-70
lines changed

commands/completion.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

commands/completion_others.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build !darwin && !linux && !freebsd && !openbsd
2+
// +build !darwin,!linux,!freebsd,!openbsd
3+
4+
package commands
5+
6+
import (
7+
"github.com/posener/complete"
8+
"github.com/symfony-cli/console"
9+
)
10+
11+
func autocompleteComposerWrapper(context *console.Context, args complete.Args) []string {
12+
return []string{}
13+
}
14+
15+
func autocompleteSymfonyConsoleWrapper(context *console.Context, args complete.Args) []string {
16+
return []string{}
17+
}

commands/completion_posix.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//go:build darwin || linux || freebsd || openbsd
2+
3+
package commands
4+
5+
import (
6+
"embed"
7+
"fmt"
8+
"io"
9+
"os"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/posener/complete"
14+
"github.com/symfony-cli/console"
15+
"github.com/symfony-cli/symfony-cli/local/php"
16+
"github.com/symfony-cli/terminal"
17+
)
18+
19+
// completionTemplates holds our custom shell completions templates.
20+
//
21+
//go:embed resources/completion.*
22+
var completionTemplates embed.FS
23+
24+
func init() {
25+
// override console completion templates with our custom ones
26+
console.CompletionTemplates = completionTemplates
27+
}
28+
29+
func autocompleteSymfonyConsoleWrapper(context *console.Context, words complete.Args) []string {
30+
args := buildSymfonyAutocompleteArgs("console", words)
31+
// Composer does not support those options yet, so we only use them for Symfony Console
32+
args = append(args, "-a1", fmt.Sprintf("-s%s", console.GuessShell()))
33+
34+
os.Exit(php.SymonyConsoleExecutor(args).Execute(false))
35+
36+
// unreachable
37+
return []string{}
38+
}
39+
40+
func autocompleteComposerWrapper(context *console.Context, words complete.Args) []string {
41+
args := buildSymfonyAutocompleteArgs("composer", words)
42+
// Composer does not support multiple shell yet, so we only use the default one
43+
args = append(args, "-sbash")
44+
45+
res := php.Composer("", args, []string{}, context.App.Writer, context.App.ErrWriter, io.Discard, terminal.Logger)
46+
os.Exit(res.ExitCode())
47+
48+
// unreachable
49+
return []string{}
50+
}
51+
52+
func buildSymfonyAutocompleteArgs(wrappedCommand string, words complete.Args) []string {
53+
current, err := strconv.Atoi(os.Getenv("CURRENT"))
54+
if err != nil {
55+
current = 1
56+
} else {
57+
// we decrease one position corresponding to `symfony` command
58+
current -= 1
59+
}
60+
61+
args := make([]string, 0, len(words.All))
62+
// build the inputs command line that Symfony expects
63+
for _, input := range words.All {
64+
if input = strings.TrimSpace(input); input != "" {
65+
66+
// remove quotes from typed values
67+
quote := input[0]
68+
if quote == '\'' || quote == '"' {
69+
input = strings.TrimPrefix(input, string(quote))
70+
input = strings.TrimSuffix(input, string(quote))
71+
}
72+
73+
args = append(args, fmt.Sprintf("-i%s", input))
74+
}
75+
}
76+
77+
return append([]string{
78+
"_complete", "--no-interaction",
79+
fmt.Sprintf("-c%d", current),
80+
fmt.Sprintf("-i%s", wrappedCommand),
81+
}, args...)
82+
}

commands/resources/completion.bash

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@
2323
# - https://github.com/scop/bash-completion/blob/master/completions/sudo
2424
#
2525

26-
# this wrapper function allows us to let Symfony knows how to call the
27-
# `bin/console` using the Symfony CLI binary (to ensure the right env and PHP
28-
# versions are used)
29-
_{{ .App.HelpName }}_console() {
30-
# shellcheck disable=SC2068
31-
{{ .CurrentBinaryInvocation }} console $@
32-
}
33-
3426
_complete_{{ .App.HelpName }}() {
3527

3628
# Use the default completion for shell redirect operators.
@@ -45,19 +37,56 @@ _complete_{{ .App.HelpName }}() {
4537
for (( i=1; i <= COMP_CWORD; i++ )); do
4638
if [[ "${COMP_WORDS[i]}" != -* ]]; then
4739
case "${COMP_WORDS[i]}" in
48-
console)
49-
_SF_CMD="_{{ .App.HelpName }}_console" _command_offset $i
50-
return
51-
;;
52-
composer{{range $name := (.App.Command "php").Names }}|{{$name}}{{end}}{{range $name := (.App.Command "run").Names }}|{{$name}}{{end}})
40+
{{range $i, $name := (.App.Command "php").Names }}{{if $i}}|{{end}}{{$name}}{{end}}{{range $name := (.App.Command "run").Names }}|{{$name}}{{end}})
5341
_command_offset $i
5442
return
5543
;;
5644
esac;
5745
fi
5846
done
5947

60-
IFS=$'\n' COMPREPLY=( $(COMP_LINE="${COMP_LINE}" COMP_POINT="${COMP_POINT}" COMP_DEBUG="$COMP_DEBUG" {{ .CurrentBinaryPath }} self:autocomplete) )
48+
# Use newline as only separator to allow space in completion values
49+
IFS=$'\n'
50+
51+
local cur prev words cword
52+
_get_comp_words_by_ref -n := cur prev words cword
53+
54+
local sfcomplete
55+
if sfcomplete=$(COMP_LINE="${COMP_LINE}" COMP_POINT="${COMP_POINT}" COMP_DEBUG="$COMP_DEBUG" CURRENT="$cword" {{ .CurrentBinaryPath }} self:autocomplete 2>&1); then
56+
local quote suggestions
57+
57AE quote=${cur:0:1}
58+
59+
# Use single quotes by default if suggestions contains backslash (FQCN)
60+
if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then
61+
quote=\'
62+
fi
63+
64+
if [ "$quote" == \' ]; then
65+
# single quotes: no additional escaping (does not accept ' in values)
66+
suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
67+
elif [ "$quote" == \" ]; then
68+
# double quotes: double escaping for \ $ ` "
69+
suggestions=$(for s in $sfcomplete; do
70+
s=${s//\\/\\\\}
71+
s=${s//\$/\\\$}
72+
s=${s//\`/\\\`}
73+
s=${s//\"/\\\"}
74+
printf $'%q%q%q\n' "$quote" "$s" "$quote";
75+
done)
76+
else
77+
# no quotes: double escaping
78+
suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done)
79+
fi
80+
COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur")))
81+
__ltrim_colon_completions "$cur"
82+
else
83+
if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then
84+
>&2 echo
85+
>&2 echo $sfcomplete
86+
fi
87+
88+
return 1
89+
fi
6190
}
6291

6392
complete -F _complete_{{ .App.HelpName }} {{ .App.HelpName }}

commands/resources/completion.fish

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,10 @@ function __complete_{{ .App.HelpName }}
2727
set -lx COMP_LINE (commandline -cp)
2828
test -z (commandline -ct)
2929
and set COMP_LINE "$COMP_LINE "
30+
set -x CURRENT (count (commandline -oc))
3031
{{ .CurrentBinaryInvocation }} self:autocomplete
3132
end
3233

33-
# this wrapper function allows us to call Symfony autocompletion letting it
34-
# knows how to call the `bin/console` using the Symfony CLI binary (to ensure
35-
# the right env and PHP versions are used)
36-
function __complete_{{ .App.HelpName }}_console
37-
set -x _SF_CMD "{{ .CurrentBinaryInvocation }}" "console"
38-
__fish_complete_subcommand
39-
end
40-
41-
complete -f -c '{{ .App.HelpName }}' -n "__fish_seen_subcommand_from console" -a '(__complete_{{ .App.HelpName }}_console)' -f
42-
complete -f -c '{{ .App.HelpName }}' -n "__fish_seen_subcommand_from composer" -a '(__fish_complete_subcommand)'
4334
complete -f -c '{{ .App.HelpName }}' -n "__fish_seen_subcommand_from {{range $i, $name := (.App.Command "php").Names }}{{if $i}} {{end}}{{$name}}{{end}}" -a '(__fish_complete_subcommand)'
4435
complete -f -c '{{ .App.HelpName }}' -n "__fish_seen_subcommand_from {{range $i, $name := (.App.Command "run").Names }}{{if $i}} {{end}}{{$name}}{{end}}" -a '(__fish_complete_subcommand --fcs-skip=2)'
45-
complete -f -c '{{ .App.HelpName }}' -n "not __fish_seen_subcommand_from console composer {{range $i, $name := (.App.Command "php").Names }}{{if $i}} {{end}}{{$name}}{{end}} {{range $i, $name := (.App.Command "run").Names }}{{if $i}} {{end}}{{$name}}{{end}}" -a '(__complete_{{ .App.HelpName }})'
36+
complete -f -c '{{ .App.HelpName }}' -n "not __fish_seen_subcommand_from {{range $i, $name := (.App.Command "php").Names }}{{if $i}} {{end}}{{$name}}{{end}} {{range $i, $name := (.App.Command "run").Names }}{{if $i}} {{end}}{{$name}}{{end}}" -a '(__complete_{{ .App.HelpName }})'

commands/resources/completion.zsh

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,6 @@
2626
# - https://stackoverflow.com/a/13547531
2727
#
2828

29-
# this wrapper function allows us to let Symfony knows how to call the
30-
# `bin/console` using the Symfony CLI binary (to ensure the right env and PHP
31-
# versions are used)
32-
_{{ .App.HelpName }}_console() {
33-
# shellcheck disable=SC2068
34-
{{ .CurrentBinaryInvocation }} console $@
35-
}
36-
3729
_complete_{{ .App.HelpName }}() {
3830
local lastParam flagPrefix requestComp out comp
3931
local -a completions
@@ -56,13 +48,10 @@ _complete_{{ .App.HelpName }}() {
5648
for ((i = 1; i <= $#words; i++)); do
5749
if [[ "${words[i]}" != -* ]]; then
5850
case "${words[i]}" in
59-
console)
60-
shift words
51+
console|composer)
6152
(( CURRENT-- ))
62-
_SF_CMD="_{{ .App.HelpName }}_console" _normal
63-
return
6453
;;
65-
composer{{range $name := (.App.Command "php").Names }}|{{$name}}{{end}})
54+
{{range $i, $name := (.App.Command "php").Names }}{{if $i}}|{{end}}{{$name}}{{end}})
6655
shift words
6756
(( CURRENT-- ))
6857
_normal
@@ -82,11 +71,16 @@ _complete_{{ .App.HelpName }}() {
8271

8372
while IFS='\n' read -r comp; do
8473
if [ -n "$comp" ]; then
74+
# If requested, completions are returned with a description.
75+
# The description is preceded by a TAB character.
76+
# For zsh's _describe, we need to use a : instead of a TAB.
8577
# We first need to escape any : as part of the completion itself.
8678
comp=${comp//:/\\:}
79+
local tab=$(printf '\t')
80+
comp=${comp//$tab/:}
8781
completions+=${comp}
8882
fi
89-
done < <(COMP_LINE="$words" ${words[0]} ${_SF_CMD:-${words[1]}} self:autocomplete)
83+
done < <(COMP_LINE="$words" CURRENT="$CURRENT" ${words[0]} ${_SF_CMD:-${words[1]}} self:autocomplete)
9084

9185
# Let inbuilt _describe handle completions
9286
eval _describe "completions" completions $flagPrefix

commands/wrappers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ var (
3131
Hidden: console.Hide,
3232
// we use an alias to avoid the command being shown in the help but
3333
// still be available for completion
34-
Aliases: []*console.Alias{{Name: "composer"}},
34+
Aliases: []*console.Alias{{Name: "composer"}},
35+
ShellComplete: autocompleteComposerWrapper,
3536
Action: func(c *console.Context) error {
3637
return console.IncorrectUsageError{ParentError: errors.New(`This command can only be run as "symfony composer"`)}
3738
},
@@ -45,6 +46,7 @@ var (
4546
Action: func(c *console.Context) error {
4647
return console.IncorrectUsageError{ParentError: errors.New(`This command can only be run as "symfony console"`)}
4748
},
49+
ShellComplete: autocompleteSymfonyConsoleWrapper,
4850
}
4951
phpWrapper = &console.Command{
5052
Usage: "Runs the named binary using the configured PHP version",

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/nxadm/tail v1.4.11
1818
github.com/olekukonko/tablewriter v0.0.5
1919
github.com/pkg/errors v0.9.1
20+
github.com/posener/complete v1.2.3
2021
github.com/rjeczalik/notify v0.9.3
2122
github.com/rs/xid v1.5.0
2223
github.com/rs/zerolog v1.33.0
@@ -25,7 +26,7 @@ require (
2526
github.com/soheilhy/cmux v0.1.5
2627
github.com/stoicperlman/fls v0.0.0-20171222144224-f073b7a01081
2728
github.com/symfony-cli/cert v1.0.6
28-
github.com/symfony-cli/console v1.1.3
29+
github.com/symfony-cli/console v1.1.4
2930
github.com/symfony-cli/phpstore v1.0.12
3031
github.com/symfony-cli/terminal v1.0.7
3132
golang.org/x/sync v0.7.0
@@ -65,7 +66,6 @@ require (
6566
github.com/opencontainers/go-digest v1.0.0 // indirect
6667
github.com/opencontainers/image-spec v1.1.0 // indirect
6768
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
68-
github.com/posener/complete v1.2.3 // indirect
6969
github.com/rivo/uniseg v0.4.7 // indirect
7070
github.com/rogpeppe/go-internal v1.12.0 // indirect
7171
github.com/sirupsen/logrus v1.9.3 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ github.com/symfony-cli/console v1.1.2 h1:YVQbl4i03cE0r3QJ/RJs7LWaC3pEy2JI4qhFfL6
156156
github.com/symfony-cli/console v1.1.2/go.mod h1:AB4ZxA593cyS/1NhwnDEUChIPaGuddFqooipam1vyS8=
157157
github.com/symfony-cli/console v1.1.3 h1:ejzr9LdNe7d7FLIpeTyZm89nSgnlPhaqK7IPeyglV9o=
158158
github.com/symfony-cli/console v1.1.3/go.mod h1:AB4ZxA593cyS/1NhwnDEUChIPaGuddFqooipam1vyS8=
159+
github.com/symfony-cli/console v1.1.4 h1:A/rzNY8HiZd4r6ip2H2HCtnxwYFdC87eYnPL9H/RucM=
160+
github.com/symfony-cli/console v1.1.4/go.mod h1:AB4ZxA593cyS/1NhwnDEUChIPaGuddFqooipam1vyS8=
159161
github.com/symfony-cli/phpstore v1.0.12 h1:2mKJrDielSCW+7B+63w6HebmSBcB4qV7uuvNrIjLkoA=
160162
github.com/symfony-cli/phpstore v1.0.12/go.mod h1:U29bdJBPs9p28PzLIRKfKfKkaiH0kacdyufl3eSB1d4=
161163
github.com/symfony-cli/terminal v1.0.7 h1:57L9PUTE2cHfQtP8Ti8dyiiPEYlQ1NBIDpMJ3RPEGPc=

local/php/symfony.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package php
2+
3+
import "os"
4+
5+
// ComposerExecutor returns an Executor prepared to run Symfony Console
6+
func SymonyConsoleExecutor(args []string) *Executor {
7+
consolePath := "bin/console"
8+
if _, err := os.Stat("app/console"); err == nil {
9+
consolePath = "app/console"
10+
}
11+
12+
return &Executor{
13+
BinName: "php",
14+
Args: append([]string{"php", consolePath}, args...),
15+
}
16+
}

main.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,8 @@ func main() {
7373
}
7474
// called via "symfony console"?
7575
if len(args) >= 2 && args[1] == "console" {
76-
args[1] = "bin/console"
77-
if _, err := os.Stat("app/console"); err == nil {
78-
args[1] = "app/console"
79-
}
80-
e := &php.Executor{
81-
BinName: "php",
82-
Args: args,
83-
ExtraEnv: getCliExtraEnv(),
84-
}
76+
e := php.SymonyConsoleExecutor(args[2:])
77+
e.ExtraEnv = getCliExtraEnv()
8578
os.Exit(e.Execute(false))
8679
}
8780
// called via "symfony composer"?

0 commit comments

Comments
 (0)
0