tutorial source program para mac how descargar code emacs elisp

emacs - source - ¿Cómo mantener las variables dir-locales cuando se cambian los modos principales?



emacs source code (3)

Me estoy comprometiendo con un proyecto en el que las sangrías y las pestañas estándar tienen una anchura de 3 caracteres, y usan una combinación de HTML, PHP y JavaScript. Como utilizo Emacs para todo y solo quiero la sangría de 3 caracteres para este proyecto, configuré un archivo ".dir-locals.el" en la raíz del proyecto para aplicarlo a todos los archivos / todos los modos:

; Match projets''s default indent of 3 spaces per level- and don''t add tabs ( (nil . ( (tab-width . 3) (c-basic-offset . 3) (indent-tabs-mode . nil) )) )

Lo cual funciona bien cuando abro un archivo por primera vez. El problema ocurre cuando se cambian los modos principales, por ejemplo, para trabajar en un fragmento de HTML literal dentro de un archivo PHP. Entonces pierdo todas las variables dir-locales.

También probé explícitamente todos los modos que uso en ".dir-locals.el" y agregué a mi archivo .emacs "dir-locals-set-class-variables / dir-locals-set-directory-class ". Me alegra decir que todos se comportan de manera consistente, inicialmente configurando las variables locales del directorio y luego perdiéndolos cuando cambio el modo principal.

Estoy usando GNU Emacs 24.3.1.

¿Cuál es una forma elegante de volver a cargar variables locales de dir al cambiar el modo principal de un búfer?

- editar - Gracias por las excelentes respuestas y comentarios tanto de Aaron como de Phillips. Después de publicar aquí, pensé que "olía" como un error, así que ingresé un informe a GNU, les enviaré una referencia a estas discusiones.


Mi opinión sobre esto:

(add-hook ''after-change-major-mode-hook #''hack-local-variables)

y también

(defun my-normal-mode-advice (function &rest ...) (let ((after-change-major-mode-hook (remq #''hack-local-variables after-change-major-mode-hook))) (apply function ...)))

si puedes vivir con lo molesto

Haciendo after-change-major-mode-hook buffer-local mientras que localmente lo dejamos!

mensaje o

(defun my-normal-mode-advice (function &rest ...) (remove-hook ''after-change-major-mode-hook #''hack-local-variables) (unwind-protect (apply function ...) (add-hook ''after-change-major-mode-hook #''hack-local-variables)))

de lo contrario y finalmente

(advice-add #''normal-mode :around #''my-normal-mode-advice)


Según los comentarios a la respuesta de Aaron Miller, aquí hay un resumen de lo que ocurre cuando se llama a una función de modo (con una explicación de los modos derivados); cómo llamar a un modo manualmente difiere de Emacs llamándolo automáticamente; y donde after-change-major-mode-hook y hack-local-variables encajan en esto, en el contexto del siguiente código sugerido:

(add-hook ''after-change-major-mode-hook ''hack-local-variables)

Después de visitar un archivo, Emacs llama normal-mode que "establece el modo principal apropiado y las uniones de variables locales del búfer" para el búfer. Lo hace llamando primero a set-auto-mode , e inmediatamente después llamando a hack-local-variables , que determina todas las hack-local-variables directorio local y local de archivo para el búfer, y establece sus valores en consecuencia.

Para obtener detalles sobre cómo el set-auto-mode elige el modo para llamar, vea Ch i g (elisp) Auto Major Mode RET . En realidad, involucra alguna interacción temprana de variables locales (necesita verificar una variable de mode , por lo que hay una búsqueda específica para eso antes de que se establezca el modo), pero el procesamiento de la variable local "correcta" ocurre después.

Cuando se llama realmente a la función de modo seleccionada, hay una secuencia inteligente de eventos que vale la pena detallar. Esto requiere que comprendamos un poco sobre "modos derivados" y "ganchos de modo retrasado" ...

Modos derivados y ganchos de modo

La mayoría de los modos principales se definen con el modo macro define-derived-mode . (Por supuesto, no hay nada que te (defun foo-mode ...) escribir (defun foo-mode ...) y hacer lo que quieras, pero si quieres asegurarte de que tu modo principal funciona bien con el resto de Emacs, usarás las macros estándar .)

Cuando define un modo derivado, debe especificar el modo padre del cual deriva. Si el modo no tiene un elemento primario lógico, usted todavía usa esta macro para definirlo (para obtener todos los beneficios estándar), y simplemente especifica nil para el padre. Alternativamente, podría especificar fundamental-mode como padre, ya que el efecto es muy similar a nil , como veremos momentáneamente.

define-derived-mode define la función de modo para usted utilizando una plantilla estándar, y lo primero que ocurre cuando se llama a la función de modo es:

(delay-mode-hooks (PARENT-MODE) ,@body ...)

o si no se establece ningún padre:

(delay-mode-hooks (kill-all-local-variables) ,@body ...)

Como fundamental-mode sí mismo llama (kill-all-local-variables) y luego regresa inmediatamente cuando se llama en esta situación, el efecto de especificarlo como el padre es equivalente a si el padre era nil .

Tenga en cuenta que kill-all-local-variables ejecuta change-major-mode-hook antes de hacer cualquier otra cosa, por lo que será el primer hook que se ejecute durante toda esta secuencia (y ocurre mientras el modo principal anterior aún está activo, antes cualquiera de los códigos para el nuevo modo ha sido evaluado).

Entonces eso es lo primero que sucede. Lo último que hace la función de modo es llamar (run-mode-hooks MODE-HOOK) para su propia variable MODE-HOOK (este nombre de variable es literalmente el nombre del símbolo de la función de modo con un sufijo -hook ).

Entonces, si consideramos un modo llamado child-mode que se deriva de parent-mode que se deriva de grandparent-mode , toda la cadena de eventos cuando llamamos (child-mode) ve así:

(delay-mode-hooks (delay-mode-hooks (delay-mode-hooks (kill-all-local-variables) ;; runs change-major-mode-hook ,@grandparent-body) (run-mode-hooks ''grandparent-mode-hook) ,@parent-body) (run-mode-hooks ''parent-mode-hook) ,@child-body) (run-mode-hooks ''child-mode-hook)

¿Qué hace delay-mode-hooks ? Simplemente vincula los delay-mode-hooks variables, que se comprueban mediante run-mode-hooks . Cuando esta variable no es nil , run-mode-hooks simplemente coloca su argumento en una lista de enlaces que se ejecutarán en el futuro, y lo devuelve inmediatamente.

Solo cuando delay-mode-hooks es nil , los run-mode-hooks realmente ejecutarán los hooks. En el ejemplo anterior, esto no es hasta que se llama (run-mode-hooks ''child-mode-hook) .

Para el caso general de (run-mode-hooks HOOKS) , los siguientes enlaces se ejecutan en secuencia:

  • change-major-mode-after-body-hook
  • delayed-mode-hooks en delayed-mode-hooks (en la secuencia en la que de otro modo se habrían ejecutado)
  • HOOKS (siendo el argumento para run-mode-hooks )
  • after-change-major-mode-hook

Entonces cuando llamamos (child-mode) , la secuencia completa es:

(run-hooks ''change-major-mode-hook) ;; actually the first thing done by (kill-all-local-variables) ;; <-- this function ,@grandparent-body ,@parent-body ,@child-body (run-hooks ''change-major-mode-after-body-hook) (run-hooks ''grandparent-mode-hook) (run-hooks ''parent-mode-hook) (run-hooks ''child-mode-hook) (run-hooks ''after-change-major-mode-hook)

Volver a las variables locales ...

Lo que nos lleva de vuelta a after-change-major-mode-hook y usándolo para llamar a hack-local-variables :

(add-hook ''after-change-major-mode-hook ''hack-local-variables)

Ahora podemos ver claramente que si hacemos esto, hay dos posibles secuencias de nota:

  1. Cambiamos manualmente a foo-mode :

    (foo-mode) => (kill-all-local-variables) => [...] => (run-hooks ''after-change-major-mode-hook) => (hack-local-variables)

  2. Visitamos un archivo para el cual foo-mode es la opción automática:

    (normal-mode) => (set-auto-mode) => (foo-mode) => (kill-all-local-variables) => [...] => (run-hooks ''after-change-major-mode-hook) => (hack-local-variables) => (hack-local-variables)

¿Es un problema que hack-local-variables ejecute dos veces? Tal vez tal vez no. Como mínimo, es un poco ineficiente, pero eso probablemente no sea una preocupación importante para la mayoría de las personas. Para mí, lo principal es que no me gustaría confiar en que este arreglo siempre sea ​​correcto en todas las situaciones, ya que ciertamente no es el comportamiento esperado.

(Personalmente , realmente hago que esto ocurra en ciertos casos específicos, y funciona bien, pero, por supuesto, esos casos se prueban fácilmente, mientras que hacerlo de manera estándar significa que todos los casos se ven afectados y las pruebas no son prácticas).

Así que propondría un pequeño ajuste a la técnica, de modo que nuestra llamada adicional a la función no ocurra si se está ejecutando el normal-mode :

(defvar my-hack-local-variables-after-major-mode-change t "Whether to process local variables after a major mode change. Disabled by advice if the mode change is triggered by `normal-mode'', as local variables are processed automatically in that instance.") (defadvice normal-mode (around my-do-not-hack-local-variables-twice) "Prevents `after-change-major-mode-hook'' from processing local variables. See `my-after-change-major-mode-hack-local-variables''." (let ((my-hack-local-variables-after-major-mode-change nil)) ad-do-it)) (ad-activate ''normal-mode) (add-hook ''after-change-major-mode-hook ''my-after-change-major-mode-hack-local-variables) (defun my-after-change-major-mode-hack-local-variables () "Callback function for `after-change-major-mode-hook''." (when my-hack-local-variables-after-major-mode-change (hack-local-variables)))

Desventajas a esto?

El principal es que ya no puede cambiar el modo de un buffer que establece su modo principal usando una variable local. O más bien, se cambiará de nuevo inmediatamente como resultado del procesamiento variable local.

Eso no es imposible de superar, pero voy a llamarlo fuera del alcance por el momento :)


Tenga en cuenta que no he probado esto , por lo que puede producir resultados no deseados que van desde las variables dir-locales que no se aplican, hasta Emacs que intenta estrangular a su gato; por cualquier definición sensata de cómo debe comportarse Emacs, es casi seguro que es una trampa. Por otro lado, todo está en la biblioteca estándar, por lo que no puede ser un pecado. (Espero.)

Evalúa lo siguiente:

(add-hook ''after-change-major-mode-hook ''hack-dir-local-variables-non-file-buffer)

A partir de ese momento, cuando cambie los modos principales, las variables dir-locales deberían (creo) volverse a aplicar inmediatamente después del cambio.

Si no funciona o no te gusta, puedes deshacerlo sin reiniciar Emacs reemplazando ''add-hook'' con ''remove-hook'' y evaluando el formulario nuevamente.