…esto no es un subtítulo…
2015-07-23
GNU Emacs es un editor de textos potentísimo. Por debajo, es un intérprete de Emacs Lisp, un lenguaje de programación de la familia de Lisp. En esta serie de artículos vamos a mostrar algunas formas de adaptar Emacs a nuestras necesidades con algunos casos prácticos.
En el artículo de hoy, crearemos una función para ejecutar un programa externo periódicamente y mostrar su salida en un búfer: una réplica de las herramientas watch de GNU/Linux, cmdwatch de FreeBSD y gnuwatch de OpenBSD. Vamos a usar temporizadores, así que el gestor de temporizadores del último artículo puede ayudarnos durante el desarrollo.
El código de este artículo funciona en GNU Emacs 24.4.
Las herramientas watch, cmdwatch y gnuwatch sirven para ejecutar periódicamente una orden y mostrar su salida a pantalla completa en una consola. Queremos hacer algo equivalente en Emacs: ejecutar periódicamente un programa externo y mostrar su última salida en un búfer.
Vamos a construir una función interactiva que preguntará al usuario qué orden ejecutar y cada cuánto tiempo hacerlo. Después, la función creará un temporizador (con run-with-timer) que se ocupará de ejecutar nuestro programa externo de forma asíncrona con async-shell-command. La salida irá en un búfer con el nombre *Watch: PROGRAMA* (donde PROGRAMA es el programa externo que hemos mandado ejecutar); si eliminamos este búfer, ya no tiene sentido que se ejecute el temporizador, así que lo eliminaremos con una función hook añadida a kill-buffer-hook (para que se ejecute al matar el búfer).
La función, que acepta la orden a ejecutar (command) y los segundos entre ejecuciones (interval), es así:
(defun watch (command interval)
"Run shell COMMAND every INTERVAL seconds showing the results
in a dedicated buffer."
(interactive (list (read-shell-command "Shell command: " nil nil)
(read-number "Update interval (seconds): ")))
(let* ((buffer-name (format "*Watch: %s*" command))
(buffer (generate-new-buffer buffer-name)))
(switch-to-buffer buffer)
(lexical-let ((timer (run-with-timer 0
interval
'watch--callback
buffer
command)))
(add-hook 'kill-buffer-hook
(lambda () (cancel-timer timer)) nil t))))
Analicémosla punto por punto:
(interactive "sShell command: \nnUpdate interval (seconds): ")
La función watch--callback, que es la que se ejecuta cada vez que salta el temporizador, lo único que hace es llamar a la orden con async-shell-command:
(defun watch--callback (buffer command)
(async-shell-command command buffer))ime
Esto tiene el problema de que se muestra el búfer cada vez que salta el temporizador, lo que puede ser molesto. Para evitarlo, hacemos lo que recomienda la documentación de async-shell-command:
(push '("^\*Watch: .*\*" display-buffer-no-window)
display-buffer-alist)
Ejecutamos esto solamente una vez. El elemento que hemos añadido a display-buffer-alist es una lista con dos campos: una expresión regular que casa con el esquema de nombres que hemos usado para nuestros búferes y la función display-buffer-no-window, que se encarga de que no se muestre el búfer si ya está oculto.
Cada vez termina la ejecución del programa externo con async-shell-command, aparece un mensaje en el minibúfer. Esto puede ser muy molesto, así que buscaremos una forma de evitarlo.
No estamos permitiendo una forma sencilla de parar el temporizador o cambiar el intervalo entre ejecuciones. Podemos usar nuestro nuevo y flamante gestor de temporizadores, pero es mejor ofrecer esa funcionalidad directamente.
Categorías: Informática