Navegación mejorada en Org Mode con org-speed-commands
Una funcionalidad muy potente en Org Mode, a la par que poco conocida a no ser que
escarbemos con cierto celo en el Manual, es la de los comandos rápidos; o Speed
Commands, como se los conoce en el original. Por defecto vienen desactivados, pero
podemos activarlos con sólo aplicarle un valor non-nil
a la variable
org-use-speed-commands
. ¿Y qué vienen a hacer estos comandos rápidos? Pues en su versión
de fábrica, que ya de por sí es muy completa y podrá satisfacer al común de los usuarios,
nos permite lanzar una serie de comandos relacionados, en principio, con los encabezados
(aunque veremos también que pueden extenderse a más escenarios), pulsando una única tecla.
Pero, y aquí reside lo esencial, siempre y cuando nos encontremos en un determinado
contexto. Por defecto, tal contexto se describe cuando el cursor está justo al principio
de la línea del encabezado sobre el que queramos ejecutar el comando de turno. O sea, se
trata de una suerte de edición modal1 en pequeñas dosis y controlada.
Estos comandos están almacenados en la lista org-speed-commands
, que es extensa ya de
fábrica. La forma más rápida de saber qué comandos están incluidos allí y qué letra tienen
asignada es pulsando «?» en el contexto de activación, o sea, a comienzo de línea del
encabezado. Por supuesto, el uso habitual irá discriminando, y siempre podremos también
modificar la lista a nuestro gusto, añadiendo y quitando comandos. Algunos son
palmariamente útiles. Por ejemplo: si estamos en el contexto de activación (recuérdese,
insisto, a comienzo de línea en un encabezado cualquiera) y pulsamos la letra «p»,
saltamos al encabezado anterior; con «n», vamos al siguiente encabezado; con «b», al
encabezado anterior del mismo nivel; con «f», lo mismo pero al encabezado siguiente. Más
ejemplos: «r» (de «right») nos baja un nivel el encabezado siguiente (o sea, va añadiendo
más asteriscos); «l» (de «left»), lo mismo pero subiendo un nivel (quita asteriscos); las
versiones de los dos anteriores comandos que bajan o suben de nivel el encabezado presente
junto a todo el subárbol (consecuentemente), son las letras mayúsculas «R» y «L». Y
algunos ejemplos más: «=
» nos lanza la vista de columnas; «c» pliega o despliega el
sub-árbol (lo equivalente al tabulador; para circular entre todas las vistas, que sería lo
equivalente a mayúsculas más tabulador, bastaría con pulsar «C» mayúscula); «:» inserta
etiquetas; «#» comenta el encabezado (o sea, añade la coletilla COMMENT
al título). Y un
largo etcétera.
Personalizando y extendiendo los comandos rápidos para los encabezados
Como vemos, se trata de una funcionalidad que puede resultar muy útil para la navegación a través de un documento de Org. Yo la venía usando bastante, pero últimamente se me ocurrió que no estaría de más un poco de personalización y ciertas modificaciones. Partí de la siguiente premisa: yo, que no soy especialmente devoto de la edición modal, podría admitir subir la dosis (pero manteniéndome dentro del control para no desmelenarlo todo), una vez asumido el hecho de que los encabezados es algo a que dedico poco tiempo de edición, después de que establezco el título. A decir verdad, paso más tiempo editando el «meta-contenido» de los encabezados que el contenido propiamente dicho: estados TODO, propiedades, etiquetas, posicionamiento, etc. Podríamos entender que el contenido en bruto del título es un metadato más, y que se podría editar llegado el caso como tal metadato. Así pues, con semejante premisa en mente, mi extensión de los comandos rápidos consistiría en estos tres puntos:
- Extender el contexto de activación a todo el encabezado, y no sólo al comienzo de la línea. Una vez que lo logramos, la velocidad de navegación se incrementa exponencialmente. Y tal vez, añadido a esto, sería buena idea extender el contexto de activación al comienzo del documento de Org (o sea, la posición «1» del búfer), que es donde nos aparece el cursor cada vez que visitamos un documento.
- Por defecto, cuando por descuido u olvido pulsamos una letra que no está asociada a un comando rápido, esta letra, simplemente, se imprime. Lo cual resulta muy incómodo y no parece consistente. A mi juicio, el comportamiento apropiado sería un modo dual más estricto: si la letra no está asociada a un comando, devolver un mensaje de error, pero nunca imprimir la letra. Necesitamos, por tanto, alternar en este modo dual entre el modo-comando y el modo-edición. Sí, lo asumo, estamos bordeando peligrosamente el infierno de la edición modal estilo Vim, pero repito que lo podemos seguir teniendo bajo control. O eso espero…
- Como consecuencia de lo anterior, claro, necesitaríamos una función que activara o desactivara los comandos rápidos a voluntad, una especie de «modo de edición», para poder escribir en el encabezado cuando lo necesitemos.
Pasemos a describir cómo he ido logrando estas tres metas.
Extendiendo el contexto de activación
Hemos dicho que queremos activar nuestros comandos rápidos en cualquier parte del
encabezado y en la posición «1» del búfer. Lograrlo es sencillo y el procedimiento está
bien explicado en el doc string de org-use-speed-commands
: bastaría con dar a esta
variable una función anónima que incluyese un condicional, a fin de delimitar el contexto.
Es decir, que devolviera valor non-nil
si tenemos el puntero en el contexto deseado. Mi
nueva configuración de la variable quedó, entonces, tal que así:
(setq org-use-speed-commands (lambda () (or (eq (point) 1) (org-in-regexp "^\\*+\s+.+"))))
Activación «estricta» de los comandos
Ya hemos dicho que no queremos que se imprima la letra cuando pulsamos la tecla equivocada
y no está asociada a ningún comando. Esto es, queremos el contexto de activación vedado a
cualquier tipo de edición manual, a no ser que desactivemos momentáneamente la
funcionalidad, como veremos más adelante. Para lograr este modo estricto tendremos que
modificar ligeramente el final de la función org-speed-commands-activate
. Escribimos
nuestra versión modificada y la superponemos a la de fábrica mediante un advice-add
con
la palabra clave :override
:
(defun mi-org-speed-command-activate (keys) "Mi versión modificada de `org-speed-command-activate' que devuelve un mensaje de error si pulso una tecla no asociada a un comando rápido" (when (or (and (bolp) (looking-at org-outline-regexp)) (and (functionp org-use-speed-commands) (funcall org-use-speed-commands))) (let ((defined (cdr (assoc keys (if (boundp 'org-speed-commands-user) (append org-speed-commands org-speed-commands-user) org-speed-commands))))) (if defined defined (error "Comando rápido no definido: \"h\" para ayuda"))))) (advice-add 'org-speed-command-activate :override #'mi-org-speed-command-activate)
Permitir activar y desactivar rápidamente los comandos rápidos
Las modificaciones anteriores nos han dejado, como hemos anticipado, en una suerte de modo «dual». Por tanto, necesitamos una función que nos alterne rápidamente entre modo de edición y modo de comandos. Y —pequeña floritura— para que nos recuerde cuando estamos en el modo de edición, se encerrará el contenido del encabezado en una caja (como podemos observar en la fig. 1):
(defun mi-org-alterna-speed-commands () (interactive) (if org-use-speed-commands (progn (setq org-use-speed-commands nil) (with-eval-after-load 'org (set-face-attribute 'org-level-1 nil :box t) (set-face-attribute 'org-level-2 nil :box t) (set-face-attribute 'org-level-3 nil :box t) (set-face-attribute 'org-level-4 nil :box t) (set-face-attribute 'org-level-5 nil :box t) (set-face-attribute 'org-level-6 nil :box t) (set-face-attribute 'org-level-7 nil :box t) (set-face-attribute 'org-level-8 nil :box t)) (message "speed-commands desactivado")) (setq org-use-speed-commands (lambda () (or (eq (point) 1) (org-in-regexp "^\\*+\s+.+")))) (with-eval-after-load 'org (set-face-attribute 'org-level-1 nil :box nil) (set-face-attribute 'org-level-2 nil :box nil) (set-face-attribute 'org-level-3 nil :box nil) (set-face-attribute 'org-level-4 nil :box nil) (set-face-attribute 'org-level-5 nil :box nil) (set-face-attribute 'org-level-6 nil :box nil) (set-face-attribute 'org-level-7 nil :box nil) (set-face-attribute 'org-level-8 nil :box nil)) (message "speed-commands activado")))
Y, para acceder rápidamente a esta función, le asigno un atajo de teclado cómodo:
(with-eval-after-load 'org (define-key org-mode-map (kbd "M-i") 'mi-org-alterna-speed-commands))
Algunas mejoras más
Una vez añadidas estas modificaciones, podemos pensar en ciertas funciones personalizadas
para incorporar a la lista org-speed-commands
, así como en eliminar de esa variable las
que no usemos demasiado. Naturalmente, eso ya depende de las preferencias y tipo de uso de
cada cual. Aquí describiré algunas de las funciones que yo he incluido.
La primera que me vino a la mente, y ya que nuestro contexto de activación ahora está también en el comienzo del búfer (posición «1»), es la de saltar desde cualquier encabezado a esa posición:
("q" . beginning-of-buffer)
Continuando con mis funciones añadidas, aquí un par de funciones que insertan un nuevo
encabezado justo a continuación del encabezado actual, preguntando por el contenido
en el minibúfer. La segunda versión inserta un encabezado con valor TODO
:
(defun mi-org-insert-heading () (interactive) (let ((tit (read-from-minibuffer "título: "))) (call-interactively 'org-insert-heading-respect-content) (insert tit)))
(defun mi-org-insert-todo-heading () (interactive) (let ((tit (read-from-minibuffer "título: "))) (call-interactively 'org-insert-todo-heading-respect-content) (insert tit)))
Y sus correspondientes teclas asignadas:
("i" . mi-org-insert-heading) ("y" . mi-org-insert-todo-heading)
Y ya, para terminar, porque no queremos ser demasiado prolijos con la lista, creo que en
la variable de fábrica se echaban en falta dos acciones muy habituales (al menos en mi uso
cotidiano), como son la de añadir y editar propiedades y la de abrir el menú de
org-attach
:
("P" . org-set-property) ("A" . org-attach)
Y con eso ya tendríamos nuestras mejoras incorporadas. Pero, ya que nos hemos venido arriba, descubrimos que el ámbito de los encabezados no es el único donde pueden operar los comandos rápidos, como veremos en la sección siguiente.
Comandos rápidos para los enlaces
La función org-speed-command-activate
(que vimos más arriba) no es la única que se
encarga de activar comandos rápidos. De hecho, ésta está incluida en el hook
org-speed-command-hook
, que agrupa todas las funciones destinadas a lanzar este tipo de
comandos. Podemos escribir las nuestras propias y añadirlas allí, y a mí se me ocurrió la
idea de escribir una específica para los enlaces de Org. ¿Qué acciones añadir a esta
función de manera que se activen cuando tenemos el cursor dentro de un enlace? Las dos más
obvias son la de editar el enlace y la de activarlo. Además yó ya tenía una hydra
definida hace tiempo para agrupar varias funciones que previamente había ido escribiendo y
que iban destinadas a ejecutarlas sobre un link: copiar el archivo-ruta de un link a otro
directorio, renombrarlo, abrirlo con un programa externo (Gimp por defecto), adjuntarlo a
un correo electrónico, eliminar el link, etc. Pero eso ya será objeto de otra GNUta.
Aquí lo que interesa es definir una nueva lista de acciones para nuestros comandos rápidos
en los enlaces, que serán sólo tres: activar el enlace, editarlo y activar mi hydra:
(setq mi-org-link-speed-commands '(("o" . org-open-at-point) ("e" . org-insert-link) ("v" . hydra-link-org/body)))
Y ya sólo nos quedaría la función para lanzarlos con el contexto adecuado. Pero, ojo, los
enlaces no sólo pueden estar en el texto, también en un encabezado. Yo tengo cientos de
encabezados que son enlaces. Así que nuestro condicional para activar los comandos rápidos
en los enlaces debe dejar claro que no queremos que se activen cuando estamos en un
encabezado, pues de lo contrario entraría en conflicto con la función para los comandos
rápidos en los encabezados, que vimos en la sección anterior. Si queremos que los enlaces
de los encabezados también se beneficien de los comandos rápidos, no nos queda otra que
añadir estas tres acciones de nuestra nueva lista también a la lista org-speed-commands
,
pero teniendo cuidado, naturalmente, de no duplicar letras de activación. Lo ideal es que
sean siempre las mismas letras («o», «e», «v»), de modo que tendremos que reasignar nuevas
letras a otros comandos ya definidos en esa lista pare evitar ambigüedades.
Hechas, en fin, estas aclaraciones, nuestra función activadora de los comandos rápidos en los enlaces de Org quedaría tal que así:
(defun mi-org-speed-command-link-activate (keys) "Mi función para activar los comandos rápidos en los links, siempre que no sea un link en el encabezado" (when (and (org-in-regexp org-any-link-re 100) (not (org-in-regexp "^\\*+\s+.+"))) (let ((defined (cdr (assoc keys mi-org-link-speed-commands)))) (if defined defined (error "Speed command not defined: \"h\" for help")))))
Por supuesto, esta función también tiene un modo estricto, de forma que si pulsamos la tecla equivocada nos devolverá un mensaje de error. Tendremos que volver a echar mano de la función para cambiar entre modo de edición y modo de comandos. Una vez más, estamos acercándonos peligrosamente a la edición modal, pero, como sucede con los encabezados, la edición manual de los enlaces es muy raro hacerla.
Y nos quedaría, por último, asignar nuestra nueva función al hook
org-speed-command-hook
:
(add-hook 'org-speed-command-hook #'mi-org-speed-command-link-activate)
∞
Publicado: 07/05/22
Última actualización: 07/08/22
Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.
Notas:
O sea, al estilo Vi/Vim, donde se cuenta con un modo de edición, otro de inserción de texto, otro de navegación, etc.