Cuaderno de GNUtas

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


Índice general

Acerca de...

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.

Notas:

1

Para más detalles, remito a esta GNUta y también a esta otra, sobre cómo enlazar el contenido de un servidor DLNA.

© Juan Manuel Macías
Creado con esmero en
GNU Emacs