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{ @@ -234,6 +235,20 @@ 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}); + + if (!H) { + C.replyError(ErrorCode::InternalError, llvm::toString(H.takeError())); + return; + } + + C.reply(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 @@ -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 @@ -315,6 +315,8 @@ std::vector findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger); +Hover getHover(ParsedAST &AST, Position Pos, clangd::Logger &Logger); + /// 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 @@ -27,6 +27,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/CrashRecoveryContext.h" +#include "llvm/Support/Errc.h" #include "llvm/Support/Format.h" #include @@ -209,6 +210,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; } @@ -969,27 +971,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 @@ -997,9 +1008,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; } @@ -1010,31 +1021,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; @@ -1057,16 +1043,94 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + MacroInfos.push_back(MacroInf); } } } } }; +/// Obtains scope for in which a NamedDecl is contained +std::string 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; +} + +/// Returns the file buffer data that the given SourceRange points to. +llvm::ErrorOr getBufferDataFromSourceRange(ParsedAST &AST, + Location L) { + StringRef Ref; + const FileEntry *F = + AST.getASTContext().getSourceManager().getFileManager().getFile( + L.uri.file); + if (!F) + return llvm::errc::no_such_file_or_directory; + + FileID FID = AST.getASTContext().getSourceManager().translateFile(F); + Ref = AST.getASTContext().getSourceManager().getBufferData(FID); + unsigned Start = AST.getASTContext().getSourceManager().getFileOffset( + AST.getASTContext().getSourceManager().translateFileLineCol( + F, L.range.start.line + 1, L.range.start.character + 1)); + unsigned End = AST.getASTContext().getSourceManager().getFileOffset( + AST.getASTContext().getSourceManager().translateFileLineCol( + F, L.range.end.line + 1, L.range.end.character + 1)); + Ref = Ref.slice(Start, End); + return Ref; +} + } // namespace +llvm::ErrorOr +getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + SourceLocation LocStart = + SourceMgr.getExpansionLoc(ValSourceRange.getBegin()); + const FileEntry *F = + SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart)); + if (!F) + return llvm::errc::no_such_file_or_directory; + SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0, + SourceMgr, LangOpts); + Position Begin; + Begin.line = SourceMgr.getExpansionLineNumber(LocStart) - 1; + Begin.character = SourceMgr.getExpansionColumnNumber(LocStart) - 1; + Position End; + End.line = SourceMgr.getExpansionLineNumber(LocEnd) - 1; + End.character = SourceMgr.getExpansionColumnNumber(LocEnd) - 1; + Range R = {Begin, End}; + Location L; + + 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(); @@ -1076,7 +1140,7 @@ 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; @@ -1085,9 +1149,141 @@ IndexOpts.IndexFunctionLocals = true; indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), - DeclLocationsFinder, IndexOpts); + DeclMacrosFinder, IndexOpts); + + std::vector Decls = DeclMacrosFinder->takeDecls(); + std::vector MacroInfos = + DeclMacrosFinder->takeMacroInfos(); + std::vector Result; + + for (auto Item : Decls) { + auto L = getDeclarationLocation(AST, Item->getSourceRange()); + if (L) + Result.push_back(L.get()); + } + + for (auto Item : MacroInfos) { + SourceRange SR(Item->getDefinitionLoc(), Item->getDefinitionEndLoc()); + auto L = getDeclarationLocation(AST, SR); + if (L) + Result.push_back(L.get()); + } + + return Result; +} + +Hover clangd::getHover(ParsedAST &AST, Position Pos, clangd::Logger &Logger) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); + if (!FE) + return Hover(); + + SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); + + 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); + + auto Decls = DeclMacrosFinder->takeDecls(); + if (!Decls.empty() && Decls[0]) { + const Decl *LocationDecl = Decls[0]; + std::vector HoverContents; + + // Compute scope as the first "section" of the hover. + if (const NamedDecl *NamedD = dyn_cast(LocationDecl)) { + std::string Scope = getScope(NamedD, AST.getASTContext().getLangOpts()); + if (!Scope.empty()) { + MarkedString Info = {"", "C++", "In " + Scope}; + HoverContents.push_back(Info); + } + } + + // Adjust beginning of hover text depending on the presence of templates and + // comments. + TemplateDecl *TDec = LocationDecl->getDescribedTemplate(); + const Decl *BeginDecl = TDec ? TDec : LocationDecl; + SourceLocation BeginLocation = BeginDecl->getSourceRange().getBegin(); + RawComment *RC = AST.getASTContext().getRawCommentForDeclNoCache(BeginDecl); + if (RC) + BeginLocation = RC->getLocStart(); + + // Adjust end of hover text for things that have braces/bodies. We don't + // want to show the whole definition of a function, class, etc. + SourceLocation EndLocation = LocationDecl->getSourceRange().getEnd(); + if (auto FuncDecl = dyn_cast(LocationDecl)) { + if (FuncDecl->isThisDeclarationADefinition() && FuncDecl->getBody()) + EndLocation = FuncDecl->getBody()->getLocStart(); + } else if (auto TagDeclaration = dyn_cast(LocationDecl)) { + if (TagDeclaration->isThisDeclarationADefinition()) + EndLocation = TagDeclaration->getBraceRange().getBegin(); + } else if (dyn_cast(LocationDecl)) { + SourceLocation BeforeLBraceLoc = Lexer::getLocForEndOfToken( + LocationDecl->getLocation(), 0, SourceMgr, LangOpts); + if (BeforeLBraceLoc.isValid()) + EndLocation = BeforeLBraceLoc; + } + + SourceRange SR(BeginLocation, EndLocation); + if (SR.isValid()) { + auto L = getDeclarationLocation(AST, SR); + if (L) { + auto Ref = getBufferDataFromSourceRange(AST, *L); + if (Ref) { + Hover H; + if (SourceMgr.isInMainFile(BeginLocation)) + H.range = L->range; + MarkedString MS = {"", "C++", *Ref}; + HoverContents.push_back(MS); + H.contents = std::move(HoverContents); + return H; + } + } + } + } + + auto MacroInfos = DeclMacrosFinder->takeMacroInfos(); + if (!MacroInfos.empty() && MacroInfos[0]) { + const MacroInfo *MacroInf = MacroInfos[0]; + SourceRange SR(MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc()); + if (SR.isValid()) { + auto L = getDeclarationLocation(AST, SR); + if (L) { + auto Ref = getBufferDataFromSourceRange(AST, *L); + if (Ref) { + Hover H; + FileID FID = + SourceMgr.getFileID(SourceMgr.getExpansionLoc(SR.getBegin())); + bool IsInMainFile = FID.isValid() && SourceMgr.getMainFileID() == FID; + if (!IsInMainFile) { + // Special case when a macro is defined in a preamble, it could + // still be in the "main file", below the inclusions. The + // SourceManager considers the preamble and the main file as two + // different FileIDs but the FileEntries should match. + bool IsInPreamble = FID == SourceMgr.getPreambleFileID(); + if (IsInPreamble) + IsInMainFile = + SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()) == + SourceMgr.getFileEntryForID(FID); + } + if (IsInMainFile) + H.range = L->range; + MarkedString MS = {"", "C++", "#define " + Ref->str()}; + H.contents.push_back(MS); + return H; + } + } + } + } - return DeclLocationsFinder->takeLocations(); + return Hover(); } void ParsedAST::ensurePreambleDeclsDeserialized() { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -397,6 +397,47 @@ }; bool fromJSON(const json::Expr &, TextDocumentPositionParams &); +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; +}; +json::Expr toJSON(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; +}; +json::Expr toJSON(const Hover &H); + /// The kind of a completion entry. enum class CompletionItemKind { Missing = 0, Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -286,6 +286,27 @@ O.map("position", R.position); } +json::Expr toJSON(const MarkedString &MS) { + return json::obj{ + {"language", MS.language}, + {"value", MS.codeBlockValue}, + }; +} + + +json::Expr toJSON(const Hover &H) { + if (H.range.hasValue()) { + return json::obj{ + {"contents", json::ary(H.contents)}, + {"range", H.range.getValue()}, + }; + } + + return json::obj{ + {"contents", json::ary(H.contents)}, + }; +} + json::Expr toJSON(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 @@ -70,6 +70,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,56 @@ +# 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: 611 + +{"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};}\n//comments test\ntemplate\nT templateTest(T foo) {\nreturn foo;}\ntemplate\nclass classTemplateTest {\npublic: T test;};\nint main() {\nint a;\na;\nint b = ns1::test;\nns1::MyClass* Params;\nParams->anotherOperation();\nMACRO;\nint temp = 5;\ntemplateTest(temp);classTemplateTest test;}\n"}}} + +Content-Length: 144 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":0,"character":12}}} +# 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":21,"character":1}}} +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"int a"}],"range":{"end":{"character":5,"line":20},"start":{"character":0,"line":20}}}} + +Content-Length: 145 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":22,"character":15}}} +# 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":23,"character":10}}} +# 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":24,"character":13}}} +# 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: 144 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":25,"character":1}}} +# 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":26,"character":8}}} +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"int temp = 5"}],"range":{"end":{"character":12,"line":26},"start":{"character":0,"line":26}}}} + +Content-Length: 144 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":27,"character":1}}} +# CHECK: {"id":1,"jsonrpc":"2.0","result":{"contents":[{"language":"C++","value":"//comments test\ntemplate\nT templateTest(T foo) {"}],"range":{"end":{"character":23,"line":14},"start":{"character":0,"line":12}}}} + +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": [