Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -73,6 +73,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 @@ -68,6 +68,7 @@ }}, {"definitionProvider", true}, {"documentHighlightProvider", true}, + {"hoverProvider", true}, {"renameProvider", true}, {"executeCommandProvider", json::obj{ @@ -245,15 +246,16 @@ void ClangdLSPServer::onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) { - auto Items = Server.findDefinitions( - C, Params.textDocument.uri.file, + auto Items = Server.findDefinitions(C, + Params.textDocument.uri.file, Position{Params.position.line, Params.position.character}); if (!Items) return replyError(C, ErrorCode::InvalidParams, - llvm::toString(Items.takeError())); + llvm::toString(Items.takeError())); reply(C, json::ary(Items->Value)); } + void ClangdLSPServer::onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) { llvm::Optional Result = Server.switchSourceHeader(Params.uri.file); @@ -277,6 +279,19 @@ reply(C, json::ary(Highlights->Value)); } +void ClangdLSPServer::onCodeHover(Ctx C, TextDocumentPositionParams &Params) { + auto H = Server.findHover(C, + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}); + + if (!H) { + replyError(C, ErrorCode::InternalError, llvm::toString(H.takeError())); + return; + } + + reply(C, H->Value); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, const clangd::CodeCompleteOptions &CCOpts, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -285,6 +285,9 @@ llvm::Expected>> findDocumentHighlights(const Context &Ctx, PathRef File, Position Pos); + /// Get code hover for a given position. + llvm::Expected> findHover(const Context &Ctx, PathRef File, Position Pos); + /// Run formatting for \p Rng inside \p File with content \p Code. llvm::Expected formatRange(StringRef Code, PathRef File, Range Rng); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -428,6 +428,7 @@ Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx](ParsedAST *AST) { if (!AST) return; + Result = clangd::findDefinitions(Ctx, *AST, Pos); }); return make_tagged(std::move(Result), TaggedFS.Tag); @@ -508,89 +509,109 @@ llvm::Expected>> ClangdServer::findDocumentHighlights(const Context &Ctx, PathRef File, - Position Pos) { - auto FileContents = DraftMgr.getDraft(File); - if (!FileContents.Draft) - return llvm::make_error( - "findDocumentHighlights called on non-added file", - llvm::errc::invalid_argument); + Position Pos) { + auto FileContents = DraftMgr.getDraft(File); + if (!FileContents.Draft) + return llvm::make_error( + "findDocumentHighlights called on non-added file", + llvm::errc::invalid_argument); + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + std::shared_ptr Resources = Units.getFile(File); + if (!Resources) + return llvm::make_error( + "findDocumentHighlights called on non-added file", + llvm::errc::invalid_argument); + + std::vector Result; + llvm::Optional Err; + Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err, + &Result](ParsedAST *AST) { + if (!AST) { + Err = llvm::make_error("Invalid AST", + llvm::errc::invalid_argument); + return; + } + Result = clangd::findDocumentHighlights(Ctx, *AST, Pos); + }); + + if (Err) + return std::move(*Err); + return make_tagged(Result, TaggedFS.Tag); +} +llvm::Expected> ClangdServer::findHover(const Context &Ctx, PathRef File, + Position Pos) { + Hover FinalHover; auto TaggedFS = FSProvider.getTaggedFileSystem(File); std::shared_ptr Resources = Units.getFile(File); if (!Resources) return llvm::make_error( - "findDocumentHighlights called on non-added file", + "Calling findHover on non-added file", llvm::errc::invalid_argument); - - std::vector Result; - llvm::Optional Err; - Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err, - &Result](ParsedAST *AST) { - if (!AST) { - Err = llvm::make_error("Invalid AST", - llvm::errc::invalid_argument); - return; - } - Result = clangd::findDocumentHighlights(Ctx, *AST, Pos); + Resources->getAST().get()->runUnderLock( + [Pos, &FinalHover, &Ctx, this](ParsedAST *AST) { + if (!AST) + return; + FinalHover = clangd::getHover(Ctx, *AST, Pos); }); - if (Err) - return std::move(*Err); - return make_tagged(Result, TaggedFS.Tag); + return make_tagged(std::move(FinalHover), TaggedFS.Tag); } std::future ClangdServer::scheduleReparseAndDiags( - Context Ctx, PathRef File, VersionedDraft Contents, - std::shared_ptr Resources, - Tagged> TaggedFS) { - - assert(Contents.Draft && "Draft must have contents"); - UniqueFunction>(const Context &)> - DeferredRebuild = - Resources->deferRebuild(*Contents.Draft, TaggedFS.Value); - std::promise DonePromise; - std::future DoneFuture = DonePromise.get_future(); - - DocVersion Version = Contents.Version; - Path FileStr = File; - VFSTag Tag = TaggedFS.Tag; - auto ReparseAndPublishDiags = - [this, FileStr, Version, - Tag](UniqueFunction>( - const Context &)> - DeferredRebuild, - std::promise DonePromise, Context Ctx) -> void { - auto Guard = onScopeExit([&]() { DonePromise.set_value(std::move(Ctx)); }); - - auto CurrentVersion = DraftMgr.getVersion(FileStr); - if (CurrentVersion != Version) - return; // This request is outdated - - auto Diags = DeferredRebuild(Ctx); - if (!Diags) - return; // A new reparse was requested before this one completed. - - // We need to serialize access to resulting diagnostics to avoid calling - // `onDiagnosticsReady` in the wrong order. - std::lock_guard DiagsLock(DiagnosticsMutex); - DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr]; - // FIXME(ibiryukov): get rid of '<' comparison here. In the current - // implementation diagnostics will not be reported after version counters' - // overflow. This should not happen in practice, since DocVersion is a - // 64-bit unsigned integer. - if (Version < LastReportedDiagsVersion) - return; - LastReportedDiagsVersion = Version; - - DiagConsumer.onDiagnosticsReady(FileStr, - make_tagged(std::move(*Diags), Tag)); - }; - - WorkScheduler.addToFront(std::move(ReparseAndPublishDiags), - std::move(DeferredRebuild), std::move(DonePromise), - std::move(Ctx)); - return DoneFuture; + Context Ctx, PathRef File, VersionedDraft Contents, + std::shared_ptr Resources, + Tagged> TaggedFS) { + + assert(Contents.Draft && "Draft must have contents"); + UniqueFunction>(const Context &)> + DeferredRebuild = + Resources->deferRebuild(*Contents.Draft, TaggedFS.Value); + std::promise DonePromise; + std::future DoneFuture = DonePromise.get_future(); + + DocVersion Version = Contents.Version; + Path FileStr = File; + VFSTag Tag = TaggedFS.Tag; + auto ReparseAndPublishDiags = + [this, FileStr, Version, + Tag](UniqueFunction>( + const Context &)> + DeferredRebuild, + std::promise DonePromise, Context Ctx) -> void { + auto Guard = onScopeExit([&]() { DonePromise.set_value(std::move(Ctx)); }); + + auto CurrentVersion = DraftMgr.getVersion(FileStr); + if (CurrentVersion != Version) + return; // This request is outdated + + auto Diags = DeferredRebuild(Ctx); + if (!Diags) + return; // A new reparse was requested before this one completed. + + // We need to serialize access to resulting diagnostics to avoid calling + // `onDiagnosticsReady` in the wrong order. + std::lock_guard DiagsLock(DiagnosticsMutex); + DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[FileStr]; + // FIXME(ibiryukov): get rid of '<' comparison here. In the current + // implementation diagnostics will not be reported after version counters' + // overflow. This should not happen in practice, since DocVersion is a + // 64-bit unsigned integer. + if (Version < LastReportedDiagsVersion) + return; + LastReportedDiagsVersion = Version; + + DiagConsumer.onDiagnosticsReady(FileStr, + make_tagged(std::move(*Diags), Tag)); + }; + + WorkScheduler.addToFront(std::move(ReparseAndPublishDiags), + std::move(DeferredRebuild), std::move(DonePromise), + std::move(Ctx)); + return DoneFuture; } std::future Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -265,6 +265,8 @@ std::vector findDocumentHighlights(const Context &Ctx, ParsedAST &AST, Position Pos); +Hover getHover(const Context &Ctx, ParsedAST &AST, Position Pos); + /// 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 @@ -26,6 +26,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 #include @@ -280,7 +281,7 @@ return Mgr.getMacroArgExpandedLocation(InputLoc); } -/// Finds declarations locations that a given source location refers to. +/// Finds declarations and macros that a given source location refers to. class DeclarationAndMacrosFinder : public index::IndexDataConsumer { std::vector Decls; std::vector MacroInfos; @@ -308,7 +309,7 @@ std::sort(MacroInfos.begin(), MacroInfos.end()); auto Last = std::unique(MacroInfos.begin(), MacroInfos.end()); MacroInfos.erase(Last, MacroInfos.end()); - return std::move(MacroInfos); + return MacroInfos; } bool @@ -424,18 +425,88 @@ } }; +/// Returns a NamedDecl that could be used to print the qualifier. +const Decl *getScopeOfDecl(const Decl *D) { + // Code from NamedDecl::printQualifiedName, we should probably move it into + const DeclContext *Ctx = D->getDeclContext(); + + // For ObjC methods, look through categories and use the interface as context. + if (auto *MD = dyn_cast(D)) + if (auto *ID = MD->getClassInterface()) + Ctx = ID; + + if (Ctx->isFunctionOrMethod()) { + /// This is a function-local entity. + return nullptr; + } + + return D; +} + +std::string printScopeOfDecl(const Decl *Scope, const LangOptions &LangOpts) { + if (Scope) { + if (isa(Scope)) + return "global namespace"; + + if (isa(Scope)) { + PrintingPolicy WithScopePP(LangOpts); + std::string WithScopeBuf; + llvm::raw_string_ostream WithScopeOS(WithScopeBuf); + dyn_cast(Scope)->printQualifiedName(WithScopeOS, WithScopePP); + std::string ResWithScope = WithScopeOS.str(); + if (!ResWithScope.empty()) { + std::string Res; + // Get non-qualified name + std::string PrintedNameBuf; + llvm::raw_string_ostream PrintedNameOS(PrintedNameBuf); + dyn_cast(Scope)->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 ""; +} + +/// Returns the file buffer data that the given SourceRange points to. +llvm::Expected getBufferDataFromSourceRange(ParsedAST &AST, + Location L) { + StringRef Ref; + const FileEntry *F = + AST.getASTContext().getSourceManager().getFileManager().getFile( + L.uri.file); + if (!F) + return ""; + + 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::Optional +llvm::ErrorOr getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); - SourceLocation LocStart = ValSourceRange.getBegin(); - + SourceLocation LocStart = + SourceMgr.getExpansionLoc(ValSourceRange.getBegin()); const FileEntry *F = SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart)); if (!F) - return llvm::None; + return llvm::errc::no_such_file_or_directory; SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0, SourceMgr, LangOpts); Position Begin; @@ -529,6 +600,128 @@ return DocHighlightsFinder->takeHighlights(); } +Hover getHoverContents(const Decl *D, ParsedAST &AST) { + const Decl *LocationDecl = D; + std::vector HoverContents; + + // Compute scope as the first "section" of the hover. + if (const NamedDecl *NamedD = dyn_cast(LocationDecl)) { + auto Scope = printScopeOfDecl(getScopeOfDecl(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, AST.getASTContext().getSourceManager(), + AST.getASTContext().getLangOpts()); + 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 (AST.getASTContext().getSourceManager().isInMainFile(BeginLocation)) + H.range = L->range; + MarkedString MS = {"", "C++", *Ref}; + HoverContents.push_back(MS); + H.contents = std::move(HoverContents); + return H; + } + } + } + return Hover(); +} + +Hover getHoverContents(const MacroInfo *MacroInf, ParsedAST &AST) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + 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 Hover(); +} + +Hover clangd::getHover(const Context &Ctx, ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + 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]) + return getHoverContents(Decls[0], AST); + + auto MacroInfos = DeclMacrosFinder->takeMacroInfos(); + if (!MacroInfos.empty() && MacroInfos[0]) + return getHoverContents(MacroInfos[0], AST); + + return Hover(); +} + void ParsedAST::ensurePreambleDeclsDeserialized() { if (PreambleDeclsDeserialized || !Preamble) return; @@ -745,6 +938,7 @@ &IgnoreDiagnostics, false); CI = createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine, VFS); + CI->LangOpts->CommentOpts.ParseAllComments = true; // createInvocationFromCommandLine sets DisableFree. CI->getFrontendOpts().DisableFree = false; } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -397,6 +397,41 @@ }; 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 @@ -285,6 +285,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 @@ -57,6 +57,7 @@ virtual void onRename(Ctx C, RenameParams &Parames) = 0; virtual void onDocumentHighlight(Ctx C, TextDocumentPositionParams &Params) = 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 @@ -69,6 +69,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); Register("textDocument/documentHighlight", 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 @@ -31,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": [ Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -31,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": [