What this server understands about C: how a file is identified as C, which language features it reads, what the
semantic layer can answer, what each LSP feature gives, and which diagnostics exist. Configuration and the limits are in
configuration.md; the same reference for C++ is cpp-support.md.
| Item | Rule |
|---|---|
| Extension | .c is a C source; .h belongs to whichever language includes it (the C++ search list is the wider one, so a header read on its own is read as C++) |
-std= |
Wins over the extension: c89/c99/c11/c17/c23/gnu17 and so on are C; c++… is C++ |
| Indexed extensions | c cc cpp cxx c++ h hh hpp hxx h++ ipp inl tpp tcc cppm ixx, plus anything in [workspace].source_extensions (for example cuh) |
| Header extensions | the header family above, plus def mpp cuh |
| Where files come from | compile_commands.json → .cppls.toml → the project scan; an #include no search path can place falls back to matching the tail of its path against the tree |
Each construct is a feature bit opened by -std= or [compile].standard; a standard such as c17 does not open the
C23 bits.
| Standard | Constructs accepted |
|---|---|
| C89 | the baseline: declarations before statements; no // comments, no long long |
| C99 | // comments, long long, restrict, inline, _Bool, variable-length arrays, flexible array members, compound literals, variadic macros, designated initialisers |
| C11 | _Generic, _Atomic, _Static_assert, _Alignas/_Alignof, _Noreturn, anonymous struct/union, _Thread_local |
| C17 | no syntax of its own (C11 plus the defect reports); the feature bits exist so the standard can be asked |
| C23 | [[attribute]], nullptr, constexpr, _BitInt(N), auto type inference, enumerations with a fixed underlying type, the empty initialiser {}, #elifdef/#elifndef, typeof/typeof_unqual |
| GNU extensions | statement expressions ({ … }), label addresses &&label, ?: with the middle operand omitted, asm blocks, nested functions, __typeof__/__typeof, __attribute__, __builtin_* calls |
| MSVC extensions | __try/__except/__finally, __int8…__int64, calling conventions such as __cdecl/__stdcall |
Not supported: C23's #embed. Every other C23 construct is in the table.
One row is read in every standard, because the compilers do: _BitInt(N) is C23's, and gcc 15 accepts
struct bits { unsigned _BitInt(3) three : 3; _BitInt(13) signed_wide : 13; _BitInt(5) plain; }; with -std=c99,
-std=c11, -std=c17 and -std=gnu89 (exit 0, no diagnostic), while clang 18 accepts it in -std=c++23. A reader
that gated it on C23 misread a legal declaration as a call whenever the standard was not detected — no compile database,
a header opened on its own — and the struct came out with no members at all. The gate is open in every standard the
server knows; the detector around it still requires a parenthesised width followed by an identifier, so a variable named
_BitInt is untouched.
| Ability | What it does |
|---|---|
#include |
angle and quote forms; search paths from the database, the configuration or the toolchain; a path nothing places falls back to tail matching; each directive records its resolution |
#define |
object-like and function-like, variadic (... and __VA_ARGS__), # stringification, ## pasting, __VA_OPT__ |
#undef |
recorded, so the macro environment is tracked point by point |
| Conditionals | #if/#elif/#else/#endif, #ifdef/#ifndef, C23's #elifdef/#elifndef; expressions are evaluated as integer constants |
#pragma, _Pragma |
recorded and ignored as the standard says |
| Predefined macros | __STDC__ and __STDC_VERSION__ follow the selected standard; the toolchain's own markers (_MSC_VER, _WIN32, __GNUC__, __clang__) follow the compiler |
| Macro environment | the set of macros visible at each position is recorded, and hover, completion, references and rename ask it rather than searching the text |
| Ability | What it answers |
|---|---|
| Scopes | blocks, function parameters and bodies, struct/union/enum (including C23 fixed underlying types), typedef names, goto labels, file scope; an anonymous struct/union member is promoted to the enclosing type |
| Types | built-in types, pointers, arrays, function pointers, bit-fields, typedef chains, qualifiers, compound literals, variable-length arrays |
| Names | visibility along the scope chain, extern/static redeclarations, __typeof__, names that a macro produced |
| Members | struct/union member lists (anonymous members, bit-fields, flexible array members); the right of ./-> is answered from the object's type |
| Calls | argument count, argument types, variadic functions, return type; a call through a function pointer is answered from the pointer's type |
| Constants | #if expressions, array extents, enumerator values |
| The index | the cross-file declaration table, the #include graph, member-list caches, and the summary cache (.cppls) |
| Feature | What it gives for C |
|---|---|
textDocument/completion |
names in scope (locals → the file → included headers), ./-> members, macros, keywords, #include file names; with the range to replace, a sortText that is the analysis's own order, snippets when the client supports them, and completionItem/resolve for documentation |
textDocument/hover |
macros first (a #define is its value), then the declaration (signature/type), then the type of the expression; the comment next to the declaration comes with it |
textDocument/signatureHelp |
the list of overloads with activeParameter — the position of the argument being typed |
textDocument/definition |
the declaration of a macro, type, function or variable, across files through the index |
textDocument/implementation |
a function declaration → its definition, across files |
textDocument/typeDefinition |
a typedef/struct name → the definition of its type |
textDocument/references |
exact references for macros, including the ones reached under a conditional #include or #if (marked as uncertain); an ordinary name is not answered |
textDocument/rename + prepareRename |
macros only; an ordinary name is refused with the protocol's null, because editing half of a name's uses is a broken build written into the user's files |
textDocument/documentHighlight |
occurrences of the same name in the current file |
textDocument/documentSymbol |
the file's own declaration tree, including what #if 0 removes (the reader is editing it), with a range for the whole declaration and one for the name |
workspace/symbol |
the declarations the project index holds, by name |
callHierarchy |
incoming and outgoing calls of a function |
textDocument/selectionRange |
the chain of syntax nodes around the cursor |
textDocument/foldingRange |
brace blocks, comments, #region, runs of #include |
textDocument/documentLink |
the path of an #include, as a link to the file |
textDocument/inlayHint |
parameter names at call sites |
textDocument/codeAction |
source.include: an unresolved name → an edit adding the header that declares it |
textDocument/semanticTokens |
the standard types (namespace, type, class, enum, typeParameter, parameter, variable, property, enumMember, function, method, macro, keyword, modifier, operator, decorator) and, inside string literals, printf/scanf format fields (formatSpecifier, formatArgument, formatConversion, formatEscape) |
textDocument/diagnostic |
pull mode: syntax errors plus the rules in §6 |
textDocument/formatting, rangeFormatting, willSaveWaitUntil |
whole-file and range formatting, reading the common subset of .clang-format |
workspace/didChangeConfiguration |
.cppls.toml and editor settings — see configuration.md |