Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -71,6 +71,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,7 +225,15 @@ if (!Items) return C.replyError(ErrorCode::InvalidParams, llvm::toString(Items.takeError())); - C.reply(json::ary(Items->Value)); + + std::vector LocationVector; + auto test = Items->Value; + + for (auto Item : Items->Value) { + LocationVector.push_back(Item); + } + + C.reply(json::ary(LocationVector)); } void ClangdLSPServer::onSwitchSourceHeader(Ctx C, @@ -234,6 +243,18 @@ 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) + return C.replyError(ErrorCode::InvalidParams, + llvm::toString(H.takeError())); + + C.reply(Hover::unparse(H->Value)); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, bool SnippetCompletions, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -287,6 +287,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 @@ -8,6 +8,7 @@ //===-------------------------------------------------------------------===// #include "ClangdServer.h" +#include "Protocol.h" #include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" @@ -428,6 +429,7 @@ llvm::Expected>> ClangdServer::findDefinitions(PathRef File, Position Pos) { + auto TaggedFS = FSProvider.getTaggedFileSystem(File); std::shared_ptr Resources = Units.getFile(File); @@ -442,6 +444,7 @@ return; Result = clangd::findDefinitions(*AST, Pos, Logger); }); + return make_tagged(std::move(Result), TaggedFS.Tag); } @@ -504,6 +507,31 @@ return llvm::None; } +llvm::Expected> ClangdServer::findHover(PathRef File, + Position Pos) { + // Default Hover values + std::vector EmptyVector; + Position BeginPosition = {0, 0}; + Position EndPosition = {0, 0}; + Range DefaultRange = {BeginPosition, EndPosition}; + Hover FinalHover = {EmptyVector, DefaultRange}; + + 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; + // Only show hover for the first found definition, we could potentially + // expand this in a later patch. + 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 @@ -319,6 +319,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 @@ -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; } @@ -940,7 +941,8 @@ /// Finds declarations locations that a given source location refers to. class DeclarationLocationsFinder : public index::IndexDataConsumer { - std::vector DeclarationLocations; + std::vector DeclarationDecls; + std::vector DeclarationMacroInfs; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; @@ -951,14 +953,23 @@ 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(DeclarationDecls.begin(), DeclarationDecls.end()); + auto Last = std::unique(DeclarationDecls.begin(), DeclarationDecls.end()); + DeclarationDecls.erase(Last, DeclarationDecls.end()); + return std::move(DeclarationDecls); + } + + 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(DeclarationMacroInfs.begin(), DeclarationMacroInfs.end()); + auto Last = + std::unique(DeclarationMacroInfs.begin(), DeclarationMacroInfs.end()); + DeclarationMacroInfs.erase(Last, DeclarationMacroInfs.end()); + return std::move(DeclarationMacroInfs); } bool @@ -966,12 +977,17 @@ ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { - if (isSearchedLocation(FID, Offset)) { - addDeclarationLocation(D->getSourceRange()); - } + + if (isSearchedLocation(FID, Offset)) + DeclarationDecls.push_back(D); return true; } + std::vector getDeclarationDecls() { return DeclarationDecls; }; + std::vector getMacroInfos() { + return DeclarationMacroInfs; + }; + private: bool isSearchedLocation(FileID FID, unsigned Offset) const { const SourceManager &SourceMgr = AST.getSourceManager(); @@ -979,31 +995,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; @@ -1026,8 +1017,7 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + DeclarationMacroInfs.push_back(MacroInf); } } } @@ -1036,6 +1026,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(); @@ -1056,7 +1072,186 @@ indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), DeclLocationsFinder, IndexOpts); - return DeclLocationsFinder->takeLocations(); + std::vector Decls = DeclLocationsFinder->takeDecls(); + std::vector MacroInfos = + DeclLocationsFinder->takeMacroInfos(); + std::vector Result; + + for (auto Item : Decls) { + Location L = getDeclarationLocation(AST, Item->getSourceRange()); + Result.push_back(L); + } + + for (auto Item : MacroInfos) { + SourceRange SR = + SourceRange(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; + Position BeginPosition = {0, 0}; + Position EndPosition = {0, 0}; + Range DefaultRange = {BeginPosition, EndPosition}; + Hover H = {EmptyVector, DefaultRange}; + unsigned Start, End; + 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 DeclLocationsFinder = 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(), + DeclLocationsFinder, IndexOpts); + + if (!DeclLocationsFinder->getDeclarationDecls().empty()) { + const Decl *LocationDecl = DeclLocationsFinder->takeDecls()[0]; + + if (LocationDecl) { + SourceRange SR = LocationDecl->getSourceRange(); + if (auto FuncDecl = dyn_cast(LocationDecl)) { + SourceLocation NewLocation; + if (AST.getASTContext().getRawCommentForDeclNoCache(LocationDecl)) { + NewLocation = AST.getASTContext() + .getRawCommentForDeclNoCache(LocationDecl) + ->getLocStart(); + SR = + SourceRange(NewLocation, LocationDecl->getSourceRange().getEnd()); + } + if (FuncDecl->isThisDeclarationADefinition() && FuncDecl->getBody()) + SR = SourceRange(LocationDecl->getSourceRange().getBegin(), + FuncDecl->getBody()->getLocStart()); + } else if (auto ClassDecl = dyn_cast(LocationDecl)) { + if (!ClassDecl->isInterface() && !ClassDecl->isUnion() && + ClassDecl->isThisDeclarationADefinition()) + SR = SourceRange(LocationDecl->getSourceRange().getBegin(), + ClassDecl->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)) { + // FIXME: find a way to get the hover for the type that is being aliased + 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; + const FileEntry *FE = SourceMgr.getFileManager().getFile(L.uri.file); + FileID FID = SourceMgr.translateFile(FE); + Ref = SourceMgr.getBufferData(FID); + Start = SourceMgr.getFileOffset(SourceMgr.translateFileLineCol( + FE, L.range.start.line + 1, L.range.start.character + 1)); + End = SourceMgr.getFileOffset(SourceMgr.translateFileLineCol( + FE, L.range.end.line + 1, L.range.end.character + 1)); + Ref = Ref.slice(Start, End); + } + + if (const NamedDecl *NamedD = dyn_cast(LocationDecl)) { + + // 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 "::" + PrintingPolicy WithScopePP(AST.getASTContext().getLangOpts()); + std::string WithScopeBuf; + llvm::raw_string_ostream WithScopeOS(WithScopeBuf); + NamedD->printQualifiedName(WithScopeOS, WithScopePP); + + // Get all contexts for current NamedDecl + const DeclContext *Ctx = NamedD->getDeclContext(); + + // For ObjC methods, look through categories and use the interface as + // context. + if (auto *MD = dyn_cast(NamedD)) + if (auto *ID = MD->getClassInterface()) + Ctx = ID; + + typedef SmallVector ContextsTy; + ContextsTy Contexts; + + // Collect contexts. + while (Ctx && isa(Ctx)) { + Contexts.push_back(Ctx); + Ctx = Ctx->getParent(); + } + + std::string ResWithScope = WithScopeOS.str(); + if (!ResWithScope.empty()) { + // Get non-qualified name + std::string PrintedNameBuf; + llvm::raw_string_ostream PrintedNameOS(PrintedNameBuf); + NamedD->printName(PrintedNameOS); + auto Last = ResWithScope.rfind(PrintedNameOS.str()); + if (Last != std::string::npos) { + std::string Res = ResWithScope.substr(0, Last); + if (Res.length() > 2 && + Res.substr(Res.length() - 2, Res.length()) == "::") + Res = Res.substr(0, Res.length() - 2); + + 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 (!DeclLocationsFinder->getMacroInfos().empty()) { + const MacroInfo *MacroInf = DeclLocationsFinder->takeMacroInfos()[0]; + if (MacroInf) { + SourceRange SR = SourceRange(MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc()); + if (SR.isValid()) { + Location L = getDeclarationLocation(AST, SR); + H.range = L.range; + const FileEntry *FE = SourceMgr.getFileManager().getFile(L.uri.file); + FileID FID = SourceMgr.translateFile(FE); + Ref = SourceMgr.getBufferData(FID); + Start = SourceMgr.getFileOffset(SourceMgr.translateFileLineCol( + FE, L.range.start.line + 1, L.range.start.character + 1)); + End = SourceMgr.getFileOffset(SourceMgr.translateFileLineCol( + FE, L.range.end.line + 1, L.range.end.character + 1)); + Ref = Ref.slice(Start, End); + } + } + MarkedString MS = {"", "C++", "#define " + Ref.str()}; + H.contents.push_back(MS); + return H; + } + + return H; } void ParsedAST::ensurePreambleDeclsDeserialized() { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -449,6 +449,53 @@ 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 { + + Hover(std::vector contents, Range r) + : contents(contents), range(r) {} + + /** + * 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,34 @@ 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 + std::vector EmptyVector; + Position BeginPosition = {0,0}; + Position EndPosition = {0,0}; + Range DefaultRange = {BeginPosition, EndPosition}; + Hover H = {EmptyVector, DefaultRange}; + 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": [ @@ -40,6 +41,7 @@ # CHECK-NEXT: "textDocumentSync": 1 # CHECK-NEXT: } # CHECK-NEXT: } + Content-Length: 44 {"jsonrpc":"2.0","id":3,"method":"shutdown"} Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -5,6 +5,7 @@ Content-Length: 143 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}} + # CHECK: "id": 0, # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { @@ -30,6 +31,7 @@ # CHECK-NEXT: "clangd.applyFix" # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "hoverProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "signatureHelpProvider": { # CHECK-NEXT: "triggerCharacters": [