Diseñar tu vocabulario: prefijos, colisiones y documentación
Un conjunto de atajos no es un lenguaje. Convenciones de prefijos y espacios de nombres, cómo auditar qué teclas están realmente libres tras los mapeos nativos que Neovim añade de fábrica, la trampa del retardo por prefijo compartido, y cómo generar una chuleta a partir de tu propia tabla de mapeos.
Llegado este punto sabes crear mapeos, comandos y operadores. El problema deja de ser técnico y pasa a ser de diseño de lenguaje: cincuenta atajos sin sistema son peores que quince con gramática, porque los primeros hay que recordarlos uno a uno y los segundos se deducen. Esta lección trata la parte que ninguna documentación de plugin te va a enseñar.
- Estructurar tus mapeos con prefijos que formen un espacio de nombres deducible en lugar de memorizable.
- Auditar qué teclas están realmente libres, contando los mapeos que Neovim instala de fábrica.
- Evitar el retardo por prefijo compartido y entender el papel de
timeoutlenyttimeoutlen. - Documentar el vocabulario con
descy generar una chuleta desde la propia tabla de mapeos.
Prefijos: construir un espacio de nombres
Un prefijo convierte una tecla en un dominio. La convención que mejor escala usa dos niveles y una regla fija: el primer carácter tras <leader> nombra el dominio, el segundo nombra la acción dentro de ese dominio.
| Prefijo | Dominio | Ejemplos |
|---|---|---|
<leader>f |
Buscar archivos y texto | <leader>ff, <leader>fg, <leader>fr |
<leader>g |
Git | <leader>gs, <leader>gb, <leader>gd |
<leader>c |
Código y LSP | <leader>cf, <leader>cr, <leader>ca |
<leader>b |
Buffers | <leader>bd, <leader>bn |
<leader>u |
Interruptores | <leader>uw, <leader>un |
<leader>x |
Diagnósticos y listas | <leader>xx, <leader>xq |
Junto a <leader> conviven tres espacios de nombres nativos que deberías respetar en lugar de reinventar. Los corchetes [ y ] significan anterior y siguiente dentro de una secuencia, y Neovim ya trae de serie [q y ]q para la lista de correcciones, [b y ]b para buffers, [d y ]d para diagnósticos, [c y ]c para trozos de diferencia. La tecla g es el cajón de acciones extendidas, y z gobierna pliegues, desplazamiento y ortografía. Un mapeo tuyo que respete estas convenciones se aprende solo; uno que las contradiga te costará una fricción diaria.
<leader>bd se lee “buffer, delete” y <leader>gs se lee “git, status”. Fija ese orden y no lo rompas jamás: en cuanto un mapeo tuyo invierte el criterio, pierdes la capacidad de deducir atajos que nunca aprendiste, que es justamente lo que un espacio de nombres te compra.
Qué teclas están libres de verdad
Neovim instala mapeos por defecto que crecen con cada versión, y la lista sigue en :h default-mappings. El espacio gr está ocupado por las acciones de LSP: grn renombra, gra ejecuta una acción de código, grr lista referencias, gri va a la implementación y grt al tipo. Además K muestra documentación flotante y gO lista los símbolos del documento. Antes de reclamar cualquier tecla, la pregunta correcta no es “me parece que está libre” sino esta:
:verbose nmap gs → dónde se fijó por última vez ese mapeo
:map <leader>f → todo lo que cuelga de ese prefijo
:h default-mappings → qué reclama Neovim de fábrica
:verbose te dice el archivo y la línea exactos donde se fijó el mapeo por última vez, y resuelve en segundos el misterio de qué plugin te ha robado una tecla. La versión programática es más potente, porque puedes ejecutarla al arrancar y convertir una colisión en un aviso.
local function ocupada(modo, lhs)
local objetivo = vim.fn.keytrans(vim.keycode(lhs))
for _, m in ipairs(vim.api.nvim_get_keymap(modo)) do
if vim.fn.keytrans(m.lhs) == objetivo then
return m.desc or m.rhs or 'mapeo sin descripción'
end
end
end
print(ocupada('n', 'grn'))
La opción unique = true hace lo mismo de forma declarativa: si el mapeo ya existe, el arranque falla con E227 en lugar de sobrescribir en silencio. Mientras diseñas tu vocabulario es la red de seguridad más barata que existe. Y si decides quedarte con una tecla nativa, la forma limpia de liberarla es vim.keymap.del, no sobrescribirla y olvidarlo.
Territorio ocupado
s y S los reclaman los plugins de salto y de delimitadores, gr pertenece al LSP, Q reproduce el último registro grabado y Y copia hasta el final de línea. Reclamarlos es legítimo, pero debe ser una decisión consciente y anotada.
Territorio cómodo y libre
<leader> con cualquier segunda tecla, los pares con [ y ] que no colisionen con lo nativo, y combinaciones bajo g de dos letras que no empiecen por r. Ahí es donde debe crecer tu vocabulario.
El retardo por prefijo compartido
Cuando un mapeo es prefijo estricto de otro, Neovim no puede saber si has terminado de teclear y espera timeoutlen milisegundos antes de decidir. Si mapeas a la vez <leader>b y <leader>bd, cada pulsación del primero arrastra medio segundo de parálisis. La regla de diseño es sencilla y no admite excepciones cómodas: los prefijos son ramas y los mapeos son hojas; ninguna rama puede ser también una hoja.
flowchart TB R[Tecla lider] --> B[Rama b de buffers] R --> G[Rama g de git] B --> B1[Hoja bd cerrar buffer] B --> B2[Hoja bn siguiente buffer] G --> G1[Hoja gs estado] G --> G2[Hoja gd diferencia] R --> MAL[Hoja b directa provoca espera] style MAL fill:#f38ba8,color:#11111b style B1 fill:#a6e3a1,color:#11111b style G1 fill:#a6e3a1,color:#11111b
El mismo fenómeno tiene una variante que muerde en modo insertar. Si mapeas jk para salir a normal, cada j que teclees quedará retenido hasta que expire el temporizador o llegue otra tecla; en texto en castellano, donde j aparece con frecuencia, la latencia es perceptible. Y no confundas timeoutlen, que gobierna la ambigüedad entre mapeos, con ttimeoutlen, que gobierna la espera de las secuencias de escape del terminal y debe ser muy pequeña para que <Esc> responda al instante.
vim.o.timeout = true
vim.o.timeoutlen = 400 -- ambigüedad entre mapeos tuyos
vim.o.ttimeoutlen = 10 -- códigos del terminal: cuanto más bajo, mejor
Para mapeos locales de buffer que compiten con un prefijo global, nowait = true corta la espera de raíz y resuelve el caso de las ventanas efímeras, donde q debe cerrar de inmediato aunque exista algún q seguido de más teclas en el ámbito global.
Documentar lo que has construido
Un vocabulario sin documentación se erosiona: los atajos que usas a diario sobreviven, y los quincenales se olvidan y se vuelven espacio muerto. La disciplina mínima es desc en absolutamente todos los mapeos, porque convierte tu tabla en una base de datos legible por which-key, por la ayuda y por tus propios scripts.
vim.api.nvim_create_user_command('Chuleta', function()
local filas = { '| Modo | Tecla | Acción |', '|---|---|---|' }
for _, modo in ipairs({ 'n', 'x', 'o', 'i', 't' }) do
for _, m in ipairs(vim.api.nvim_get_keymap(modo)) do
if m.desc and m.desc ~= '' then
filas[#filas + 1] = ('| %s | %s | %s |'):format(modo, vim.fn.keytrans(m.lhs), m.desc)
end
end
end
vim.cmd('enew')
vim.bo.filetype = 'markdown'
vim.bo.buftype = 'nofile'
vim.api.nvim_buf_set_lines(0, 0, -1, false, filas)
end, { desc = 'Volcar todos los atajos documentados a un buffer' })
Ese comando de veinte líneas produce una chuleta que nunca se desactualiza, porque no describe lo que creías haber configurado sino lo que hay cargado ahora mismo. Invierte el sentido habitual de la documentación: en lugar de escribir un archivo que envejece, interrogas al sistema.
El error de perspectiva que arruina las configuraciones grandes es tratarlas como un artefacto personal y por tanto exento de las reglas que aplicarías a cualquier interfaz pública. Pero un vocabulario de atajos cumple todas las condiciones de una API: tiene usuarios —tú, en distintos momentos y con distintos grados de memoria—, tiene un coste de aprendizaje, tiene compatibilidad hacia atrás que romper, y tiene un espacio de nombres finito que se satura. De ahí se derivan consecuencias concretas y poco intuitivas. La primera es que la consistencia vale más que la optimalidad local: un atajo ligeramente peor pero que sigue el patrón del resto se recuerda, y uno perfecto pero excepcional se olvida, porque el coste dominante no es pulsar la tecla sino recuperarla de la memoria. La segunda es que existe un umbral de amortización: aprender un atajo cuesta del orden de decenas de repeticiones deliberadas, así que una acción que ejecutas dos veces al mes nunca amortiza una tecla, y su sitio natural es un comando de usuario con nombre descriptivo y completado, no una pulsación que tendrás que buscar cada vez. La tercera es que la escasez es real: el espacio de teclas cómodas es diminuto y Neovim reclama parte de él en cada versión, de modo que gastar s o gr en un capricho hoy te obliga a un realojo doloroso mañana. La cuarta, y la más incómoda, es que un vocabulario tiene que poder encogerse: revisar cada cierto tiempo qué mapeos no has usado y borrarlos es tan valioso como añadirlos, porque cada atajo muerto consume espacio de nombres, alarga los menús de which-key y ensucia la deducción. Diseña por tanto como diseñarías una biblioteca que otros van a usar: dominios coherentes, nombres deducibles, documentación generada del propio sistema y una poda periódica. La diferencia entre una configuración que se disfruta a los tres años y una que se abandona a los seis meses casi nunca está en los plugins elegidos, sino en si alguien trató sus teclas como un lenguaje o como un cajón.
Organiza tus mapeos en dominios de dos niveles con el sustantivo delante del verbo, y respeta los espacios nativos: [ y ] para anterior y siguiente, g para acciones extendidas, z para pliegues y ortografía. Antes de reclamar una tecla consúltala con :verbose nmap o con nvim_get_keymap, recordando que gr pertenece al LSP desde las versiones recientes. Ningún prefijo puede ser también un mapeo completo, o pagarás timeoutlen en cada pulsación; ttimeoutlen es otra cosa y debe ser mínimo. Pon desc en todo, usa unique mientras diseñas, nowait en mapeos locales, y genera la chuleta desde la tabla real en lugar de escribirla a mano.
- Vuelca todos tus mapeos con
desca un buffer markdown y cuenta cuántos carecen de descripción; corrígelos. - Busca en tu configuración algún prefijo que sea también un mapeo completo, mide el retardo subiendo
timeoutlena 2000 y luego elimina la ambigüedad. - Comprueba con
:verbose nmapquién posees,gs,gry<C-s>en tu instalación actual y decide si alguna colisión merece un realojo. - Reescribe tus mapeos de un dominio entero para que sigan el patrón sustantivo primero, y verifica en which-key que el grupo queda coherente.
- Localiza los cinco atajos que no has usado en un mes, decide cuáles se convierten en comandos de usuario y borra el resto.