Le démarrage d’Emacs

Introduction

Il y a quelques temps de cela (je viens de me rendre compte que cela fait plus de 10 ans déjà…), je commençais à utiliser Emacs. Et, comme beaucoup je suppose, pour configurer, je copiais/collais des bouts de code dans mon .emacs sans chercher à comprendre ce que ça faisait (comment ça c’est encore le cas ‽). Typiquement, j’avais plein de :

(autoload 'php-mode "php-mode" "Major mode for editing php code." t)
(add-to-list 'auto-mode-alist '("\\.php$" . php-mode))

plutôt obscurs.

Et au fil du temps, les lignes s’accumulaient, et Emacs n’était plus aussi rapide à charger.

Je me suis alors intéressé à comment le rendre plus rapide au démarrage. Bien que je me sois rendu compte que la meilleure solution pour moi serait d’utiliser emacsclient1, j’ai d’abord fait du nettoyage, en utilisant le plus possible customize et en regroupant ça dans un seul fichier avec :

(setq custom-file "~/.emacs.d/config/custom.el") (load custom-file)

Puis j’ai supprimé tout ce dont je ne me servais en fait pas, et je me suis penché sur ce qu’il se passe au démarrage d’Emacs, pour savoir si je ne lui faisais pas faire des choses inutiles. Et, avec les différentes versions d’Emacs certainement, j’ai bien vu que la plupart de ces lignes étaient (devenues ?) inutiles.

Comprendre ce qui est chargé et lancé au démarrage.

Cette partie ne se focalise pas sur tout ce qui est fait au démarrage, mais mets l’accent sur qu’est-ce que charge Emacs, et où il va chercher ce qu’il charge, pour mieux comprendre comment faire son .emacs, et qu’il soit plus léger/rapide (charger un fichier = ouvrir + évaluer tout ce qu’il y a dedans).

Un bon résumé des opérations effectuées par Emacs est disponible, comme toujours, dans le manuel : Startup-Summary (manuel elisp).

La variable top-level contient la fonction (en fait la form, objet Lisp qui est sensé être évalué ~ expression, mais je laisserai form par la suite) qui sera lancée au démarrage. Par défaut, c’est normal-top-level.

normal-top-level

La fonction normal-top-level est donc appelée au lancement d’Emacs. On peut la voir dans le fichier startup.el 2 (pour trouver le fichier, taper dans Emacs M-x find-library startup. Ou, via l’aide avec C-h f normal-top-level (juste q pour fermer les fenêtres d’aide), si emacs2x-el est installé, la première ligne devrait être normal-top-level is a compiled Lisp function in `startup.el'. avec startup.el qui est un lien).

Elle :

  • fait diverses configurations tout au long de son appel (qui ne nous concerne pas pour le sujet présent) ;
  • cherche des subdirs.el/leim-list.el dans chaque dossier de load-path et les charge3 ;
  • lance la fonction command-line ;
  • lance des hooks comme emacs-startup-hook et window-setup.

    Bref, elle initialise, remplit un peu plus la variable load-path, et surtout délègue à command-line.

command-line

Aussi située dans startup.el, cette fonction :

  • fait aussi des configurations tout du long ;
  • initialise before-init-time ;
  • met en chemin absolu les noms de fichiers dans load-history (C-h v load-history pour prendre peur) ;
  • definit le fichier init de quel utilisateur il faut utiliser ;
  • parse quelques options de la ligne de commande (-d, -D, -q, -Q, -nbc, etc.) ;
  • lance before-init-hook ;
  • charge site-run-file ;
  • cherche le fichier init (cf plus tard) s’il existe et le lit (puis default.el) ;
  • charge le fichier abbrev, s’il existe et qu’Emacs n’est pas en -batch ;
  • si un dossier de package existe, initialise le système de package (package-initialize) ;
  • initialise after-init-time ;
  • lance after-init-hook ;
  • lance command-line-1 (avec le reste des arguments) ;
  • appelle kill-emacs si option -batch ;
  • lance le serveur, si Emacs est lancé en démon ;
  • lance emacs-session-restore.

C’est donc là que sont chargés les fichiers de configuration : site puis init et default. Il faut voir également les hooks before-init-hook et after-init-hook (qui sont définis à nil par défaut, mais dont la valeur peut changer selon les systèmes Init-File (manuel elisp))

La fonction command-line-1 boucle pour chaque option, exécute l’option (potentiellement -L pour ajouter des dossiers à load-path) et se positionne sur le bon buffer, ou affiche le startup screen.

Avant d’ajouter des choses dans votre .emacs, il faut donc vérifier que ce n’est pas déjà ajouté dans site-run-file.

Le fichier init

Je traduis ici très grossièrement les parties intéressantes du manuel (Init-File (manuel emacs), manuel que je vous encourage à lire).

Quand Emacs démarre, il essaie de charger un fichier d’initialisation qui dit comment l’utilisateur souhaite que démarre Emacs (suivant l’utilisateur, voir Find-Init (manuel emacs)). Emacs cherche le fichier init parmi ~/.emacs, ~/.emacs.el, ou ~/.emacs.d/init.el.

Il peut également y avoir un fichier init par défaut, qui est la bibliothèque default.el, trouvé dans le load-path. Si elle existe, elle est chargée à chaque démarrage d’Emacs (sauf si -q), mais le fichier init est chargé en premier. S’il met inhibit-default-init à non-nil, default n’est pas chargé.

Il peut y a voir aussi un fichier de démarrage de « site », site-start.el. Il est chargé avant le fichier init, sauf si --no-site-file.

Les fichiers default.el et site-start.el peuvent être dans n’importe quel dossier de la variable load-path (souvent dans un dossier site-lisp comme : /usr/local/share/emacs/site-lisp).

Pour voir le load-path de votre Emacs en cours : C-h v load-path.

Pour connaître votre fichier init, et donc savoir quel fichier ~/.emacs(.d/init)?.elc? a été chargé, C-h v user-init-file.

En bref

Au démarrage, il se passe tout un tas de choses, dont le chargement de plein de fichiers2. Je vais résumé ici les fichiers chargés (et les fonctions lancées) qui peuvent avoir un impact sur load-path, et sur lesquels l’utilisateur peut avoir une influence.

Le load-path initial contient quelque chose comme :

/.../emacs24/share/emacs/24.3.50/site-lisp
/.../emacs24/share/emacs/site-lisp
/.../emacs24/share/emacs/24.3.50/lisp

avant même qu’Emacs ne soit lancé.

Puis sont ajoutés les sous-dossiers de ces dossiers (qui sont dans les subdirs.el respectifs) récursivement.

Ensuite, Emacs :

  • lance before-init-hook ;
  • charge le fichier site-run-file (si présent et si pas -no-site-file) qui est commun à tous les utilisateurs et que seul l’admin du site devrait toucher (normalement site-start.el) ;
  • charge le fichier init de l’utilisateur (si pas -q), qui peut être ~/.emacs, ~/.emacs.el, ou ~/.emacs.d/init.el ;
  • charge le fichier default (normalement default.el) sauf si -q ou si inhibit-default-init n’est pas nil ;
  • lance after-init-hook ;
  • ajoute les dossiers passé en argument à l’option -L ou -directory.

Concernant le chargement des fichiers : par défaut (variable auto-compression-mode), il cherche la version compressée (.gz, jka-compr-load-suffixes), et si ce n’est pas précisé autrement lors de l’appel de load, il cherche avec les extensions .el, .elc et sans extension.

Savoir comment charger une fonction/un fichier.

On va voir ici ce que fait la fonction autoload et si c’est bien elle qu’il faut appeler.

Rien de mieux qu’une traduction approximative du manuel et de l’aide pour connaître nos options. Si vous lisez l’anglais, allez donc plutôt voir Loading (manuel elisp), puis allez au résumé.

How Programs Do Loading: The load function and others.

How-Programs-Do-Loading (manuel elisp)

  • Function: load filename &optional missing-ok nomessage nosuffix must-suffix :

    Ouvre un fichier lisp, évalue, ferme le fichier.

    Cherche d’abord filename.elc. Si existe, est chargé. Sinon, cherche filename.el. Si existe, est chargé. Sinon, cherche filename et charge si existe. (Attention à foo.el.el : (load "foo.el") marche…)

    Si auto-compression-mode est activé (cas par défaut) cherche la version compressée si le fichier n’est pas trouvé, puis passe aux suivants. Décompresse et charge si existe (ajoute chacun des suffixes dans jka-compr-load-suffixes).

    Si nosuffix non-nil, n’essaie pas d’ajouter .el/.elc

    Si must-suffix non-nil, load insiste sur le fait que le nom du fichier doit finir par .el/.elc.

    Si filename est un nom de fichier relatif (foo ou baz/foo.bar), load cherche le fichier par la variable load-path. Il charge le premier fichier possible. Si nil dans load-path, essaie le dossier courant. Les trois premiers fichiers dans le premier dossier de load-path sont tentés, puis les trois du second dossier, etc.

    Peu importe où Emacs trouve le fichier (nom/dossier) la variable load-file-name sera valorisée à ce nom de fichier.

    Des messages comme ‘Loading foo...’ et ‘Loading foo...done’ apparaissent, sauf si nomessage est non-nil.

    Si load ne trouve pas le fichier, normalement, il signale l’erreur file-error. Mais si missing-ok non-nil, alors load retourne seulement nil.

    load retourne t si le fichier est chargé avec succès.

    • Donc load nomfichier [retourneNilSiAbsent?SinonErreur nonVerbeux? tenteDAjouter.el/.elc? doitFinirPar.el/.elc?]

      Du coup, on utilisera le plus souvent (load "chose") ou (load "chose.el" t t t) (avec des variantes sur missing-ok et nomessage).

      C-h f load : Charger un fichier enregistre ses définitions, ses appels provide et require dans load-history. La variable load-in-progress est non-nil pendant le chargement, load-file-name contientle nom du fichier.

  • Command: load-file filename :

    Charge le fichier filename. Si filemane est un nom relatif, le dossier courant est présumé. N’utilise pas load-path, n’utilise pas de suffixes, mais cherche une version compressée.

    • En très gros, fait (load (expand-file-name file) nil nil t)).
  • Command: load-library library :

    Cette commande charge la bibliothèque library. Comme load, sauf que lit son argument de façon interactive.

    • En gros, fait (load library).

Load Suffixes: Details about the suffixes that load tries.

Quels suffixes sont tentés (.el, etc.): voir point précédent.

Library Search: Finding a library to load.

Library-Search (manuel elisp)

Quand Emacs cherche une bibliothèque, il cherche dans load-path qui contient des chaînes de caractères correspondant à des dossiers ou nil pour le dossier courant.

À chaque démarrage, il initialise load-path par la variable d’environnement EMACSLOADPATH. Si elle n’est pas défini, Emacs initialise load-path avec /usr/local/share/emacs/version/site-lisp et /usr/local/share/emacs/site-lisp et ajoute à load-path les sous-dossiers récursivement (sauf si ne commence pas par chiffre/lettre, vaut CVS ou RCS, contient un fichier .nosearch). Puis, comme vu précédemment, ajoute ceux indiqués par -L, et ceux du fichier init de l’utilisateur.

  • Command: locate-library library &optional nosuffix path interactive-call :

    Trouve le vrai nom de la bibliothèque library. Cherche comme le fait load, l’argument nosuffix ayant le même sens : ne pas ajouter .elc ou .el au nom.

    Si path est non-nil, cette liste de dossier est utilisée au lieu de load-path.

    Si la commande est appelé depuis un programme, retourne le nom en tant que string. Si appelé avec M-x l’argument interactive-call est t, et le résultat est affichée dans la zone d’affichage des messages (echo area).

  • Command: list-load-path-shadows &optional stringp :

    Cette commande montre une liste de fichiers Emacs lisp « occultés » (shadowed). Un fichier occulté est un fichier qui ne sera normalement pas chargé alors qu’il est dans un dossier dans load-path, mais parce qu’un autre fichier au nom similaire se situe dans un dossier de load-path.

    Par exemple, si load-path vaut ("/opt/emacs/site-lisp" "/usr/share/emacs/23.3/lisp") et que dans les deux dossiers un fichier foo.el existe, alors (require 'foo) ne chargera jamais le fichier du second dossier. Une telle situation peut indiquer un problème dans la façon dont Emacs a été installé.

    Quand appelée depuis Lisp, cette fonction affiche un message contenant la liste des fichiers occultés, plutôt que de les afficher dans un buffer. Si l’argument stringp est non-nil, elle retourne les fichiers occultés dans un string.

Loading Non-ASCII: Non-ASCII characters in Emacs Lisp files.

Pas pertinent pour le sujet.

Autoload: Setting up a function to autoload.

Autoload (manuel elisp)

autoload permet d’enregistrer l’existence d’une fonction ou macro, et repousse le chargement du fichier qui la définit. Le premier appel à la fonction charge automatiquement la bonne bibliothèque, pour installer la vraie définition (et d’autres choses associées), puis exécute la définition comme si cela avait déjà été chargé. L’autoloading peut également être déclanché en regardant la doc d’une fonction (C-h f).

Il y a deux façons de faire une fonction « autoloaded » : en appelant autoload, et en écrivant un commentaire « magique » avant la vraie définition. autoload est la primitive bas niveau pour l’autochargement ; n’importe quel programme Lisp peut appeler autoload à n’importe quel moment. Les commentaires magiques sont le moyen le plus pratique de faire « autoloader » une fonction, pour les paquets installés avec Emacs. Ces commentaires ne font rien en eux-même, mais ils servent de guide pour la commande update-file-autoloads, qui construit les appels à autoload et fait en sorte de les exécuter quand Emacs est construit.

  • Function: autoload function filename &optional docstring interactive type

    Cette fonction définit la fonction (ou macro) function pour pouvoir charger automatiquement à partir du fichier filename. La chaîne filename spécifie le fichier à charger pour obtenir la vraie définition de la fonction.

    Si filename ne contient ni un nom de dossier, ni les suffixes .el ou .elc, cette fonction ajoute un de ces suffixes, et ne chargera pas à partir d’un fichier dont le nom est juste filename sans suffixe. (La variable load-suffixes spécifie les suffixes nécessaires.)

    L’argument docstring est la chaîne contenant la doc de la fonction. Mettre cette documentation permet de la regarder sans charger la vraie définition. Normalement, elle devrait être identique à celle de la définition de la fonction. Si ce n’est pas le cas, la doc de la définition de la fonction prendra effet quand elle sera chargée.

    Si interactive est non-nil, la fonction peut être appelée interactivement. Cela permet à la complétion dans M-x de marcher sans charger la vraie définition de la fonction. La vraie façon dont la fonction est interactive est inutile, sauf si l’utilisateur appelle vraiment la fonction, et dans ce cas la vraie définition est chargée.

    Il est possible d’autoloader les macros et raccourcis clavier comme les fonctions ordinaires. Mettre macro dans type si la fonction est en fait une macro, keymap si c’est un raccourci clavier.

    Si function a déjà une définition qui est non vide et qui n’est pas un objet autoload, la fonction ne fait rien et retourne nil. Sinon, elle construit un objet autoload, et le met en tant que définition de la fonction function.

  • Function: autoloadp object

    Cette fonction retourne non-nil si object est un objet autoloadé.

    Le fichier autoloadé contient généralement d’autres définitions et peut faire des require ou provide. Si le fichier n’est pas complétement chargé (à cause d’une erreur dans l’évaluation de son contenu), toutes les définitions des fonctions et provide qui sont apparus dans le chargement sont défaits. Ceci est fait pour s’assurer que la prochaine tentative d’appel à une fonction autochargée de ce fichier essayera à nouveau de charger le fichier pour être sûr que les fonctions marchent.

    Si le fichier autochargé échoue à définir la fonction ou macro Lisp désirée, alors une erreur est signalée « Autoloading failed to define function function-name ».

    (La suite concerne moins le sujet, mais c’est intéressant, donc je mets pour me souvenir.)

    Un commentaire magique autoload (souvent appelé autoload cookie) est composé de ;;;###autoload, sur une ligne, tout seul, juste avant la vraie définition de la fonction. La commande M-x update-file-autoloads écrit l’appel autoload correspondant dans loaddefs.el. (La chaîne commentaire et le fichier peuvent être changés, voir plus bas.) Construire Emacs charge loaddefs.el et appelle autoload. M-x update-directory-autoloads est encore plus puissant ; ça met à jour les autoloads pour tous les fichiers dans le dossier courant.

    Le même commentaire magique peut copier n’importe quel type de form dans loaddefs.el. La form qui suit le commentaire magique est copié tel quel, sauf si c’est une des forms que autoload gère différemment (par exemple en convertissant en appel autoload)  :

    Les définitions des objets fonction :

    defun et defmacro [comme on vient de voir pour ceux qui suivent] ; et aussi cl-defun, cl-defmacro et define-overloadable-function.

    Les définitions des major/minor modes :

    define-minor-mode, define-globalized-minor-mode, define-generic-mode, define-derived-mode, easy-mmode-define-minor-mode, easy-mmode-define-global-mode, define-compilation-mode, et define-global-minor-mode.

    D’autres types de définition :

    defcustom, defgroup, defclass, et define-skeleton.

    On peut aussi utiliser un commentaire magique pour exécuter une form au moment de la construction sans l’exécuter quand le fichier lui-même est chargé. Pour ce faire, écrire la form sur la même ligne que le commentaire magique. Puisque c’est un commentaire, ça ne fait rien quand on charge le fichier source ; mais M-x update-file-autoloads le copie dans loaddefs.el, où il sera exécuté lors de la construction d’Emacs.

    Un exemple de fonction avec le commentaire magique :

;;;###autoload
(defun doctor ()
  "Switch to *doctor* buffer and start giving psychotherapy."
  (interactive)
  (switch-to-buffer "*doctor*")
  (doctor-mode))

Ce qui est écrit dans loaddefs.el :

(autoload (quote doctor) "doctor" "\
Switch to *doctor* buffer and start giving psychotherapy.

\(fn)" t nil)

Si on a écrit une définition de fonction avec une macro qui n’est pas celle utilisée d’habitude (par exemple mydefunmacro), l’utilisation d’un commentaire magique normal copierait toute la définition dans loaddefs.el (puisque ce n’est pas une des exceptions vues précédemment). Ce n’est pas ce que l’on souhaite. Pour mettre le bon appel autoload dans loaddefs.el, il faut plutôt écrire :

;;;###autoload (autoload 'foo "myfile")
(mydefunmacro foo
  ...)

Comme dit précédemment, on peut utiliser une chaîne différente pour le cookie autoload et quand même avoir l’appel autoload correspondant écrit dans un fichier différent de loaddefs.el.

  • Variable: generate-autoload-cookie

    La valeur de cette variable doit être une chaîne dont la syntaxe est une commande Lisp. M-x update-file-autoloads copie la form Lisp qui suit le cookie dans le fichier autoload qu’il génère.

  • Variable: generated-autoload-file

    La valeur de cette variable nomme un fichier Emacs Lisp où les appels autoload doivent aller (par défaut loaddefs.el)

    Pour charger explicitement la bibliothèque spécifiée par un objet autoload, on peut utiliser la fonction :

  • Function: autoload-do-load autoload &optional name macro-only

    Cette fonction effectue le chargement spécifié par autoload, que devrait être un objet autoload.

Repeated Loading: Precautions about loading a file twice.

Comme le dit le titre. Intéressant, mais pas pour notre problème.

Named Features: Loading a library if it isn't already loaded.

provide et require sont une autre façon de charger automatiquement des fichiers. Le chargement automatique est déclanchée en appelant une fonction particulière, mais une feature est chargée la première fois qu’un autre programme la demande, par son nom.

Un nom de feature est un symbole qui représente une collection de fonctions, variables, etc. Le fichier qui les définit doit faire un provide de la feature.

Un autre programme qui les utilise peut s’assurer qu’elles sont définies en faisant un require de la feature. Cela charge le fichier de définitions s’il n’était pas déjà chargé (en regardant la variable globale features). Si le fichier n’a pas d’appel « haut niveau » à provide, require signale une erreur.

Where Defined: Finding which file defined a certain symbol.

Avec la fonction symbol-file (qui cherche dans load-history). (Par exemple, évaluez (C-x e derrière la parenthèse fermante) dans un buffer (symbol-file 'emacs-lisp-mode) ou (symbol-file 'split-window).)

Unloading: How to "unload" a library that was loaded.

unload-feature feature (qui s’aide lui aussi de load-history) « décharge » la librairie qui fait un provide feature. Ça enlève les définitions des fonctions, macros… crées par des defun, defalias, defsubst, defmacro, et restaure les autoloads.

Hooks for Loading: Providing code to be run when particular libraries are loaded.

On peut demander à ce que du code soit exécuté chaque fois qu’Emacs charge une bibliothèque, en utilisant la variable after-load-functions :

— Variable: after-load-functions

Ce hook est lancé après le chargement d’un fichier. Chaque fonction dans le hook est appelé avec un seul argument, le nom avec chemin absolu du fichier qui vient d’être chargé.

Si on veut que du code soit exécuté quand une bibliothèque en particulier est chargée, il faut utiliser eval-after-load :

— Function: eval-after-load library form

Cette fonction évalue form à la fin du chargement du fichier library, chaque fois que library est chargée. Si elle est déjà chargée, elle évalue form immédiatement. Par exemple :

(eval-after-load "edebug" '(def-edebug-spec c-point t))

Normalement, les programmes Lisp bien faits ne devraient pas utiliser eval-after-load. Si on a besoin d’examiner et d’initialiser des variables définies dans une autre bibliothèque (celles définies pour un usage extérieur à la bibliothèque), on peut le faire immédiatement. Il n’y a pas besoin d’attendre qu’une bibliothèque soit chargée. Si on a besoin d’appeler des fonctions définies dans cette bibliothèque, il faut charger la bibliothèque (de préférence avec require).

— Variable: after-load-alist

Contient une alist construite par eval-after-load, contenant les expressions à évaluer quand certaines bibliothèques sont chargées.

En bref

Pour charger un fichier (et évaluer ce qu’il y a dedans), on utilise load. Mais, pour dire à emacs « Hey, la fonction foo est dans le fichier bar.el, mais tu charges (c’est-à-dire évalues) le fichier qui contient la fonction que quand t’en as vraiment besoin. », et ainsi pouvoir utiliser foo sans charger le fichier, on utilise autoload. Par exemple,

(autoload 'foo "bar" "Some docstring." t)

dans le .emacs, va dire que la fonction php-mode se trouve dans un fichier php-mode (avec extension .el ou .elc).

De plus, si dans un fichier « standard » il y a des lignes ;;;###autoload, alors pas besoin d’exécuter des (autoload ...), le fichier loaddefs.el contient certainement l’appel qui va bien.

Si par contre c’est votre fichier, et que vous voulez ajouter les fonctions qui suivent les ;;;###autoload à loaddefs.el, il faut appeler update-file-autoloads (ou update-directory-autoloads).

Comment est choisi le mode d’un fichier

Choosing-Modes (manuel emacs) Auto-Major-Mode (manuel elisp)

Quand on ouvre un fichier, avec find-file fichier ou recover-file, les fonctions appellent normal-mode fichier (dans files.el).

normal-mode fichier appelle fundamental-mode sur le buffer puis appelle set-auto-mode.

Quand on regarde C-h f set-auto-mode :

La fonction cherche un mode de plusieurs façons :

  • Elle cherche un -*-foo-mode-*- (voir C-h f set-auto-mode-1) dans le buffer.
  • Si pas trouvé, cherche un #!/ à la première ligne (en fait C-h v auto-mode-interpreter-regexp pour être exact).
  • Si pas trouvé, cherche une regexp pour la première ligne du buffer (dans C-h v magic-mode-alist (défaut à vide)). Par exemple ("<\\?xml " . xml-mode) dit que si la première ligne commence par <\\?xml, on passe en xml-mode.
  • Si pas trouvé, cherche une regexp sur le nom de fichier, souvent l’extension (dans C-h v auto-mode-alist (case-sensitive, puis insensitive)). Par exemple ("\\.svgz?\\'" . xml-mode) dit que si le fichier se termine par .svg ou .svgz, on passe en xml-mode.
  • Si pas trouvé, cherche une regexp pour la première ligne du buffer (dans C-h v magic-fallback-mode-alist).
  • Si toujours pas trouvé, on appelle set-buffer-major-mode qui dit que :
    • si le buffer est *scratch*, initial-major-mode ;
    • sinon major-mode (c’est-à-dire fundamental-mode par défaut).

La plupart du temps, une ligne (add-to-list 'auto-mode-alist '("\\.[ch]\\'" . c-mode)) sera présente dans le fichier (précédée ou non d’un commentaire magique).

Bonus

Specifying-File-Variable (manuel emacs)

Le -*- ne sert en fait pas qu’à mettre le mode. On peut aussi mettre des variables spécialement pour un fichier. (Pour tous les fichiers d’un dossier, c’est possible aussi, avec un fichier .dir-locals.el Directory-Variables (manuel emacs).)

Par exemple,

;; -*- mode: Lisp; fill-column: 75; comment-column: 50; -*-

mettra en mode Lisp et instanciera les deux variables. (Plutôt que d’éditer à la main, on peut utiliser M-x add-file-local-variable-prop-line).

Normalement, c’est sur la première ligne, mais pour les scripts shell (entre autres) où la première ligne sert à connaître l’interpréteur, il peut être sur la seconde ligne.

Ces variables peuvent également être définies à la fin du fichier (avec M-x add-file-local-variable par exemple) sous forme de liste :

/* Local Variables:  */
/* mode: c           */
/* comment-column: 0 */
/* End:              */

La liste doit commencer par Local Variables: et se terminer par End:. Emacs reconnaît automatiquement le préfixe (et comme ici, éventuellement suffixe) en cherchant autour Local Variables:.

Pour activer un mode mineur, il faut utiliser eval: :

;; Local Variables:
;; eval: (eldoc-mode)
;; eval: (font-lock-mode -1)
;; End:

Comme ça peut être dangereux, Emacs peut demander si on veut vraiment les utiliser (Safe-File-Variables (manuel emacs), C-h v safe-local-variable-values).

Hooks

Les hooks (manuel elisp) sont des variables où l’on peut stocker une ou des fonctions à appeler pour une occasion particulière. Emacs permet ainsi de modifier le comportement de certaines fonctions.

Après avoir chargé un fichier, le mode est choisi, puis appelé. Pour ajouter des fonctions ou appeler des modes mineurs sur ces fichiers, on peut donc utiliser les hooks. Généralement pour chaque mode, il existe un hook qui est exécuté après le chargement du mode : pour nomdumode-mode il y a nomdumode-mode-hook. La dernière chose qu’un major-mode fait, c’est appeler run-mode-hooks. Cette fonction lance change-major-mode-after-body-hook, nomdumode-mode-hook puis after-change-major-mode-hook.

Si un mode dérive d’un autre, le -mode-hook du parent est d’abord exécuté. Du coup, pour changer tous les modes de programmation, il suffit d’ajouter une fonction à prog-mode-hook.

Setting-Hooks (manuel elisp)

Un exemple qui utilise le mode hook pour mettre passer en Auto Fill mode quand on passe en mode Lisp Interaction :

(add-hook 'lisp-interaction-mode-hook 'auto-fill-mode)

Normalement, la fonction que l’on ajoute au hook ne prend pas d’argument, mais il est possible de contourner cela avec les fonctions lambda (ModeHooks (emacswiki)) :

(add-hook 'text-mode-hook (lambda () (set-fill-column 72)))
(add-hook 'text-mode-hook (lambda () (column-number-mode 1)))

Hooks-for-Loading (manuel elisp)

(Je reprends ce que je dis plus haut, en ajoutant des exemples.)

On peut également demander à ce que du code soit exécuté à chaque fois qu’Emacs charge une bibliothèque avec le hook after-load-functions (attention, chaque fonction du hook est appelée avec un argument : le chemin complet du fichier qui vient d’être chargé).

Si on veut que le code soit exécuté à chaque fois qu’une bibliothèque est chargée, il faut utiliser eval-after-load. La fonction évalue le code à la fin de chaque chargement de la bibliothèque. Si elle est déjà chargée, le code est exécuté immédiatement.

Par exemple, pour charger la bibliothèque my-foo-config après que file-foo.txt ait été chargé :

(eval-after-load "file-foo.txt" '(load "my-foo-config"))

Pour exécuter la fonction foo-bar après avoir chargé le mode foobar :

(eval-after-load "foobar-mode" '(foo-bar"))

Différences

On peut parfois utiliser indifférement l’une ou l’autre des méthodes. Mais, le code de eval-after-load sera exécuté seulement une fois, et donc est généralement utilisé pour par exemple instancier des valeurs globales par défaut ou configurer un keymap par défaut pour un certain mode. Dans le code du eval-after-load, il n’y a pas non plus de notion de buffer courant.

Les nomdumode-mode-hooks, eux, sont exécutés pour chaque buffer dans lequel le mode sera activé.

Conclusion

Que fait le code du début

Reprenons notre bout de code du début :

(autoload 'php-mode "php-mode" "Major mode for editing php code." t)
(add-to-list 'auto-mode-alist '("\\.php$" . php-mode))

La première ligne indique donc à Emacs où chercher la fonction php-mode – dans un fichier php-mode.el (ou .elc) – mais que pour le moment, ce n’est pas la peine de charger et d’évaluer le fichier (et donc la fonction).

La deuxième indique que quand Emacs va ouvrir un fichier, s’il n’a pas trouvé de ligne contenant -*-foo-mode-*-, de shebang (#!) indiquant quel mode charger, et que le fichier se termine par .php, alors, il faut lancer la fonction php-mode.

Ces lignes permettent donc de (et sont nécessaire pour) passer en php-mode quand on ouvre un fichier dont l’extension est .php sans forcément charger le fichier php-mode.el.

C’est plutôt pas mal, sauf que, ces lignes sont sans doute totalement inutiles… Pour s’en rendre compte, il suffit de lancer Emacs sans fichier de config avec -q ou --no-init-file (voire -Q qui fait --no-init-file --no-site-file --no-splash), et de regarder si le fichier qu’on veut ouvrir se met bien dans le bon mode. Ou alors, de regarder auto-mode-alist, ainsi que loaddefs.el.

Comment configurer un mode

En utilisant les hooks. La façon la plus simple est d’utiliser :

(add-hook 'text-mode-hook 'auto-fill-mode)

Si on veut utiliser une fonction avec argument, il faut passer par des fonctions lambda :

(add-hook 'latex-mode-hook (lambda () (auto-fill-mode -1)))

Voilà, je ne sais pas si c’est plus clair pour vous, mais en tout cas, ça m’a permis d’en apprendre un peu plus sur Emacs. (Et encore, je ne vous ai pas parlé de hack-local-variables-hook, du fichier .dir-locals.el (manuel emacs), de…

(Merci aux relecteurs.)

Footnotes:

1

Voir par exemple cette configuration

2

Rien ne sert de modifier startup.el, le fichier sert « seulement » à construire les exécutables Emacs DumpingEmacs (emacswiki). Il est chargé par loadup.el (qui est en fait la première chose chargée (source c)) après plusieurs autres bibliothèques comme :

  • subr : qui contient des fonctions lisp comme push, caar, last, add-hook, with-temp-buffer, save-window-excursion, etc.) ;
  • custom : qui contient le code nécessaire à la déclaration et l’initialisation des options via customize ;
  • window : qui touche aux windows et frames (delete-other-window, split-window, switch-to-buffer, etc.) ;
  • files : ce qui touche aux fichiers (find-file, recover-file, save-buffer, write-file, etc.) ;
3

C’est un peu compliqué de savoir comment est définit load-path (source c) à la compilation d’Emacs (comme ça dépend de comment ça a été compilé) et donc connaître les additions des répertoires, mais comme les (sous-)dossiers sont ajoutés à la fin, on peut retrouver sa valeur au lancement d’Emacs.

Autres billets

Date: <2014-06-07>

Generated by Emacs 24.3.1 (Org mode 8.2.4) - Show Org source (htmlized)

CSS inspired by Tontof, colors by Chaotic Soul

Validate