Notxor tiene un blog

Defenestrando la vida


Variables de modo, keymaps y menús

Estamos acostumbrados a ajustar los paquetes que instalamos para que se comporten de una u otra forma y eso es lo que vamos a hacer ahora con nuestro modo de ejemplo datos-mode. El código de ejemplo utilizará dos variables para ello.

Imaginaos que estamos muy cansados de escribir el día de la fecha y el pie de firma en los documentos que escribo y quiero que lo haga Emacs por mí, porque soy así de vago y lo automatizo todo. Voy a crear una función que lo haga por mí. ¡Eso está «chupao»!¡Al lío! El pie de firma suele tener un formato más o menos fijo: Normalmente el lugar donde se firma, la fecha y a continuación quién lo firma.

(defun escribe-pie-firma ()
  "Escribe el pie de firma en el lugar del punto."
  (interactive)
  (insert (concat "En Macondo, a "
                  (format-time-string "%d de %B de %Y")
                  "\nFdo: Aureliano Buendía")))

La función es sencilla y se explica sola: inserta una cadena que ha formado concat juntando varias cadenas, el lugar, el día y la firma. Las he puesto en tres líneas para que se vean a simple vista y no se nos despisten.

Variables

Pero ¿qué ocurre si quiero que mi modo lo utilice también otra persona? ¿Y si tengo que firmar estando en otro lugar? Pues nada más fácil que poner eso en variables, de manera que cambiando el valor de la variable, cambia el texto que se introducirá.

;; Definir variables globales
(defvar datos-lugar
  "Macondo"
  "Lugar que se escribirá con la función de pie de firma.")

(defvar datos-usuario
  "Aureliano Buendía"
  "Usuario que es escribirá con la función de pie de firma.")

Hemos definido dos variables, datos-lugar y datos-usuario, para guardar aquellas cadenas que pueden variar. Se puede apreciar que las he inicializado con dos valores. Lo habitual es que se dejaran vacías o nil y luego en el código controlar qué comportamiento es el adecuado por defecto. Pero eso añadía complejidad a la función de ejemplo con varias formas if que podían confundir al novato pareciendo que la función definitiva es más compleja de lo que en realidad es:

(defun escribe-pie-firma ()
  "Escribe el pie de firma en el lugar del punto."
  (interactive)
  (insert (concat "En " datos-lugar ", a "
                  (format-time-string "%d de %B de %Y")
                  "\nFdo: " datos-usuario)))

De este modo, la función es idéntica a la anterior, pero colocando las variables en el lugar adecuado. Si la utilizamos directamente veremos que el resultado es equivalente... ¿pero qué ocurre si en nuestro init.el colocamos el siguiente código después de la importación del paquete?

(setq datos-lugar "Notxería")
(setq datos-usuario "Notxor de la Notxería")

¡Ahí va! !Así es como funcionan las configuraciones de Emacs¡ Algunos pensarán: ¡Qué tontería, eso ya lo sabíamos! Por contra otros acabarán de comprender que realmente init.el no es más que un fichero de elisp que funciona ajustando los distintos paquetes al comportamiento que queremos de ellos.

Keymap

También habrá quien piense que al final escribir M-x escribe-dia o M-x escribe-pie-firma no me ahorran mucho trabajo porque sigo teniendo mucho que escribir. Quizá se estén preguntado o incluso estén tentados a establecer combinaciones de teclas para las funciones. Eso lo podemos hacer también desde el mismo modo, pero además me facilitará que pueda explicar algunas cosas más adelante.

Definir keymap:

;; Definir las combinaciones de teclas del modo
(defvar datos-mode-map
  (let ((datos-mode-map (make-keymap)))
    (define-key datos-mode-map "\C-c\C-f" 'escribe-dia)
    (define-key datos-mode-map "\C-c\C-p" 'escribe-pie-firma)
    datos-mode-map)
  "Kaymap para datos-mode.")

Como se puede ver nuestro keymap lo llamamos datos-mode-map y lo metemos en una variable con ese nombre. Se utiliza la forma let para crear con make-keymap un keymap local al que añadir las combinaciones que necesito con la forma define-key. Esta forma necesita como primer argumento el keymap en el que definirá la combinación que se pasa en forma de cadena y por último la función que debe llamar. Cuando se han añadido todas las combinaciones que necesitamos, la forma let evalúa el keymap que se ha modificado para que se establezca en la variable... creo que es más complicado de explicar que de entender viendo el código.

Ahora hay que decirle al modo cuál es su keymap, por tanto modifico la definición para añadirlo. La definición quedará así:

;; Definición del modo menor
(define-minor-mode datos-mode
  "Toggle Datos mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; El valor inicial.
  :init-value nil
  ;; Indicador de modo en la línea.
  :lighter " Datos"
  ;; Keymap para el modo
  :keymap datos-mode-map)

Como se puede apreciar, sólo he añadido :keymap datos-mode-map. Y ahora cuando recargo el mi modo ya puedo utilizar la combinación de teclas que está definida (y que puedo cambiar en la configuración de init.el si quiero).

Menú

Hace tiempo que dejé de utilizar los menús con profusión, aunque de vez en cuando los miro y los uso, más con el objeto de memorizar la combinación de teclas que por comodidad. Pero hay gente que se pierde con estas cosas de la tecnología y necesita un buen menú en el que pinchar. Para ellos es más claro y siempre mi modo parecerá mucho más «pofesioná» (a ojos del público común) si tiene un menú.

Para definir el menú hay varios modos, pero el que recomiendo para novatos como yo es el macro easy-menu-define. Pongo el código y lo explico:

(easy-menu-define datos-menu datos-mode-map
  "Menú para el modo datos-mode."
  '("Datos"
    ["Escribir día" escribe-dia]
    ["Escribir pie de firma" escribe-pie-firma]))

El macro define datos-menu como el menú para el modo y utiliza para ello datos-mode-map como keymap. Luego vemos que está el comentario para documentar qué es y una lista. La primera cadena "Datos" será la que se muestre en el menú superior de Emacs y luego están dos vectores para cada uno de los widgets de menú que aparecerán. Se define la cadena que mostrará el menú y la función a la que asociarlo.

Conclusión

Como vemos, con muy poco código tentemos un modo funcional y que da el pego, con chismáticos en los que pinchar quien sea más pusilánime y necesite llevar la flechita por toda la pantalla para asegurarse en su lentitud que lo está haciendo correctamente.

Pongo también el código completo tal y como ha quedado en el fichero datos-mode.el por si alguien se ha perdido desde las explicaciones de la anterior entrega y ésta.

;;; Inicio de datos-mode
(defvar datos-mode-hook nil)

;; Definir las combinaciones de teclas del modo
(defvar datos-mode-map
  (let ((datos-mode-map (make-keymap)))
    (define-key datos-mode-map "\C-c\C-f" 'escribe-dia)
    (define-key datos-mode-map "\C-c\C-p" 'escribe-pie-firma)
    datos-mode-map)
  "Kaymap para datos-mode.")

;; Definir variables globales
(defvar datos-lugar
  "Macondo"
  "Lugar que se escribirá con la función de pie de firma.")

(defvar datos-usuario
  "Aureliano Buendía"
  "Usuario que es escribirá con la función de pie de firma.")

(easy-menu-define datos-menu datos-mode-map
  "Menú para el modo datos-mode."
  '("Datos"
    ["Escribir día" escribe-dia]
    ["Escribir pie de firma" escribe-pie-firma]))

;; Definición del modo menor
(define-minor-mode datos-mode
  "Toggle Datos mode.
Interactivamente sin argumento, este comando des/activa el modo."
  ;; El valor inicial.
  :init-value nil
  ;; Indicador de modo en la línea.
  :lighter " Datos"
  ;; Keymap para el modo
  :keymap datos-mode-map)

(defun escribe-dia (&opcional cadena-formato)
  "Escribe el día de la fecha en lugar del punto."
  (interactive "P\nsCadena de formato: ")
  (if (> (length cadena-formato) 0)
      (insert (format-time-string cadena-formato))
    (insert (format-time-string "%Y-%m-%d"))))

(defun escribe-pie-firma ()
  "Escribe el pie de firma en el lugar del punto."
  (interactive)
  (insert (concat "En " datos-lugar ", a "
                  (format-time-string "%d de %B de %Y")
                  "\nFdo: " datos-usuario)))

;; Informa que este fichero proporciona el modo datos-mode
(provide 'datos-mode)
;;; Fin de datos-mode

Como se puede ver, contamos ya con una plantilla para hacer un modo muy sencilla que podemos abarcar aún de un vistazo.


Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.