Buffers scratch: superficies de texto sin archivo
Buffers que no representan ningún fichero: qué hace exactamente el flag scratch, cómo lo definen buftype, bufhidden, swapfile y buflisted, y cómo vestirlos con filetype, nombre y teclas locales sin dejar fugas.
Casi todo lo que un plugin muestra —un listado de resultados, una previsualización, un panel de estado, la salida de un comando— vive en un buffer que no corresponde a ningún archivo del disco. Neovim llama a esa categoría buffers scratch, y no son un tipo especial de objeto: son un buffer normal con un puñado de opciones puestas de forma que el editor deje de tratarlo como un documento.
- Saber qué opciones activa exactamente el flag
scratchdenvim_create_buf. - Distinguir los valores de
buftypey elegir el correcto para cada superficie. - Controlar el ciclo de vida con
bufhidden,buflistedyswapfile. - Vestir el buffer con
filetype, nombre y teclas locales sin dejar buffers huérfanos.
Scratch no es un tipo, es un preajuste
La función nvim_create_buf(listed, scratch) recibe dos booleanos. El primero decide si el buffer aparece en :ls y en la navegación con :bnext. El segundo, pese a su nombre, no crea una clase distinta de buffer: se limita a fijar tres opciones de golpe.
local api = vim.api
-- la forma corta
local buf = api.nvim_create_buf(false, true)
-- exactamente equivalente, escrito a mano
local buf2 = api.nvim_create_buf(false, false)
vim.bo[buf2].buftype = "nofile"
vim.bo[buf2].bufhidden = "hide"
vim.bo[buf2].swapfile = false
Que sea un preajuste y no un tipo tiene una consecuencia práctica: puedes desviarte de él. Un buffer scratch al que luego le pones bufhidden a wipe sigue siendo perfectamente legítimo, y de hecho suele ser lo que quieres para superficies efímeras.
listed = false
Fuera de :ls, fuera de :bnext. El usuario no tropieza con él por accidente.
scratch = true
Aplica buftype a nofile, bufhidden a hide y desactiva el fichero de intercambio.
No es inmutable
Sigue siendo modifiable. Si quieres solo lectura, tienes que pedirlo.
El cuarteto que define el comportamiento
Cuatro opciones locales de buffer determinan cómo lo trata el editor. Se leen y escriben con vim.bo[bufnr] o, de forma más explícita, con nvim_set_option_value pasando el buffer como destino.
api.nvim_set_option_value("buftype", "nofile", { buf = buf })
api.nvim_set_option_value("bufhidden", "wipe", { buf = buf })
api.nvim_set_option_value("swapfile", false, { buf = buf })
api.nvim_set_option_value("buflisted", false, { buf = buf })
buftype declara qué representa el contenido. Vacío significa “un archivo normal”. nofile significa que el texto no corresponde a nada persistente y que :w no tiene sentido. nowrite permite un nombre de archivo real pero prohíbe escribirlo. acwrite es el caso sofisticado: el buffer no se guarda por sí solo, pero dispara BufWriteCmd para que tu código decida qué significa guardar. Los valores help, quickfix, terminal y prompt los gestiona el propio editor y no deberías fijarlos a mano.
bufhidden decide qué ocurre cuando el buffer deja de estar visible en ninguna ventana. Con hide sobrevive indefinidamente en memoria; con unload se descarga pero conserva su identidad y sus marcas; con delete se borra de la lista; con wipe desaparece por completo, incluidas marcas y variables locales. Para un panel que reconstruyes cada vez, wipe es la elección higiénica.
swapfile desactivado evita crear ficheros de intercambio para contenido que no vale la pena recuperar, y de paso evita el diálogo de fichero de intercambio existente cuando dos instancias generan el mismo nombre.
buflisted solo afecta a la visibilidad en listados y a la navegación secuencial; no cambia el ciclo de vida.
flowchart TD C[Creo el buffer] --> V[Visible en alguna ventana] V -->|se cierra la ventana| H[Deja de estar visible] H -->|bufhidden hide| M[Sigue en memoria] H -->|bufhidden unload| D[Descargado pero valido] H -->|bufhidden wipe| X[Destruido por completo] M --> R[Puedo reutilizarlo] D --> R X --> C style X fill:#f38ba8,color:#11111b style R fill:#a6e3a1,color:#11111b
Vestir la superficie
Un buffer scratch recién creado es texto plano y mudo. Tres gestos lo convierten en una interfaz.
El primero es el filetype, que dispara el autocomando FileType y con él el resaltado, sea de Treesitter o de sintaxis clásica. Puedes reutilizar un lenguaje real para colorear la salida, o inventar uno propio para que otros plugins y tus propios autocomandos puedan reconocer tu panel.
vim.bo[buf].filetype = "milateral"
El segundo es el nombre. nvim_buf_set_name da al buffer una etiqueta visible en la línea de estado y en los listados, pero falla con error si otro buffer ya tiene ese nombre exacto. En superficies que puedes abrir varias veces, incluye el identificador en el nombre.
pcall(api.nvim_buf_set_name, buf, "milateral://" .. buf)
El tercero son las teclas locales. Un panel que no se cierra con q resulta hostil, y una tecla global para eso sería una invasión. La opción buffer de vim.keymap.set limita el mapeo a ese buffer y desaparece con él.
vim.keymap.set("n", "q", function()
local win = vim.fn.bufwinid(buf)
if win ~= -1 then api.nvim_win_close(win, true) end
end, { buffer = buf, nowait = true, desc = "cerrar el panel" })
Sobre la escritura: un buffer scratch nace modifiable. Si el contenido lo genera tu código, deja modifiable desactivada de cara al usuario y ábrela solo durante el render. Y desactiva modified después de escribir, para que el editor no pregunte por cambios sin guardar.
local function render(b, lineas)
if not api.nvim_buf_is_valid(b) then return end
vim.bo[b].modifiable = true
api.nvim_buf_set_lines(b, 0, -1, false, lineas)
vim.bo[b].modifiable = false
vim.bo[b].modified = false
end
Reutilizar en lugar de acumular
El fallo más común en plugins con paneles es crear un buffer nuevo en cada invocación. Como bufhidden por defecto en scratch es hide, esos buffers no se van: se acumulan invisibles, cada uno con sus autocomandos y sus extmarks. Tras una sesión larga hay decenas.
El patrón robusto guarda el identificador en el estado del módulo y lo valida antes de reutilizarlo.
local M = { buf = nil }
function M.obtener()
if M.buf and api.nvim_buf_is_valid(M.buf) then
return M.buf
end
M.buf = api.nvim_create_buf(false, true)
vim.bo[M.buf].bufhidden = "hide"
vim.bo[M.buf].filetype = "milateral"
return M.buf
end
Si además quieres detectar el buffer desde fuera del módulo, marca su identidad con una variable de buffer en lugar de fiarte del nombre. Las variables locales sobreviven a los renombrados y desaparecen con el buffer.
vim.b[M.buf].milateral = true
local function es_mio(b)
local ok, v = pcall(function() return vim.b[b].milateral end)
return ok and v == true
end
La validación no es opcional. El usuario puede haber hecho :bwipeout sobre tu buffer, o un :%bd masivo puede habérselo llevado por delante. Un identificador guardado sin comprobar es una bomba de relojería que estalla como error críptico varios minutos después.
La tentación es ver el scratch como “un sitio donde volcar texto”. La lectura correcta es que cada opción que fijas es una promesa que le haces al resto del editor. Al poner buftype a nofile prometes que nadie debe intentar guardarlo, y a cambio el editor deja de avisar de cambios sin guardar y de bloquear la salida. Al poner bufhidden a wipe prometes que su contenido es reconstruible, y a cambio el editor te libera la memoria sin preguntar. Al ponerle un filetype propio abres una puerta pública: cualquiera puede escribir un autocomando sobre tu panel sin conocer tu API. Ese es el verdadero motivo por el que los plugins bien diseñados se integran entre sí sin acuerdos previos: no comparten funciones, comparten los mismos contratos declarados en opciones de buffer. Cuando dudes qué valor poner, no preguntes qué te conviene a ti, pregunta qué le estás prometiendo al editor.
- Crea un buffer scratch con
nvim_create_bufy comprueba con:setlocal buftype?que valenofile. - Escribe la misma configuración a mano sobre un buffer normal y verifica que se comporta igual.
- Dale un
filetypeinventado y añade un autocomandoFileTypeque activecursorlineen él. - Implementa la función
obtenercon reutilización y comprueba que abrir el panel diez veces deja un solo buffer en:ls!. - Investiga: cambia
bufhiddenentrehideywipey observa en:ls!qué sobrevive al cerrar la ventana.