Nebiroth updated this revision to Diff 127131. Nebiroth added a comment. Rebase on master
Repository: rCTE Clang Tools Extra 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 @@ -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-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/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<class T>\nT templateTest(T foo) {\nreturn foo;}\ntemplate<classT>\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<int> 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<class T>\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: 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: 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<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. + /// + llvm::Optional<Range> 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/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -265,6 +265,8 @@ std::vector<DocumentHighlight> 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 @@ -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 <algorithm> @@ -218,6 +219,7 @@ template <class T> bool futureIsReady(std::shared_future<T> const &Future) { return Future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + } } // namespace @@ -282,7 +284,8 @@ 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<const Decl *> Decls; std::vector<const MacroInfo *> MacroInfos; @@ -297,20 +300,21 @@ : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} std::vector<const Decl *> takeDecls() { - // Don't keep the same declaration multiple times. + // Don't keep the same location multiple times. // This can happen when nodes in the AST are visited twice. std::sort(Decls.begin(), Decls.end()); auto Last = std::unique(Decls.begin(), Decls.end()); Decls.erase(Last, Decls.end()); - return std::move(Decls); + return Decls; } std::vector<const MacroInfo *> 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 std::move(MacroInfos); + return MacroInfos; } bool @@ -426,26 +430,76 @@ } }; +/// 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<StringRef> 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::Optional<Location> +llvm::ErrorOr<Location> 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; - Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; - Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; + Begin.line = SourceMgr.getExpansionLineNumber(LocStart) - 1; + Begin.character = SourceMgr.getExpansionColumnNumber(LocStart) - 1; Position End; - End.line = SourceMgr.getSpellingLineNumber(LocEnd) - 1; - End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1; + End.line = SourceMgr.getExpansionLineNumber(LocEnd) - 1; + End.character = SourceMgr.getExpansionColumnNumber(LocEnd) - 1; Range R = {Begin, End}; Location L; @@ -501,10 +555,90 @@ std::vector<DocumentHighlight> clangd::findDocumentHighlights(const Context &Ctx, ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); + if (!FE) + return {}; + + SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); + + auto DeclMacrosFinder = std::make_shared<DeclarationAndMacrosFinder>( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor()); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + // Macro occurences are not currently handled. + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DeclMacrosFinder, IndexOpts); + + std::vector<const Decl *> SelectedDecls = DeclMacrosFinder->takeDecls(); + + auto DocHighlightsFinder = std::make_shared<DocumentHighlightsFinder>( + llvm::errs(), AST.getASTContext(), AST.getPreprocessor(), SelectedDecls); + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DocHighlightsFinder, IndexOpts); + + return DocHighlightsFinder->takeHighlights(); +} + +// Returns SourceLocation pointing to the beginning of function comments when +// hovering on a function. This function backtracks from the starting location +// one line at a time to determine what will be included. +SourceLocation getFunctionComments(ParsedAST &AST, const Decl *D) { + + const SourceManager &SM = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + + SourceLocation LocStart = D->getSourceRange().getBegin(); + SourceLocation LocEnd = + Lexer::getLocForEndOfToken(D->getSourceRange().getEnd(), 0, SM, LangOpts); + Position Begin; + Begin.line = SM.getSpellingLineNumber(LocStart) - 1; + Begin.character = SM.getSpellingColumnNumber(LocStart) - 1; + Position End; + End.line = SM.getSpellingLineNumber(LocEnd) - 1; + End.character = SM.getSpellingColumnNumber(LocEnd) - 1; + Range R = {Begin, End}; + Location StartLocation; + StartLocation.uri = + URI::fromFile(SM.getFilename(SM.getSpellingLoc(LocStart))); + StartLocation.range = R; + + SourceLocation LocationCandidate; + + const FileEntry *FE = SM.getFileManager().getFile(StartLocation.uri.file); + FileID ID = SM.translateFile(FE); + StringRef Ref = SM.getBufferData(ID); + int StartLine = + StartLocation.range.start.line; // previous line from the actual start + bool done = false; + while (!done && StartLine > 0) { + unsigned Start = + SM.getFileOffset(SM.translateFileLineCol(FE, StartLine, 1)); + unsigned End = SM.getFileOffset(SM.translateFileLineCol(FE, StartLine, 50)); + StringRef CurrentBuffer = Ref.slice(Start, End); + if (CurrentBuffer.find("//") != std::string::npos && + CurrentBuffer.find("}") == std::string::npos) + LocationCandidate = SM.translateFileLineCol(FE, StartLine, 1); + else + done = true; + + StartLine--; + } + + return LocationCandidate; +} + +Hover clangd::getHover(const Context &Ctx, ParsedAST &AST, Position Pos) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); const FileEntry *FE = SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); if (!FE) - return {}; + return Hover(); SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); @@ -515,20 +649,103 @@ IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = true; - - // Macro occurences are not currently handled. indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), DeclMacrosFinder, IndexOpts); - std::vector<const Decl *> SelectedDecls = DeclMacrosFinder->takeDecls(); - - auto DocHighlightsFinder = std::make_shared<DocumentHighlightsFinder>( - llvm::errs(), AST.getASTContext(), AST.getPreprocessor(), SelectedDecls); + auto Decls = DeclMacrosFinder->takeDecls(); + if (!Decls.empty() && Decls[0]) { + const Decl *LocationDecl = Decls[0]; + std::vector<MarkedString> HoverContents; + + // Compute scope as the first "section" of the hover. + if (const NamedDecl *NamedD = dyn_cast<NamedDecl>(LocationDecl)) { + std::string Scope = getScope(NamedD, AST.getASTContext().getLangOpts()); + if (!Scope.empty()) { + MarkedString Info = {"", "C++", "In " + Scope}; + HoverContents.push_back(Info); + } + } - indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), - DocHighlightsFinder, IndexOpts); + // 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<FunctionDecl>(LocationDecl)) { + if (FuncDecl->isThisDeclarationADefinition() && FuncDecl->getBody()) + EndLocation = FuncDecl->getBody()->getLocStart(); + } else if (auto TagDeclaration = dyn_cast<TagDecl>(LocationDecl)) { + if (TagDeclaration->isThisDeclarationADefinition()) + EndLocation = TagDeclaration->getBraceRange().getBegin(); + } else if (dyn_cast<NamespaceDecl>(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 DocHighlightsFinder->takeHighlights(); + return Hover(); } void ParsedAST::ensurePreambleDeclsDeserialized() { @@ -748,6 +965,7 @@ CI = createInvocationFromCommandLine(ArgStrs, CommandLineDiagsEngine, VFS); // createInvocationFromCommandLine sets DisableFree. + CI->LangOpts->CommentOpts.ParseAllComments = true; CI->getFrontendOpts().DisableFree = false; } assert(CI && "Couldn't create CompilerInvocation"); Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -300,6 +300,15 @@ llvm::Expected<tooling::Replacements> formatOnType(StringRef Code, PathRef File, Position Pos); + llvm::Expected<Tagged<Hover>> findHover(const Context &Ctx, 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. + std::vector<tooling::Replacement> formatFile(PathRef File); + /// Run formatting after a character was typed at \p Pos in \p File. + std::vector<tooling::Replacement> formatOnType(PathRef File, Position Pos); + /// Rename all occurrences of the symbol at the \p Pos in \p File to /// \p NewName. Expected<std::vector<tooling::Replacement>> rename(const Context &Ctx, Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -425,9 +425,10 @@ llvm::errc::invalid_argument); std::vector<Location> Result; - Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx](ParsedAST *AST) { + Resources->getAST().get()->runUnderLock([Pos, &Result, &Ctx, this](ParsedAST *AST) { if (!AST) return; + Result = clangd::findDefinitions(Ctx, *AST, Pos); }); return make_tagged(std::move(Result), TaggedFS.Tag); @@ -508,89 +509,106 @@ llvm::Expected<Tagged<std::vector<DocumentHighlight>>> ClangdServer::findDocumentHighlights(const Context &Ctx, PathRef File, - Position Pos) { - auto FileContents = DraftMgr.getDraft(File); - if (!FileContents.Draft) - return llvm::make_error<llvm::StringError>( - "findDocumentHighlights called on non-added file", - llvm::errc::invalid_argument); + Position Pos) { + auto FileContents = DraftMgr.getDraft(File); + if (!FileContents.Draft) + return llvm::make_error<llvm::StringError>( + "findDocumentHighlights called on non-added file", + llvm::errc::invalid_argument); + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + std::shared_ptr<CppFile> Resources = Units.getFile(File); + if (!Resources) + return llvm::make_error<llvm::StringError>( + "findDocumentHighlights called on non-added file", + llvm::errc::invalid_argument); + + std::vector<DocumentHighlight> Result; + llvm::Optional<llvm::Error> Err; + Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err, + &Result](ParsedAST *AST) { + if (!AST) { + Err = llvm::make_error<llvm::StringError>("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<Tagged<Hover>> ClangdServer::findHover(const Context &Ctx, PathRef File, + Position Pos) { + Hover FinalHover; auto TaggedFS = FSProvider.getTaggedFileSystem(File); std::shared_ptr<CppFile> Resources = Units.getFile(File); - if (!Resources) - return llvm::make_error<llvm::StringError>( - "findDocumentHighlights called on non-added file", - llvm::errc::invalid_argument); - - std::vector<DocumentHighlight> Result; - llvm::Optional<llvm::Error> Err; - Resources->getAST().get()->runUnderLock([Pos, &Ctx, &Err, - &Result](ParsedAST *AST) { - if (!AST) { - Err = llvm::make_error<llvm::StringError>("Invalid AST", - llvm::errc::invalid_argument); - return; - } - Result = clangd::findDocumentHighlights(Ctx, *AST, Pos); + assert(Resources && "Calling findHover on non-added file"); + 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<Context> ClangdServer::scheduleReparseAndDiags( - Context Ctx, PathRef File, VersionedDraft Contents, - std::shared_ptr<CppFile> Resources, - Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) { - - assert(Contents.Draft && "Draft must have contents"); - UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>(const Context &)> - DeferredRebuild = - Resources->deferRebuild(*Contents.Draft, TaggedFS.Value); - std::promise<Context> DonePromise; - std::future<Context> DoneFuture = DonePromise.get_future(); - - DocVersion Version = Contents.Version; - Path FileStr = File; - VFSTag Tag = TaggedFS.Tag; - auto ReparseAndPublishDiags = - [this, FileStr, Version, - Tag](UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>( - const Context &)> - DeferredRebuild, - std::promise<Context> 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<std::mutex> 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<CppFile> Resources, + Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) { + + assert(Contents.Draft && "Draft must have contents"); + UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>(const Context &)> + DeferredRebuild = + Resources->deferRebuild(*Contents.Draft, TaggedFS.Value); + std::promise<Context> DonePromise; + std::future<Context> DoneFuture = DonePromise.get_future(); + + DocVersion Version = Contents.Version; + Path FileStr = File; + VFSTag Tag = TaggedFS.Tag; + auto ReparseAndPublishDiags = + [this, FileStr, Version, + Tag](UniqueFunction<llvm::Optional<std::vector<DiagWithFixIts>>( + const Context &)> + DeferredRebuild, + std::promise<Context> 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<std::mutex> 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<Context> 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<TextEdit> getFixIts(StringRef File, const clangd::Diagnostic &D); Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -69,6 +69,7 @@ }}, {"definitionProvider", true}, {"documentHighlightProvider", true}, + {"hoverProvider", true}, {"renameProvider", true}, {"executeCommandProvider", json::obj{ @@ -246,15 +247,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<Path> Result = Server.switchSourceHeader(Params.uri.file); @@ -278,6 +280,20 @@ 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,
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits