Más ardides de la Odisea: de «hipérboles» y versos
El paquete para Emacs Gnu Hyperbole es una biblioteca peculiar y bastante idiosincrásica.
De cuando en vez surge alguna nueva versión de la misma, y el acontecimiento siempre acaba
suscitando opiniones encendidas y encontradas entorno a su presunta complejidad o a si
tiene o no tiene elementos o intereses comunes con Org Mode. En las discusiones habituales
nunca me acababa de quedar claro a qué se dedica, en realidad, Hyperbole
, y si aquello
que hace —sea lo que sea— me sería útil. Pero como era tanta la polvareda que
levantaba con cada reaparición, no pude resistirme a la curiosidad de instalarlo y darle
una oportunidad. Lo cierto es que, tras unos cuantos trasteos, llegué a la conclusión de
que este paquete tenía partes que, en efecto, me iban a resultar tremendamente prácticas,
especialmente en mi traducción de la Odisea (work in progress). De eso, precisamente, tratará esta
GNUta, y sirva también como pequeña introducción de Hyperbole
, para que el posible
y amable lector pueda extraer algunos ejemplos y aplicarlos a su uso personal.
Insistiendo, desde luego, en lo de «pequeña introducción», ya que Hyperbole
es casi tan
variopinto y extenso como el modo del unicornio.
Es esa cualidad «multiforme», de hecho, la que está detrás de buena parte de los
malentendidos o despistes en torno a Hyperbole
. La diferencia esencial con el también
multiforme Org estriba en que éste ha sabido ramificarse a partir de una suerte de
lenguaje simple, limitado y articulado. Haga lo que haga Org y todas las bibliotecas que
van surgiendo a su sombra, no es difícil para el usuario establecer conversaciones
complejas después de aprender los primeros rudimentos. Con Hyperbole
eso no es tan obvio
o, al menos, no queda en la superficie. Pero, pasada esa primera barrera, nos damos cuenta
de que el laberinto no es tan enrevesado como lo pintan. Simplemente, es un laberinto que
no ha sabido venderse tan bien como Org. Por tanto, hay que entender Hyperbole
como un
conjunto de herramientas (variadas, eso sí, y con aparente poca relación entre ellas y su
campo de acción) para la gestión de información y la navegación de dicha información
(datos, textos, archivos) a través de GNU Emacs. Eso es, en cierto sentido, Org. La buena
noticia es que ambos modos no tienen por qué ser incompatibles.
Hyperbole
puede convertirse, pues, en un estupendo y utilísimo complemento para Org
Mode, especialmente en una de las partes del primero que más me ha interesado: su sistema
de enlaces implícitos. Algo que Org tiene ya ensayado tímidamente (y sólo a nivel de
documento) en sus Radio Targets, pero que Hyperbole
ha explotado de una forma
admirable e increíblemente productiva.
Pero, ¿Qué entiende Hyperbole
por un enlace o botón «implícito»? Pues, para decirlo en
pocas palabras: cuando una parte del texto de nuestro documento o búfer cumple con una
serie de contextos, se ejecutará una acción determinada si pulsamos sobre ese segmento de
texto una suerte de secuencia activadora universal, lo que Hyperbole
llama la «action
key». Por supuesto, podemos asignar a esa «action key» el atajo que se nos antoje y, como
ya decimos, sirve para activar cualquier tipo de botón implícito. El paquete ya viene con
un nutrido repertorio de botones implícitos de fábrica, de tal forma que si activamos la
«action key» sobre algo que nos parezca sospechoso de tener cualidades de enlace
implícito, casi seguro acertaremos. Un ejemplo muy útil: pulsada esa «action key» sobre
cualquier símbolo de una expresión Elisp, saltaremos automáticamente a la definición o
declaración de ese símbolo (función, variable, lo que sea) en su correspondiente archivo
fuente.
También podemos definir —y aquí está gran parte de la gracia— nuestros propios enlaces
implícitos de una forma bastante sencilla y transparente. Yo aproveché para definir alguno
que otro a fin de facilitar mi trabajo en la Odisea. Pero antes de detallar uno en
concreto, describiré brevemente cómo tengo configurado el paquete Hyperbole
en mi
archivo de inicio.
Mi configuración del paquete Hyperbole
No es necesario realmente si lo instalamos todo desde GNU Elpa, pero por si acaso, nunca viene de más requerir la biblioteca:
(require 'hyperbole)
Y cargamos el modo en el arranque:
(hyperbole-mode 1)
Con esto ya tendríamos suficiente para trabajar con Hyperbole
sin contratiempos. Sin
embargo, y ya que sólo existe una acción universal (una «action key», vaya), me hizo
gracia la posibilidad de poder contar con una acción «secundaria» que ejecutase otra cosa en
el mismo contexto. Estudiando un poco el código de Hyperbole
encontré que no era muy
difícil lograrlo. Bastaría, primero, con declarar una variable:
(defvar mi-hyp-alt-act nil)
y definir esta función, a la cual le asignaremos el atajo pertinente a nuestro gusto:
(defun mi-hyp-action-key-alt () "Acción alternativa para `action-key'" (interactive) (let ((mi-hyp-alt-act t)) (action-key)))
Un ejemplo de enlace implícito de Hyperbole
en mi traducción de la Odisea
Funciones previas como acción para el enlaces
Mucho antes de trastear con Hyperbole
yo ya tenía definidas un par de funciones que me
permitían saltar del verso traducido al verso original (y viceversa). El verso de destino
se resalta temporalmente con la siguiente función:
(defun mi-resaltado-temporal-linea () (hl-line-mode) (run-with-timer 1 nil (lambda () (hl-line-mode -1))))
Esta sería la función que hace el camino griego-español. Téngase en cuenta que:
- Cada canto en griego es un único archivo de Org independiente, nombrado siempre con una misma estructura;
- La traducción de todos los cantos está en un solo documento de Org, llamada
Odisea.org
; - Para la función
numero-verso-actual
, véase esta otra GNUta, aquí.
(defun verso-odisea-gr-a-es () "Salta al mismo número de verso en que está el cursor desde el canto original a su traducción" (interactive) (let* ((num-verso (numero-verso-actual)) (buf (buffer-name)) (num-canto (string-to-number (when (string-match "\\([0-9]+\\)" buf) (match-string 1 buf))))) (find-file-noselect "~/Git/Traduccion_Odisea/CANTOS/Odisea.org") (pop-to-buffer "Odisea.org") (org-overview) (goto-char (point-min)) (re-search-forward "\\* .+ Odisea" nil t) (org-show-children) (org-next-visible-heading num-canto) (org-show-subtree) (re-search-forward "#\\+begin_verse\\|#\\+BEGIN_VERSE") (ir-a-verso-actual num-verso) (recenter 6) (mi-resaltado-temporal-linea) (message (format "Verso %s del canto %s" (number-to-string num-verso) (number-to-string num-canto)))))
Aquí, la función que hace el camino inverso. El número de canto lo extraigo en este caso de una propiedad ad hoc.
(defun verso-odisea-es-a-gr () "Salta al mismo número de verso en que está el cursor desde el canto original a su traducción (versión español a griego)" (interactive) (let* ((num-verso (numero-verso-actual)) (canto (org-entry-get nil "Canto")) (buf-format (concat "~/Git/Traduccion_Odisea/CANTOS/canto" canto "gr.org"))) (find-file-noselect buf-format) (pop-to-buffer (concat "canto" canto "gr.org")) (org-overview) (goto-char (point-min)) (org-show-subtree) (re-search-forward "#\\+begin_verse\\|#\\+BEGIN_VERSE") (ir-a-verso-actual num-verso) (recenter 6) (mi-resaltado-temporal-linea) (message (format "Verso %s del canto %s" (number-to-string num-verso) (number-to-string num-canto)))))
Estas dos funciones cuentan con las dos siguientes variantes. La única diferencia aquí es que el verso se muestra en el minibúfer. Son para consulta más rápida, sin necesidad de saltar al archivo correspondiente. Es decir, que si tenemos el cursor en un verso determinado de la traducción, el minibúfer muestra el verso original. Y viceversa:
(defun verso-odisea-var-gr-a-es () "Muestra en el minibúfer mismo número de verso en que está el cursor desde el canto original a su traducción" (interactive) (let* ((num-verso (numero-verso-actual)) (buf (buffer-name)) (num-canto (string-to-number (when (string-match "\\([0-9]+\\)" buf) (match-string 1 buf)))) (linea (with-current-buffer (find-file-noselect "~/Git/Traduccion_Odisea/CANTOS/Odisea.org") (org-overview) (goto-char (point-min)) (re-search-forward "\\* .+ Odisea" nil t) (org-show-children) (org-next-visible-heading num-canto) (org-show-subtree) (re-search-forward "#\\+begin_verse\\|#\\+BEGIN_VERSE") (ir-a-verso-actual num-verso) (donde-odisea) (buffer-substring (line-beginning-position) (line-end-position))))) (message linea))) (defun verso-odisea-var-es-a-gr () "Muestra en el minibúfer mismo número de verso en que está el cursor desde el canto original a su traducción (versión español a griego)" (interactive) (let* ((num-verso (numero-verso-actual)) (canto (org-entry-get nil "Canto")) (buf-format (concat "~/Git/Traduccion_Odisea/CANTOS/canto" canto "gr.org")) (linea (with-current-buffer (find-file-noselect buf-format) (org-overview) (goto-char (point-min)) (org-show-subtree) (re-search-forward "#\\+begin_verse\\|#\\+BEGIN_VERSE") (ir-a-verso-actual num-verso) (buffer-substring (line-beginning-position) (line-end-position))))) (message linea)))
El enlace implícito de Hyperbole
Y aquí viene la parte que toca a Hyperbole
. Hasta ahora, las funciones descritas en el
anterior apartado las llamaba normalmente desde un atajo. Pero como lo acabé haciendo muy
a menudo, y ya que el encuentro con Hyperbole
estaba reciente, me pregunté por qué no
convertir cada verso de la Odisea, original y traducción, en un enlace implícito que
ejecutase esas funciones. Por supuesto, había que limitar un poco más el contexto, pero
tampoco hacía falta ser demasiado puntilloso. Para que se ejecutase la acción, en suma,
tendrían que cumplirse sólo estas dos condiciones:
- Estar dentro del proyecto Git de mi traducción de la Odisea
- Tener el cursor exactamente al principio de la línea.
No me hizo falta, realmente, afinar más en el contexto, pues yo ya sabía dónde iba a
activar esos enlaces implícitos: justamente al principio de cada verso. Y esto me pareció
un verdadero hallazgo de Hyperbole
(y algo en concreto donde va más allá de Org): el
enlace no está ahí, en el texto, esperándome (como sucede en Org), sino en la intención de
quien lo va a activar. De ahí lo de «implícito».
Además, para aprovechar los dos juegos de funciones me vino aquí de perlas mi invento de la acción secundaria. De tal suerte que la acción «primaria» me mostraría el verso correspondiente en el minibúfer y la secundaria me llevaría al correspondiente documento.
La definición de nuevos enlaces implícitos en Hyperbole
es bastante sencilla. Se realiza
mediante la función defib
, la cual tiene dos partes claramente separadas: la primera,
para establecer los contextos y condicionales en que se debe ejecutar el nuevo enlace. La
segunda, para la acción a ejutar una vez activado el enlace con la «action key». Ésta se
aplica como argumento de la expresión hact
(y, a su vez, podemos añadir los argumentos
necesarios). Lo único a tener en cuenta en esta parte es que, si la acción se trata de una
función, no puede pasar como símbolo sino con un apóstrofe o «quote», para evitar que se
evalúe. Por supuesto, podemos también introducir una función anónima «lambda».
Mi enlace implícito, por tanto, quedaba tal que así (y se puede apreciar un ejemplo de uso en la fig. 1):
(defib od-versos () "TODO" (when (and (equal (projectile-project-root) (expand-file-name "~/Git/Traduccion_Odisea/")) (eq (point) (line-beginning-position))) ;; las acciones primaria y secundaria a ejecutar (if (not mi-hyp-alt-act) (if (string-match-p "gr" (buffer-file-name)) (hact 'verso-odisea-var-gr-a-es) (hact 'verso-odisea-var-es-a-gr)) (if (string-match-p "gr" (buffer-file-name)) (hact 'verso-odisea-gr-a-es) (hact 'verso-odisea-es-a-gr)))))
∞
Publicado: 07/08/22
Última actualización: 16/08/23
Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.