Cuaderno de GNUtas

Editar celdas con (mucho) contenido en las tablas de Org Mode

El problema

Si un Dante redivivo reescribiese su Commedia sobre el poco estimulante tema del formato de las tablas tratadas en un ordenador, probablemente dividiría su plan de ruta en:

  • Tablas en LATEX: inferno,
  • Tablas en un procesador de texto: purgatorio,
  • Tablas en Org Mode: Paradiso (o casi).

El «casi» de las tablas del modo del Unicornio que rozan el cielo nos deja en ocasiones, en efecto, un sabor agridulce cuando nos toca bregar con tablas de cierta envergadura. Y es que lo que supone una virtud para las tablas de Org —su vocación decididamente visual, cercana a lo que sería un WYSIWYG («what you see is what you get») en texto plano, unido ello a las facilidades de formato rápido dentro de Emacs— trae también su condena, al estar sometidos el usuario y su tabla a lo que podríamos llamar la «tiranía de la línea única». Esto es, que toda fila ha de ser forzosamente una línea de texto. Naturalmente, no suele haber problema con tablas de datos escuetos, como la siguiente, que en nuestro Org no sólo luce bien sino que podemos manejarnos en ella de una forma bastante confortable:

| Fruta    | Verdura       | Bebidas  |
|----------+---------------+----------|
| peras    | acelgas       | vino     |
| manzanas | repollo       | cerveza  |
| ciruelas | judías verdes | limonada |

El problema viene cuando nuestra tabla resulta más literaria y verbosa de lo acostumbrado, y tenemos que introducir mucho contenido en una celda, incluso párrafos enteros. En el código de LATEX no habría inconveniente, toda vez que las tablas, tal y como las entiende LATEX en su archivo fuente, carecen de esa vena visual de las tablas de Org y son un WYSIWYM («what you see is what you mean») puro. Correremos el peligro, a cambio, de enredarnos con demasiada facilidad. Imaginemos que queremos añadir un contenido de un párrafo largo, justificado a ambos márgenes, a una tabla en LATEX (generalmente mediante el entorno tabularx del paquete del mismo nombre, especialista en estas lides). A LATEX le bastará que pongamos esto, feo y espeso como él solo, pero que LATEX comprende como a alguien de su propio pueblo:

Lorem ipsum & Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec hendrerit
tempor tellus. Donec pretium posuere tellus. Proin quam nisl, tincidunt et, mattis eget,
convallis nec, purus. Cum sociis natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Nulla posuere. Donec vitae dolor. Nullam tristique diam non
turpis. Cras placerat accumsan nulla. Nullam rutrum. Nam vestibulum accumsan nisl. & dolor \\

Algo similar no podremos hacerlo en Org, a no ser que estemos usando un monitor con el ancho suficiente para que quepa el párrafo (o los párrafos) de la celda en una sola línea. Incluso si se diera ese caso, leer un párrafo en una sola línea no es una forma demasiado natural de leer. Y, como lo que sucederá probablemente es que la tabla no quepa entre los márgenes de la ventana expandida de Emacs, Org se verá incapaz de dar formato a algo decente y nos entregará a los ojos un verdadero revoltijo de palabras y barras, generalmente ininteligible.

Al menos en lo que hace a la parte visual podemos arreglar algo el desaguisado si evaluamos en la celda y columnas desbocadas la función org-table-toggle-column-width, que nos estrechará esa parte según una anchura indicada. Pero a poco que queramos editar la celda, tendremos que volverla a ensanchar (llamando a la misma función) y nos encontraremos otra vez con el galimatías. Este tipo de escenarios, por tanto, no resultan en Org ni eficientes ni productivos. Se impone alguna solución.

¿Posibles soluciones?

Hace no mucho salió este tema de las tablas díscolas y obesas en la lista de correo de Org. De hecho que se trata de una cuestión que surge cíclicamente en el mundillo orgiano, y cada vez que lo hace —como también en esta última, enésima revisión— cada cual va aportando buenamente sus imaginarias soluciones y hasta su desespero. De esta vez se propuso añadir una sintaxis extra a este tipo de tablas. A muchos no nos hizo demasiada gracia la idea, pues principalmente se abren dos problemas: a) Cómo encajar esa nueva sintaxis de manera coherente con todos los exportadores de Org; y b) Cómo encajarla a su vez con la propia vocación visual de las tablas de Org y la resistencia que va a ofrecer la ya mencionada tiranía de la línea única. Y una tercera objeción que se me ocurre: el mayor pecado de Org (o tentación al menos) sería querer ser como una traducción de LATEX, lo que lo llevaría a convertirse en algo tan complejo como LATEX, pero sin un objeto o sentido para ello. Org perdería dos propiedades que le son esenciales, y donde además se revela muy superior a otros formatos de marcas ligeras como Markdown: su ligereza y su agnosticismo de cara al formato de salida.

Entre ese debate, siempre grato y ocioso, a mí se me ocurrió otra posibilidad que parece gustó a algunos colisteros: si el problema de base es, al cabo, un problema de edición y comodidad para dicha edición, ¿por qué no llevamos esto al terreno de Org y hacemos que las celdas que tengan mucho contenido se puedan editar en un búfer dedicado, una suerte de org-edit-special pero para tablas y celdas?

Como tenía algo de tiempo libre, me dediqué a garrapatear un par de funciones en Elisp, como prueba de concepto.

Mi código

Y lo que escribí viene a ser, más o menos, lo que sigue, algo muy crudo de momento (aunque funciona), una prueba de concepto más que otra cosa. La idea viene a ser que, cuando pasamos al búfer de edición, la línea única se convierte en un párrafo editable (se le ha aplicado el fill-paragraph), pero que al volver a la celda, recupere su condición de línea única, para lo que me ha venido muy bien la función unfill-paragraph (tomada de la wiki de Emacs).

Defino además, dentro de Org, un par de macros de conveniencia que en la salida a LATEX me producirá los comandos \newline y \par:

#+MACRO: nl @@latex:\newline{}@@
#+MACRO: par @@latex:\par{}@@

Estas macro deberían colocarse allí donde queremos que en la exportación hagan un corte de línea o un nuevo párrafo. Para una versión más depurada de mi código se podrían definir nuevas marcas para integrarlas en la sintaxis de Org.

Y por último, antes de meternos en harina, tengo definida siempre esta función para simplificar las acciones de reemplazo de cadenas de texto:

(defun reemplaza (antes despues)
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward antes nil t)
      (replace-match despues t nil))))

Y empezamos. Aquí va primero la función que nos lleva al búfer de edición desde nuestra columna ofuscada mediante org-table-toggle-column-width:

(defun mi-edicion-celda ()
  (interactive)
  (if (not (equal (org-element-type (org-element-at-point)) 'table-row))
      (error "No estás en una tabla!")
    (setq mi-buf (current-buffer))
    (setq desde (save-excursion
                  (re-search-backward "|" nil t)
                  (forward-char)
                  (point)))
    (setq hasta (save-excursion
                  (re-search-forward "|" nil t)
                  (forward-char -1)
                  (point)))
    (let ((celda (buffer-substring-no-properties desde hasta)))
      (when (get-buffer "edit-celda")
        (kill-buffer "edit-celda"))
      (get-buffer-create "edit-celda")
      (set-buffer "edit-celda")
      (insert celda)
      (org-mode)
      (mark-whole-buffer)
      (org-fill-paragraph)
      (deactivate-mark)
      (reemplaza "{{{nl}}}" "{{{nl}}}\n")
      (reemplaza "{{{par}}}" "{{{par}}}\n")
      (pop-to-buffer "edit-celda"))))

Si no queremos conservar los cambios en el búfer de edición, nos bastaría con matarlo sin más. Pero si los queremos guardar, esta función nos llevará de vuelta a nuestra tabla, recompondrá la línea única y volverá a estrechar la columna:

(defun salir-edicion-celda-y-guardar ()
  (interactive)
  (reemplaza "\n\n+" "\n")
  (save-excursion
    (goto-char (point-max))
    (when (looking-at-p "^$")
      (re-search-backward ".")
      (end-of-line)
      (delete-blank-lines)
      (delete-forward-char 1)))
  (mark-whole-buffer)
  (call-interactively 'unfill-paragraph)
  (deactivate-mark)
  (setq bufer-edit (buffer-substring-no-properties (point-min) (point-max)))
  (if (window-full-width-p)
      (kill-buffer)
    (kill-buffer-and-window))
  (switch-to-buffer mi-buf)
  (save-restriction
    (narrow-to-region desde hasta)
    (delete-region (point-min) (point-max))
    (insert bufer-edit)
    (save-buffer))
  (org-table-toggle-column-width))

Y en este vídeo podemos ver el código en acción.

Publicado: 02/04/21

Última actualización: 16/08/23


Í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