Cuaderno de GNUtas

Unificando y afinando búsquedas con Helm

Del potente y versátil buscador interactivo y autocompletivo Helm ya hablamos algo aquí hace un tiempo. El caso es que yo hago un uso bastante intensivo —y hasta entusiasta— de esta completa herramienta, además de que me permití la osadía de escribirle esta extensión para gestionar las fuentes del sistema y ejecutar sobre el candidato que tiene el foco ciertas acciones relacionadas con LATEX. Pero con todo lo útil que resulta Helm, siempre podremos afinar un poco más, para alcanzar un grado superior de confort. No hablaremos aquí de modificaciones en el comportamiento y aspecto de la interfaz y el búfer de Helm (eso daría para otra GNUta), sino más bien de ciertas mejoras a la hora de unificar las búsquedas. Y es que la virtud de Helm, su prolijidad, puede ser a menudo uno de sus defectos, o al menos una de las barreras que suelen intimidar al recién llegado.

Veamos: usamos Helm para mil y una cosas, y cada instancia de éste extrae su información y su lista de candidatos de una fuente diferente (lo que en la jerga helmiana llamaremos «sources»). Y cada una de estas listas, además, puede incluir una serie de acciones a llamar sobre el candidato escogido, aquel sobre el cual recae el foco. La acción por defecto se activa con un simple ENTER y al resto de acciones se accede mediante un menú activado con la tecla tabulador o por un atajo de teclado. Ahora bien, en lugar de tener varios atajos para varias instancias de Helm (buscar búferes abiertos, marcadores, archivos recientes y demás), ¿por qué no incluir todas esas listas de fuentes en una única instancia? Naturalmente, como el número de fuentes es exagerado, no parece buena idea cargarlas todas de esa forma. Primero, porque se ralentizaría horriblemente la instancia de Helm al tener que minar información de listas muy variopintas, generadas a partir de presupuestos no menos variopintos; segundo, porque habrá fuentes que usemos con más frecuencia que otras. A mí, por ejemplo, me resulta muy productivo y cómodo unificar las siguientes fuentes:

  1. búferes
  2. marcadores
  3. locate (es decir, llama al comando de shell del mismo nombre)
  4. búferes o archivos recientes

y obtener, con sólo escribir unas letras, toda la información de búferes abiertos, archivos indexados en la base de datos de locate, marcadores y búferes recientemente visitados. Un gran paso en el camino hacia la sencillez, incluso para una maquinaria tan cargada de engranajes como es Helm.

helm-mini

Extraer información de múltiples fuentes de Helm se puede lograr de muchas formas, pero a mi juicio la más sencilla es mediante la versión de Helm llamada helm-mini. Así, nos bastaría con configurar la siguiente variable con una lista de fuentes a nuestro gusto:

(setq helm-mini-default-sources '(helm-source-buffers-list
				  helm-source-recentf
				  helm-source-buffer-not-found
				  helm-source-bookmarks
				  helm-source-bookmark-set
				  helm-source-locate))

y asignar luego algún atajo de conveniencia a la función helm-mini. Pero aún podemos afinar un poco más las cosas si pensamos en los encabezados o árboles de Org que vamos abriendo y visitando. Una de las cosas fascinantes, en efecto, que nos proporciona el modo del Unicornio, es recordarnos la forma de actuar de las antiguas máquinas Lisp, donde lo que primaba era el objeto, no el archivo o el directorio, como luego impuso la forma de trabajar de Unix. En Org —que no deja de ser una interfaz de alto nivel para Elisp— nos importa más el encabezado o árbol (el objeto), independientemente de en qué archivo se encuentre y a qué nivel de cuál directorio. Esa forma de pensar supone una importante liberación del encorsetamiento Unix, que tanto ha influido en la informática de los últimos decenios.

Si hablamos de Org, por tanto, ese lugar donde casi siempre estamos moviéndonos, es más productivo pensar en el concepto de encabezados recientes más que en el de archivos recientes. Pero antes conviene hablar de otra herramienta maravillosa llamada org-ql.

org-ql y helm-org-ql

org-ql es una biblioteca para ejecutar una amplia batería de búsquedas a través de nuestros documentos de Org, como si éstos fueran una suerte de base de datos. helm-org-ql, a su vez, es la interfaz de Helm para facilitar dichas búsquedas con la potencia helmiana. Ambas bibliotecas están escritas por Adam Porter y pueden descargarse tanto desde su repositorio de GitHub como desde Melpa. Desde luego merecen una GNUta sólo para ellas cualquier día de estos, pero aquí nos bastará con centrarnos en un punto, a saber, que cuando uno se habitúa a usarlas se convierten casi en el único medio para acceder a los encabezados de los archivos Org, independientemente de dónde estén esos archivos. Por tanto se me ocurrió que podrían ser un buen punto de partida para almacenar todos los encabezados visitados como otra fuente para Helm.

Antes de seguir conviene señalar que existe un org-recent-headers así como un helm-org-recent-headers, ambos también del mismo autor, pero a diferencia de la familia org-ql estas dos bibliotecas no me acababan de convencer tanto en su forma de proceder como en la de entregar los resultados. Para mi uso personal necesitaba algo mucho más simple pero también rápido y accesible, sin demasiadas complicaciones ni adornos. Así que se me ocurrió ensayar el siguiente código Elisp.

Una fuente de Helm para los encabezados accedidos a través de helm-org-ql

Comenzamos por definir la siguiente variable, que va a ser una lista de listas, o una alist o lista asociativa donde cada elemento es un cons cell, cuyo car es el nombre del encabezado (pero precedido de la cadena visitado -- para que sea fácil de localizar cuando lanzo helm-mini), y el cdr es otro conscell que incluye el nombre del búfer y su posición en dicho búfer:

(setq lista-helm-org-ql-visitados nil)

Sobreescribo la acción de helm-org-ql (helm-org-ql-show-marker) que salta al candidato cuando lanzo helm-org-ql. Simplemente, añade un nuevo elemento a la lista con cada acción de visitar un encabezado:

(defun elemento-a-lista-visitados-helm-org-ql (marker)
  "Show heading at MARKER."
  (interactive)
  ;; This function is necessary because `helm-org-goto-marker' calls
  ;; `re-search-backward' to go backward to the start of a heading,
  ;; which, when the marker is already at the desired heading, causes
  ;; it to go to the previous heading.  I don't know why it does that.
  (switch-to-buffer (marker-buffer marker))
  (goto-char marker)
  (org-show-entry)
  (add-to-list 'lista-helm-org-ql-visitados `(,(concat "visitados --" (nth 4 (org-heading-components)))
					      (,(buffer-name) ,(marker-position marker)))))

(advice-add 'helm-org-ql-show-marker :override  #'elemento-a-lista-visitados-helm-org-ql)

Esta función crea una lista de candidatos con los nombres de cada encabezado (serán los nombres que nos aparezcan en la lista de candidatos de Helm):

(defun candidatos-helm-org-ql-visitados ()
  "Devuelve una lista con los encabezados visitados mediante
`helm-org-ql'"
  (mapcar
   (lambda (x)
     (car x))
   lista-helm-org-ql-visitados))

La lista de acciones. De momento, visitar el encabezado reciente:

(setq acciones-helm-org-ql-visitados
      (helm-make-actions
       "visitar el encabezado visitado" (lambda (x)
					  (let* ((cons-destino (cdr (assoc x lista-helm-org-ql-visitados)))
						 (buf (nth 0 (car cons-destino)))
						 (marker-pos (nth 1 (car cons-destino))))
					    (switch-to-buffer buf)
					    (goto-char marker-pos)
					    (org-show-entry)))))

Y sólo queda definir la fuente para helm-mini:

(setq mi-helm-org-ql-encabezados-recientes
      '((name . "Encabezados de Org recientes")
	(candidates . candidatos-helm-org-ql-visitados)
	(action . acciones-helm-org-ql-visitados)))

que añadiremos a la lista de fuentes:

(setq helm-mini-default-sources '(helm-source-buffers-list
				  helm-source-recentf
				  helm-source-buffer-not-found
				  helm-source-bookmarks
				  helm-source-bookmark-set
				  helm-source-locate
				  mi-helm-org-ql-encabezados-recientes))

Publicado: 26/04/21

Última actualización: 26/04/21


Índice general

Acerca de...

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

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