23 Sep 2009

Indenter proprement tout un buffer avec Emacs

Category: Emacs

Résumé

Dans cet article nous verrons comment indenter un buffer entier dans Emacs sans changer la place du curseur puis comment implémenter une routine qui évite de modifier l'indentation des parties verbatim en mode d'édition (x)html.

La méthode habituelle

Habituellement, pour indenter tout le buffer dans Emacs, on utilise la succession de raccourcis clavier C-x h immédiatement suivi de C-M-\ (quatre doigts pour ce raccourci, mais cela reste moins difficile que le piano ;-)).

  • C-x h sélectionne tout le buffer ;
  • C-M-\ indente toute la sélection donc, ici, tout le buffer.
Bon... Ben... c'est parfait. Pourquoi en faire un flan article ?

Problèmes et solutions

Les problèmes

  1. lorsque l'on sélectionne tout le buffer, on perd la place du curseur ;
  2. dans certains modes, comme html-mode ou nxhtml-mode, si le buffer contient des parties en verbatim, elles sont aussi indentées ; ce qui n'est pas souhaitable.

Indenter sans changer la place du curseur

La méthode est relativement simple pour qui connaît un peu le lisp. Il suffit de créer une fonction qui indente la région qui s'étend du début du buffer jusqu'à le fin ; ce qui donne :

(defun pi-indent-whole-buffer ()
  "Indent the whole buffer without change the point."
  (interactive)
    (indent-region (point-min) (point-max) nil))
(global-set-key (kbd "<C-S-iso-lefttab>") 'pi-indent-whole-buffer)
    

La dernière ligne de code suggère qu'il suffit dorénavant de presser C-S-TAB pour indenter tout le buffer, et cela ne changera pas la place du point. Reste le problème des parties de code en verbatim...

Ne pas indenter les parties verbatim

En mode html (que ce soit en html-mode ou même en nxhtml-mode) l'indentation du buffer casse l'indentation du code compris entre les balises <pre> et </pre>. Pour indenter proprement un tel buffer, la méthode est relativement simple ; il suffit d'indenter du début du buffer jusqu'au premier <pre>, puis de la fermeture </pre> jusqu'au <pre> suivant, et ainsi de suite, jusqu'à la fin du buffer.
Cela se concrétise par le petit code Elisp suivant :

(defun pi-indent-whole-html-buffer nil
  (interactive)
  (save-excursion
    (beginning-of-buffer)
    (let ((ppoint (point)))
      (while (search-forward-regexp "<pre.*?>"  (point-max) t)
        (indent-region ppoint (point) nil)
        (search-forward-regexp "</pre>" (point-max) t)
        (setq ppoint (point)))
      (indent-region ppoint (point-max) nil))))
  

Conclusion

Il ne reste plus qu'à combiner les deux codes précédents pour fabriquer une jolie routine qui indente proprement tout le buffer sans casser l'indentation (souvent durement gagnée) du code compris entre les balises <pre> et </pre> dans un fichier (x)html. C'est ce code que l'on pourra mettre dans son fichier de configuration, le fameux .emacs.

; --------------------------
; * Indente tout le buffer *
(defun pi-indent-whole-html-buffer nil
  (interactive)
  (save-excursion
    (beginning-of-buffer)
    (let ((ppoint (point)))
      (while (search-forward-regexp "<pre.*?>"  (point-max) t)
        (indent-region ppoint (point) nil)
        (search-forward-regexp "</pre>" (point-max) t)
        (setq ppoint (point)))
      (indent-region ppoint (point-max) nil))))

(defun pi-indent-whole-buffer ()
  (interactive)
  (if (assoc-ignore-case major-mode (list "xhtml-mode" "html-mode" "nxhtml-mode"))
      (pi-indent-whole-html-buffer)
    (indent-region (point-min) (point-max) nil)))
(global-set-key (kbd "<C-S-iso-lefttab>") 'pi-indent-whole-buffer)
  

Je vous laisse le soin d'ajouter vos propres tags pour les autres modes qui posent un problème similaire.