Lidiando con el portapapeles en Nvim+Linux+Wayland
Por Arrecio
Cuando hablamos de portapapeles en Linux hablamos de PRIMARY y de CLIPBOARD. Las operaciones copy/paste son utilizadas masivamente por los usuarios de los sistemas informáticos y son esenciales especialmente para desarrolladores. Su comportamiento en entornos gráficos sigue una serie de principios establecidos y discutidos por freedesktop.org que habla también de SECUNDARY. Cada una de estas variables pueden contener datos distintos los cuales no tienen porqué ser texto plano sino cualquier otro tipo de dato asociado a su MIME/Type.
Establecidos los principios, los servidores gráficos Xorg y Wayland deben implementar su propia gestión del portapapeles.
Aunque desde freedesktop indican que primary funciona de manera prácticamente transparente, copiando automáticamente las selecciones y pegando con el botón dentral del ratón, la realidad es que esto no parece funcionar de tal manera, al menos para mi ya que mi entorno tiene otros usos para ese botón central.
Sea como fuere mientras en Windows se trabaja con un "único portapapeles" en Linux esto no sucede y lo que me ha llevado a enfrentar algún problema por incoherencias con el pegado desde el mismo cuando uso Nvim.
Lo que me ha llevado a escribir esto
Si dentro de Nvim seleccionas texto y pulsas botón derecho del ratón obtienes opciones para el manejo del copiado y pegado de texto. Esto funciona correctamente, si te diriges a otra aplicación como por ejemplo el navegador Firefox, y copias texto con Ctrl-C y luego lo pegas con Ctrl-V o Shift-Ins en la ventana de ese navegador funciona correctamente, cuando lo pegas en con el menu contextual de Nvim se pega correctamente y si lo haces con Shift-Ins también.
En Nvim Ctrl-V tiene otras funciones, la combinación para el pegado es
Ctrl-Shift-Vpero Shift-Ins realiza el mismo comportamiento y al menos a mi me resulta más cómoda.
Si vuelvo a seleccionar algo en Nvim y usamos el botón derecho para copiar y posteriormente pegar, el contenido del portapapeles parece actualizado, pero si vuelves a usar Shift-Ins se pega en mi caso el contenido copiado del navegador.
Lo que a mi me sucede:
- Copia un texto del navegador Firefox ya sea con Ctrl-C o con la opción de copiar del menu contextual, lo llamo [Firefox].
- Copio un texto del buffer de Nvim utilizando el menu contextual, lo llamo [Nvim].
- Si pego con el botón derecho en Nvim obtengo [Nvim], si lo hago con Shift-Ins obtengo [Firefox].
- Si vuelvo a Firefox y pego con el menu contextual obtengo [Nvim] y si pego con Ctrl-V o Shift-Ins pego igualmente [Nvim].
En Nvim existe un lugar propio donde alojar datos para copiar y pegar, algo que funciona como un portapapeles interno. Esto se hace con las operaciones yank, delete y paste. Para hacer que Nvim utilice el portapapeles del sistema en lugar del interno se puede configurar con vim.opt.clipboard = "unnamedplus". Más información con :h clipboard en Nvim.
Al final el comportamiento esperado según lo dicho por freedesktop queda en manos de quien implementa el comportamiento de los menús contextuales y las respuestas a la pulsación de las teclas.
Para profundizar en el tema en el siguiente enlace podemos ver como Wayland opera con la Primary selection y la documentación del ext data control que es el encargado de operar con el portapapeles
Mi gestor de portapapeles: wl-clipboard
Como gestor de portapapeles en Wayland uso wl-clipboard el cual instalo en mi Arch con sudo pacman -S wl-clipboard.
Este paquete lo que hace es "engancharse" a las operaciones de copiado y pegado de Wayland enviando el contenido a wl-copy y tratándolo de obtener con wl-paste.
Podemos ver el contenido del portapeles con los comandos wl-paste para ver el contenido de CLIPBOARD o wl-paste --primary para ver el contenido de PRIMARY.
Reproduciendo los pasos que indiqué en el apartado enterior y usando después los comandos dados se sacan las siguientes conclusiones.
- Lo copiado en Firefox (y probablemente en cualquier otra aplicación GTK) acaba tanto en PRIMARY como en CLIPBOARD.
- Lo copiado en Nvim con el menu contextual se copia únicamente a CLIPBOARD.
- Lo pegado en Nvim con el menu contextual es lo que existe en CLIPBOARD, mientras que lo pegado con Shift-Ins es el contenido de PRIMARY.
- Lo pegado en Firefox es el contenido de CLIPBOARD.
Hablando de otras aplicaciones por ejemplo mi aplicación para termina es WezTerm permite copiar seleccionando y utilizando Ctrl-C o simplemente haciendo click con el botón derecho y el comportamiento por defecto para esta operación copia la selección tanto en PRIMARY como en CLIPBOARD aunque puede establecerse otro comportamiento mediante configuración eligiendo únicamente uno de los dos.
Para no fallar usar Ctrl-R
Para evitar comportamientos extraños una opción sería forzar una sincronización permanente para lo que necesitaríamos ejecutar automáticamente algunas llamadas a wl-copy en segundo plano, al menos si usas wl-clipboard.
En mi caso pienso que lo mejor es lidiar con ello. Una vez que conoces la existencia de dos portapapeles distintos, o como prefieras llamarlos, todo resulta un poco más sencillo de manejar.
En el caso de Nvim hay que saber que Shift-Ins pega desde PRIMARY y si la aplicación desde la que copiaste utiliza únicamente CLIPBOARD tendrás que usar el menu contextual para pegar y en cualquier caso siempre está la opción de usar Ctrl-R en modo inserción (copiar desde registro, :h i_CTRL-R) y decidir qué insertar, ofreciéndose una diversidad de opciones entre las que estan la copia desde el proveedor de portapapeles del sistema. Con la tecla + inserta texto desde CLIPBOARD y con la * desde PRIMARY. Respecto a ese portapapeles interno del que hablábamos las tecla " inserta su contenido.
Tanto + como * se consideran registros. Con Ctrl-R en modo inserción realizamos operaciones de lectura sobre los mismos y los insertamos en el lugar del cursor. Para realizar la escritura se debe pulsar " en modo visual tras lo que obtendremos la lista de registros, seleccionado el registro correspondiente este quedará a la espera de datos, con la tecla y podremos insertarles el texto seleccionado en ese modo visual. Vimos que Shift-Ins por defecto copiaba del PRIMARY y la realidad es que desconozco una combinación de teclas para copiar al PRIMARY (aunque podría crearla). Con "*y haces esa operación.
La modificación del contenido de ambos registros se realiza mediante el proveedor de clipboard o portapapeles el cual tiene inicialmente un comportamiento por defecto en función del proveedor de portapapeles del sistema detectado pero que puede sobreescribirse mediante la definición de la variable clipboard. Para cambiar el comportamiento de manera global se puede hacer por ejemplo lo siguiente:
vim.g.clipboard = {
name = "wl-clipboard",
copy = {
["+"] = 'wl-copy --foreground --type text/plain',
["*"] = 'wl-copy --foreground --primary --type text/plain',
},
paste = {
["+"] = (function()
return vim.fn.systemlist('wl-paste --no-newline|sed -e "s/\r$//"', {''}, 1) -- '1' keeps empty lines
end),
["*"] = (function()
return vim.fn.systemlist('wl-paste --primary --no-newline|sed -e "s/\r$//"', {''}, 1)
end),
},
cache_enabled = true
}
Con ello cambiamos el comportamiento de la escritura y la escritura en los registros. Si hacemos :h g:clipboard encontramos información sobre como configurar el proveedor de portapapeles.
Solución particular para Nvim
Por otro lado si queremos cambiar el comportamiento de Ctrl-Shift-V, y de paso el de Ctrl-Ins, podemos modificar otra variable global llamada paste (ver h: paste). Modificando ese objeto (ver :h vim.paste()) podemos solucionar el problema para Nvim forzando que siempre se utilice el proveedor de CLIPBOARD. En mi caso al utilizar wl-clipboard obtengo el contenido con la llamada al comando del sistema wl-paste sin argumentos (con --primary obtendría el contenido de PRIMARY).
vim.paste = function()
clipboard = vim.fn.system('wl-paste')
lines = {}
for s in clipboard:gmatch("[^\r\n]+") do
table.insert(lines, s)
end
vim.api.nvim_put(lines, 'c', true, true)
end
Con esta modificación Shift-Ins insertará siempre lo que devuelva wl-paste y no únicamente cuando se trate de contenido catalogado como texto plano. Esto se puede evitar con la comprobación inicial del contenido buscando text/plain en el resultado de wl-paste --list.