Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -72,6 +72,7 @@ void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override; void onCommand(Ctx C, ExecuteCommandParams &Params) override; void onRename(Ctx C, RenameParams &Parames) override; + void onCodeHover(Ctx C, TextDocumentPositionParams &Params) override; std::vector getFixIts(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -57,6 +57,7 @@ {"triggerCharacters", {"(", ","}}, }}, {"definitionProvider", true}, + {"hoverProvider", true}, {"renameProvider", true}, {"executeCommandProvider", json::obj{ @@ -224,6 +225,7 @@ if (!Items) return C.replyError(ErrorCode::InvalidParams, llvm::toString(Items.takeError())); + C.reply(json::ary(Items->Value)); } @@ -234,6 +236,15 @@ C.reply(Result ? URI::fromFile(*Result).uri : ""); } +void ClangdLSPServer::onCodeHover(Ctx C, TextDocumentPositionParams &Params) { + + auto H = Server.findHover( + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}); + + C.reply(Hover::unparse(H->Value)); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, const clangd::CodeCompleteOptions &CCOpts, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -284,6 +284,8 @@ /// given a header file and vice versa. llvm::Optional switchSourceHeader(PathRef Path); + llvm::Expected> findHover(PathRef File, Position Pos); + /// Run formatting for \p Rng inside \p File. std::vector formatRange(PathRef File, Range Rng); /// Run formatting for the whole \p File. Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -445,9 +445,9 @@ return; Result = clangd::findDefinitions(*AST, Pos, Logger); }); + return make_tagged(std::move(Result), TaggedFS.Tag); } - llvm::Optional ClangdServer::switchSourceHeader(PathRef Path) { StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx", @@ -507,6 +507,23 @@ return llvm::None; } +llvm::Expected> ClangdServer::findHover(PathRef File, + Position Pos) { + Hover FinalHover; + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + std::shared_ptr Resources = Units.getFile(File); + assert(Resources && "Calling findHover on non-added file"); + Resources->getAST().get()->runUnderLock( + [Pos, &FinalHover, this](ParsedAST *AST) { + if (!AST) + return; + FinalHover = clangd::getHover(*AST, Pos, Logger); + }); + + return make_tagged(std::move(FinalHover), TaggedFS.Tag); +} + std::future ClangdServer::scheduleReparseAndDiags( PathRef File, VersionedDraft Contents, std::shared_ptr Resources, Tagged> TaggedFS) { Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -313,6 +313,13 @@ std::vector findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger); +Hover getHover(ParsedAST &AST, Position Pos, clangd::Logger &Logger); + +std::string getScope(const NamedDecl *ND, const LangOptions &LangOpts); + +StringRef getDataBufferFromSourceRange(ParsedAST &AST, SourceRange SR, + Location L); + /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS); Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -209,6 +209,7 @@ // EndSourceFile), but that won't happen if DisableFree is set to true. // Since createInvocationFromCommandLine sets it to true, we have to override // it. + CI->LangOpts->CommentOpts.ParseAllComments = true; CI->getFrontendOpts().DisableFree = false; return CI; } @@ -939,27 +940,36 @@ return Mgr.getMacroArgExpandedLocation(InputLoc); } -/// Finds declarations locations that a given source location refers to. -class DeclarationLocationsFinder : public index::IndexDataConsumer { - std::vector DeclarationLocations; +/// Finds declarations and macros that a given source location refers to. +class DeclarationAndMacrosFinder : public index::IndexDataConsumer { + std::vector Decls; + std::vector MacroInfos; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; public: - DeclarationLocationsFinder(raw_ostream &OS, + DeclarationAndMacrosFinder(raw_ostream &OS, const SourceLocation &SearchedLocation, ASTContext &AST, Preprocessor &PP) : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} - std::vector takeLocations() { + std::vector takeDecls() { // Don't keep the same location multiple times. // This can happen when nodes in the AST are visited twice. - std::sort(DeclarationLocations.begin(), DeclarationLocations.end()); - auto last = - std::unique(DeclarationLocations.begin(), DeclarationLocations.end()); - DeclarationLocations.erase(last, DeclarationLocations.end()); - return std::move(DeclarationLocations); + std::sort(Decls.begin(), Decls.end()); + auto Last = std::unique(Decls.begin(), Decls.end()); + Decls.erase(Last, Decls.end()); + return Decls; + } + + std::vector takeMacroInfos() { + // Don't keep the same Macro info multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(MacroInfos.begin(), MacroInfos.end()); + auto Last = std::unique(MacroInfos.begin(), MacroInfos.end()); + MacroInfos.erase(Last, MacroInfos.end()); + return MacroInfos; } bool @@ -967,9 +977,9 @@ ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { - if (isSearchedLocation(FID, Offset)) { - addDeclarationLocation(D->getSourceRange()); - } + + if (isSearchedLocation(FID, Offset)) + Decls.push_back(D); return true; } @@ -980,31 +990,6 @@ SourceMgr.getFileID(SearchedLocation) == FID; } - void addDeclarationLocation(const SourceRange &ValSourceRange) { - const SourceManager &SourceMgr = AST.getSourceManager(); - const LangOptions &LangOpts = AST.getLangOpts(); - SourceLocation LocStart = ValSourceRange.getBegin(); - SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), - 0, SourceMgr, LangOpts); - Position Begin; - Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; - Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; - Position End; - End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; - End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; - Range R = {Begin, End}; - Location L; - if (const FileEntry *F = - SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart))) { - StringRef FilePath = F->tryGetRealPathName(); - if (FilePath.empty()) - FilePath = F->getName(); - L.uri = URI::fromFile(FilePath); - L.range = R; - DeclarationLocations.push_back(L); - } - } - void finish() override { // Also handle possible macro at the searched location. Token Result; @@ -1027,8 +1012,7 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + MacroInfos.push_back(MacroInf); } } } @@ -1037,6 +1021,32 @@ } // namespace +Location getDeclarationLocation(ParsedAST &AST, + const SourceRange &ValSourceRange) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + SourceLocation LocStart = ValSourceRange.getBegin(); + SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0, + SourceMgr, LangOpts); + Position Begin; + Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; + Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; + Position End; + End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; + End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; + Range R = {Begin, End}; + Location L; + if (const FileEntry *F = + SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart))) { + StringRef FilePath = F->tryGetRealPathName(); + if (FilePath.empty()) + FilePath = F->getName(); + L.uri = URI::fromFile(FilePath); + L.range = R; + } + return L; +} + std::vector clangd::findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); @@ -1046,7 +1056,52 @@ SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); - auto DeclLocationsFinder = std::make_shared( + auto DeclMacrosFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor()); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DeclMacrosFinder, IndexOpts); + + std::vector Decls = DeclMacrosFinder->takeDecls(); + std::vector MacroInfos = + DeclMacrosFinder->takeMacroInfos(); + std::vector Result; + + for (auto Item : Decls) { + Location L = getDeclarationLocation(AST, Item->getSourceRange()); + Result.push_back(L); + } + + for (auto Item : MacroInfos) { + SourceRange SR(Item->getDefinitionLoc(), Item->getDefinitionEndLoc()); + Location L = getDeclarationLocation(AST, SR); + Result.push_back(L); + } + + return Result; +} + +Hover clangd::getHover(ParsedAST &AST, Position Pos, clangd::Logger &Logger) { + + // Default Hover values + std::vector EmptyVector; + Hover H; + StringRef Ref; + + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); + if (!FE) + return H; + + SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); + + auto DeclMacrosFinder = std::make_shared( llvm::errs(), SourceLocationBeg, AST.getASTContext(), AST.getPreprocessor()); index::IndexingOptions IndexOpts; @@ -1055,9 +1110,148 @@ IndexOpts.IndexFunctionLocals = true; indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), - DeclLocationsFinder, IndexOpts); + DeclMacrosFinder, IndexOpts); + + std::vector DeclVector = DeclMacrosFinder->takeDecls(); + + if (!DeclVector.empty()) { + const Decl *LocationDecl = DeclVector[0]; + + if (LocationDecl) { + SourceRange SR = LocationDecl->getSourceRange(); + SourceLocation NewLocation; + + if (LocationDecl->getDescribedTemplate()) { + RawComment *RC = AST.getASTContext().getRawCommentForDeclNoCache( + LocationDecl->getDescribedTemplate()); + if (RC) { + NewLocation = RC->getLocStart(); + } else { + NewLocation = + LocationDecl->getDescribedTemplate()->getSourceRange().getBegin(); + } + } else { + RawComment *RC = + AST.getASTContext().getRawCommentForDeclNoCache(LocationDecl); + if (RC) { + NewLocation = RC->getLocStart(); + SR = + SourceRange(NewLocation, LocationDecl->getSourceRange().getEnd()); + } + } + SourceLocation BeginLocation; + (NewLocation.isValid()) ? (BeginLocation = NewLocation) + : (BeginLocation = SR.getBegin()); + + if (auto FuncDecl = dyn_cast(LocationDecl)) { + SR = + SourceRange(BeginLocation, LocationDecl->getSourceRange().getEnd()); + if (FuncDecl->isThisDeclarationADefinition() && FuncDecl->getBody()) + SR = SourceRange(BeginLocation, FuncDecl->getBody()->getLocStart()); + } else if (auto TagDeclaration = dyn_cast(LocationDecl)) { + if (TagDeclaration->isThisDeclarationADefinition()) + SR = SourceRange(BeginLocation, + TagDeclaration->getBraceRange().getBegin()); + + } else if (auto NameDecl = dyn_cast(LocationDecl)) { + SourceLocation BeforeLBraceLoc = Lexer::getLocForEndOfToken( + LocationDecl->getLocation(), 0, SourceMgr, LangOpts); + if (BeforeLBraceLoc.isValid()) + SR = SourceRange(NameDecl->getLocStart(), BeforeLBraceLoc); + + } else if (dyn_cast(LocationDecl)) { + SR = LocationDecl->getSourceRange(); + // for everything else in ValueDecl, so lvalues of variables, function + // designations and enum constants + } else if (dyn_cast(LocationDecl)) { + SR = LocationDecl->getSourceRange(); + } + + if (SR.isValid()) { + Location L = getDeclarationLocation(AST, SR); + H.range = L.range; + Ref = getDataBufferFromSourceRange(AST, SR, L); + } + + if (const NamedDecl *NamedD = dyn_cast(LocationDecl)) { + std::string Res = + clangd::getScope(NamedD, AST.getASTContext().getLangOpts()); + if (!Res.empty()) { + MarkedString Info = {"", "C++", "In " + Res}; + H.contents.push_back(Info); + } + } + } + MarkedString MS = {"", "C++", Ref}; + H.contents.push_back(MS); + return H; + } + + if (!DeclMacrosFinder->takeMacroInfos().empty()) { + const MacroInfo *MacroInf = DeclMacrosFinder->takeMacroInfos()[0]; + if (MacroInf) { + SourceRange SR = SourceRange(MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc()); + if (SR.isValid()) { + Location L = getDeclarationLocation(AST, SR); + H.range = L.range; + Ref = getDataBufferFromSourceRange(AST, SR, L); + } + } + MarkedString MS = {"", "C++", "#define " + Ref.str()}; + H.contents.push_back(MS); + return H; + } + + return H; +} + +/// Obtains scope for in which a NamedDecl is contained +std::string clangd::getScope(const NamedDecl *ND, const LangOptions &LangOpts) { + // Get the full qualified name, the non-qualified name and then diff + // them. If there's something left, use that as the scope in the hover, + // trimming the extra "::" + std::string Res; + PrintingPolicy WithScopePP(LangOpts); + std::string WithScopeBuf; + llvm::raw_string_ostream WithScopeOS(WithScopeBuf); + ND->printQualifiedName(WithScopeOS, WithScopePP); + + std::string ResWithScope = WithScopeOS.str(); + if (!ResWithScope.empty()) { + // Get non-qualified name + std::string PrintedNameBuf; + llvm::raw_string_ostream PrintedNameOS(PrintedNameBuf); + ND->printName(PrintedNameOS); + auto Last = ResWithScope.rfind(PrintedNameOS.str()); + if (Last != std::string::npos) { + Res = ResWithScope.substr(0, Last); + if (Res.length() > 2 && + Res.substr(Res.length() - 2, Res.length()) == "::") + Res = Res.substr(0, Res.length() - 2); + } + } + return Res; +} - return DeclLocationsFinder->takeLocations(); +/// Returns the file buffer data that the given SourceRange points to. +StringRef clangd::getDataBufferFromSourceRange(ParsedAST &AST, SourceRange SR, + Location L) { + StringRef Ref; + + const FileEntry *FE = + AST.getASTContext().getSourceManager().getFileManager().getFile( + L.uri.file); + FileID FID = AST.getASTContext().getSourceManager().translateFile(FE); + Ref = AST.getASTContext().getSourceManager().getBufferData(FID); + unsigned Start = AST.getASTContext().getSourceManager().getFileOffset( + AST.getASTContext().getSourceManager().translateFileLineCol( + FE, L.range.start.line + 1, L.range.start.character + 1)); + unsigned End = AST.getASTContext().getSourceManager().getFileOffset( + AST.getASTContext().getSourceManager().translateFileLineCol( + FE, L.range.end.line + 1, L.range.end.character + 1)); + Ref = Ref.slice(Start, End); + return Ref; } void ParsedAST::ensurePreambleDeclsDeserialized() { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -449,6 +449,50 @@ parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); }; +struct MarkedString { + /** + * MarkedString can be used to render human readable text. It is either a + * markdown string + * or a code-block that provides a language and a code snippet. The language + * identifier + * is sematically equal to the optional language identifier in fenced code + * blocks in GitHub + * issues. See + * https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + * + * The pair of a language and a value is an equivalent to markdown: + * ``` + * ${language} + * ${value} + * ``` + * + * Note that markdown strings will be sanitized - that means html will be + * escaped. + */ + + std::string markdownString; + std::string language; + std::string codeBlockValue; + + static json::Expr unparse(const MarkedString &MS); +}; + +struct Hover { + + /** + * The hover's content + */ + std::vector contents; + + /** + * An optional range is a range inside a text document + * that is used to visualize a hover, e.g. by changing the background color. + */ + llvm::Optional range; + + static json::Expr unparse(const Hover &H); +}; + /// The kind of a completion entry. enum class CompletionItemKind { Missing = 0, Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -1014,6 +1014,30 @@ return Result; } +json::Expr Hover::unparse(const Hover &H) { + if (H.range.hasValue()) { + return json::obj{ + {"contents", json::ary(H.contents)}, + {"range", H.range.getValue()}, + }; + } else { + // Default Hover values + Range DefaultRange; + Hover H; + return json::obj{ + {"contents", json::ary(H.contents)}, + {"range", DefaultRange}, + }; + } +} + +json::Expr MarkedString::unparse(const MarkedString &MS) { + return json::obj{ + {"language", MS.language}, + {"value", MS.codeBlockValue}, + }; +} + json::Expr CompletionItem::unparse(const CompletionItem &CI) { assert(!CI.label.empty() && "completion item label is required"); json::obj Result{{"label", CI.label}}; Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -54,6 +54,7 @@ virtual void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) = 0; virtual void onCommand(Ctx C, ExecuteCommandParams &Params) = 0; virtual void onRename(Ctx C, RenameParams &Parames) = 0; + virtual void onCodeHover(Ctx C, TextDocumentPositionParams &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -72,6 +72,7 @@ Register("textDocument/switchSourceHeader", &ProtocolCallbacks::onSwitchSourceHeader); Register("textDocument/rename", &ProtocolCallbacks::onRename); + Register("textDocument/hover", &ProtocolCallbacks::onCodeHover); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); } Index: test/clangd/hover.test =================================================================== --- /dev/null +++ test/clangd/hover.test @@ -0,0 +1,52 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %s +# It is absolutely vital that this file has CRLF line endings. +# +Content-Length: 125 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} + +Content-Length: 407 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#define MACRO 1\nnamespace ns1 {\nint test = 5;\nstruct MyClass {\nint xasd;\nvoid anotherOperation() {\n}\nstatic int foo(MyClass*) {\nreturn 0;\n}\n\n};}\nint main() {\nint a;\na;\nint b = ns1::test;\nns1::MyClass* Params;\nParams->anotherOperation();\nMACRO;}\n"}}} + +Content-Length: 144 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":12}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"#define MACRO 1"}],"range":{"end":{"character":15,"line":0},"start":{"character":8,"line":0}}}} + +Content-Length: 144 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":14,"character":1}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"int a"}],"range":{"end":{"character":5,"line":13},"start":{"character":0,"line":13}}}} + +Content-Length: 145 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":15,"character":15}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"In ns1"},{"language":"C++","value":"int test = 5"}],"range":{"end":{"character":12,"line":2},"start":{"character":0,"line":2}}}} + +Content-Length: 145 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":16,"character":10}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"In ns1"},{"language":"C++","value":"struct MyClass {"}],"range":{"end":{"character":16,"line":3},"start":{"character":0,"line":3}}}} + +Content-Length: 145 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":13}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"In ns1::MyClass"},{"language":"C++","value":"void anotherOperation() {"}],"range":{"end":{"character":25,"line":5},"start":{"character":0,"line":5}}}} + +Content-Length: 145 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":18,"character":1}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"#define MACRO 1"}],"range":{"end":{"character":15,"line":0},"start":{"character":8,"line":0}}}} + +Content-Length: 44 + +{"jsonrpc":"2.0","id":3,"method":"shutdown"} + + Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -30,6 +30,7 @@ # CHECK-NEXT: "clangd.applyFix" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [ Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -30,6 +30,7 @@ # CHECK-NEXT: "clangd.applyFix" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [