Cuaderno de GNUtas

Revoltijo Elisp (III)

Una función que intercala las líneas de dos textos

Como sucede casi siempre, esta función nació de una necesidad concreta. Para la composición tipográfica de un libro en que andaba trabajando, tenía que intercalar los versos de un poema (traducción) con los de otro poema (original), de manera que el verso 1 del poema A fuera seguido por el verso 1 del poema B. Y así sucesivamente. De esta situación puntual podemos, por tanto, extraer un escenario más universal. A saber:

  1. Nos encontramos con dos textos (A y B) que tienen el mismo número de líneas (condición sine qua non para que nuestra función tenga sentido).
  2. Queremos conseguir una especie de copia/pega «inteligente», mediante el cual las líneas del texto B se intercalen con las del texto A: A1/B1, A2/B2, etc.

Aquí jugará un papel importante el alma de los Lisp, las listas, pues la gracia está en convertir ambos textos en una lista y concatenarlas. Definimos una primera función para el texto de partida (el que queremos copiar, el texto B), donde nos viene como anillo al dedo la expresión split-string que nos permite crear una lista a partir de una cadena, pero declarando qué tipo de elemento o corte ha de tenerse en cuenta para distinguir y separar los componentes de la lista. En este caso, como queremos almacenar cada línea del texto en la lista, el corte ha de ser un salto de línea (\n):

(defun lineas-a-lista ()
  "Esta primera función almacena en una lista cada línea de un
  texto marcado como elemento de la lista"
  (if (region-active-p)
      (progn
        (narrow-to-region (region-beginning) (region-end))
        (split-string (buffer-string) "\n" nil))
    (error "ninguna región marcada")))

La siguiente función se encarga de definir la variable lineas-b y darle el valor de la lista de líneas del texto B:

(defun captura-lineas-a ()
    "captura en una lista las líneas del primer texto que marcamos"
    (interactive)
    (setq lineas-b (lineas-a-lista))
    (deactivate-mark)
    (widen))

Y, por último, llegamos a la función que se encarga de intercalar en el texto A las líneas capturadas mediante la función anterior en el texto B. Generamos una tercera lista (lineas c), cuyo valor será el de las dos listas anteriores intercaladas. Aquí todo el trabajo lo hace la expresión -interleave del paquete dash (le sacamos jugo también aquí), muy útil. Ya sólo quedaría eliminar el texto A e insertar en su lugar la nueva lista, convertida en una nueva cadena.

(defun intercala-lineas ()
    "intercala líneas en el siguiente texto marcado"
    (interactive)
    (setq lineas-a (lineas-a-lista))
    (setq lineas-c (-interleave lineas-a lineas-b))
    (replace-regexp ".+" "" nil (region-beginning) (region-end))
    (deactivate-mark)
    (widen)
    (insert
     (mapconcat 'identity lineas-c "\n")))

Y como un gif vale más que mil palabras:

intercala-lineas-emacs2.gif

Publicado: 04/10/2019

Ú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