Ecrire portable Elisp

8

Idéalement, j'aimerais pouvoir stocker l'intégralité du contenu de mon .emacs.drépertoire et le faire "fonctionner" sur tous les Emacs dans lesquels je le charge, tout en profitant de toutes les fonctionnalités de l'environnement spécifique, telles que les systèmes de fenêtrage GUI.

Je ne cherche pas une encyclopédie de fonctionnalités incompatibles. Je voudrais juste savoir comment vérifier les fonctionnalités, le système d'exploitation, la version, les graphiques, etc., et comment en tirer parti sans casser le code des autres configurations.

Quelles techniques de base puis-je utiliser pour écrire Elisp qui fonctionne dans plusieurs versions d'Emacs courantes dans la nature (par exemple 22.x +) et sur plusieurs plates-formes sous-jacentes (par exemple OSX, Linux, Windows et autres * nix), tout en tirant parti de la plate-forme et des fonctionnalités spécifiques à la version, le cas échéant?

Paul Miller
la source
1
@Malabarba Si la question est interprétée comme «énumérer toutes les différences entre les différentes versions et plates-formes», oui, c'est beaucoup trop large. Mais les techniques de base - tester si les identifiants sont liés, se remettre des erreurs, tester window-system, etc. peuvent être raisonnablement répondues ici.
Gilles 'SO- arrête d'être méchant'
1
Emacs 22 n'est plus «récent». Même Emacs 23 est daté.
lunaryorn
1
J'ai inclus emacs 22 car il est courant dans la nature. Par exemple, il est fourni avec OSX 10.9.
Paul Miller,
Tout utilisateur sérieux d'Emacs sur un Mac va télécharger une version récente. Emacs 22 est uniquement inclus dans OS X car c'est la dernière version qui a été concédée sous GPLv2 (et IMO, ils devraient simplement la supprimer complètement). Je pense que 23+ est beaucoup plus utile (l'écurie Debian IIRC utilise encore 23).
shosti
2
Sheesh. La personne qui pose la question pose des questions sur Emacs 22+ et vous voulez "corriger" cette demande pour insister sur une version plus récente? Que faire ensuite - quelqu'un pose la question forward-charet que vous souhaitez que la question soit modifiée à la scroll-upplace? Si l'OP veut la compatibilité Emacs 22+, laissez-le tranquille. Et non, la question posée ne concerne pas seulement OS X. Et oui, il y a encore beaucoup de gens qui utilisent d'anciennes versions d'Emacs (certaines même plus anciennes que 22, FWIW).
Drew

Réponses:

10

Elisp est un langage interprété. Vous pouvez mettre du code spécifique à la version dans votre .emacs, mais le protéger en testant au moment du chargement qu'il fonctionne sur la bonne version.

(if (is-new-feature-available)
    (shiny-new-feature)
  (old-less-nifty-feature))

Ce code fonctionnera dans toutes les versions car il (shiny-new-feature)n'est évalué que lorsqu'il (is-new-feature-available)renvoie true. Une grande partie de cette réponse est consacrée à la mise en œuvre (is-new-feature-available).

Faire face à différents ensembles de fonctionnalités

Il vaut mieux tester si une fonctionnalité est disponible que de tester la version Emacs. Parfois, la fonctionnalité peut être disponible en tant que package facultatif. Si vous souhaitez exécuter du code dans XEmacs ou une autre variante d'Emacs, il se peut qu'il ait acquis les mêmes fonctionnalités dans différentes versions. Utilisez la fonction boundppour tester si une variable est disponible et fboundppour tester si une fonction est disponible.

Par exemple, l'extrait de code suivant lie une clé pour basculer visual-line-modesi elle est disponible, et longlines-modesinon.

(global-set-key "\eml" (if (fboundp 'visual-line-mode)
                           'visual-line-mode
                         'longlines-mode))

Parfois, plutôt que de tester la fonctionnalité, il est plus facile d'exécuter un petit morceau de code et d' ignorer les erreurs dues à des fonctions non définies, des arguments invalides, etc. Ne faites pas cela pour de grandes quantités de code, car cela rendra votre code très difficile à déboguer.

Par exemple, je ne veux pas voir de barre d'outils. Les anciennes versions d'Emacs n'en avaient pas du tout. GNU Emacs et XEmacs ont ajouté cette fonctionnalité de différentes manières et en ont fait la valeur par défaut. Voici comment je les désactive. La set-specifierfonction est spécifique à XEmacs et default-toolbar-visible-pest spécifique aux versions assez récentes d'Emacs; l'utilisation condition-caseprend en charge les deux exigences. GNU Emacs fournit une fonction dédiée donc je teste simplement si cette fonction est disponible.

;; For XEmacs
(condition-case nil
    (set-specifier default-toolbar-visible-p nil)
  (error nil))
;; For GNU Emacs
(if (fboundp 'tool-bar-mode)
    (tool-bar-mode 0))

Certains noms de visage changent au fil des versions. Utilisez faceppour tester la disponibilité d'un nom de visage.

(let ((face (if (facep 'mode-line) 'mode-line 'modeline)))
  (set-face-background face …))

Parfois, vous souhaiterez peut-être charger un joli package s'il est présent et ne rien faire si le package n'est pas disponible. requirea un argument facultatif pour cela.

(require 'tex-site nil t) ;; Load AUCTeX if available

Cet argument a été introduit dans GNU Emacs 20.4 et n'est pas disponible dans XEmacs, donc si vous voulez remonter aussi loin, vous devrez soit l'enrouler condition-casesoit l'utiliser à la loadplace (ce qui ne vérifie pas les bibliothèques déjà chargées) .

Limitez les dépendances de version aux fonctionnalités de niveau utilisateur. N'utilisez pas de nouvelles fonctionnalités de programmation qui ne sont pas disponibles dans toutes les versions que vous souhaitez prendre en charge: vous devrez fournir une version de compatibilité pour les anciennes versions, et il est plus facile de maintenir une seule version.

Parfois, vous avez besoin d'une fonctionnalité à de nombreux endroits, et elle est disponible sur toutes les implémentations qui vous intéressent, mais d'une manière différente. C'est surtout le cas si vous voulez prendre en charge à la fois XEmacs et GNU Emacs: ils avaient une tendance frustrante à se copier les fonctionnalités les uns des autres mais pas leur interface. Dans ce cas, la définition d'une fonction de compatibilité est plus pratique que le test au point d'utilisation.

Par exemple, le code suivant définit une fonction qui renvoie le système de fenêtres du cadre actuel, la méthode GNU moderne, la méthode XEmacs moderne et la méthode à l'ancienne lorsque vous ne pouviez pas combiner des cadres de terminal et d'interface graphique dans la même instance.

(defalias 'compat-window-system
  (cond
   ((fboundp 'window-system) #'window-system)
   ((fboundp 'device-type)
    (lambda (&optional frame)
      (device-type (frame-device frame))))
   (t
    (lambda (&optional frame) window-system))))

Dépendances de l'environnement

Il n'y a pas beaucoup de code qui doit dépendre de la plate-forme. La variable system-typeindique le système d'exploitation. Je l'utilise exclusivement pour activer quelques hacks pour ms-dos(oui, mes fichiers sont aussi anciens) et windows-nt.

Vous voudrez peut-être ajouter des répertoires à votre chemin de recherche exécutable ( PATH), mais il est généralement préférable de le faire en dehors d'Emacs, dans votre .profilesystème de type Unix et via le panneau de configuration de Windows. Pour tester si un programme externe est disponible, appelez executable-find.

Pour le code qui doit agir différemment selon le type d'interface graphique, le cas échéant, cochez window-typeou ses successeurs (voir ci-dessus).

Fichiers d'initialisation

Pour une compatibilité maximale, insérez votre code ~/.emacs. GNU Emacs a commencé à chercher dans la ~/emacs.dversion 22. XEmacs a commencé à chercher ~/.xemacsdans la version 21.4. Une autre approche consiste à mettre du code de compatibilité ~/.emacset à terminer en chargeant votre fichier principal. Mettez (setq load-home-init-file t)quelque part pour éviter que les versions récentes¹ de XEmacs ne vous demandent si vous souhaitez déplacer votre .emacsemplacement XEmacs uniquement.

Différentes versions d'Emacs peuvent avoir une extension différente et incompatible pour certaines macros. Ne partagez donc pas vos fichiers compilés en octets entre les versions, compilez les fichiers sur chaque machine.

Parfois, une fonctionnalité est obsolète, mais vous souhaitez toujours l'utiliser car c'est tout ce qu'il y a dans une autre version que vous souhaitez prendre en charge. Les avertissements du compilateur d'octets proviennent de la byte-obsolete-variablepropriété.

(cond
 ((not (boundp 'desktop-enable))
  (defvaralias 'desktop-enable 'desktop-save-mode))
 ((get 'desktop-enable 'byte-obsolete-variable)
  (put 'desktop-enable 'byte-obsolete-variable nil)))

¹ Relativement parlant, par rapport aux XEmac plus anciens.

Gilles 'SO- arrête d'être méchant'
la source
6

(Wiki de la communauté. Veuillez ajouter le vôtre!)

  • S'il existe une fonction, ajoutée dans une version plus récente d'Emacs, que vous souhaitez utiliser, vérifiez si elle est définie avec fboundpet définissez une fonction de compatibilité si elle n'est pas définie.

    Il est considéré comme une mauvaise idée de donner à la fonction de compatibilité le même nom que la fonction réelle, car d'autres codes Elisp peuvent utiliser la même fboundpastuce. Par conséquent, utilisez un préfixe pour la fonction de compatibilité et utilisez defaliasle même nom s'il est défini. Par exemple:

    (if (fboundp 'propertize)
        (defalias 'my-propertize 'propertize)
      (defun my-propertize (string &rest properties)
        "Return a copy of STRING with text properties added.
    
     [Note: this docstring has been copied from the Emacs 21 version]
    
    First argument is the string to copy.
    Remaining arguments form a sequence of PROPERTY VALUE pairs for text
    properties to add to the result."
        (let ((str (copy-sequence string)))
          (add-text-properties 0 (length str)
                               properties
                               str)
          str)))
    
  • Si un élément de configuration ne s'applique qu'à un certain système d'exploitation, il existe plusieurs possibilités. Vous pouvez vérifier la system-typevariable qui retourne gnu/linux, darwin, windows-ntet quelques autres (voir le docstring).

    Vous pouvez être tenté d'utiliser window-system, bien que sa docstring indique que «l'utilisation de cette variable en tant que booléen est déconseillée» et recommande de l'utiliser à la display-graphic-pplace. Notez qu'Emacs est capable d'utiliser différents types d'affichages pour différentes images de nos jours (par exemple, une image sur un terminal et une autre dans une fenêtre "appropriée"), cela peut donc surprendre. Utilisez current-frame-configurationou get-buffer-window-listdans votre elisp pour faire le bon choix.

  • Vous voudrez peut-être vérifier si vous utilisez la bonne version d'emacs. Utilisez featurep pour vérifier la variante. Par exemple:

    (when (featurep 'xemacs)
       (require 'fsf-compat))
    

    Vous pouvez également l'utiliser pour vérifier que des modules spécifiques sont chargés. Par exemple, si vous n'utilisez qu'un petit et facile à définir par défaut de common-lisp, vous pouvez opter pour la définition au lieu d'exiger. Par exemple:

    (unless (featurep 'cl)
       (defun caaar (x)
          "Return the `car' of the `car' of the `car' of X."
          (car (car (car x)))))
    
  • Évitez de stocker des fichiers .elc. Ceux-ci ne sont pas compatibles en amont ou en aval entre certaines versions d'Emacs.

legoscia
la source
0

Si vous utilisez des fonctions de cl-libmais ne souhaitez pas utiliser les versions obsolètes sans espace de noms, faites de la bibliothèque de compatibilité cl-lib une dépendance de votre projet. Cela vous permettra d'utiliser les cl-fonctions d' espaces de noms tout en conservant la compatibilité descendante.

shosti
la source
load-library: Impossible d'ouvrir le fichier de chargement: cl-lib - Pourquoi voudrais-je cl-libquand même? De quel avantage dispose-t-il cl, qui existe depuis au moins 20 ans?
Gilles 'SO- arrête d'être méchant'
1
clest obsolète et sera probablement supprimé à terme. Son utilisation dans le code a provoqué des avertissements du compilateur d'octets pendant assez longtemps. Je pense que la raison en est qu'ils veulent réutiliser certains des clnoms (comme dolist) avec une sémantique différente.
shosti