Artisan Numérique

/développement/éditeur de texte/vim/conceal/folding/ VIM, le pliage et le masquage

VIM comme tout éditeur qui se respecte, dispose d'un système permettant de replier des portions de texte selon divers critères. Mais là où VIM se surpasse, c'est une fois encore dans sa capacité de personnalisation. Là où la majorité des éditeur "pense à notre place", VIM propose pas moins de 5 modes de pliage différent dont un totalement personnalisable.Ajoutez à cela la fonctionnalité de masquage apportée par VIM 7.3, et l'on obtient des choses bien sympathiques.

Méthodes de pliage

Comme soufflé en introduction, VIM dispose de cinq méthodes pour gérer le pliage. Ce mode est modifiable à travers le réglage foldmethod.

  • manual permet de placer les points de pliage à la mano.
  • indent permet de plier par niveau d'indentation (pratique pour du python).
  • syntax, un peu plus chaud pour débuter, cela indique que c'est le fichier de coloration syntaxique qui contrôle les pliages.
  • diff, un pliage adapté au format diff. Seules sont pliées les lignes qui ne correspondent pas à du texte modifié.
  • marker qui permet de définir des séquences de lettres marquant le début et la fin du pliage.
  • expr, le fin du fin, qui permet de définir le pliage à travers une fonction.

Pliage par marqueur

Pour débuter nous allons nous intéresser au mode le plus simple, par marqueur. Il faut donc ouvrir vim, faire une sauvegarde d'un fichier vide :w mon_text.txt et modifier le mode de marquage pour ce fichier :

:setlocal foldmethod=marker
Activation du pliage par marqueurs

Pour rappel, setlocal applique le réglage au seul buffer en cours. Si on voulait définir de manière générale (tout buffer confondu) le mode de pliage, on aurait utilisé set.

Par défaut, le marqueur de début de pliage est {{{ et }}} pour la fin. Ainsi nous pouvons taper dans notre fichier :

Ceci est le titre de mon pliage {{{
Et ceci est
son corps...
}}}

Placez vous maintenant dans le corps de votre pliage et tapez za (en mode normal hein ! :-). Pouf, le code est plié. Un coup de za supplémentaire et il s'ouvre à nouveau.

Il est aussi possible de faire du pliage imbriqué de la manière suivante :

Ceci est le titre de mon pliage {{{
Et ceci est
  encore un pliage {{{
    son corps...
  }}}
}}}

Avec za vous pouvez plier le niveau inférieur, plus le niveau supérieur. Notez qu'un coup de flèche droite ou de barre d'espace sur un pliage permet aussi de l'ouvrir.

Je ne vais pas détailler toutes les commandes utilisables sur les pliages, il y en a un bon paquet. Faite un coup de :help fold-commands pour avoir tous les détails.

Bien évidemment, le marqueur est paramétrable. Il est contenu dans le réglage foldmarker et contient par défault {{{,}}}, logique.

Dans certain cas le marqueur de fin n'est pas utile et l'on préfèrera définir le marquage d'un niveau. C'est possible en standard de la manière suivante :

Ceci est le titre de mon pliage {{{1
Et ceci est
  encore un pliage {{{2
    son corps...
  et encore un... {{{2
Un puis un autre {{{1

Avec cette notation plus besoin de marqueur de fin car vim arrêta le pliage du premier {{{2 juste avant le second, et celui du second juste avant le {{{1.

Pour conclure sur ce point, notez que vous avez aussi un réglage pour déterminer le niveau de pliage à afficher : set foldlevel=1. En gros 0 c'est pour tout plier et 9999 pour tout déplier :).

Au passage, un truc bien pratique dans vim. Lorsque vous mettez quelque part dans un fichier un vim:foldlevel=1, cela règle le niveau de pliage automatiquement à la ré-ouverture du fichier. Notez que ce n'est pas spécifique au pliage, vous pouvez insérer dans cette ligne tout réglage VIM (tabstop, format de fichier, etc).

Libellé de pliage

Vous pouvez maintenant considérer que la ligne affichée pour indiquer un pliage est moche. Et en effet, elle l'est. Mais là aussi VIM permet de tout changer à notre sauce.

Pour commencer dans le simple, il est possible de changer le caractère de remplissage de la ligne.

:set fillchars+=fold:X
altération du caractère de remplissage

Maintenant, un X marque le trésor. Notez au passage que ce réglage permet de modifier tous les caractères de remplissage (barre vertical de séparation, caractère de ligne vide, etc). Comme d'hab, :help fillchars.

Pour passer au niveau supérieur, voyons comment définir totalement notre libellé. Pour cela créons un fichier test.vim et ajoutons y une fonction

function MonLabelDePliage()
  " Récupération de la ligne à la position v:foldstart
  let line = getline(v:foldstart)
  " On supprime les caractères innutiles
  let line = substitute(line, '\v\/\*|--\s+|\#\s+|"\s+|\*\/|\{'.'\{\{\d=', '', 'g')
  " On ajoute un pipe devant
  let line = " | ".line
  return line
endfunction

Sauvez le fichier, puis pour charger la fonction, faite un :source % (% veut dire "nom de fichier du buffer courant").

Il suffit maintenant d'indiquer à VIM l'usage de cette fonction pour écrire un libellé de pliage.

:setlocal foldtext=MonLabelDePliage()

Si tout s'est bien passé, vous deviez voir vos pliage sous la forme de la ligne d'origine, privée du marqueur, et avec un | en tête de ligne. Je n'ai pas dit que j'allais améliorer le rendu, mais juste vous montrer comment faire ;-).

Il est aussi bien évidemment possible de changer la colorisation de la ligne de pliage par

:highlight Folded ctermfg=26 guifg=#2B2B5E

Colonne de pliage

Outre le label, il est aussi possible d'ajouter une colonne sur la gauche pour indiquer visuellement les marquage par :setlocal foldcolumn=3. 3 indique que vous désirez que la colonne prenne 3 caractères de large, vous pouvez mettre moins ou plus, ou 0 pour que la colonne disparaisse.

Pour contrôler la colorisation de cette colonne :

:highlight FoldColumn ctermfg=26 guifg=#2B2B5E ctermbg=18 guibg=#B8B8AC

Pliage par expression

La méthode par marqueur ne vous convient pas ? Aucune ne vous convient en réalité ? Pas de soucis, on passe en mode "Expression" :

:setlocal foldmethod=expr

Dans ce mode, nous pouvons définir une fonction VIM qui serra appelée pour chaque ligne du buffer en cours et qui renverra des informations de pliage. Pour illustrer cela imaginons que nous voulions plier notre fichier texte par niveau d'indentation. Nous allons donc taper la commande suivante :

:setlocal foldexpr=indent(v:lnum)+1

Ceci fait, nous allons modifier notre texte pour quelque chose comme cela (les espaces en début de ligne sont des tabulation) :

Niveau 1
  Niveau 2
    Niveau 3

Maintenant vous devez pouvoir plier le niveau 2, et le niveau 3. Le principe de l'expression que nous avons saisie est assez simple. La fonction VIMScript indent prend en paramètre un numéro de ligne du buffer en cours et qui renvoie le niveau d'indentation allant de 0 à N. Nous lui fournissons donc le paramètre v:lnum qui correspond au numéro de ligne à évaluer. Nous ajoutons 1 au résultat car renvoyer 0 signifie que la ligne serait au même niveau que la précédente.

Bien évidemment il est possible de faire des choses un peu plus complexes que cela en passant par la création d'un fonction. Pour tester cela, ajoutez une fonction à notre test.vim créé plus haut.

function! MonExpressionDePliage(lnum)
  return 1+indent(a:lnum)
endfunction

Sauvez le fichier, puis pour charger la fonction, faite un :source % (% veut dire "nom de fichier du buffer courant"). Maintenant vous pouvez revenir à notre fichier texte initial et taper :setlocal foldexpr=MonExpressionDePliage(v:lnum), tout simplement.

Les marqueurs c'est moche !!

En fait oui, c'est vrai, mais c'est aussi tellement pratique. Mais cela serait tout de même sympa de pouvoir les masquer.

Et bien c'est possible depuis la version 7.3 de vim. Cette dernière ajoute en effet un nouveau concept dans la vue du buffer, la possibilité de définir une région qui sera masqué sous certaines conditions et éventuellement remplacée par un caractère de votre choix.

Soyons clair cette fonctionnalité n'a rien à voir avec le pliage mais trouve ici un usage intéressant. Techniquement il s'agit d'une extension du système de coloration syntaxique. Pour masquer les marqueurs standard, cela donne quelque chose comme cela :

:syntax match Marker "\v\{\{\{\d*" conceal containedin=ALL cchar=❭

Vous aurez reconnu l'expression régulière permettant de matcher le marqueur avec ou sans niveau (\d*). Les points intéressant sont conceal qui demande à VIM de masquer l'expression matchée. cchar qui indique le caractère à utiliser en replacement (ici un petit chevron UTF8), et enfin containedin=ALL indique juste que la règle peut être appliquée à n'importe quel élément de syntaxe (les marqueurs peuvent être n'importe où).

En validant cette ligne, pouf, les marqueurs devraient disparaître, remplacés par notre caractère de substitution. Pour contrôler l'affichage du marqueur, nous utilisons le réglage concealcursor qui détermine les modes dans lesquels l'expression est cachée lorsque l'on se trouve sur la ligne du masquage. Pour que le marqueur ne soit par exemple ré-affiché qu'en mode "insertion", il suffit d'émettre :setlocal concealcursor=nc. Ainsi même si l'on est sur la ligne du masquage, il restera actif pour les modes n (normal) et c (commande) mais pas pour le mode i (insertion).

Enfin vous pouvez coloriser le caractère de masquage pour qu'il se voit un peu mieux

:highlight Conceal ctermfg=24 guifg=#8B2252

Paramétrage de VIM

Ici nous avons beaucoup testé en tapant directement les commandes. Pour pérenniser tout cela, vous pouvez mettre vos fonctions de libellé et de pliage dans ~/.vimrc, faire des choses spécifiques à certains type de fichiers dans ~/.vim/ftplugin/mon-type.vim ou encore ajouter les commandes highlight dans votre schéma de couleurs ~/.vim/colors/xxx.vim. Notez juste que vous devrez fait du set plutôt que du setlocal partout sauf dans les ftplugin. À titre d'exemple, voici ce que j'ai dans mon ~/.vimrc :

" Un plus joli caractère pour le remplissage des libellés
set fillchars+=fold:·

" Deux colonne pour le folding c'est suffisant
set foldcolumn=2

" Ma fonction pour les libellés
function! Viral_FoldingLabel()
  let line = "".getline(v:foldstart)
  let line = substitute(line, '\v\/\*|--\s+|\#\s+|"\s+|\*\/|\{'.'\{\{\d=', '', 'g')
  let line = substitute(line, '\v^\s+', '', 'g')
  let line = substitute(line, '\v\s*$', '', 'g')
  if (v:foldlevel>1)
    let line = repeat(' ',&sw*(v:foldlevel-1)).'❭ '.line
  else
    let line = '| '.line
  endif
  return line.' '
endfunction

" Définition des libellés custom
set foldtext=Viral_FoldingLabel()

" Usage du pliage par marqueurs par défaut
set foldmethod=marker

" Quelque raccourcis pratiques
nmap z1 :setlocal foldlevel=0<CR>
nmap z2 :setlocal foldlevel=1<CR>
nmap z3 :setlocal foldlevel=2<CR>
nmap z4 :setlocal foldlevel=3<CR>
nmap z5 :setlocal foldlevel=4<CR>
nmap z6 :setlocal foldlevel=6<CR>
nmap z0 :setlocal foldlevel=9999<CR>

" Masquage des marqueurs
syntax match Marker "\v\{\{\{\d*" conceal containedin=ALL cchar=❭

Conclusion

VIM est réellement un éditeur hors norme. Il n'est pas forcement le plus accessible (euphémisme ? ;-) mais permet de se plier à toutes les exigences ou presque. J'ai cependant quelques regrets (on devient exigeant ;-). Il est en effet dommage que l'on ne puisse pas mieux coloriser les libellés de pliage (mettre un mot en valeur, ou coloriser le niveau de replis). De même il aurait été sympa qu'au lieu d'un caractère de masquage, une chaîne, ou mieux une expression soit utilisable.

Mais bon, dans l'ensemble VIM est pour moi le premier éditeur qui fournit un système de pliage réellement efficace et utilisable.