DEV Community

Jérôme TAMARELLE
Jérôme TAMARELLE

Posted on AI-assisted

L'autocomplétion zsh avec Composer et Symfony console

tl;dr

Le script de completion est sur ce gist, et cette commande installe tout d'un coup :

mkdir -p ~/.config/zsh/completions
curl -fsSL https://gist.githubusercontent.com/GromNaN/00d92b92cb29a7db913784f436295a91/raw/_console \
  -o ~/.config/zsh/completions/_console
grep -qF 'zsh/completions' ~/.zshrc || cat >> ~/.zshrc <<'EOF'

# Completion zsh pour bin/console et composer
fpath=(~/.config/zsh/completions $fpath)
autoload -Uz compinit && compinit
EOF
Enter fullscreen mode Exit fullscreen mode

Si tu utilises bin/console au quotidien, tu as sûrement déjà profité de l'autocomplétion dans ton terminal : tu tapes bin/console cache:cl, tu appuies sur Tab, et hop, cache:clear apparaît tout seul. Pratique. Maintenant essaie la même chose avec composer... et là, rien. Voici pourquoi, et comment le débloquer quand même.

Un peu d'histoire

L'autocomplétion native dans le terminal est arrivée dans le composant symfony/console en version 5.4, avec un support du bash. L'idée est simple : chaque commande Symfony expose une commande cachée _complete, et un petit script shell (généré via bin/console completion) va l'interroger à chaque appui sur Tab pour proposer des suggestions dynamiques (noms de commandes, options, mais aussi des valeurs métier comme des noms d'entités ou d'environnements).

Dans les versions suivantes, Symfony a ajouté le support de fish, puis de zsh. Si tu es sur zsh (comme moi) et que ton projet tourne avec une version récente de symfony/console, tu peux générer ton script avec :

bin/console completion zsh
Enter fullscreen mode Exit fullscreen mode

Et ça fonctionne très bien.

Composer, le mauvais élève (malgré lui)

Sauf que si tu tentes la même chose avec Composer :

composer completion zsh
Enter fullscreen mode Exit fullscreen mode
Detected shell "zsh", which is not supported by Symfony shell completion (supported shells: "bash").
Enter fullscreen mode Exit fullscreen mode

Composer n'a tout simplement pas la version de symfony/console qui sait générer un script zsh ou fish. Et ce n'est pas un oubli : Composer tient à rester compatible avec PHP 7.2, ce qui l'oblige à rester figé sur symfony/console 5.4, la toute première version qui a introduit l'autocomplétion, avec uniquement le support de bash. Un vrai dilemme entre compatibilité descendante et benefice des dernières fonctionnalités: Composer a fait le choix de la compatibilité.

Le contournement

La bonne nouvelle, c'est que le script de complétion n'est qu'un script shell parmi d'autres : rien n'empêche de le récupérer sur la dernière version de Symfony et de l'adapter pour fonctionner avec une version plus ancienne du composant.

En regardant le script généré pour zsh, on voit qu'à chaque Tab, il appelle la commande cachée _complete de l'application, avec un flag -s qui précise le format de sortie attendu (bash, zsh ou fish) :

requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a1 -c$((CURRENT-1))"
Enter fullscreen mode Exit fullscreen mode

Et là, bonne surprise : la commande _complete de Composer répond très bien à -s bash, et son résultat brut (une liste de suggestions séparées par des tabulations) reste parfaitement lisible par la partie du script qui affiche les propositions dans zsh. Cette partie-là ne dépend pas du flag -s, elle se contente de lire du texte ligne par ligne.

Autrement dit : on peut prendre le script zsh généré par un bin/console récent, et le faire pointer vers Composer en changeant simplement -szsh en -sbash. Un seul script sert donc pour les deux commandes, avec juste un case pour choisir le bon flag selon le binaire appelé :

case "${words[1]:t}" in
    composer) shellFlag=bash ;;
    *) shellFlag=zsh ;;
esac
Enter fullscreen mode Exit fullscreen mode

Petit détail au passage : la commande _complete de Composer n'accepte pas l'option -a (version de l'API de complétion), seulement son ancien nom -S, aujourd'hui déprécié mais toujours accepté par les versions récentes de symfony/console. Il suffit donc d'utiliser -S1 partout pour que ça marche avec les deux.

Note : si tu utilises déjà Symfony CLI (le binaire symfony), tu n'as même pas besoin de ce bricolage. Depuis la version 5.10, il gère nativement l'auto-complétion pour bash, zsh et fish, aussi bien pour lui-même (symfony [TAB]) que pour symfony console [TAB] et symfony composer [TAB], en faisant transparemment le pont vers Composer. Pratique si tu jongles avec plusieurs projets et plusieurs versions de PHP.

Installation, étape par étape

Une solution classique consiste à mettre eval "$(bin/console completion zsh)" dans son .zshrc, mais ça veut dire relancer PHP à chaque ouverture de terminal, juste pour régénérer un script qui ne change presque jamais. Autant le générer une bonne fois pour toutes et le stocker sur disque.

J'ai mis le script final (déjà adapté pour Composer) dans un gist, pour éviter de le recopier à la main.

1. Choisir un dossier pour les scripts de complétion

zsh découvre les fonctions de complétion via une variable appelée fpath : une liste de dossiers dans lesquels il va chercher des fichiers dont le nom commence par un underscore (_console, _git, _docker...). Chaque fichier de ce type définit la complétion d'une ou plusieurs commandes.

On va créer un dossier dédié à nos propres scripts, pour ne pas toucher à ceux installés par le système ou par Homebrew :

mkdir -p ~/.config/zsh/completions
Enter fullscreen mode Exit fullscreen mode

2. Récupérer le script

Ce script est celui généré par bin/console completion zsh, avec les deux ajustements vus plus haut : le case sur ${words[1]:t} pour choisir -sbash ou -szsh selon le binaire, et -S1 à la place de -a1. Plutôt que de le régénérer et de le retoucher à la main, on le télécharge directement depuis le gist :

curl -fsSL https://gist.githubusercontent.com/GromNaN/00d92b92cb29a7db913784f436295a91/raw/_console \
  -o ~/.config/zsh/completions/_console
Enter fullscreen mode Exit fullscreen mode

3. Charger le dossier dans .zshrc

Il reste à dire à zsh d'aller regarder dans ce dossier, et d'activer son système de complétion (compsys) s'il ne l'est pas déjà :

cat >> ~/.zshrc <<'EOF'

# Completion zsh pour bin/console et composer
fpath=(~/.config/zsh/completions $fpath)
autoload -Uz compinit && compinit
EOF
Enter fullscreen mode Exit fullscreen mode

L'ordre compte : fpath doit être mis à jour avant l'appel à compinit, sinon il ne verra pas notre nouveau dossier.

Recharger et tester

On ouvre un nouveau terminal, ou on recharge la config à chaud :

source ~/.zshrc
Enter fullscreen mode Exit fullscreen mode

Puis on teste :

bin/console cache:cl<TAB>   # doit compléter en cache:clear
composer rem<TAB>           # doit compléter en remove
Enter fullscreen mode Exit fullscreen mode

Et voilà ! console et composer sont tous les deux complétés, avec un seul et même script à maintenir.

Ce que ça change concrètement

Une fois en place, Composer devient beaucoup plus agréable à utiliser :

  • composer remove <Tab> ou composer reinstall <Tab> complètent avec les packages réellement installés dans ton composer.json, pas besoin de retourner voir le fichier pour retrouver le nom exact.
  • composer require sy<Tab> propose des noms de packages Packagist, triés par popularité. Fini le copier-coller depuis le site.
  • Toutes les commandes et options de Composer (install, update, config, --no-dev, etc.) se complètent aussi, comme pour n'importe quelle commande Symfony Console.

Pour conclure

C'est une astuce de terminal toute bête, mais qui change vraiment le quotidien. Le jour où Composer changera sa politique de compatibilité PHP et mettra à jour symfony/console, ce petit bricolage deviendra inutile, et tant mieux. En attendant, ça fonctionne, et ça améliore l'expérience d'utilisation de composer.

Top comments (0)