6. Utiliser Emacs comme environnement de développement

6.1. Emacs

Les systèmes Unix ne s'accompagnent malheureusement pas du type d'environnement de développement intégré du genre ``tout ce que vous avez toujours voulu et encore beaucoup plus en un seul monstrueux paquetage'' dont disposent d'autres systèmes[1]. Il est cependant possible de mettre au point votre propre environnement. Il ne sera peut-être pas aussi esthétique et il ne sera peut-être pas aussi intégré, mais vous pourrez le configurer comme vous le voulez. Et il est gratuit. En plus, vous en avez les sources.

Emacs est la clé de tout. Il y a bien des gens qui le décrient, mais nombreux sont ceux qui l'aiment. Si vous êtes des premiers, j'ai peur que cette section n'ait que peu d'intérêt pour vous. Il vous faudra aussi pas mal de mémoire pour l'utiliser. Je conseille 8Mo en mode texte et 16Mo sous X comme strict minimum pour avoir des temps de réponse raisonnables.

Emacs est essentiellement un éditeur extrêmement configurable - il a de fait été configuré au point de ressembler plus à un système d'exploitation qu'à un éditeur! De nombreux développeurs et administrateurs système passent le plus clair de leur temps à travailler sous Emacs, le quittant seulement pour se déconnecter.

Il est impossible de même résumer ici tout ce qu'Emacs est capable de faire, mais voici quelques fonctionnalités qui intéressent les développeurs:

Et sans aucun doute bien d'autres choses qui m'ont échappées.

Emacs peut être installé sous FreeBSD sous forme de logiciel porté.

Une fois qu'il est installé, lancez-le et tapez C-h t - ce qui signifie maintenir enfoncée la touche Ctrl, taper h, relâcher la touche Ctrl, et appuyer ensuite sur t -  pour lire le guide d'Emacs (Vous pouvez aussi utiliser la souris pour sélectionner Emacs Tutorial - ``Guide Emacs'' - depuis le menu Help - ``Aide'').

Bien qu'Emacs ait des menus, il vaut la peine d'apprendre à utiliser les raccourcis claviers, parce qu'il est bien plus rapide quand vous éditez quelque chose d'appuyer sur deux ou trois touches que de courir après la souris et cliquer ensuite au bon endroit. Si, par ailleurs, vous discutez avec des utilisateurs expérimentés d'Emacs, vous vous apercevrez qu'ils utilisent assez couramment des expressions comme ``M-x replace-s RET foo RET bar RET'', il peut donc servir de comprendre ce qu'ils veulent dire. Et de toute façon, Emacs a bien plus de fonctions utiles qu'il ne peut en tenir sur une barre de menus.

Il est heureusement assez facile de découvrir les raccourcis claviers, ils sont affichés dans les menus. Je vous conseille d'utiliser les menus pour, par exemple, ouvrir un fichier jusqu'à ce que vous compreniez comment cela marche et ayez suffisamment confiance en vous, puis d'essayer C-x C-f. Une fois que cela vous convient, passez à une autre des commandes des menus.

Si vous ne vous rappelez pas ce que fait une combinaison donnée de touches, choisissez Describe Key - ``Description d'une touche'' - dans le menu Help - ``Aide'' - et tapez cette combinaison - Emacs vous dira ce qu'elle fait. Vous pouvez aussi utiliser le choix Command Apropos - ``A propos d'une commande'' - pour connaître toutes les commandes comportant un mot donné et les touches qui leur correspondent.

Au fait, l'expression plus haut signifie: enfoncer la touche Méta, appuyer sur x, relâcher la touche Méta, taper replace-s (abréviation de replace-string - ``remplacer une chaîne de caractères'' - une autre caractéristique d'Emacs est de vous permettre d'abréger les commandes), appuyer sur Entrée, taper foo (la chaîne que vous voulez remplacer), appuyer sur Entrée, taper bar (la chaîne avec laquelle vous voulez remplacer foo) et appuyer encore sur Entrée. Emacs effectuera alors l'opération de recherche et remplacement que vous venez de demander.

Si vous vous demandez ce qu'est la touche Méta, c'est une touche spéciale qu'ont beaucoup de stations Unix. Malheureusement, les PCs n'en ont pas, c'est habituellement la touche Alt qui est utilisée (ou si vous n'avez pas de chance, la touche Echap).

Oh, et pour sortir d'Emacs, tapez C-x C-c (Ce qui signifie: enfoncer la touche Ctrl, appuyer sur c, appuyer sur x et relâcher la touche Ctrl). S'il y a des fichiers ouverts que vous n'avez pas sauvegardés, Emacs vous demandera si vous voulez les sauvegarder. (Oubliez que la documentation dit que la méthode habituelle pour quitter Emacs est d'utiliser C-z - cela laisse Emacs actif en tâche de fond et n'est réellement utile que si vous êtes sur un système qui ne gère pas de terminaux virtuels).

6.2. Configurer Emacs

Emacs fait des choses admirables; certaines fonctionnalités sont incorporées, d'autres doivent être configurées.

Au lieu d'utiliser un langage de macros-instructions propriétaires, Emacs se sert d'une version de Lisp spécialement adaptée aux éditeurs, connue sous le nom de Emacs Lisp. Ce peut être très utile si vous voulez aller plus loin et apprendre ensuite par exemple Common Lisp, parce qu'il est considérablement plus léger que Common Lisp (quoique qu'encore assez imposant!).

La meilleure façon d'apprendre Emacs Lisp est de télécharger le Guide Emacs Lisp.

Il n'y a cependant pas besoin de connaître quoique ce soit à Lisp pour commencer à configurer Emacs, parce que j'ai inclu un fichier .emacs d'exemple, qui devrait suffire au début. Copiez-le simplement dans votre répertoire utilisateur et relancez Emacs, s'il s'exécute déjà; il lira les commandes du fichier et (je l'espère) vous fournira une configuration de base utile.

6.3. Un exemple de fichier .emacs

Il contient malheureusement beaucoup trop de choses pour tout expliquer en détails; il y a cependant un ou deux points intéressants à mentionner.



Exemple 1. Un exemple de fichier .emacs

;; -*-Emacs-Lisp-*-

;; Ce fichier est conçu pour être relu; la variable
;; first-time est utilisée pour éviter les problèmes
;; que cela pourra poser.
(defvar first-time t 
  "Indicateur signifiant que le fichier .emacs est lu pour la première fois")

;; Méta
(global-set-key "\M- " 'set-mark-command)
(global-set-key "\M-\C-h" 'backward-kill-word)
(global-set-key "\M-\C-r" 'query-replace)
(global-set-key "\M-r" 'replace-string)
(global-set-key "\M-g" 'goto-line)
(global-set-key "\M-h" 'help-command)

;; Touches fonction
(global-set-key [f1] 'manual-entry)
(global-set-key [f2] 'info)
(global-set-key [f3] 'repeat-complex-command)
(global-set-key [f4] 'advertised-undo)
(global-set-key [f5] 'eval-current-buffer)
(global-set-key [f6] 'buffer-menu)
(global-set-key [f7] 'other-window)
(global-set-key [f8] 'find-file)
(global-set-key [f9] 'save-buffer)
(global-set-key [f10] 'next-error)
(global-set-key [f11] 'compile)
(global-set-key [f12] 'grep)
(global-set-key [C-f1] 'compile)
(global-set-key [C-f2] 'grep)
(global-set-key [C-f3] 'next-error)
(global-set-key [C-f4] 'previous-error)
(global-set-key [C-f5] 'display-faces)
(global-set-key [C-f8] 'dired)
(global-set-key [C-f10] 'kill-compilation)

;; Touches curseur
(global-set-key [up] "\C-p")
(global-set-key [down] "\C-n")
(global-set-key [left] "\C-b")
(global-set-key [right] "\C-f")
(global-set-key [home] "\C-a")
(global-set-key [end] "\C-e")
(global-set-key [prior] "\M-v")
(global-set-key [next] "\C-v")
(global-set-key [C-up] "\M-\C-b")
(global-set-key [C-down] "\M-\C-f")
(global-set-key [C-left] "\M-b")
(global-set-key [C-right] "\M-f")
(global-set-key [C-home] "\M-<")
(global-set-key [C-end] "\M->")
(global-set-key [C-prior] "\M-<")
(global-set-key [C-next] "\M->")

;; Souris
(global-set-key [mouse-3] 'imenu)

;; Divers
(global-set-key [C-tab] "\C-q\t")   ; Ctrl tab = caractère tabulation.
(setq backup-by-copying-when-mismatch t)

;; 'y' ou <CR> équivaut à yes, 'n' à no.
(fset 'yes-or-no-p 'y-or-n-p)
    (define-key query-replace-map [return] 'act)
    (define-key query-replace-map [?\C-m] 'act)

;; Paquetages à charger
(require 'desktop)
(require 'tar-mode)

;; Mode diff évolué
(autoload 'ediff-buffers "ediff" "Interface Emacs intelligente pour diff" t)
(autoload 'ediff-files "ediff" "Interface Emacs intelligente pour diff" t)
(autoload 'ediff-files-remote "ediff"
  "Interface Emacs intelligente pour diff")
(if first-time
    (setq auto-mode-alist
          (append '(("\\.cpp$" . c++-mode)
                     ("\\.hpp$" . c++-mode)
                     ("\\.lsp$" . lisp-mode)
                     ("\\.scm$" . scheme-mode)
                     ("\\.pl$" . perl-mode)
                     ) auto-mode-alist)))

;; Mise en valeur syntaxique automatique
(defvar font-lock-auto-mode-list 
  (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'lisp-mode 'perl-mode 'scheme-mode)
  "Listes des modes à démarrer toujours avec mise en valeur")

(defvar font-lock-mode-keyword-alist
  '((c++-c-mode . c-font-lock-keywords)
    (perl-mode . perl-font-lock-keywords))
  "Associations entre modes et mots-clés")

(defun font-lock-auto-mode-select ()
  "Sélectionne automatiquement type de mise en valeur si le major mode courant est dans font-lock-auto-mode-list"
  (if (memq major-mode font-lock-auto-mode-list) 
      (progn
      (font-lock-mode t))
    )
  )

(global-set-key [M-f1] 'font-lock-fontify-buffer)

;; Nouveau dabbrev
;(require 'new-dabbrev)
(setq dabbrev-always-check-other-buffers t)
(setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
(add-hook 'emacs-lisp-mode-hook
          '(lambda () 
            (set (make-local-variable 'dabbrev-case-fold-search) nil)
            (set (make-local-variable 'dabbrev-case-replace) nil)))
(add-hook 'c-mode-hook
          '(lambda () 
            (set (make-local-variable 'dabbrev-case-fold-search) nil)
            (set (make-local-variable 'dabbrev-case-replace) nil)))
(add-hook 'text-mode-hook
          '(lambda () 
            (set (make-local-variable 'dabbrev-case-fold-search) t)
            (set (make-local-variable 'dabbrev-case-replace) t)))

;; Mode C++ et C...
(defun my-c++-mode-hook ()
  (setq tab-width 4)
  (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (define-key c++-mode-map "\C-ce" 'c-comment-edit)
  (setq c++-auto-hungry-initial-state 'none)
  (setq c++-delete-function 'backward-delete-char)
  (setq c++-tab-always-indent t)
  (setq c-indent-level 4)
  (setq c-continued-statement-offset 4)
  (setq c++-empty-arglist-indent 4))

(defun my-c-mode-hook ()
  (setq tab-width 4)
  (define-key c-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (define-key c-mode-map "\C-ce" 'c-comment-edit)
  (setq c-auto-hungry-initial-state 'none)
  (setq c-delete-function 'backward-delete-char)
  (setq c-tab-always-indent t)
;; indentation style BSD
  (setq c-indent-level 4)
  (setq c-continued-statement-offset 4)
  (setq c-brace-offset -4)
  (setq c-argdecl-indent 0)
  (setq c-label-offset -4))

;; Mode Perl...
(defun my-perl-mode-hook ()
  (setq tab-width 4)
  (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (setq perl-indent-level 4)
  (setq perl-continued-statement-offset 4))

;; Mode Scheme...
(defun my-scheme-mode-hook ()
  (define-key scheme-mode-map "\C-m" 'reindent-then-newline-and-indent))

;; Mode Emacs-Lisp...
(defun my-lisp-mode-hook ()
  (define-key lisp-mode-map "\C-m" 'reindent-then-newline-and-indent)
  (define-key lisp-mode-map "\C-i" 'lisp-indent-line)
  (define-key lisp-mode-map "\C-j" 'eval-print-last-sexp))

;; Ajouts des précédents
(add-hook 'c++-mode-hook 'my-c++-mode-hook)
(add-hook 'c-mode-hook 'my-c-mode-hook)
(add-hook 'scheme-mode-hook 'my-scheme-mode-hook)
(add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook)
(add-hook 'lisp-mode-hook 'my-lisp-mode-hook)
(add-hook 'perl-mode-hook 'my-perl-mode-hook)

;; L'inverse de next-error
(defun previous-error (n)
  "Aller à l'erreur de compilation précédente et au code correspondant."
  (interactive "p")
  (next-error (- n)))
;; Divers...
(transient-mark-mode 1)
(setq mark-even-if-inactive t)
(setq visible-bell nil)
(setq next-line-add-newlines nil)
(setq compile-command "make")
(setq suggest-key-bindings nil)
(put 'eval-expression 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'set-goal-column 'disabled nil)

;; Recherche dans les archives Elisp
(autoload 'format-lisp-code-directory "lispdir" nil t)
(autoload 'lisp-dir-apropos "lispdir" nil t)
(autoload 'lisp-dir-retrieve "lispdir" nil t)
(autoload 'lisp-dir-verify "lispdir" nil t)

;; Mise en valeur syntaxique
(defun my-make-face (face colour &optional bold)
  "Créer une apparence pour une couleur, éventuellement en gras"
  (make-face face)
  (copy-face 'default face)
  (set-face-foreground face colour)
  (if bold (make-face-bold face))
  )

(if (eq window-system 'x)
    (progn
      (my-make-face 'blue "blue")
      (my-make-face 'red "red")
      (my-make-face 'green "dark green")
      (setq font-lock-comment-face 'blue)
      (setq font-lock-string-face 'bold)
      (setq font-lock-type-face 'bold)
      (setq font-lock-keyword-face 'bold)
      (setq font-lock-function-name-face 'red)
      (setq font-lock-doc-string-face 'green)
      (add-hook 'find-file-hooks 'font-lock-auto-mode-select)

      (setq baud-rate 1000000)
      (global-set-key "\C-cmm" 'menu-bar-mode)
      (global-set-key "\C-cms" 'scroll-bar-mode)
      (global-set-key [backspace] 'backward-delete-char)
                                       ;      (global-set-key [delete] 'delete-char)
      (standard-display-european t)
      (load-library "iso-transl")))

;; X11 ou PC écrivant directement à l'écran
(if window-system
    (progn
      ;;      (global-set-key [M-f1] 'hilit-repaint-command)
      ;;      (global-set-key [M-f2] [?\C-u M-f1])
      (setq hilit-mode-enable-list  
             '(not text-mode c-mode c++-mode emacs-lisp-mode lisp-mode
                   scheme-mode)
             hilit-auto-highlight nil
             hilit-auto-rehighlight 'visible
             hilit-inhibit-hooks nil
             hilit-inhibit-rebinding t)
      (require 'hilit19)
      (require 'paren))
  (setq baud-rate 2400)         ; Pour les connections série lentes
  )

;; Terminal type TTY 
(if (and (not window-system)
         (not (equal system-type 'ms-dos)))
    (progn
      (if first-time
          (progn
            (keyboard-translate ?\C-h ?\C-?)
            (keyboard-translate ?\C-? ?\C-h)))))

;; Sous UNIX
(if (not (equal system-type 'ms-dos))
    (progn
      (if first-time
          (server-start))))

;; Ajouter ici toute modification d'apparence des caractères
(add-hook 'term-setup-hook 'my-term-setup-hook)
(defun my-term-setup-hook ()
  (if (eq window-system 'pc)
      (progn
;;  (set-face-background 'default "red")
    )))

;; Restaurer le "bureau" - le faire le plus tard possible
(if first-time
    (progn
      (desktop-load-default)
      (desktop-read)))

;; Indique que le fichier a été lu au moins une fois
(setq first-time nil)

;; Plus besoin de déboguer quoi que ce soit maintenant.
(setq debug-on-error nil)

;; C'est tout
(message "OK, %s%s" (user-login-name) ".")

6.4. Permettre à Emacs de comprendre de nouveaux langages

Bon, tout cela est très bien si vous ne voulez programmer qu'avec les langages déjà introduits dans le fichier .emacs (C, C++, Perl, Lisp et Scheme), mais que se passe-t-il quand un nouveau langage appelé ``whizbang'' fait son apparition, avec plein de nouvelles fonctionnalités attrayantes?

La première chose à faire est de regarder si whizbang s'accompagne de fichiers de configuration d'Emacs pour ce langage. Ces fichiers ont généralement comme extension .el, raccourci pour ``Emacs Lisp''. Par exemple, si whizbang est un logiciel porté pour FreeBSD, ces fichiers peuvent être repérés par:

% find /usr/ports/lang/whizbang -name "*.el" -print
et il faut les installer en le copiant dans le répertoire du ``site Lisp'' d'Emacs. Sous FreeBSD 2.1.0-RELEASE, c'est le répertoire /usr/local/share/emacs/site-lisp.

Ainsi par exemple, si la commande précédente nous donnait:

/usr/ports/lang/whizbang/work/misc/whizbang.el
nous le copierions alors comme suit:
% cp /usr/ports/lang/whizbang/work/misc/whizbang.el /usr/local/share/emacs/site-lisp


Décidons ensuite de l'extension que doivent avoir les fichiers source whizbang. Supposons, pour les besoins de l'exemple, qu'ils se terminent tous par .wiz. Il faut ajouter une entrée à notre fichier .emacs, pour être sûr qu'Emacs puisse utiliser les informations du fichier whizbang.el.

Recherchons l'entrée auto-mode-alist dans .emacs et ajoutons une ligne pour whizbang, par exemple:

...
("\\.lsp$" . lisp-mode)
("\\.wiz$" . whizbang-mode)
("\\.scm$" . scheme-mode)
...
       
Cela signifie qu'Emacs passera automatiquement en whizbang-mode à l'édition d'un fichier d'extension .wiz.

Juste après, il y a une entrée font-lock-auto-mode-list. Ajoutez-y whizbang-mode comme ceci:

;; Auto font-lock-mode
(defvar font-lock-auto-mode-list 
  (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'whizbang-mode 'lisp-mode 'perl-mode 'scheme-mode)
  "Listes des modes à démarrer toujours en font-lock-mode")
       
Ce qui signifie qu'Emacs activera toujours le font-lock-mode (i.e., la mise en valeur syntaxique) à l'édition d'un fichier .wiz.

Cela suffit. S'il y a autre chose que vous voulez automatiser à l'ouverture d'un fichier .wiz, vous pouvez ajouter un whizbang-mode hook (voyez mon my-scheme-mode-hook pour avoir un exemple simple qui ajoute un auto-indent - indentation automatique).

Notes

[1]

Au moins, pas à moins que vous ne soyez prêt à les payer une somme astronomique.

[2]

De nombreux utilisateurs d'Emacs affectent à leur variable d'environnement EDITOR la valeur emacsclient de façon à ce que ce soit ce qui se produise chaque fois qu'ils ont besoin d'éditer un fichier.

Ce document, ainsi que d'autres peut être téléchargé sur ftp.FreeBSD.org/pub/FreeBSD/doc/.

Pour toutes questions à propos de FreeBSD, lisez la documentation avant de contacter <questions@FreeBSD.org>.
Pour les questions sur cette documentation, contactez <doc@FreeBSD.org>.