El editor como IDE
clangd convierte cualquier editor en un IDE de C: compile_commands.json como contrato, autocompletado con tipos reales, diagnósticos en vivo y clang-tidy integrado, todo cableado dentro de Neovim.
Un IDE de C no es un editor con colores: es un editor que entiende tu programa. Y entender un programa en C exige saber exactamente con qué banderas, con qué macros y contra qué cabeceras se compila cada fichero. clangd resuelve ese problema aplicando la única estrategia honesta: usar el compilador de verdad como motor de análisis. Lo que sigue es cómo darle esa información y cómo conectarlo a Neovim.
- Entender qué es
clangdy por qué necesita las banderas reales de compilación. - Generar
compile_commands.jsondesde cualquier sistema de construcción. - Cablear
clangden Neovim con la API nativa de LSP. - Afinar diagnósticos en vivo,
clang-tidyy pistas de tipos en línea.
clangd: el compilador como servidor de lenguaje
clangd no analiza tu código con heurísticas ni con expresiones regulares: reutiliza el front-end de Clang, construye el AST completo del fichero y responde preguntas sobre él a través del protocolo LSP. Ir a la definición, renombrar, listar referencias, completar miembros de un struct, avisar de un error de tipos: todo sale del mismo análisis que produciría el binario.
Esa fidelidad tiene un precio: clangd necesita saber cómo se compila cada fichero. Un #include que resuelve o no según -I, un bloque que existe o no según -D, una construcción de C23 que solo se acepta con -std=c23. Sin esa información, clangd adivina, y adivinar en C significa inundar el búfer de errores falsos.
flowchart LR M[meson o cmake o bear] --> J[compile commands json] J --> D[clangd con las banderas reales] D --> N[Neovim via LSP] N --> A[Autocompletado con tipos] N --> B[Diagnosticos en vivo] N --> C[Ir a definicion y renombrar] N --> E[clang-tidy integrado] style J fill:#f9e2af,color:#11111b style D fill:#a6e3a1,color:#11111b
compile_commands.json: el contrato
Es un fichero JSON en la raíz del proyecto —o en el directorio de construcción— con una entrada por unidad de traducción: directorio de trabajo, comando exacto y fichero. Es un formato estándar de LLVM, no un invento de clangd, y lo consumen también clang-tidy y include-what-you-use.
[
{
"directory": "/home/dev/proyecto/build",
"command": "cc -std=c23 -Wall -Wextra -I../include -DDEBUG=1 -c ../src/lista.c",
"file": "../src/lista.c"
}
]
Casi nunca se escribe a mano. Cada sistema de construcción lo genera:
# Meson: lo genera siempre, en el directorio de build
meson setup build && ln -sf build/compile_commands.json .
# CMake
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -sf build/compile_commands.json .
# Make y cualquier otra cosa: bear intercepta las llamadas al compilador
bear -- make clean all
Para un proyecto de un puñado de ficheros sin sistema de construcción, existe el atajo compile_flags.txt: una bandera por línea, aplicada a todo el árbol.
-std=c23
-Wall
-Wextra
-Iinclude
clangd busca compile_commands.json en el directorio del fichero abierto y va subiendo por los padres. Si vive dentro de build/, crea un enlace simbólico en la raíz o dile dónde está con la bandera --compile-commands-dir. Un clangd sin base de datos es un clangd que inventa.
Integrar clangd en Neovim
Neovim 0.11 trajo una API de LSP nativa que hace innecesaria la capa de configuración externa para servidores sencillos. Bastan dos llamadas: describir el servidor y activarlo.
-- ~/.config/nvim/lua/config/clangd.lua
vim.lsp.config('clangd', {
cmd = {
'clangd',
'--background-index', -- indexa el proyecto entero en segundo plano
'--clang-tidy', -- diagnosticos de clang-tidy en vivo
'--completion-style=detailed',
'--header-insertion=iwyu', -- inserta el include correcto al completar
'--function-arg-placeholders',
},
filetypes = { 'c', 'cpp' },
root_markers = { 'compile_commands.json', 'compile_flags.txt', '.git' },
})
vim.lsp.enable('clangd')
Y los atajos que convierten el servidor en un IDE de verdad. Neovim ya trae por defecto grn para renombrar, gra para acciones de código y grr para referencias; el resto conviene fijarlo a mano:
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
local opts = { buffer = args.buf }
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', '<leader>h', '<cmd>LspClangdSwitchSourceHeader<cr>', opts)
vim.lsp.inlay_hint.enable(true, { bufnr = args.buf })
end,
})
El salto entre .c y .h merece atención: no es una función del protocolo LSP, es una extensión propia de clangd que Neovim expone como comando. Es el atajo que más se usa en un proyecto real de C.
Diagnósticos en vivo y afinado
Un fichero .clangd en la raíz del proyecto permite corregir la base de datos sin tocar el sistema de construcción: añadir banderas, quitar las que clangd no entiende, y elegir qué comprobaciones de clang-tidy se ejecutan.
# .clangd
CompileFlags:
Add: [-std=c23, -Wall, -Wextra]
Remove: [-mno-direct-extern-access]
Diagnostics:
ClangTidy:
Add: [bugprone-*, cert-*, performance-*]
Remove: [bugprone-easily-swappable-parameters]
UnusedIncludes: Strict
InlayHints:
Enabled: true
ParameterNames: true
DeducedTypes: true
Con --clang-tidy activo dejas de tener solo errores del compilador: tienes análisis semántico mientras escribes. bugprone-* caza patrones de bug conocidos, cert-* aplica reglas de código seguro. Aparecen subrayados en el búfer antes de guardar, y muchos traen acción de código para arreglarlos.
Índice de fondo
--background-index construye un índice persistente del proyecto en .cache/clangd. Es lo que hace que buscar referencias sea instantáneo en un árbol grande.
Cuando algo falla
:checkhealth vim.lsp y :LspLog responden casi siempre. El error más común es un compile_commands.json desactualizado tras añadir ficheros.
La diferencia entre un editor con resaltado y un entorno de desarrollo real es de naturaleza, no de grado. El resaltado es sintáctico: reconoce formas. clangd es semántico: conoce tipos, ámbitos, expansiones de macro y resolución de nombres, porque es literalmente el mismo front-end que produce el binario. Por eso el paso decisivo de esta lección no es instalar un plugin, sino generar compile_commands.json: ese fichero elimina la brecha entre lo que el editor cree que tienes y lo que el compilador realmente compila. En C esa brecha es peculiarmente letal, porque el preprocesador hace que el mismo texto signifique cosas distintas según las macros y las rutas de inclusión activas; un editor sin las banderas reales no está viendo tu programa, está viendo una hipótesis sobre tu programa. Cuando el circuito se cierra, el ciclo de realimentación se comprime de minutos a milisegundos: el error de tipos aparece bajo el cursor, no en un registro de CI veinte minutos después. Esa compresión temporal es la mejora de productividad más grande disponible en C, y cuesta una línea de configuración.
- Genera
compile_commands.jsonen un proyecto tuyo conbear, Meson o CMake, y enlázalo en la raíz. - Configura
clangden Neovim convim.lsp.configyvim.lsp.enable, y verifica con:checkhealth vim.lspque se ha adjuntado al búfer. - Escribe un error de tipos deliberado y comprueba que el subrayado aparece sin guardar.
- Activa
--clang-tidycon las comprobacionesbugprone-*y arregla el primer aviso usando la acción de código. - Crea un
.clangdconInlayHintsactivo y compara la legibilidad de una función con muchos parámetros antes y después. - Borra el
compile_commands.jsona propósito, observa los errores falsos que aparecen, y entiende exactamente qué información perdió el servidor.