Comment gérer avec élégance les erreurs dans le fichier init

20

Je voudrais un moyen de détecter les erreurs lors de l'exécution de mon fichier init, puis de les gérer avec élégance. Un grand nombre de mes personnalisations et raccourcis clavier les plus importants s'affichent à la fin de mon fichier init pour s'assurer que les autres paramètres ne sont pas appliqués par-dessus eux. Le problème est que lorsque l'initialisation s'interrompt tôt, je me sens totalement paralysé en essayant de déboguer le problème sans que mes liaisons de touches et paramètres familiers soient appliqués.

Existe-t-il un moyen de terminer avec élégance le processus d'initialisation lorsqu'une erreur se produit?

nispio
la source

Réponses:

9

Deux options, dont aucune n'est parfaite, me viennent à l'esprit. Tout d'abord, vous pouvez envelopper la plupart de votre code d'initialisation précoce (c'est-à-dire avant qu'il n'atteigne vos personnalisations) dans (ignore-errors ...). S'il y a des erreurs, cependant, il n'y aura pas beaucoup de commentaires - ignore-errorsretournera tout simplement nil.

Une option plus complexe serait d'envelopper le code potentiellement bogué dans une combinaison de unwind-protectet with-demoted-errors(avec debug-on-errorla valeur nil). Ce dernier fera une erreur gracieusement lors de la première erreur qu'il rencontrera et signalera le message d'erreur au *Messages*tampon pour votre inspection. Pendant ce temps, le reste du unwind-protectcorps (vraisemblablement vos personnalisations) sera évalué. Ainsi, par exemple:

(unwind-protect
    (let ((debug-on-error nil))
      (with-demoted-errors
        (message "The world is about to end")
        (sleep-for 2)
        (/ 10 0)                        ; divide by zero signals an error
        (message "This will never evaluate")
        (sleep-for 2)
        (setq some-var 5)))
  (message "Here are the unwind forms that will always evaluate")
  (sleep-for 2)
  (setq some-var 10)
  (setq another-var "puppies")
  (message "All done!"))
Dan
la source
1
Sympa, je n'en ai pas parlé with-demoted-errors. Vous pouvez y ajouter un argument de type chaîne "LOOK OVER HERE!!! %s", de sorte que vous risquez moins de manquer l'erreur dans le tampon des messages.
Malabarba
@Malabarba Cette forme de with-demoted-errorsest uniquement disponible en 24.4
lunaryorn
@lunaryorn Merci, je ne le savais pas.
Malabarba
En fait, la version sur laquelle je suis est 24.3.1.
Dan
8

@Dan a bien décrit comment transformer des erreurs en messages. Vous pouvez également faire tout ce que vous voulez avec des erreurs en utilisant condition-case. Encore une autre option est d'utiliser unwind-protect.

Je m'en tiendrai condition-caseici, sans aucune raison.

Attraper l'erreur

Cela devrait toujours garantir que vos définitions de clés soient évaluées, indépendamment de ce qui s'est passé à l'intérieur condition-case. Toute erreur est stockée à l'intérieur init-error.

(defvar init-error nil 
  "The error which happened.")

(condition-case the-error
    (progn
      ;; Do the dangerous stuff here.
      (require 'what-I-want))
  (error
   ;; This is only evaluated if there's an error.
   (setq init-error the-error)))

;;; Do the safe stuff here.
(define-key uncrippling-map "\C-h" 'help!)

Le rejeter

Ensuite, renvoyez simplement l'erreur. Il existe plusieurs façons de le faire, en voici une.

;;; Throw the error again here.
(when init-error
  (funcall #'signal (car init-error) (cdr init-error)))
Malabarba
la source
unwind-protectentraîne la ré-augmentation immédiate de l'erreur, après l'exécution du code que vous avez inséré dans sa clause de sauvetage. C'est comme finallydans un langage comme Java, plutôt que catch.
sanityinc
2

Les autres réponses ont assez bien couvert les fonctionnalités de gestion des erreurs de bas niveau qui seront utiles dans un cas comme celui-ci. Une autre approche qui peut aider est la modularité. Par exemple, je divise mon fichier d'initialisation en plusieurs fichiers différents (en utilisant providele cas échéant), et je les charge en utilisant cette fonction au lieu de require:

(defun my/require-softly (feature &optional filename)
  "As `require', but instead of an error just print a message.

If there is an error, its message will be included in the message
printed.

Like `require', the return value will be FEATURE if the load was
successful (or unnecessary) and nil if not."
  (condition-case err
      (require feature filename) 
    (error (message "Error loading %s: \"%s\""
                    (if filename (format "%s (%s)" feature filename) feature)
                    (error-message-string err))
           nil)))

Une erreur lors du chargement d'un fichier de cette manière affichera toujours un message, mais cela n'empêchera pas l'exécution de quoi que ce soit en dehors du fichier où l'erreur s'est réellement produite.

Bien sûr, cette fonction n'est pas vraiment différente de l'habillage d'un requireappel with-demoted-errors(je l'ai écrit avant que je le sache with-demoted-errors), mais le point important est que vous pouvez essentiellement implémenter quelque chose comme la combinaison de with-demoted-errorset unwind-protectsans l'habillage de Dan (potentiellement très longue) blocs de code.

Aaron Harris
la source
Cette fonction était exactement ce que je recherchais. Mon emacs démarre maintenant malgré le signalement d'une erreur. Après cela, je charge juste mon fichier init et eval-buffer. Merci de l'avoir posté.
Kevin