Notxor tiene un blog

Defenestrando la vida


Creando un modo menor, sencillo y sin muchas pretensiones

Para seguir con el cursillo de elisp voy a crear un nuevo modo. Va a ser un minor mode, es decir, un modo que se puede activar como secundario a otros modos generales. Lo primero que necesito es un fichero que guarde el código en él y que se cargue desde la configuración de Emacs.

Crear el fichero de modo

Tengo la costumbre de ir metiendo todos mis proyectos en un directorio con ese nombre dentro de mi directorio home. Es decir, voy a crear un directorio de proyecto que me sirva para trabajar programando. Obvio aquí toda la configuración de git o cualquier otra herramienta de programación que se utilice de manera externa al propio Emacs. El resultado final es crear un fichero que se llamará ~/proyectos/datos-mode/datos-mode.el

Para que lo cargue Emacs cuando arranque, debemos definir algunas cosas en el fichero init.el:

;; Configuración de prueba de creación de datos-mode
(add-to-list 'load-path "~/proyectos/datos-mode")
(require 'datos-mode)

He añadido básicamente dos instrucciones: la primera añade a la lista de paths el lugar donde guardo el fichero; la segunda solicita cargar el modo datos-mode. Bien, muy bonito, pero de momento carga en vacío. ¿Qué código tengo que meter en el fichero datos-mode para que funcione?

De momento, cargo el siguiente código, que me sirve como plantilla de modos:

;; 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.
  nil
  ;; Indicador de modo en la línea.
  " Datos")

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

La primera forma, define-minor-mode, es la que crea el modo. Sin entrar en todas las opciones que soporta, de momento vemos que le decimos que comience desactivado con nil y que el nombre que aparecerá en la línea de estado será " Datos". Hay que remarcar el espacio inicial para que no se pegue al modo que se liste antes.

La segunda forma, provide, es el espejo del require que hemos puesto en el init.el, le informa a Emacs que el modo requerido en la configuración lo proporciona nuestro fichero o paquete.

Podemos mejorarlo si en lugar de utilizar el modo posicional, utilizamos los nombres de los argumentos:

(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")

Algo de contenido para probar

Vale, muy bonito todo, pero tengo un modo que no hace nada. Eso sí, si recargo Emacs resulta que puedo llamar hacer M-x datos-mode y me aparece el modo en la línea de estado.

Pero aún no hace nada y eso no sirve de mucho, vamos a hacer que tener instalado nuestro «modo» en el sistema sirva de algo. Algo que suelo necesitar bastante cuando trasteo con los datos es introducir el día de la fecha, normalmente en formato ISO. Entre las formas define-minor-mode y provide defino una función que me ayude a introducir la fecha con un comando:

(defun escribe-dia ()
  "Escribe el día de la fecha en el lugar del punto."
  (interactive)
  (insert (format-time-string "%Y-%m-%d")))

Si recargamos Emacs podemos llamar a M-x escribe-dia y en el lugar donde esté el cursor escribirá 2019-02-15, utilizando la forma insert después de llamar a format-time-string con una cadena de formato concreta, la ISO, se pueden consultar los formatos que vienen en el manual por si nos viniera mejor otro formato. Todo esto está muy bien, pero tampoco es una mejora sustancial... ¿qué tendría que hacer si necesito escribir la fecha en modo largo, o europeo, o americano? ¿Puedo hacer que la función me pregunte cómo quiero la salida en lugar de una función para cada formato de los que uso? La respuesta es sí, pongo el código y lo explico más despacio:

(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"))))

Analizando los cambios entre las dos versiones. Lo primero que puede llamar la atención es el uso de &opcional en los argumentos, lo que hace que cadena-formato pueda existir o no. Si no existe o es una cadena vacía "", se llama a la función con el formato.

Lo siguiente que llama la atención es cómo está escrita la forma interactive donde se añade una cadena que puede parecer un tanto extraña. Así que ahí va elemento a elemento: P indica que es interactivo y admite un argumento que proporcionará el usuario. \n equivale a pulsar <RET> cuando interactuamos. s especifica que el argumento será una cadena (string). Y por último una cadena que funciona como prompt de la función.

A continuación se evalúa si cadena-formato tiene contenido y si es así se lo pasará a la función; en caso contrario, se utiliza una cadena de formato que devuelve la cadena en formato ISO.

Si ahora recargamos Emacs y llamamos a la función escribe-dia nos aparecerá el mensaje Cadena de formato: en el buffer y podemos meter cualquier texto en él. Por ejemplo, si introduces a %d de %B de %Y devolverá una cadena como a 15 de febrero de 2019. Si no introducimos ninguna cadena, el prompt devolverá una vacía y la función utilizará la que hemos puesto por defecto.

Conclusión

Un pequeño paso hacia hacer un modo. Hay algunos problemas que nos podemos encontrar y muchas cosas por hacer aún. No sólo en el modo sino también en nuestra función de insertar fecha, si hay algo que puede fallar es la cadena que el usuario introduzca en nuestra función, aunque en realidad lo que hará insert será introducir «la mejor cadena posible.


Comentarios