Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -155,6 +155,8 @@ RealFileSystemProvider FSProvider; /// Options used for code completion clangd::CodeCompleteOptions CCOpts; + /// Options used for diagnostics. + ClangdDiagnosticOptions DiagOpts; /// The supported kinds of the client. SymbolKindBitset SupportedSymbolKinds; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -82,6 +82,8 @@ CCOpts.EnableSnippets = Params.capabilities.textDocument.completion.completionItem.snippetSupport; + DiagOpts.EmbedFixesInDiagnostics = + Params.capabilities.textDocument.publishDiagnostics.clangdFixSupport; if (Params.capabilities.workspace && Params.capabilities.workspace->symbol && Params.capabilities.workspace->symbol->symbolKind) { @@ -486,11 +488,25 @@ DiagnosticToReplacementMap LocalFixIts; // Temporary storage for (auto &Diag : Diagnostics) { toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef Fixes) { - DiagnosticsJSON.push_back(json::Object{ + json::Object LSPDiag({ {"range", Diag.range}, {"severity", Diag.severity}, {"message", Diag.message}, }); + // LSP extension: embed the fixes in the diagnostic. + if (DiagOpts.EmbedFixesInDiagnostics && !Fixes.empty()) { + json::Array ClangdFixes; + for (const auto &Fix : Fixes) { + WorkspaceEdit WE; + URIForFile URI{File}; + WE.changes = {{URI.uri(), std::vector(Fix.Edits.begin(), + Fix.Edits.end())}}; + ClangdFixes.push_back( + json::Object{{"edit", toJSON(WE)}, {"title", Fix.Message}}); + } + LSPDiag["clangd_fixes"] = std::move(ClangdFixes); + } + DiagnosticsJSON.push_back(std::move(LSPDiag)); auto &FixItsForDiagnostic = LocalFixIts[Diag]; std::copy(Fixes.begin(), Fixes.end(), Index: clangd/Diagnostics.h =================================================================== --- clangd/Diagnostics.h +++ clangd/Diagnostics.h @@ -23,6 +23,12 @@ namespace clang { namespace clangd { +struct ClangdDiagnosticOptions { + /// If true, Clangd uses an LSP extension to embed the fixes with the + /// diagnostics that are sent to the client. + bool EmbedFixesInDiagnostics = false; +}; + /// Contains basic information about a diagnostic. struct DiagBase { std::string Message; Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -247,6 +247,18 @@ }; bool fromJSON(const llvm::json::Value &, CompletionClientCapabilities &); +struct PublishDiagnosticsClientCapabilities { + // Whether the client accepts diagnostics with related information. + // NOTE: not used by clangd at the moment. + // bool relatedInformation; + + /// Whether the client accepts diagnostics with fixes attached using the + /// "clangd_fixes" extension. + bool clangdFixSupport = false; +}; +bool fromJSON(const llvm::json::Value &, + PublishDiagnosticsClientCapabilities &); + /// A symbol kind. enum class SymbolKind { File = 1, @@ -313,6 +325,9 @@ struct TextDocumentClientCapabilities { /// Capabilities specific to the `textDocument/completion` CompletionClientCapabilities completion; + + /// Capabilities specific to the 'textDocument/publishDiagnostics' + PublishDiagnosticsClientCapabilities publishDiagnostics; }; bool fromJSON(const llvm::json::Value &, TextDocumentClientCapabilities &); Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -178,6 +178,15 @@ return true; } +bool fromJSON(const llvm::json::Value &Params, + PublishDiagnosticsClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("clangdFixSupport", R.clangdFixSupport); + return true; +} + bool fromJSON(const json::Value &E, SymbolKind &Out) { if (auto T = E.getAsInteger()) { if (*T < static_cast(SymbolKind::File) || @@ -240,6 +249,7 @@ if (!O) return false; O.map("completion", R.completion); + O.map("publishDiagnostics", R.publishDiagnostics); return true; }