Nebiroth updated this revision to Diff 120894. Nebiroth added a comment. Code hover now displays declaration of symbol instead of source code by default. Code hover now displays context information such as namespace and class name. Array of MarkedString objects is now sent as response in JSON.
https://reviews.llvm.org/D35894 Files: clangd/ClangdLSPServer.cpp clangd/ClangdLSPServer.h clangd/ClangdServer.cpp clangd/ClangdServer.h clangd/ClangdUnit.cpp clangd/ClangdUnit.h clangd/Protocol.cpp clangd/Protocol.h clangd/ProtocolHandlers.cpp clangd/ProtocolHandlers.h test/clangd/hover.test test/clangd/initialize-params-invalid.test test/clangd/initialize-params.test
Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -5,16 +5,17 @@ Content-Length: 143 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 535 +# CHECK: Content-Length: 568 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ # CHECK: "textDocumentSync": 1, # CHECK: "documentFormattingProvider": true, # CHECK: "documentRangeFormattingProvider": true, # CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, # CHECK: "codeActionProvider": true, # CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, # CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, -# CHECK: "definitionProvider": true +# CHECK: "definitionProvider": true, +# CHECK: "hoverProvider": true # CHECK: }}} # Content-Length: 44 Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -5,16 +5,17 @@ Content-Length: 142 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 535 +# CHECK: Content-Length: 568 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ # CHECK: "textDocumentSync": 1, # CHECK: "documentFormattingProvider": true, # CHECK: "documentRangeFormattingProvider": true, # CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]}, # CHECK: "codeActionProvider": true, # CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, # CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, -# CHECK: "definitionProvider": true +# CHECK: "definitionProvider": true, +# CHECK: "hoverProvider": true # CHECK: }}} # Content-Length: 44 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: {"jsonrpc":"2.0","id":1,"result":{"contents": ["#define statement",{"language": "C++", "value": "MACRO 1"}], "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 15}}}} + +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: {"jsonrpc":"2.0","id":1,"result":{"contents": [{"language": "C++", "value": "int a"}], "range": {"start": {"line": 13, "character": 0}, "end": {"line": 13, "character": 5}}}} + +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: {"jsonrpc":"2.0","id":1,"result":{"contents": ["In namespace named ns1",{"language": "C++", "value": "int test = 5"}], "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 12}}}} + +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: {"jsonrpc":"2.0","id":1,"result":{"contents": ["In namespace named ns1",{"language": "C++", "value": "struct MyClass {\nint xasd;\nvoid anotherOperation() {\n}\nstatic int foo(MyClass*) {\nreturn 0;\n}\n\n}"}], "range": {"start": {"line": 3, "character": 0}, "end": {"line": 11, "character": 1}}}} + +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: {"jsonrpc":"2.0","id":1,"result":{"contents": ["In class named ns1::MyClass",{"language": "C++", "value": "void anotherOperation() {\n}"}], "range": {"start": {"line": 5, "character": 0}, "end": {"line": 6, "character": 1}}}} + +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: {"jsonrpc":"2.0","id":1,"result":{"contents": ["#define statement",{"language": "C++", "value": "MACRO 1"}], "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 15}}}} + +Content-Length: 44 + +{"jsonrpc":"2.0","id":3,"method":"shutdown"} + + Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -52,6 +52,7 @@ virtual void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) = 0; virtual void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) = 0; virtual void onFileEvent(Ctx C, DidChangeWatchedFilesParams &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 @@ -67,5 +67,6 @@ Register("textDocument/definition", &ProtocolCallbacks::onGoToDefinition); Register("textDocument/switchSourceHeader", &ProtocolCallbacks::onSwitchSourceHeader); + Register("textDocument/hover", &ProtocolCallbacks::onCodeHover); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -21,6 +21,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H +#include "clang/Basic/SourceLocation.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/YAMLParser.h" #include <string> @@ -393,6 +394,60 @@ 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. + */ + + MarkedString(std::string markdown) + : markdownString(markdown), codeBlockLanguage(""), codeBlockValue("") {} + + MarkedString(std::string blockLanguage, std::string blockValue) + : markdownString(""), codeBlockLanguage(blockLanguage), + codeBlockValue(blockValue) {} + + std::string markdownString; + std::string codeBlockLanguage; + std::string codeBlockValue; + + static std::string unparse(const MarkedString &MS); +}; + +struct Hover { + + Hover(std::vector<MarkedString> contents, Range r) + : contents(contents), range(r) {} + + /** + * The hover's content + */ + std::vector<MarkedString> 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. + */ + Range range; + + static std::string 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 @@ -880,6 +880,36 @@ return Result; } +std::string Hover::unparse(const Hover &H) { + std::string Result; + std::string ContentsStr; + for (auto &Contents : H.contents) { + ContentsStr += MarkedString::unparse(Contents) + ","; + } + if (!ContentsStr.empty()) + ContentsStr.pop_back(); + llvm::raw_string_ostream(Result) << llvm::format( + R"({"contents": [%s], "range": %s})", ContentsStr.c_str(), + Range::unparse(H.range).c_str()); + return Result; +} + +std::string MarkedString::unparse(const MarkedString &MS) { + std::string Result; + if (MS.markdownString != "") { + llvm::raw_string_ostream(Result) << llvm::format( + R"("%s")", llvm::yaml::escape(MS.markdownString).c_str()); + } else { + + llvm::raw_string_ostream(Result) + << llvm::format(R"({"language": "%s", "value": "%s"})", + (llvm::yaml::escape(MS.codeBlockLanguage)).c_str(), + (llvm::yaml::escape(MS.codeBlockValue)).c_str()); + } + + return Result; +} + std::string CompletionItem::unparse(const CompletionItem &CI) { std::string Result = "{"; llvm::raw_string_ostream Os(Result); Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -305,8 +305,10 @@ clangd::Logger &Logger); /// Get definition of symbol at a specified \p Pos. -std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos, - clangd::Logger &Logger); +std::vector<std::pair<Location, const Decl *>> +findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger); + +Hover getHover(ParsedAST &AST, std::pair<Location, const Decl *> LocationDecl); /// For testing/debugging purposes. Note that this method deserializes all /// unserialized Decls, so use with care. Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -911,7 +911,7 @@ /// Finds declarations locations that a given source location refers to. class DeclarationLocationsFinder : public index::IndexDataConsumer { - std::vector<Location> DeclarationLocations; + std::vector<std::pair<Location, const Decl *>> DeclarationLocations; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; @@ -922,7 +922,7 @@ ASTContext &AST, Preprocessor &PP) : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} - std::vector<Location> takeLocations() { + std::vector<std::pair<Location, const Decl *>> takeLocations() { // 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()); @@ -937,20 +937,65 @@ ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + + // Keep default value. + SourceRange SR = D->getSourceRange(); + + if (auto FuncDecl = dyn_cast<FunctionDecl>(D)) { + if (FuncDecl->isThisDeclarationADefinition()) + { + if (FuncDecl->hasBody()) + { + SR = SourceRange(D->getSourceRange().getBegin(), + FuncDecl->getBody()->getLocStart()); + } + } + } else if (auto ClassDecl = dyn_cast<TagDecl>(D)) { + if (ClassDecl->isStruct()) { + SR = SourceRange(D->getSourceRange().getBegin(), + ClassDecl->getBraceRange().getBegin()); + } else if (ClassDecl->isClass()) { + if (ClassDecl->isThisDeclarationADefinition()) + SR = SourceRange(D->getSourceRange().getBegin(), + ClassDecl->getBraceRange().getBegin()); + } else if (ClassDecl->isEnum()) { + if (ClassDecl->isThisDeclarationADefinition()) + SR = SourceRange(D->getSourceRange().getBegin(), + ClassDecl->getBraceRange().getBegin()); + } + } else if (auto NameDecl = dyn_cast<NamespaceDecl>(D)) { + SourceLocation BeforeLBraceLoc = Lexer::getLocForEndOfToken( + D->getLocation(), 0, AST.getSourceManager(), AST.getLangOpts()); + if (BeforeLBraceLoc.isValid()) + SR = SourceRange(NameDecl->getLocStart(), BeforeLBraceLoc); + else + SR = D->getSourceRange(); + } else if (dyn_cast<TypedefDecl>(D)) { + SR = D->getSourceRange(); + } + // for everything else in ValueDecl, so lvalues of variables, function + // designations and enum constants + else if (dyn_cast<ValueDecl>(D)) { + SR = D->getSourceRange(); + } + if (isSearchedLocation(FID, Offset)) { - addDeclarationLocation(D->getSourceRange()); + DeclarationLocations.push_back( + {getDeclarationLocation(D->getSourceRange()), D}); + } return true; } private: + bool isSearchedLocation(FileID FID, unsigned Offset) const { const SourceManager &SourceMgr = AST.getSourceManager(); return SourceMgr.getFileOffset(SearchedLocation) == Offset && SourceMgr.getFileID(SearchedLocation) == FID; } - void addDeclarationLocation(const SourceRange &ValSourceRange) { + Location getDeclarationLocation(const SourceRange &ValSourceRange) { const SourceManager &SourceMgr = AST.getSourceManager(); const LangOptions &LangOpts = AST.getLangOpts(); SourceLocation LocStart = ValSourceRange.getBegin(); @@ -967,7 +1012,7 @@ L.uri = URI::fromFile( SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))); L.range = R; - DeclarationLocations.push_back(L); + return L; } void finish() override { @@ -992,8 +1037,10 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + DeclarationLocations.push_back({getDeclarationLocation(SourceRange( + MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc())), + nullptr}); } } } @@ -1040,8 +1087,8 @@ } } // namespace -std::vector<Location> clangd::findDefinitions(ParsedAST &AST, Position Pos, - clangd::Logger &Logger) { +std::vector<std::pair<Location, const Decl *>> +clangd::findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) @@ -1063,6 +1110,116 @@ return DeclLocationsFinder->takeLocations(); } +Hover clangd::getHover(ParsedAST &AST, + std::pair<Location, const Decl *> LocationDecl) { + Location L = LocationDecl.first; + std::vector<MarkedString> Contents; + unsigned Start; + unsigned End; + + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + Range R; + const FileEntry *FE = SourceMgr.getFileManager().getFile(L.uri.file); + FileID id = SourceMgr.translateFile(FE); + StringRef Ref = SourceMgr.getBufferData(id); + 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 (LocationDecl.second) { + if (const NamedDecl *NamedD = dyn_cast<NamedDecl>(LocationDecl.second)) { + // 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<ObjCMethodDecl>(NamedD)) + if (auto *ID = MD->getClassInterface()) + Ctx = ID; + + typedef SmallVector<const DeclContext *, 8> ContextsTy; + ContextsTy Contexts; + + // Collect contexts. + while (Ctx && isa<NamedDecl>(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()) { + if (!Contexts.empty()) { + if (Contexts[0]->isNamespace()) { + const NamespaceDecl *ND = cast<NamespaceDecl>(Contexts[0]); + const IdentifierInfo *II = ND->getIdentifier(); + + if (!II) + Res = "anonymous namespace"; + else if (Contexts[0]->isStdNamespace()) + Res = "std namespace"; + else + Res = "namespace named " + Res; + } else if (isa<TagDecl>(Contexts[0])) { + std::string s = Contexts[0]->getDeclKindName(); + if (s == "CXXRecord") // doesnt work right currently + { + Res = "class named " + Res; + } else if (s == "Enum") { + Res = "Enum " + Res; + } + } else if (isa<FunctionDecl>(Contexts[0])) { + } + } + MarkedString Info("In " + Res); + Contents.push_back(Info); + } else { + if (isa<FunctionDecl>(NamedD)) { + MarkedString Info("global function"); + Contents.push_back(Info); + } + } + } + } + } + } else { + // If Decl is nullptr, then this was obtained form a Macro type such as + // #define, #ifdef and so on. + // TODO: support statements other than #define + // TODO: find a way to distinguish with null Decls + + MarkedString MS("#define statement"); + Contents.push_back(MS); + } + + MarkedString MS("C++", Ref); + Contents.push_back(MS); + R = L.range; + Hover H(Contents, R); + return H; +} + void ParsedAST::ensurePreambleDeclsDeserialized() { if (PendingTopLevelDecls.empty()) return; Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -62,8 +62,9 @@ Tagged(const Tagged<U> &Other) : Value(Other.Value), Tag(Other.Tag) {} template <class U> - Tagged(Tagged<U> &&Other) - : Value(std::move(Other.Value)), Tag(std::move(Other.Tag)) {} + Tagged(Tagged<U> &&Other) : Value(std::move(Other.Value)), Tag(std::move(Other.Tag)) {} + + // template <class U> T Value = T(); VFSTag Tag = VFSTag(); @@ -277,13 +278,16 @@ IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr); /// Get definition of symbol at a specified \p Line and \p Column in \p File. - llvm::Expected<Tagged<std::vector<Location>>> findDefinitions(PathRef File, + llvm::Expected<Tagged<std::vector<std::pair<Location, const Decl*>>>> findDefinitions(PathRef File, Position Pos); + /// Helper function that returns a path to the corresponding source file when /// given a header file and vice versa. llvm::Optional<Path> switchSourceHeader(PathRef Path); + Tagged<Hover> findHover(PathRef File, Position Pos); + /// Run formatting for \p Rng inside \p File. std::vector<tooling::Replacement> 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" @@ -353,17 +354,23 @@ return Result; } -llvm::Expected<Tagged<std::vector<Location>>> +llvm::Expected<Tagged<std::vector<std::pair<Location, const Decl*>>>> ClangdServer::findDefinitions(PathRef File, Position Pos) { + + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && + "findDefinitions is called for non-added document"); + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); std::shared_ptr<CppFile> Resources = Units.getFile(File); if (!Resources) return llvm::make_error<llvm::StringError>( "findDefinitions called on non-added file", llvm::errc::invalid_argument); - std::vector<Location> Result; + std::vector<std::pair<Location, const Decl *>> Result; Resources->getAST().get()->runUnderLock([Pos, &Result, this](ParsedAST *AST) { if (!AST) return; @@ -431,6 +438,36 @@ return llvm::None; } +Tagged<Hover> ClangdServer::findHover(PathRef File, Position Pos) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && "findHover is called for non-added document"); + + std::vector<MarkedString> Contents; + MarkedString MS = MarkedString("", ""); + Contents.push_back(MS); + Range R; + // Hover FinalHover(MS, R); + Hover FinalHover(Contents, R); + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + std::shared_ptr<CppFile> Resources = Units.getFile(File); + assert(Resources && "Calling findDefinitions on non-added file"); + + std::vector<std::pair<Location, const Decl *>> Result; + + Resources->getAST().get()->runUnderLock( + [Pos, &Result, &FinalHover, this](ParsedAST *AST) { + if (!AST) + return; + Result = clangd::findDefinitions(*AST, Pos, Logger); + if (!Result.empty()) { + FinalHover = clangd::getHover(*AST, Result[0]); + } + }); + + return make_tagged(std::move(FinalHover), TaggedFS.Tag); +} + std::future<void> ClangdServer::scheduleReparseAndDiags( PathRef File, VersionedDraft Contents, std::shared_ptr<CppFile> Resources, Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) { Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -69,6 +69,7 @@ void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) override; void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override; void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override; + void onCodeHover(Ctx C, TextDocumentPositionParams &Params) override; std::vector<clang::tooling::Replacement> getFixIts(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -47,7 +47,8 @@ "codeActionProvider": true, "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, "signatureHelpProvider": {"triggerCharacters": ["(",","]}, - "definitionProvider": true + "definitionProvider": true, + "hoverProvider": true }})"); if (Params.rootUri && !Params.rootUri->file.empty()) Server.setRootPath(Params.rootUri->file); @@ -178,7 +179,7 @@ std::string Locations; for (const auto &Item : Items->Value) { - Locations += Location::unparse(Item); + Locations += Location::unparse(Item.first); Locations += ","; } if (!Locations.empty()) @@ -193,6 +194,22 @@ C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")"); } +void ClangdLSPServer::onCodeHover(Ctx C, TextDocumentPositionParams &Params) { + + Hover H = + Server + .findHover(Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}) + .Value; + + if (!(H.contents[0].codeBlockLanguage == "" && + H.contents[0].markdownString == "" && + H.contents[0].codeBlockValue == "")) + C.reply(Hover::unparse(H)); + else + C.reply("[]"); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool SnippetCompletions, llvm::Optional<StringRef> ResourceDir,
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits