…esto no es un subtítulo…
2015-07-25
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, continuación del anterior, creamos unas funciones 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.
El código de este artículo funciona en GNU Emacs 24.4.
El código del artículo anterior tiene algunas deficiencias:
Cada vez que 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.
Además de esto, puede ser interesante mostrar alguna información adicional en la parte superior del búfer: el momento en el que se ejecutó por última vez el programa externo, el tiempo que transcurre entre ejecuciones, qué programa se ejecuta y en qué estado se encuentra la ejecución actual. Finalmente, como queremos poder detener y reanudar la monitorización (la ejecución periódica del programa externo), también es conveniente mostrar si estamos en pausa o en acción; la línea de modo es un buen lugar para mostrar esta información.
En vez de hacer malabarismos alrededor de async-shell-command (la función que utilizábamos para ordenar la ejecución del programa externo), vamos a usar una función de un nivel un poco más bajo, start-process-shell-command, que nos permite un control más detallado de lo que estamos haciendo.
Vamos a crear un modo mayor nuevo, watch-mode, que recogerá la funcionalidad.
Aunque no es la forma más limpia de trabajar, vamos a definir 4 variables ligadas al búfer en el que se ejecuta nuestro watch y que servirán para recoger el estado del modo:
(defvar watch--command nil
"Shell command executed by Watch.")
(defvar watch--interval 0
"Number of seconds Watch will wait between command
executions.")
(defvar watch--process nil
"Currently running Watch process.")
(defvar watch--timer nil
"Currently running Watch timer.")
(make-variable-buffer-local 'watch--command)
(make-variable-buffer-local 'watch--interval)
(make-variable-buffer-local 'watch--process)
(make-variable-buffer-local 'watch--timer)
Crearemos un búfer de monitorización igual que antes: con la función interactiva watch. El código es ahora muy sencillo gracias a que la lógica está encapsulada en otras funciones:
(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))
(watch-mode)
(watch--start command interval)
(add-hook 'kill-buffer-hook
'watch--kill-buffer-hook nil t))
Lo que hacemos es crear un búfer nuevo (igual que antes), ponerlo en nuestro nuevo modo watch-mode. Seguidamente, hacemos todas las labores de arranque del proceso de monitorización con una función que llamaremos watch--start. Finalmente, añadimos una función hook a kill-buffer-hook para cancelar el temporizador cuando se cierra el búfer.
El nuevo modo mayor watch-mode deriva de special-mode, un modo que está pensado para contenido generado dinámicamente y que no es texto que queramos editar. La declaración de nuestro modo se encarga únicamente de asignar algunas órdenes del teclado:
(define-derived-mode watch-mode special-mode "Watch"
"Major mode for executing periodically an external shell
command."
(local-set-key (kbd "i") 'watch-set-interval)
(local-set-key (kbd "p") 'watch-pause)
(local-set-key (kbd "r") 'watch-resume))
El modo special-mode hace que el búfer sea de solo lectura. Evidentemente, como tenemos que llenarlo con la salida del programa externo, habrá que hacer algo especial; lo veremos mañana.
La función watch--start, que arranca el temporizador, hace un trabajo similar al que realizaba directamente la versión de watch del artículo anterior:
(defun watch--start (command interval)
"Start Watching (periodically running a COMMMAND every INTERVAL seconds)."
(interactive)
(setq watch--command command)
(setq watch--interval interval)
(setq watch--timer
(run-with-timer 0
interval
'watch--callback
(current-buffer)))
(setq mode-line-process ": Running"))
Las principales novedades son el uso de las variables que declaramos antes y la escritura del texto ": Running" en la línea de modo.
La función interactiva watch-pause sirve para detener la monitorización. Solamente tiene sentido pausar una monitorización en marcha; indicamos que la monitorización se ha detenido asignando un valor nulo al temporizador watch--timer después de detenerlo. Tras detener el temporizador, mostramos el estado de pausa de la monitorización en la línea de modo (asignamos el valor ": Paused" a mode-line-process) y redibujamos con redraw-display para forzar que se muestre el cambio que hemos hecho, ya que a menudo puede no actualizarse la pantalla. Finalmente, matamos el proceso watch--process si está activo. El código es el siguiente:
(defun watch-pause ()
"Stop Watching (periodically running a command)."
(interactive)
(when watch--timer
(cancel-timer watch--timer)
(setq watch--timer nil)
(setq mode-line-process ": Paused")
(redraw-display))
(when (and watch--process
(not (process-live-p watch--process)))
(kill-process watch--process)))
La función interactiva watch-resume sirve para reanudar la ejecución del temporizador y es muy sencilla, ya que se limita a llamar a watch--start. Como solamente tiene sentido reanudar la ejecución cuando está detenida, nos fijamos en el valor del temporizador watch--timer: si es nulo, la monitorización está en pausa y podemos reanudar.
(defun watch-resume ()
"Resume Watching (periodically running a command)."
(interactive)
(unless watch--timer
(watch--start watch--command watch--interval)))
Cambiamos el intervalo entre ejecuciones con la función interactiva watch-set-interval, que acepta un número de segundos, pausa la monitorización, asigna el nuevo intervalo y reanuda la monitorización:
(defun watch-set-interval (seconds)
"Set the interval SECONDS between command executions in the
current Watch buffer."
(interactive "nInterval (seconds): ")
(watch-pause)
(setq watch--interval seconds)
(watch-resume))
Falta poco trabajo para tener terminado el monitorizador de programas externos watch. Mañana veremos cómo ejecutar el programa externo, qué hacer con su salida y qué hacer cuando se detiene su ejecución.
Categorías: Informática