Diseño de UI en un editor: cuándo la flotante estorba
Criterios para elegir el canal correcto en una interfaz modal y de teclado. El coste real de robar el foco, taxonomia de canales entre quickfix, mensajes, diagnosticos, texto virtual, divisiones y flotantes, las convenciones que la gente da por supuestas, y una tabla de decision para no construir interfaces que se acaban desactivando.
Al terminar las cuatro lecciones anteriores tienes una capacidad nueva y ningún criterio para usarla, que es exactamente la situación en la que se escriben las peores interfaces. La ventana flotante es tan agradable de construir que se convierte en la respuesta por defecto a cualquier necesidad de mostrar algo, y ese reflejo produce plugins que interrumpen la escritura para comunicar cosas que nadie pidió saber en ese instante. Neovim es un editor modal y gobernado por teclado, y eso impone restricciones de diseño que no se parecen a las de una aplicación con ratón: el foco es un recurso escaso, la interrupción tiene coste medible y el usuario llega con expectativas muy concretas sobre qué tecla hace qué. Esta lección es sobre elegir, no sobre construir.
- Evaluar el coste de una interfaz en términos de foco, interrupción y modo.
- Elegir entre
quickfix, mensajes, diagnósticos, texto virtual, divisiones y flotantes con un criterio explícito. - Aplicar las convenciones que el usuario da por supuestas en cualquier panel.
- Reconocer los síntomas de una interfaz que acabará desactivada.
El foco es el recurso caro
En una aplicación con ratón, un panel que aparece cuesta un vistazo. En un editor de teclado cuesta mucho más, porque hay tres recursos que se consumen a la vez y solo el primero es visible.
El espacio es el más obvio y el menos importante: lo que tapa la flotante deja de leerse. El foco es el crítico: si tu ventana lo roba, todas las teclas del usuario pasan a significar otra cosa hasta que vuelva, y ese regreso es trabajo que le has impuesto. El modo es el más sutil: una interfaz que aparece durante la escritura y exige modo normal para responder obliga a un cambio de modo que rompe la cadena de deshacer y el flujo de pensamiento.
De ahí sale la primera regla operativa: una flotante que roba el foco debe ser respuesta a una petición explícita del usuario. Si el usuario invocó un comando o pulsó una tecla, está esperando algo y el foco es apropiado. Si la información llegó sola —un servidor que terminó de analizar, una tarea de fondo, un aviso— robar el foco es siempre incorrecto, sin excepciones.
Si el usuario cierra tu panel sin leerlo, no es que se distraiga: es que la información no valía la interrupción. Y si la primera línea que la gente añade a su configuración es la que desactiva tu ventana, el problema no es que sea fea, es que era el canal equivocado para lo que tenías que decir.
Taxonomía de canales
Neovim ofrece seis vías de comunicación con propósitos distintos. Elegir bien es casi todo el diseño.
| Canal | Cuándo es la respuesta correcta |
|---|---|
quickfix |
un conjunto de posiciones que el usuario va a recorrer hasta agotarlo |
| lista de ubicaciones | lo mismo, pero local a una ventana y sin pisar la lista global |
| mensajes | un hecho puntual que no exige acción y puede consultarse después |
| diagnósticos | un juicio con severidad sobre un rango concreto de texto |
| texto virtual | información ambiental ligada a una línea, que acompaña sin exigir nada |
| flotante | contenido denso, pedido explícitamente, cuya vida es de segundos |
La lista quickfix está infravalorada y es la respuesta correcta con una frecuencia sorprendente. Su virtud no es cómo se ve, sino que integra tu resultado en un flujo de trabajo que el usuario ya domina: recorrer con :cnext y :cprev, abrir y cerrar la ventana, filtrar, guardar la lista, volver a una anterior del historial. Nada de eso lo tienes que escribir tú y nada de eso lo tendrá que aprender él.
Los mensajes tienen una virtud igual de olvidada: persisten. Un aviso mostrado en una flotante que se cierra sola desaparece para siempre; el mismo aviso emitido como mensaje sigue disponible en el historial cuando el usuario quiera mirarlo. Para cualquier cosa que el usuario podría querer consultar más tarde, el canal correcto es el que deja rastro.
El texto virtual ocupa el nicho de lo que debe estar visible pero no debe ser leído activamente: el valor de una variable, el tiempo de una prueba, el autor de la línea. Su cualidad definitoria es que no ocupa el sitio de nada, y por eso tolera estar siempre encendido.
El mismo resultado publicado por tres canales distintos deja claro que la diferencia no está en el dato sino en lo que le pides al usuario que haga con él.
local hallazgos = analizar(buf) -- lista de posiciones con texto
-- Agenda: el usuario los va a recorrer uno por uno
vim.fn.setqflist({}, " ", { title = "Hallazgos", items = hallazgos })
-- Hecho puntual: queda en el historial y no interrumpe
vim.notify(("%d hallazgos"):format(#hallazgos), vim.log.levels.INFO)
-- Ambiental: acompana la linea sin exigir nada
for _, h in ipairs(hallazgos) do
vim.api.nvim_buf_set_extmark(buf, ns, h.lnum - 1, 0, {
virt_text = { { h.text, "Comment" } }, virt_text_pos = "eol",
})
end
Fíjate en que ninguno de los tres abre una ventana. La lista quickfix la abre el usuario cuando quiere, con :copen, y esa diferencia —ofrecer en vez de imponer— es la que hace que una herramienta se pueda ejecutar cien veces al día sin cansar. Abrir la ventana tú mismo, sin que nadie lo haya pedido, es la versión educada del mismo error que roba el foco.
Agenda contra vistazo
Si el usuario va a visitar los resultados uno por uno, es una lista y pertenece a quickfix. Si va a mirarlos una vez y decidir, es un vistazo y puede ser una flotante.
Petición contra aviso
Lo que el usuario pidió puede tomar el foco. Lo que llegó solo, nunca. Es la regla que más errores de diseño evita por sí sola.
flowchart TB
a[Tengo algo que comunicar] --> b{Lo pidio el usuario ahora mismo}
b -->|no| c{Esta ligado a una linea concreta}
c -->|si| d[Diagnostico o texto virtual]
c -->|no| e[Mensaje que queda en el historial]
b -->|si| f{Son varias posiciones que va a recorrer}
f -->|si| g[Lista quickfix o de ubicaciones]
f -->|no| h{Lo va a consultar mas de un minuto}
h -->|si| i[Division normal]
h -->|no| j[Ventana flotante con foco]
style j fill:#a6e3a1,color:#11111b
style g fill:#89b4fa,color:#11111bLas convenciones que nadie te va a explicar
Un usuario de Neovim llega a tu panel con un modelo mental completo y no verbalizado. Cumplirlo hace que tu interfaz se sienta parte del editor; incumplirlo la hace sentir un cuerpo extraño por muy bien dibujada que esté.
En cualquier ventana efímera, q cierra. Es la convención más fuerte del ecosistema y la que más molesta romper. <Esc> cierra también si el panel no es editable. <CR> confirma o abre el elemento bajo el cursor. Los movimientos de siempre —j, k, <C-d>, <C-u>, gg, G— deben funcionar sin que hagas nada, lo que ocurre solo si tu panel es un buffer normal y no un dibujo con teclas capturadas.
El resto son propiedades del buffer más que teclas. Un panel informativo debe ser de solo lectura, no debe aparecer en la lista de buffers, no debe entrar en la lista de saltos ni contaminar el registro de ficheros recientes, y no debe ser un sitio donde el usuario aterrice por accidente al recorrer buffers. Los grupos de resaltado deben ser los del tema del usuario, no colores fijos: NormalFloat para el fondo, FloatBorder para el marco, FloatTitle para el título. Colores incrustados a mano se ven mal en la mitad de los temas y no hay forma de arreglarlos desde fuera.
-- Perfil minimo de un panel que se comporta como los del editor
vim.bo[buf].buftype = "nofile"
vim.bo[buf].bufhidden = "wipe"
vim.bo[buf].swapfile = false
vim.bo[buf].modifiable = false
vim.bo[buf].filetype = "mipanel" -- permite que el usuario lo enganche
vim.wo[win].wrap = false
vim.wo[win].cursorline = true
vim.wo[win].winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel"
Poner un filetype propio parece cosmético y es el detalle más generoso de la lista: convierte tu panel en algo sobre lo que el usuario puede escribir sus propios autocomandos y sus propias teclas sin tocar tu código.
Queda una convención que se incumple constantemente y que arruina la sensación de integración: no cambiar la posición del cursor del usuario sin motivo. Abrir un panel, mostrar información y devolver el cursor tres líneas más abajo de donde estaba es una de esas pequeñas traiciones que no se denuncian pero se recuerdan. Si tu interfaz necesita moverse por el buffer para calcular algo, guarda la vista con winsaveview y restáurala al terminar; si necesita saltar a otra posición porque el usuario lo pidió, deja constancia en la lista de saltos para que <C-o> funcione como espera.
Cuándo la flotante es la respuesta
Sí hay casos en los que ninguna alternativa sirve, y conviene nombrarlos para no acabar con la conclusión contraria. La flotante gana cuando el contenido es denso y transitorio y depende de la posición del cursor: la documentación de un símbolo, la firma de una función, la vista previa de un cambio, el diagnóstico completo de una línea cuyo texto no cabe. En todos ellos, la alternativa —abrir una división, ocupar espacio permanente, forzar un salto— sería peor.
También gana cuando la interacción es un diálogo corto y modal por naturaleza: elegir una opción, confirmar una operación destructiva, escribir un nombre. Ahí la interrupción no es un coste, es el propósito, y el foco tiene que estar en el diálogo porque no hay nada más que hacer hasta responderlo.
Y hay un tercer caso legítimo, el panel grande y efímero que sustituye a una división: un explorador de ficheros, un buscador, un registro de operaciones. La justificación es que ocupa mucho espacio pero durante poco tiempo, y una división para eso obligaría a reorganizar la disposición dos veces.
El contraste con lo que no debe ser flotante es igual de instructivo. Nada que el usuario vaya a tener abierto durante minutos debería flotar, porque tapar código de forma permanente es peor que ceder espacio ordenadamente: para eso están las divisiones, que el usuario ya sabe redimensionar, mover e igualar. Nada que llegue de forma asíncrona debería flotar, porque aparecer sin aviso encima del texto es la interrupción en estado puro. Y nada que dependa de un recorrido debería flotar, porque una flotante no tiene historial ni sabe navegar, mientras que la lista quickfix lleva treinta años haciendo exactamente eso.
Cualquier interfaz que construyas debe poder desactivarse desde la configuración del usuario, y ese interruptor debe existir desde el primer día. No es una concesión: es reconocer que tu criterio sobre qué merece atención no puede ser correcto para todo el mundo, y que la alternativa a un interruptor es que la gente desinstale la herramienta entera para librarse de una parte.
Hay una razón por la que las interfaces de un editor de teclado se juzgan con un criterio distinto al de cualquier otra aplicación, y no es el conservadurismo de sus usuarios. Es que en un editor modal el estado de la interfaz y el estado de la mente del usuario están acoplados de una manera que no ocurre en otros programas: las mismas teclas significan cosas distintas según el modo, el foco y el buffer, de modo que cualquier cambio que provoques en esas tres variables reescribe el significado de lo que el usuario está a punto de teclear. Una notificación que roba el foco en un navegador es una molestia; en un editor modal es un cambio de idioma a mitad de frase. Por eso el catálogo de canales que Vim y Neovim acumularon durante treinta años no es un montón de mecanismos redundantes que alguien no llegó a unificar: es una taxonomía de grados de intrusión, construida por acumulación empírica, donde cada canal ocupa un punto distinto en el compromiso entre visibilidad e interrupción. La lista quickfix es un flujo de trabajo, no una ventana. El texto virtual es visible sin ser leído. El mensaje es persistente sin ser visible. El diagnóstico es un juicio anclado a un rango. Y la flotante, la más potente de todas, es también la única que puede robarte el sitio, el foco y el modo a la vez, lo que la convierte en la que exige más justificación y no en la que menos. La madurez técnica en esta materia se reconoce por una inversión concreta del instinto: dejar de preguntar cómo muestro esto y empezar a preguntar qué merece esta información en la economía de atención de alguien que está pensando en otra cosa. Casi siempre la respuesta honesta es menos de lo que tu código es capaz de dibujar, y la mejor interfaz que escribirás en tu configuración será a menudo la que decidiste no abrir.
- Toma una herramienta tuya que use una flotante y reescríbela para publicar en
quickfix; usa las dos durante una semana y decide con datos. - Enumera las notificaciones que tu configuración produce sin que las pidas y clasifica cada una según la tabla de canales.
- Añade a un panel propio el perfil mínimo de opciones de esta lección, incluido un
filetypepropio, y escribe un autocomando externo que lo aproveche. - Sustituye todos los colores fijos de tus interfaces por grupos de resaltado enlazables y comprueba el resultado con dos temas distintos.
- Elige la interfaz de un plugin que hayas desactivado, diagnostica cuál de los tres costes —espacio, foco o modo— pagaba sin justificarlo, y propón el canal correcto.