Ventanas flotantes: geometría, capas y bordes
La ventana flotante como vista libre sobre un buffer. Anatomía de nvim_open_win, sistemas de referencia relativos al editor, a una ventana, al cursor o al ratón, anclaje por esquinas, dimensionado en celdas, apilado por zindex, bordes y estilo mínimo, y reconfiguración en caliente sin destruir la ventana.
Durante décadas la interfaz de Vim fue una teselación: cada ventana ocupaba su rectángulo, ninguna se solapaba con otra, y el espacio total se repartía por divisiones sucesivas. Ese modelo es honesto y predecible, pero incapaz de expresar la idea más común de la interfaz moderna: mostrar algo encima de lo que el usuario está mirando, sin desplazarlo, y retirarlo sin dejar rastro. La ventana flotante rompe la teselación. Es una ventana normal en todo lo demás —tiene un buffer, un cursor, opciones locales, autocomandos— pero su posición ya no la decide el árbol de divisiones, sino que la decides tú, en celdas, respecto a un origen que eliges. Todo el nivel 24 se apoya en esta primitiva, así que conviene entenderla con precisión antes de construir nada encima.
- Abrir una ventana flotante con
nvim_open_winy explicar la separación entre buffer y ventana. - Elegir el sistema de referencia adecuado con
relativey anclar por la esquina correcta. - Dimensionar en celdas, apilar con
zindexy dibujar bordes sin romper el estilo. - Reconfigurar la geometría en caliente con
nvim_win_set_configen vez de recrear la ventana.
Buffer y ventana son cosas distintas
La primera confusión que hay que desactivar es tratar la ventana flotante como si fuera un objeto único. No lo es: son dos objetos con ciclos de vida independientes. El buffer contiene el texto; la ventana es una vista sobre él. Abrir una flotante consiste en crear o reutilizar un buffer y pedirle a Neovim que dibuje una vista suya en unas coordenadas dadas.
-- 1. Un buffer sin fichero asociado, que Neovim descarta al cerrarse la ventana
local buf = vim.api.nvim_create_buf(false, true) -- listed = false, scratch = true
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "hola", "desde una flotante" })
-- 2. Una vista sobre ese buffer, colocada por coordenadas
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = 40,
height = 2,
row = 5,
col = 10,
style = "minimal",
border = "rounded",
})
Los dos argumentos previos a la tabla de configuración deciden más de lo que parece. El primero es el buffer a mostrar; el segundo, si la ventana debe recibir el foco al abrirse. Una flotante sin foco es perfectamente legítima y es lo que quieres para una nota informativa que no debe interrumpir la escritura: el cursor sigue donde estaba y el usuario no tiene que volver de ningún sitio.
El par de banderas del buffer también importa. Un buffer no listado no aparece en :ls ni en los selectores de buffer, y un buffer scratch nace con buftype a nofile, bufhidden a hide y sin marca de modificado. Esa combinación es la que evita que tu interfaz contamine la lista de trabajo del usuario, y saltársela es el origen de la queja clásica de que un plugin deja basura por todas partes.
El sistema de referencia lo cambia todo
La clave relative decide respecto a qué origen se interpretan row y col. No es un detalle cosmético: determina si tu ventana sobrevive a que el usuario redimensione el terminal, mueva el cursor o cambie de división.
| Valor | Origen de coordenadas | Uso natural |
|---|---|---|
editor |
esquina superior izquierda del área total | diálogos centrados, paneles grandes |
win |
esquina de una ventana concreta | anotaciones locales a una división |
cursor |
posición actual del cursor | documentación en línea, firmas |
mouse |
posición del puntero | menús contextuales |
Con relative a cursor las coordenadas se vuelven desplazamientos: row = 1 significa una línea por debajo del cursor, y row = -1, una por encima. Eso permite el gesto que todo el mundo espera de una ayuda contextual, que es aparecer pegada a lo que estás mirando.
La segunda mitad del problema es el anclaje. Por defecto, row y col describen dónde va la esquina superior izquierda de la flotante. Si la ventana debe aparecer encima del cursor, esa convención es la equivocada: querrías fijar la esquina inferior. Para eso está anchor, con cuatro valores que combinan norte o sur con oeste o este.
-- Una ventana que crece hacia arriba desde el cursor:
-- fijamos su esquina inferior izquierda una linea por encima
vim.api.nvim_open_win(buf, false, {
relative = "cursor",
anchor = "SW",
row = -1,
col = 0,
width = 50,
height = 8,
})
No existe una clave para centrar. Se calcula: col es la mitad del ancho disponible menos la mitad del ancho de la ventana, y lo mismo con las filas. El ancho disponible sale de vim.o.columns y el alto útil de vim.o.lines menos el espacio que ocupan la línea de comandos y la de estado. Olvidar ese descuento produce ventanas que asoman por debajo del borde inferior.
Tamaño, capas y bordes
Las dimensiones se expresan en celdas de la cuadrícula del terminal, y describen el área de texto: el borde se dibuja fuera de ese rectángulo. Una ventana de ancho 40 con borde ocupa 42 columnas en pantalla. Ignorar ese detalle es lo que hace que las flotantes centradas queden dos celdas descuadradas.
La clave zindex gobierna el apilado cuando dos flotantes se solapan: gana la de valor mayor. Neovim reserva rangos por convención —las ventanas normales viven en el nivel más bajo, los menús de completado y los mensajes ocupan valores altos— y tu interfaz debe situarse por debajo de esos elementos del sistema salvo que tengas una razón deliberada para taparlos.
border acepta nombres predefinidos como single, double, rounded, solid y shadow, y también una lista de ocho caracteres para dibujarlo a mano, opcionalmente con un grupo de resaltado por pieza. Añadir un borde es lo que convierte un rectángulo de texto flotante en algo que se lee como una ventana, porque delimita dónde acaba tu interfaz y empieza el código de debajo.
-- Borde manual: esquinas y lados en orden, empezando arriba a la izquierda
local borde = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }
-- Titulo en el borde superior, alineado al centro
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor", width = 60, height = 12, row = 3, col = 10,
style = "minimal", border = borde,
title = " Resultados ", title_pos = "center",
zindex = 50,
})
vim.wo[win].winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder"
El valor minimal de la clave style desactiva de golpe los adornos que no tienen sentido en un panel efímero: números de línea, columna de signos, plegado y resaltado de la línea del cursor. Es casi siempre lo que quieres, y apagar esas opciones una por una después es trabajo desperdiciado.
flowchart TB
a[Buffer scratch con texto] --> b[nvim_open_win con tabla de config]
b --> c{relative}
c -->|editor| d[Coordenadas absolutas en la cuadricula]
c -->|cursor| e[Desplazamiento respecto al cursor]
c -->|win| f[Relativo a otra ventana]
d --> g[anchor decide que esquina se fija]
e --> g
f --> g
g --> h[zindex resuelve el solape]
h --> i[border y title enmarcan la vista]
style b fill:#89b4fa,color:#11111b
style i fill:#a6e3a1,color:#11111bReconfigurar en caliente
Una flotante no es inmutable. nvim_win_set_config acepta la misma tabla que la apertura y la aplica a una ventana existente, lo que permite moverla, redimensionarla o cambiarle el borde sin destruirla. Esto no es un lujo: recrear la ventana pierde el cursor, el desplazamiento vertical y las opciones locales, y produce un parpadeo visible.
-- Seguir al cursor sin recrear nada
vim.api.nvim_win_set_config(win, { relative = "cursor", row = 1, col = 0 })
-- Leer la geometria vigente antes de decidir
local cfg = vim.api.nvim_win_get_config(win)
if cfg.relative ~= "" then -- cadena vacia = ventana normal
vim.api.nvim_win_set_config(win, { width = math.min(cfg.width + 10, vim.o.columns - 4) })
end
Esa comprobación de cfg.relative es la forma canónica de distinguir una flotante de una ventana normal, y la vas a necesitar en cuanto escribas código que recorra las ventanas de una pestaña. También es la vía para el gesto contrario: pasar relative a la cadena vacía convierte la flotante en una división corriente, algo que algunos plugins ofrecen para promocionar una vista previa a ventana permanente.
Dos claves más completan el repertorio y resuelven problemas concretos. focusable a falso hace que la ventana quede fuera del recorrido de <C-w>w, lo que evita que el usuario aterrice por accidente en un panel puramente informativo mientras cambia de división. Y noautocmd a verdadero suprime los eventos de entrada y salida durante la apertura, que es lo que necesitas cuando abres una flotante desde dentro de un autocomando y quieres evitar una recursión.
Conviene añadir además una defensa que casi nadie escribe la primera vez: comprobar que las dimensiones caben antes de llamar a la función. Una altura mayor que las filas disponibles no se recorta sola, produce un error, y ese error dentro de un manejador asíncrono aparece como un mensaje rojo sin contexto varios segundos después de la acción que lo causó.
Detente en lo que significa que Neovim tardara hasta 2019 en tener ventanas flotantes, porque la demora no fue técnica sino conceptual. El modelo clásico de Vim asume que la pantalla es una partición: cada píxel pertenece a exactamente un contexto, y mostrar algo nuevo obliga a quitarle sitio a algo viejo. Esa restricción no era un descuido, era una postura sobre la atención del usuario: si algo merece verse, merece espacio propio y permanente. Funcionó mientras el editor era un editor. Dejó de funcionar cuando el editor se convirtió en el cliente de una constelación de servicios asíncronos —servidores de lenguaje, motores de búsqueda, sistemas de control de versiones, modelos de lenguaje— cuyas respuestas son por naturaleza transitorias, contextuales y ligadas a una posición concreta del texto. La firma de la función que estás escribiendo no merece un rectángulo permanente; merece existir durante tres segundos, junto al cursor, y desaparecer. La flotante es la admisión de que la interfaz tiene profundidad: no un plano dividido, sino un montón de capas donde lo efímero se superpone a lo estable y se retira dejándolo intacto. Y hay una decisión de diseño en Neovim que merece admiración explícita: la capa nueva no introdujo un tipo de objeto nuevo. Una flotante es una ventana, con el mismo buffer, los mismos autocomandos, las mismas opciones locales y las mismas teclas. Eso significa que todo lo que ya sabías del editor sigue siendo cierto dentro de ella, que los plugins existentes funcionan sin cambios, y que la complejidad añadida al núcleo se reduce a una tabla de geometría. Compáralo con la alternativa —un subsistema paralelo de widgets, con su propio modelo de eventos y su propia forma de recibir teclas— y entenderás por qué esta primitiva bastó para que floreciera una generación entera de interfaces sin que el editor dejara de ser el mismo editor.
- Abre una flotante centrada de 60 por 15 sobre un buffer scratch y verifica que el borde no se sale de la pantalla al reducir el terminal a 70 columnas.
- Repite el ejercicio con
relativeacursoryanchorenSW, y comprueba que la ventana aparece encima del cursor incluso en la última línea visible. - Abre dos flotantes solapadas con
zindexdistintos, intercámbialos connvim_win_set_configy observa cuál queda encima. - Dibuja un borde manual con los ocho caracteres y aplica un grupo de resaltado propio con
winhighlightpara el marco y otro para el fondo. - Escribe una función que recorra las ventanas de la pestaña actual y devuelva solo las flotantes, usando
nvim_win_get_configy la comprobación derelative.