Org como base de datos: helm-org-ql
El hecho de que Org Mode sea un entorno orientado a los objetos (nodos o «árboles», principalmente) más que a los archivos, propicia disfrutar de una interfaz de alto nivel para Emacs donde podemos librarnos fácilmente de esa servidumbre que los sistemas Unix impusieron hace tiempo hacia el esquema directorio/fichero. Contra ese improductivo corsé ya se habían rebelado en los 80 del pasado siglo aquellas visionarias máquinas Lisp que surgieron del MIT, donde también había nacido Emacs. Y es que el propio Emacs —no lo olvidemos— es una máquina Lisp y un caballo de Troya en un sistema Unix. Por eso, precisamente, «Gnu no es Unix».
Bien, el caso es que los años de uso de Org Mode han llevado a este usuario que aquí
escribe a irse desentendiendo poco a poco de los sofocos acerca de en qué directorio me
encuentro o qué archivo estoy editando. No es que el desprendimiento sea completo: alguna
vez hay que bajar a la sentina a hacer algún apaño. Pero en lo que respecta al entorno de
trabajo en sentido estricto, lo que realmente me importa son los nodos o árboles, y
navegar por ellos, independientemente de en qué archivos con la extensión .org
se hayan
sembrado y multiplicado dichas arborescencias. Muchos de estos nodos tienen asociadas
carpetas adjuntas mediante org-attach
, donde guardo todo tipo de documentos. Para la
actividad de recorrer esos paisajes y tener siempre el nodo deseado a mano, Org Mode ya
viene de fábrica con unos cuantos recursos muy provechosos. Pero en esta GNUta daremos
cuenta de una biblioteca externa utilísima, de esos paquetes que a los pocos minutos de
uso uno cae en la cuenta de que ya no puede vivir sin él: me refiero a org-ql
y,
concretamente, a su versión para la interfaz autocompletiva Helm, helm-org-ql
, incluida
en el código del paquete. Describiremos a continuación en qué consiste esta biblioteca y
comentaremos algunos extremos de mi configuración y personalización en mi archivo de
inicio.
org-ql
es un ambicioso (a la par que logrado) proyecto de crear un lenguaje de alto
nivel para Org a fin de convertir al modo del unicornio en una versátil y potente base de
datos, y todo ello con una sintaxis lispiana y legible por humanos. Está escrito por el
prolífico autor Adam Porter («alphapapa», en su apodo de GitHub), autor de
otros muchos paquetes excelentes. Para
programar en Elisp este lenguaje es muy útil, y yo ya tengo hechos algunos pinitos. Un
simple ejemplo: imaginemos que queremos desplegar una lista con todos los nodos que tengan
el estado «done», y que pertenezcan a los archivos incluidos en la agenda. Esta expresión
puede servirnos:
(org-ql-search
org-agenda-files
'(todo "DONE"))
helm-org-ql
, donde aquí nos centraremos, no es más que la versión de ese lenguaje para
el uso cotidiano y la comodidad de Helm. Las búsquedas que podemos lanzar desde allí son
muy flexibles. La forma más simple es buscar por cadenas de texto. Por ejemplo, si entre
mis nodos de Org quiero localizar aquellos que contienen la palabra «velocípedo», no tengo
más que lanzar helm-org-ql
y empezar a escribir ese término para que comiencen a
mostrarse los resultados. Pero podemos acotar la búsqueda también mediante el uso de
comodines muy intuitivos. Supongamos que quiero buscar la palabra «velocípedo» pero sólo
en los nodos que tengan el estado «WORKING» y la etiqueta «trabajo». Pues es tan sencillo
como escribir en el input de Helm algo como:
velocípedo tags:trabajo todo:WORKING
helm-org-ql
viene por defecto con dos acciones sobre el candidato escogido: la principal
(y esperable), que nos lleva al nodo en cuestión, y una secundaria (accedida mediante el
tabulador, que es el atajo que tenemos en Helm para desplegar el menú de acciones
alternativas) para abrir el nodo buscado en un búfer indirecto. Pero yo he ido
escribiéndole unas cuantas acciones más, según me iba dictando el uso y la necesidad.
Daremos cuenta aquí de ellas, por si algún lector las encuentra útil y quiere adaptarlas a
su propio flujo de trabajo. Sólo un pequeño inciso antes de meternos en esa harina. Yo
uso, en mi día a día, dos instancias de helm-org-ql
: helm-org-ql-agenda-files
, que,
como su nombre indica, lanza las búsquedas sobre los archivos incluidos en la variable
org-agenda-files
; y, menos a menudo, helm-org-ql
, que busca sólo en el archivo de Org
donde estamos. La primera instancia es de donde saco más provecho, pues transciende el
concepto «directorio/archivo», como explicamos más arriba.
Una acción para abrir el candidato como un link de Org
Muchos de los nodos que pueblan mis documentos de Org son enlaces, como marcadores de
sitios de internet que voy almacenando (mediante org-capture
) o enlaces al servidor
multimedia de mi Raspberry, para los cuales tengo definidos un tipo especial de enlace que
me reproduce el contenido remoto, ya sea música o vídeo, mediante EMMS (Emacs Multimedia
System)1. Por tanto, una acción que me resulta utilísima es la de poder abrir el
candidato escogido en helm-org-ql
como enlace de Org. Bastaría con definirla así:
(defun mi-helm-org-ql-abre-link (marker) "abre una entrada candidata en `org-ql' si es un link" (interactive) (save-window-excursion (switch-to-buffer (marker-buffer marker)) (widen) (org-show-all) (goto-char marker) (org-open-at-point)))
Una acción para crear un link al candidato escogido
Si queremos enlazar en el punto del cursor el nodo candidato, definimos esto:
(defun mi-helm-org-ql-link-nativo (marker) "Crea un link nativo de Org hacia el candidato en el punto del cursor" (interactive) (save-excursion (save-window-excursion (switch-to-buffer (marker-buffer marker)) (goto-char marker) (setq titulo-link-org-ql (nth 4 (org-heading-components))) (call-interactively 'org-store-link))) (org-insert-link nil (car (car org-stored-links)) titulo-link-org-ql))
Pero, claro, esto inserta un link con una descripción automática. A menudo nos conformaremos con eso, sobre todo si tenemos prisa. Pero en otras ocasiones necesitaremos editar la descripcion. Para ahorrar acciones manuales, podemos definir otra acción que nos pregunta en el minibúfer por la descripción alternativa. Primero necesitamos definir esta función:
(defun edita-desc-link-reciente () (let ((desc (read-from-minibuffer "Descripción: "))) (if (not desc) (error "No se ha añadido nueva descripción") (when (org-in-regexp org-link-bracket-re 100) (save-excursion (save-restriction (narrow-to-region (match-beginning 2) (match-end 2)) (delete-region (point-min) (point-max)) (save-excursion (goto-char (point-min)) (insert desc))))))))
Y para definir nuestra nueva acción, podemos aprovechar la anterior:
(defun mi-helm-org-ql-link-nativo-desc (_) (interactive) (mi-helm-org-ql-link-nativo _) (edita-desc-link-reciente))
Una acción para abrir el diálogo de org-attach
Como comentaba más arriba, org-attach
es una biblioteca que me resulta indispensable
para poder adjuntar cualquier clase de documentos o directorios a un nodo determinado. Y,
una vez hecho eso, ya no necesito localizar la carpete adjunta sino el nodo de que
depende. Como encontrar ese nodo mediante helm-org-ql
es cosa de un periquete, una
acción que me desplegase el menú de org-attach
relativo al candidato era inevitable. La
definimos así, siguiendo la estructura de las anteriores:
(defun mi-helm-org-ql-abre-attachment (marker) "abre el cuadro de comandos de `org-attach' sobre el candidato seleccionado" (interactive) (switch-to-buffer (marker-buffer marker)) (widen) (org-show-all) (goto-char marker) (call-interactively #'org-attach))
Guardar los adjuntos de un correo abierto en Gnus en la carpeta adjunta del nodo candidato
Y ya que hablamos de adjuntos, muchas veces nos interesará guardar los archivos adjuntos
de un correo que hemos recibido en Gnus (el lector de correo y noticias de Emacs) en la
carpeta de org-attach
de algún nodo buscado con helm-org-ql
. La siguiente acción hace
eso mismo con todos los adjuntos del correo que tenemos abierto (incluyendo el cuerpo de
texto del correo, lo cual es muy útil). Si no existe una carpeta de org-attach
asociada
al nodo, la crea (y, si no existe, añade la etiqueta «ATTACH» al encabezado en cuestión):
(defun mi-helm-org-ql-guarda-adjuntos-gnus (marker) "Guarda todos los archivos adjuntos del artículo actual de `Gnus'" (interactive) (switch-to-buffer (marker-buffer marker)) (widen) (org-show-all) (goto-char marker) (when (not (member "ATTACH" (org-element-property :tags (org-element-at-point)))) (org-toggle-tag "ATTACH")) (let ((atch-dir (org-attach-dir t)) (art gnus-article-current)) (gnus-summary-save-parts "." atch-dir art)))
Asociar todas las acciones definidas a helm-org-ql
Creo que los anteriores ejemplos pueden servir como punto de partida para que el lector se anime a definir sus propias acciones. En mi caso, tengo definido un repertorio mucho más extenso del que aquí he ido describiendo, pero son para casos más idiosincrásicos de mi propio flujo de trabajo.
Ahora bien, una vez escritas nuestras acciones personalizadas, ¿cómo las asociamos a
helm-org-ql
de modo que aparezcan en el menú de acciones secundarias cada vez que
pulsemos la tecla tabulador? Pues con un sencillo add-to-list
, pero asegurándonos antes
de que se carga la biblioteca y de que añadimos a la expresión add-to-list
el argumento
opcional «APPEND» con valor non-nil
, para que nuestras acciones personalizadas se añadan
justo después de las que vienen en helm-org-ql
por defecto. Algo así como esto:
(with-eval-after-load 'helm-org-ql (add-to-list 'helm-org-ql-actions '("Abre el candidato como link" . mi-helm-org-ql-abre-link) t) (add-to-list 'helm-org-ql-actions '("Abre adjuntos" . mi-helm-org-ql-abre-attachment) t) (add-to-list 'helm-org-ql-actions '("Guarda adjuntos de Gnus" . mi-helm-org-ql-guarda-adjuntos-gnus) t) (add-to-list 'helm-org-ql-actions '("Crea un link nativo de Org" . mi-helm-org-ql-link-nativo) t) (add-to-list 'helm-org-ql-actions '("Crea un link nativo de Org, editando la descripción" . mi-helm-org-ql-link-nativo-desc) t))
∞
Publicado: 29/04/22
Última actualización: 07/08/22
Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.
Notas:
Para más detalles, remito a esta GNUta y también a esta otra, sobre cómo enlazar
el contenido de un servidor DLNA
.