Buffers scratch: superficies de texto sin archivo
Buffers que no representan ningún fichero: qué opciones activa exactamente el flag scratch de nvim_create_buf, el cuarteto buftype, bufhidden, swapfile y buflisted que define el contrato con el editor, acwrite y BufWriteCmd para redefinir qué significa guardar, y cómo vestir la superficie con filetype, nombre y teclas locales sin acumular buffers huérfanos.
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. Entender exactamente qué promete cada opción es lo que separa un panel que convive bien con el resto del editor de uno que pregunta por cambios sin guardar, deja ficheros de intercambio por el disco o se acumula invisible hasta que :ls! da miedo.
- Saber qué opciones activa exactamente el flag
scratchdenvim_create_buf, y qué añade por su cuenta. - Distinguir los valores de
buftype—incluidoacwriteconBufWriteCmd— y elegir el correcto para cada superficie. - Controlar el ciclo de vida con
bufhidden,buflistedyswapfile. - Vestir el buffer con
filetype, nombre y teclas locales, y reutilizarlo en vez de acumular huérfanos.
Scratch no es un tipo, es un preajuste
La función nvim_create_buf(listed, scratch) recibe dos booleanos. El primero fija buflisted: decide si el buffer aparece en :ls, en la navegación con :bnext y en el picker que montaste en la lección 4.1. El segundo, pese a su nombre, no crea una clase distinta de buffer: aplica el preajuste que :help scratch-buffer documenta —buftype a nofile, bufhidden a hide, swapfile desactivado— y añade dos detalles propios de la función: el buffer nace siempre con modified a falso y con modeline desactivada, para que ningún contenido que vuelques en él pueda colar una línea de modo.
local api = vim.api-- la forma cortalocal buf = api.nvim_create_buf(false, true)-- practicamente equivalente, escrito a manolocal buf2 = api.nvim_create_buf(false, false)vim.bo[buf2].buftype = "nofile"vim.bo[buf2].bufhidden = "hide"vim.bo[buf2].swapfile = falsevim.bo[buf2].modeline = falseQue 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 que se reconstruyen en cada invocación.
listed = false
Fuera de :ls, de :bnext y del picker. El usuario no tropieza con él por accidente; sigue visible en :ls!.
scratch = true
buftype a nofile, bufhidden a hide, sin fichero de intercambio. Además: sin modeline y nunca marcado como modificado al nacer.
No es inmutable
Sigue siendo modifiable. Si quieres que el usuario no lo edite, tienes que pedirlo tú, como viste en la lección 10.1.
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, con ámbito explícito, con nvim_set_option_value pasando el buffer como destino —las dos formas que ya conoces de la lección 8.2—.
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: :w a secas falla con E382 y el buffer nunca se considera modificado de cara a la salida del editor. nowrite es parecido pero el nombre sí se trata como un fichero real: sirve para vistas de solo lectura de algo que existe. Y acwrite es el caso sofisticado que merece su propia sección más abajo. Los valores help, quickfix, terminal y prompt los gestiona el propio editor —los dos últimos los explorarás en el Nivel 13— y no debes fijarlos a mano.
bufhidden decide qué ocurre cuando el buffer deja de estar visible en ninguna ventana. Su valor por defecto es la cadena vacía, que delega en la opción global hidden; los otros cuatro valores toman la decisión localmente. Con hide sobrevive indefinidamente en memoria; con unload se descarga —recuerda la lección anterior: válido pero con cero líneas— conservando identidad y opciones; con delete se descarga y sale de la lista, como un :bdelete; con wipe desaparece por completo, marcas y variables incluidas, como un :bwipeout.
swapfile desactivado evita crear ficheros de intercambio para contenido que no vale la pena recuperar, y de paso elimina el diálogo de «swap file exists» cuando dos instancias de Neovim generan una superficie con el mismo nombre.
buflisted solo afecta a la visibilidad en listados y a la navegación secuencial; no cambia el ciclo de vida.
acwrite: redefinir qué significa guardar
Con buftype a acwrite, el buffer no sabe guardarse solo: cuando el usuario ejecuta :w, Neovim dispara el autocomando BufWriteCmd y delega en él la escritura entera. Tu callback decide qué significa guardar —enviar el texto por HTTP, aplicarlo a una base de datos, confirmar una edición en un panel de renombrado masivo— y al terminar marca el buffer como no modificado.
vim.bo[buf].buftype = "acwrite"api.nvim_buf_set_name(buf, "consulta://usuarios")api.nvim_create_autocmd("BufWriteCmd", { buffer = buf, callback = function() local lineas = api.nvim_buf_get_lines(buf, 0, -1, false) aplicar(lineas) -- tu definicion de "guardar" vim.bo[buf].modified = false -- sin esto, :wq seguira quejandose end,})Este es el mecanismo detrás de herramientas tipo «edita los resultados del grep y escribe para aplicar»: la superficie parece un fichero, el flujo de guardar es el de siempre, y la semántica es tuya. Los eventos Cmd que sustituyen a una operación en vez de acompañarla los viste en el Nivel 9; BufWriteCmd es el más útil de la familia.
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 una salida —filetype = "diff" para un parche, "markdown" para documentación— o inventar uno propio para que tus autocomandos y los de cualquier usuario puedan reconocer tu panel sin conocer tu código.
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 lanza un error si otro buffer ya tiene ese nombre exacto. En superficies que pueden existir varias veces, incluye el identificador en el nombre; el prefijo con pseudo-esquema es una convención extendida que además deja claro que aquello no es una ruta.
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 —que estudiarás a fondo en el Nivel 11— 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) endend, { buffer = buf, nowait = true, desc = "cerrar el panel" })Sobre la escritura: un buffer scratch nace modifiable. Si el contenido lo genera tu código, ciérralo de cara al usuario y ábrelo solo durante el render, con el patrón de la lección 10.1. Y desactiva modified después de escribir: aunque nofile ya evita los avisos al salir, otros valores de buftype no lo hacen, y dejar la marca limpia es gratis.
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 = falseendEl nombre no identifica: la variable local sí
Si necesitas detectar tu panel desde fuera del módulo —un autocomando global, otro plugin—, no compares nombres: el usuario puede renombrar, y dos instancias chocan. Marca la identidad con una variable de buffer, que sobrevive a los renombrados y muere con el buffer:
vim.b[buf].milateral = truelocal function es_mio(b)return vim.b[b].milateral == trueendLas variables de ámbito b: las conociste en la lección 8.3; esta es su aplicación más útil en plugins.
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 el preajuste scratch deja bufhidden en hide, esos buffers no mueren al cerrar su ventana: se acumulan invisibles, cada uno con sus autocomandos, sus teclas y sus extmarks. Tras una sesión larga hay decenas, y cada uno cuesta memoria y trabajo en cada evento.
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].filetype = "milateral" vim.b[M.buf].milateral = true return M.bufendLa 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, lejos del código que la armó.
Un buffer scratch es un contrato, no un contenedor
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, y el editor paga cada promesa con un comportamiento. 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 el contenido es reconstruible, y a cambio te libera la memoria sin preguntar. Al declarar acwrite prometes que tú sabes qué significa guardar, y a cambio recibes el :w del usuario como un evento tuyo. 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, legibles por cualquiera con un :setlocal buftype?. Cuando dudes qué valor poner, no preguntes qué te conviene a ti: pregunta qué le estás prometiendo al editor, y si vas a poder cumplirlo.
Debe responder nofile, hide y false: las tres opciones del preajuste documentado en :help scratch-buffer.
E382: Cannot write, 'buftype' option is set. El contrato en acción: declaraste que el contenido no corresponde a nada persistente y el editor te toma la palabra.
La exclamación muestra también los no listados. Cada línea con u delante es un buffer que algún plugin creó con listed = false; si ves decenas del mismo, ese plugin no reutiliza.
Al perder su última ventana, el buffer desaparece por completo, como con :bwipeout. Si tu módulo guarda el id, la validación con is_valid lo detectará en la siguiente invocación.
Tu primera superficie sin archivo
- Crea un buffer scratch con
nvim_create_buf(false, true), muéstralo en una división y comprueba con:setlocal buftype? bufhidden? swapfile?los tres valores del preajuste. - Reconstruye la misma configuración a mano sobre un buffer normal y verifica que se comporta igual, incluido el
E382al intentar:w. - Dale un
filetypeinventado y añade un autocomandoFileTypeque activecursorlineen él: acabas de integrar tu panel con el sistema de eventos sin tocar tu propio código. - Implementa la función
obtenercon reutilización y comprueba que abrir el panel diez veces deja un solo buffer en:ls!. - Cambia
bufhiddenentrehideywipe, cierra la ventana en cada caso y observa en:ls!qué sobrevive. - Extra: crea un buffer
acwritecon unBufWriteCmdque imprima las líneas convim.printy limpiemodified; comprueba que:wejecuta tu callback y que:wqsale sin quejarse.