Yoran 19 décembre, 2012 - 02:22 VIM CSS mapping
VIM et les feuilles de style

Bien évidemment, VIM connaît déjà les fichiers CSS. Votre code sera coloriés et vous disposez de l'auto-complètement des propriétés ET de leurs valeurs par Control-x Control-o. Mais cet éditeur permet de faire tellement plus pour nous simplifier la vie qu'il serait dommage de ne pas pousser tout cela un peu plus loin...

Faire ses courses

Avant de commencer à personnaliser, voyons un peu ce qu'offre l'écosystème VIM concernant les feuilles de style.

  • Pour gagner un peu de temps, vous pouvez utiliser l'incontournable Snipmate pour définir des portions de code utilisées de manière récurrente.
  • Par défaut, VIM comprend la syntaxe CSS 2.0. Pour monter d'un cran, vous devrez installer un plugin comme VIM CSS3 Syntax qui est plutôt complet. Un autre plugin sans doute plus léger, Better CSS Syntax for VIM. C'est celui que j'utilise. En revanche, cela va upgrader la syntaxe (colorisation), mais pas l'auto-complètement.
  • vim css color va quant à lui générer dynamiquement le rendu visuel des couleurs que vous saisissez. Ainsi, sous gVim, #FF0000 apparaîtra en rouge, #00FF00 en vert, etc. Attention cependant, cela peut vite devenir lourdingue avec des fichiers CSS fleuves ;-). Une version alternative à ce plugin est Color Highlight qui lui fonctionne avec tous les types de fichiers. Il suffit de tapper LEADER cC pour activer/désactiver la colorisation. Et cerise sur le gâteau, il fonctionne aussi avec Vim en mode terminal (aux approximation prés évidemment).

Configuration d'un plugin pour le type de fichier CSS

Bon tout ceci est bien beau, mais comment aller un peu plus loin et ajouter sa touche perso là-dedans.

Comme vous le savez peut-être déjà, VIM dispose d'un mécanisme bien pratique permettant de déclarer un script qui se lancera uniquement à l'ouverture d'un type de fichier donné. Ce genre de script est à ranger dans le dossier ~/.vim/ftplugin/.

Pour que ceci fonctionne, il faut que vous ayez Proprement configuré vim avec la ligne suivante dans votre ~/.vimrc

filetype plugin indent on

Définition des mots-clefs

Première chose qui agace, la sélection d'un sélecteur sous le curseur ne prend pas le # ou le .. La sélection d'un mot en VIM, consiste à taper la commande viw (v passe en sélection visuelle, et iw pour Inner Word). Un raccourcis connu de cette sélection consiste à double-cliquer sur le mot.

Le concept de "mot" en VIM, est régit par le réglage iskeyword. Ce réglage contient tous les caractères qui définissent un mot. Nous allons donc y ajouter les caractères -, # et .. Pour cela, nous allons créer notre fichier ~/.vim/ftplugin/css.vim

setlocal iskeyword+=.,-,#

Notez l'usage de setlocal au lieu du classique set qui permet d'appliquer le réglage uniquement sur le buffer en cours. Ensuite le += qui permet non pas d'écraser ce que contient déjà le réglage mais juste d'y ajouter des choses. Et enfin les 3 caractères séparés par des virgules.

Une fois le fichier sauvegardé, il vous suffit de retourner sur le fichier CSS et de forcer sa relecture (commande e!) pour que le ftplugin/css.vim soit lu et la variable définie pour le buffer. Maintenant si vous double-cliquez sur un sélecteur, il doit être sélectionné en entier.

Contracter - Décontracter

En bon développeur Java de base, lorsque je voyais des fichiers CSS arriver avec toutes les propriétés d'un sélecteur sur une seule ligne, je hurlais au scandale. Avec le temps je me suis rendu compte que c'était finalement bien pratique comme notation, sous réserve de pouvoir facilement développeur une ligne puis la re-compresser.

/* version compressée */
#mon-selecteur { border:1px solid red; background-color:green }

/* version décompressée */
#mon-selecteur {
  border:1px solid red;
  background-color:green
}

Nous allons donc créer deux mappings permettant d'effectuer ces opérations. D'abord la compression :

map <silent> <buffer> Π/}<CR>V?{<CR>J:let @/=""<CR>

le choix de Œ (aka Shift-œ) est très personnel. C'est la touche au dessus du Tab qui ne me sert pas à grand chose en mode "normal".

La commande map permet de définir un mapping, c'est à dire la correspondance entre une série de touches (ici œ) et un série de commandes. Vous pouvez voir cela comme une macro car vous pourriez aussi bien taper tout ce qui suit après le œ à la main.

map s'applique au mode "normal" de VIM. C'est à dire que la touche œ reste disponible en mode "insertion" (ouf ! ;-).

silent demande à VIM de ne pas afficher chaque séquence composant la macro. Enfin buffer indique à VIM que ce mapping doit être local au buffer en cours. Cette macro ne fonctionnera donc que dans un fichier CSS.

Maintenant voyons la macro à proprement parler. /} demande à VIM de chercher (/) l'accolade qui suit le curseur. Le CR indique un retours chariot, comme vous le feriez au clavier.

La commande V passe en mode "sélection visuel par ligne". La commande ?{ fonctionne comme /} mais à l'envers. C'est à dire que VIM va chercher la première accolade ouvrant qui se trouve avant le curseur. Comme on est en mode visuel, cela revient à sélectionner toutes les lignes entre les deux accolades, celles-ci comprises.

La commande J va fusionner les lignes de la sélection et tout mettre donc sur une seule. J a en plus le bon goût de compresser les espaces de sorte à n'en laisser qu'un seul entre chaque fusion. Ainsi nous compressions les éventuelles indentations.

Pour finir, la commande :let @/="" affecte une chaine vide au registre de recherche /. Ce n'est utile que si vous avez activé la colorisation de la recherche par set hlsearch.

Pour la décompression de la ligne, le travail est un peu plus simple.

map <silent> <buffer> œ V:s/\v[{;]\s*/\0\r<CR>V%=:let @/=""<CR>

La commande V passe en mode visuel et sélectionne du coup la ligne à décompresser. Ensuite une simple commande de remplacement pour rajouter des retours chariot après les ; et les {. Petites astuces, le \v permet de contraindre VIM à ne pas utiliser son mode "magique" pour les expressions régulières, adoptant ainsi une syntaxe plus classique et plus simple (à mon goût). Enfin \0 renvoie ce qui a été trouvé, soit ; ou { selon les cas.

Pour terminer, V%=. Vous l'aurez maintenant compris, le V passe en sélection par ligne. La commande % permet de sauter d'une accolade à l'autre. Comme le traitement précédent s'achève sur l'accolade finale, ça va sélectionner le bloc de propriétés décompressées. Enfin le = permet de ré-indenter le bloc de code. Le mapping se termine ici aussi sur un let @/="" pour vider le registre de recherche.

C'est toujours assez impressionnant de voir le volume de texte nécessaire pour décrire une simple série de commandes VIM :-).

Gérer les "vendor-prefixes"

Si vous manipulez régulièrement des feuilles de styles, je ne vais pas présenter les -moz-machin-chose et autre joyeusetés qui les polluent régulièrement. Vous savez déjà que c'est un petit enfer à gérer car on se retrouve à décrire 10 fois le même comportement, une fois par navigateur et parfois par version de navigateur.

Pour les vendor prefixes qui correspondent à des propriétés CSS 3.0 (box-shadow, linear-gradient, etc.), il existe un petit outil bien pratique, cssprefixer, qui permet de n'avoir QUE le CSS3 à saisir, cette moulinette se chargeant de compléter avec tout le reste. Magique et bien moins casse-figure que des outils en javascript comme prefixfree.

CssPrefixer a cependant deux soucis. Le premier est qu'il n'aime pas qu'il y ait déjà des vendor prefixes avant d'opérer. Le second est qu'il ne bosse que sur des fichiers css et nom sur des portions.

Nous allons commencer par un petit mapping pour supprimer mes -moz-machin-chose.

map <silent> <buffer> - /}<CR>V?{<CR>:g/\v-(moz\|webkit\|o)-/d<CR>

Cette fois le mapping est associé à la touche - (pour "supprimer les préfixes"). Le début est le même que pour la compression. La différence ici est la commande :g qui au lieu de substituer va appliquer une commande à toutes les lignes qui vont être trouvées. Ici la commande est d pour "supprimer". Ce mapping va donc, curseur placé dans un bloc de propriétés contenant des vendor prefixes, les supprimer sans pitié.

Maintenant pour le mapping qui va faire appel à cssprefixer :

map <buffer> + -:call writefile(split(@*,"\n"),"/tmp/css-source")<CR>gv!cssprefixer /tmp/css-source<CR>

Bon c'est du lourd, petite explication :-)

Commençons par la commande - qui n'est autre que notre précédent mapping de suppression de vendor prefixes. On aurait pu faire un seul mapping mais il est pratique de pouvoir "nettoyer" ainsi les blocs des préfixes parasites, ne serait-ce pour y voir clair.

Suit la commande :call writefile(split(@*,"\n"),"/tmp/css-source"). C'est la fameuse création de fichier (writefile). Les données de ce fichier sont issues du registre *, celui de la sélection. @* renvoie le contenu de ce registre (comme @/ vu plus haut). Quelle sélection me direz-vous ? Et bien celle crée par le mapping - pardi :-)

Ensuite nous faisons appel à la commande gv qui rend à nouveau active cette fameuse dernière sélection (très pratique !!). Ensuite nous appelons la commande cssprefixer avec le fichier temporaire en paramètre. Le bang ! utilisé sur une sélection, permet de remplacer la dite sélection par le texte renvoyé par la commande. Le tour est donc joué.

Conclusion

Vous l'aurez compris, le thème "CSS" de cet article est avant tout un prétexte pour présenter ce que l'on peut faire avec VIM. Tout ce qui se trouve ici peut être appliqué à n'importe quel type de fichier pour fluidifier au mieux votre workflow. De même toute les commandes encapsulées dans mappings peuvent être utilisées individuellement pour votre édition quotidienne.

Maintenant, je m'arrête un peu avant l'indigestion, mais il y aurait encore pas mal de chose à dire. Il est ainsi possible de modifier l'auto-complètement de VIM juste pour le CSS de sorte à déclencher sur une propriété *-color: l'affichage d'un sélecteur de couleur. Il est aussi possible de connecter VIM et firefox de sorte à ce qu'en pressant F5 dans une feuille de style, cela recharge juste cette feuille dans le navigateur. Si cela vous intéresse je pourrais vous en parler une prochaine fois.

Vos remarques et commentaires...

maethor, le 2 janvier, 2013 - 19:08

Petite correction de bug, il y a moyen d'améliorer ta commande pour décompresser un bloc css en appliquant le remplacement sur toute la ligne, et pas seulement sur la première occurrence de { ou ;. Il suffit d'ajouter un /g à la commande de remplacement :

map <silent> <buffer> œ V:s/\v[{;]\s*/\0\r/g<CR>V%=:let @/=""<CR>

J'aimerais bien réussir à avoir les deux commandes en une seule touche, cependant. Si c'est compressé, ça décompresse, sinon, ça compresse, à la manière du folding. J'vais réfléchir à comment faire ça, mais j'ai peur que ça nécessite une fonction.

Baronsed, le 21 décembre, 2012 - 18:09

C-x C-f ? Chez moi, c'est pour compléter les noms de fichiers... comment fais-tu ça ?

Quant aux replis, pourquoi ne pas utiliser les folding ?

Yoran, le 21 décembre, 2012 - 18:19

Ah bah oui désolé, c'était C-x C-o, j'ai fourché sur le clavier :)

Sinon pour le replis, le folding n'est pas pratique du tout. En effet, l'un des intérêts du mode "compact" c'est de pouvoir utiliser la sélection par colonnes de VIM (C-V en mode normal) pour par exemple insérer un sélecteur intermédiaire (I, puis le sélecteur, puis ESC), en supprimer un, etc.

Le folding est pratique lorsque le besoin est purement visuel. Ici c'est aussi une manière de formater le code qui apporte quelque chose dans son édition.

Enfin, le folding tout génial soit-il a pour moi un énorme défaut. Il n'est pas colorisable. Du coup lorsque tout ton css est foldé tu as un gros machin façon années 60 où tu ne discerne plus rien.

Simon, le 19 décembre, 2012 - 02:48

A tester également : zencoding. Une pure merveille pour le CSS tout comme le HTML, et plus encore car il se configure pour n'importe quel langage. Zencoding permet de remplacer des chaines courtes par des équivalent plus long.

En html, si je frappe : ul>li*2>a.link{foo} < raccourcis zencoding>, alors il me pondra :

<ul>
  <li><a class="link"></li>
  <li><a class="link"></li>
</ul>

En css : p = padding, bg = background, d = display, w = width, etc.

Il est très simple et rapide de configurer des combinaisons en associant au type de fichier des combinaisons. Ainsi, un même mot clé mais dans des types de fichiers différents pourra être traité différemment.

http://www.vim.org/scripts/script.php?script_id=2981

Simon, le 19 décembre, 2012 - 10:24

+foo dans les a :)

Hobbestigrou, le 19 décembre, 2012 - 15:48

Salut,

Cool ton article, je ne fais pas d'interface, mais je dois m'y mettre, je vais donc m'intéresser plus au détail à ton article au moment venu. Autrement, comme le dit Simon, il y effectivement zencoding, est-ce que tu as déjà testé, si oui qu'en pense-tu

Simon, le 19 décembre, 2012 - 15:56

Je l'utilise tout les jours et je suis ultra fan. Je ne peux plus m'en passer.

Yoran, le 19 décembre, 2012 - 17:06

@Hobbestigrou Non je n'ai jamais testé zencoding car j'ai un soucis de "cas d'usage". Sur le principe cela semble intéressant mais dans mon type d'ouvrages, lorsque j'ai du HTML à rédiger, ce sont toujours des templates, qui impliquent donc des itérations dynamiques. Dit autrement je n'ai pas de cas où je dois mettre deux fois de suite une même balise...

@Simon du coup toi tu l'utilises dans quel cas de figure ?

Simon, le 19 décembre, 2012 - 17:20

Dans le cadre de mon taf (développement symfony).
-> intégration (avec php)
-> javascript
-> css (peu puisque ce n'est pas mon taf)
-> dans le php (j'ai mappé des conf)

Yoran, le 19 décembre, 2012 - 17:24

Et les cas de figures ? Par exemple avec PHP ou JS tu fais quoi exactement avec ZenCoding ?

Simon, le 19 décembre, 2012 - 17:45

Voilà quelques exemples :

PHP

sf:exe<raccourcis zencoding>
public function execute<Curseur positionné ici>(sfWebRequest $request) {

}

sf:form<raccourcis zencoding>
<form enctype="multipart/form-data" action="<?php echo url_for('@') ?>" method="post">
  <div><?php echo $form->renderHiddenFields() ?></div>
  <div class="form">
    <Curseur positionné ici>
  </div>
</form>

JS :

closure<raccourcis zencoding>
(function($) {
  <Curseur positionné ici>
})(jQuery);
ready<raccourcis zencoding>
$(document).ready(function() {
  <Curseur positionné ici>
});
Yoran, le 19 décembre, 2012 - 18:24

Ah ok là je comprend. Mais du coup ça fonctionne un peu comme snipmate non ? Tu as possibilité de "décorer" une sélection ? Comme par exemple sélectionner un mot clef et par le raccourcis zencoding l'encadrer avec un tag ?

Hobbestigrou, le 19 décembre, 2012 - 18:34

@Ulhume, ok je vois, c'est pareil pour moi en fait quand j'y pense, et j'utilise snipmate pour me faciliter un peu la tache.

Simon, le 19 décembre, 2012 - 18:37

Un ou plusieurs tags oui.

Typiquement, si j'ai du contenu, que je le sélectionne et que je fais le raccourcis zencoding, j'aurai un prompt (command line de vim) et je peux faire une série de tags et valider :

Contenu sélectionné :

foo bar <a href="boo">bim</a>

Prompt :

div.foo#bar>p>span

Après :

<div class="foo" id="bar"><p><span>foo bar <a href="boo">bim</a></span></p></div>

(* = prévu pour drupal qui plante sans)
EDIT: Plus maintenant ;p

Yoran, le 19 décembre, 2012 - 22:21

Intéressant cela, c'est un peu la faiblesse de snipmate.

Sinon, c'est quoi qui plante avec drupal ? Je ne suis pas certain de comprendre.

Simon, le 20 décembre, 2012 - 12:34

La balise p n'était pas affichée dans ma preview ^^

Yoran, le 21 décembre, 2012 - 18:38

Ah ok :) Ben c'était mon code pas celui de drupal pour le coup :) C'est corrigé.

Publier un nouveau commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
  • To highlight piece of code, just surround them with <code type="language"> Your code &tl;/code>>. Language can be java,c++,bash,etc... Everything Geshi support.
  • Tags HTML autorisés : <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote> <div> <p> <br>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Les adresses de pages web et de courriels sont transformées en liens automatiquement.

Plus d'informations sur les options de formatage

CAPTCHA
Cette question est là pour déterminer si vous êtes humain ou pas...