Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -556,14 +556,8 @@ std::vector Actions; for (const Diagnostic &D : Params.context.diagnostics) { for (auto &F : getFixes(Params.textDocument.uri.file(), D)) { - Actions.emplace_back(); - Actions.back().title = F.Message; - Actions.back().kind = CodeAction::QUICKFIX_KIND; + Actions.push_back(toCodeAction(F, Params.textDocument.uri)); Actions.back().diagnostics = {D}; - Actions.back().edit.emplace(); - Actions.back().edit->changes.emplace(); - (*Actions.back().edit->changes)[Params.textDocument.uri.uri()] = { - F.Edits.begin(), F.Edits.end()}; } } @@ -734,36 +728,16 @@ void ClangdLSPServer::onDiagnosticsReady(PathRef File, std::vector Diagnostics) { - json::Array DiagnosticsJSON; - + URIForFile URI(File); + std::vector LSPDiagnostics; DiagnosticToReplacementMap LocalFixIts; // Temporary storage for (auto &Diag : Diagnostics) { - toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef Fixes) { - 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); - } - if (DiagOpts.SendDiagnosticCategory && !Diag.category.empty()) - LSPDiag["category"] = Diag.category; - DiagnosticsJSON.push_back(std::move(LSPDiag)); - - auto &FixItsForDiagnostic = LocalFixIts[Diag]; - llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic)); - }); + toLSPDiags(Diag, URI, DiagOpts, + [&](clangd::Diagnostic Diag, llvm::ArrayRef Fixes) { + auto &FixItsForDiagnostic = LocalFixIts[Diag]; + llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic)); + LSPDiagnostics.push_back(std::move(Diag)); + }); } // Cache FixIts @@ -776,8 +750,8 @@ // Publish diagnostics. notify("textDocument/publishDiagnostics", json::Object{ - {"uri", URIForFile{File}}, - {"diagnostics", std::move(DiagnosticsJSON)}, + {"uri", URI}, + {"diagnostics", std::move(LSPDiagnostics)}, }); } Index: clangd/Diagnostics.h =================================================================== --- clangd/Diagnostics.h +++ clangd/Diagnostics.h @@ -78,9 +78,12 @@ /// file do not have a corresponding LSP diagnostic, but can still be included /// as part of their main diagnostic's message. void toLSPDiags( - const Diag &D, + const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, llvm::function_ref)> OutFn); +/// Convert from Fix to LSP CodeAction. +CodeAction toCodeAction(const Fix &D, const URIForFile &File); + /// Convert from clang diagnostic level to LSP severity. int getSeverity(DiagnosticsEngine::Level L); Index: clangd/Diagnostics.cpp =================================================================== --- clangd/Diagnostics.cpp +++ clangd/Diagnostics.cpp @@ -226,20 +226,37 @@ return OS; } +CodeAction toCodeAction(const Fix &F, const URIForFile &File) { + CodeAction Action; + Action.title = F.Message; + Action.kind = CodeAction::QUICKFIX_KIND; + Action.edit.emplace(); + Action.edit->changes.emplace(); + (*Action.edit->changes)[File.uri()] = {F.Edits.begin(), F.Edits.end()}; + return Action; +} + void toLSPDiags( - const Diag &D, + const Diag &D, const URIForFile &File, const ClangdDiagnosticOptions &Opts, llvm::function_ref)> OutFn) { auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic { clangd::Diagnostic Res; Res.range = D.Range; Res.severity = getSeverity(D.Severity); - Res.category = D.Category; return Res; }; { clangd::Diagnostic Main = FillBasicFields(D); Main.message = mainMessage(D); + if (Opts.EmbedFixesInDiagnostics) { + Main.codeActions.emplace(); + for (const auto &Fix : D.Fixes) + Main.codeActions->push_back(toCodeAction(Fix, File)); + } + if (Opts.SendDiagnosticCategory && !D.Category.empty()) + Main.category = D.Category; + OutFn(std::move(Main), D.Fixes); } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -323,9 +323,8 @@ /// workspace.symbol.symbolKind.valueSet llvm::Optional WorkspaceSymbolKinds; - /// Whether the client accepts diagnostics with fixes attached using the - /// "clangd_fixes" extension. - /// textDocument.publishDiagnostics.clangdFixSupport + /// Whether the client accepts diagnostics with codeActions attached inline. + /// textDocument.publishDiagnostics.codeActionsInline. bool DiagnosticFixes = false; /// Whether the client accepts diagnostics with category attached to it @@ -536,6 +535,7 @@ }; bool fromJSON(const llvm::json::Value &, DocumentSymbolParams &); +struct CodeAction; struct Diagnostic { /// The range at which the message applies. Range range; @@ -560,7 +560,12 @@ /// An LSP extension that's used to send the name of the category over to the /// client. The category typically describes the compilation stage during /// which the issue was produced, e.g. "Semantic Issue" or "Parse Issue". - std::string category; + llvm::Optional category; + + /// Clangd extension: code actions related to this diagnostic. + /// Only with capability textDocument.publishDiagnostics.codeActionsInline. + /// (These actions can also be obtained using textDocument/codeAction). + llvm::Optional> codeActions; }; llvm::json::Value toJSON(const Diagnostic &); Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -210,8 +210,8 @@ if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) { if (auto CategorySupport = Diagnostics->getBoolean("categorySupport")) R.DiagnosticCategory = *CategorySupport; - if (auto ClangdFixSupport = Diagnostics->getBoolean("clangdFixSupport")) - R.DiagnosticFixes = *ClangdFixSupport; + if (auto CodeActions = Diagnostics->getBoolean("codeActionsInline")) + R.DiagnosticFixes = *CodeActions; } if (auto *Completion = TextDocument->getObject("completion")) { if (auto *Item = Completion->getObject("completionItem")) { @@ -348,8 +348,10 @@ {"severity", D.severity}, {"message", D.message}, }; - // FIXME: this should be used for publishDiagnostics. - // FIXME: send category and fixes when appropriate. + if (D.category) + Diag["category"] = *D.category; + if (D.codeActions) + Diag["codeActions"] = D.codeActions; return std::move(Diag); } @@ -358,6 +360,7 @@ if (!O || !O.map("range", R.range) || !O.map("message", R.message)) return false; O.map("severity", R.severity); + O.map("category", R.category); return true; } Index: test/clangd/fixits-embed-in-diagnostic.test =================================================================== --- test/clangd/fixits-embed-in-diagnostic.test +++ test/clangd/fixits-embed-in-diagnostic.test @@ -1,12 +1,12 @@ # RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"clangdFixSupport":true}}},"trace":"off"}} +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"codeActionsInline":true}}},"trace":"off"}} --- {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ # CHECK-NEXT: { -# CHECK-NEXT: "clangd_fixes": [ +# CHECK-NEXT: "codeActions": [ # CHECK-NEXT: { # CHECK-NEXT: "edit": { # CHECK-NEXT: "changes": { @@ -27,6 +27,7 @@ # CHECK-NEXT: ] # CHECK-NEXT: } # CHECK-NEXT: }, +# CHECK-NEXT: "kind": "quickfix", # CHECK-NEXT: "title": "change 'union' to 'struct'" # CHECK-NEXT: } # CHECK-NEXT: ], Index: unittests/clangd/ClangdUnitTests.cpp =================================================================== --- unittests/clangd/ClangdUnitTests.cpp +++ unittests/clangd/ClangdUnitTests.cpp @@ -199,11 +199,13 @@ // Transform dianostics and check the results. std::vector>> LSPDiags; - toLSPDiags(D, [&](clangd::Diagnostic LSPDiag, - llvm::ArrayRef Fixes) { - LSPDiags.push_back({std::move(LSPDiag), - std::vector(Fixes.begin(), Fixes.end())}); - }); + toLSPDiags( + D, URIForFile("/path/to/foo/bar/main.cpp"), ClangdDiagnosticOptions(), + [&](clangd::Diagnostic LSPDiag, llvm::ArrayRef Fixes) { + LSPDiags.push_back( + {std::move(LSPDiag), + std::vector(Fixes.begin(), Fixes.end())}); + }); EXPECT_THAT( LSPDiags,