Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -69,6 +69,7 @@ void onSignatureHelp(Ctx C, TextDocumentPositionParams &Params) override; void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) override; void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override; + void onDocumentHighlight(Ctx C, TextDocumentPositionParams &Params) override; void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override; void onCommand(Ctx C, ExecuteCommandParams &Params) override; void onRename(Ctx C, RenameParams &Parames) override; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -57,12 +57,14 @@ {"triggerCharacters", {"(", ","}}, }}, {"definitionProvider", true}, + {"documentHighlightProvider", true}, {"renameProvider", true}, {"executeCommandProvider", json::obj{ {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, }}, }}}}); + if (Params.rootUri && !Params.rootUri->file.empty()) Server.setRootPath(Params.rootUri->file); else if (Params.rootPath && !Params.rootPath->empty()) @@ -234,6 +236,16 @@ C.reply(Result ? URI::fromFile(*Result).uri : ""); } +void ClangdLSPServer::onDocumentHighlight(Ctx C, + TextDocumentPositionParams &Params) { + + auto Highlights = Server.findDocumentHighlights( + Params.textDocument.uri.file, + Position{Params.position.line, Params.position.character}); + + C.reply(json::ary(Highlights->Value)); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, const clangd::CodeCompleteOptions &CCOpts, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -284,6 +284,10 @@ /// given a header file and vice versa. llvm::Optional switchSourceHeader(PathRef Path); + /// Get document highlights for a symbol hovered on. + llvm::Expected>> + findDocumentHighlights(PathRef File, Position Pos); + /// Run formatting for \p Rng inside \p File. std::vector formatRange(PathRef File, Range Rng); /// Run formatting for the whole \p File. Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -507,6 +507,26 @@ return llvm::None; } +llvm::Expected>> +ClangdServer::findDocumentHighlights(PathRef File, Position Pos) { + auto FileContents = DraftMgr.getDraft(File); + assert(FileContents.Draft && + "findDocumentHighlights is called for non-added document"); + + auto TaggedFS = FSProvider.getTaggedFileSystem(File); + + std::shared_ptr Resources = Units.getFile(File); + assert(Resources && "Calling findDocumentHighlights on non-added file"); + + std::vector Result; + Resources->getAST().get()->runUnderLock([Pos, &Result, this](ParsedAST *AST) { + if (!AST) + return; + Result = clangd::findDocumentHighlights(*AST, Pos, Logger); + }); + return make_tagged(std::move(Result), TaggedFS.Tag); +} + std::future ClangdServer::scheduleReparseAndDiags( PathRef File, VersionedDraft Contents, std::shared_ptr Resources, Tagged> TaggedFS) { Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -315,6 +315,9 @@ std::vector findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger); +std::vector +findDocumentHighlights(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 @@ -934,26 +934,35 @@ } /// Finds declarations locations that a given source location refers to. -class DeclarationLocationsFinder : public index::IndexDataConsumer { - std::vector DeclarationLocations; +class DeclarationAndMacrosFinder : public index::IndexDataConsumer { + std::vector Decls; + std::vector MacroInfos; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; public: - DeclarationLocationsFinder(raw_ostream &OS, + DeclarationAndMacrosFinder(raw_ostream &OS, const SourceLocation &SearchedLocation, ASTContext &AST, Preprocessor &PP) : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} - std::vector takeLocations() { - // Don't keep the same location multiple times. + std::vector takeDecls() { + // Don't keep the same declaration multiple times. // This can happen when nodes in the AST are visited twice. - std::sort(DeclarationLocations.begin(), DeclarationLocations.end()); - auto last = - std::unique(DeclarationLocations.begin(), DeclarationLocations.end()); - DeclarationLocations.erase(last, DeclarationLocations.end()); - return std::move(DeclarationLocations); + std::sort(Decls.begin(), Decls.end()); + auto Last = std::unique(Decls.begin(), Decls.end()); + Decls.erase(Last, Decls.end()); + return std::move(Decls); + } + + std::vector takeMacroInfos() { + // Don't keep the same Macro info multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(MacroInfos.begin(), MacroInfos.end()); + auto Last = std::unique(MacroInfos.begin(), MacroInfos.end()); + MacroInfos.erase(Last, MacroInfos.end()); + return std::move(MacroInfos); } bool @@ -961,9 +970,8 @@ ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { - if (isSearchedLocation(FID, Offset)) { - addDeclarationLocation(D->getSourceRange()); - } + if (isSearchedLocation(FID, Offset)) + Decls.push_back(D); return true; } @@ -974,31 +982,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; @@ -1021,16 +1004,111 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + MacroInfos.push_back(MacroInf); } } } } }; +/// Finds document highlights that a given list of declarations refers to. +class DocumentHighlightsFinder : public index::IndexDataConsumer { + std::vector &Decls; + std::vector DocumentHighlights; + const ASTContext &AST; + Preprocessor &PP; + +public: + DocumentHighlightsFinder(raw_ostream &OS, ASTContext &AST, Preprocessor &PP, + std::vector &Decls) + : Decls(Decls), AST(AST), PP(PP) {} + std::vector takeHighlights() { + // Don't keep the same highlight multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(DocumentHighlights.begin(), DocumentHighlights.end()); + auto last = + std::unique(DocumentHighlights.begin(), DocumentHighlights.end()); + DocumentHighlights.erase(last, DocumentHighlights.end()); + return std::move(DocumentHighlights); + } + + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, FileID FID, + unsigned Offset, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + const SourceManager &SourceMgr = AST.getSourceManager(); + if (SourceMgr.getMainFileID() != FID || + std::find(Decls.begin(), Decls.end(), D) == Decls.end()) { + return true; + } + SourceLocation Begin, End; + const LangOptions &LangOpts = AST.getLangOpts(); + SourceLocation StartOfFileLoc = SourceMgr.getLocForStartOfFile(FID); + SourceLocation HightlightStartLoc = StartOfFileLoc.getLocWithOffset(Offset); + End = + Lexer::getLocForEndOfToken(HightlightStartLoc, 0, SourceMgr, LangOpts); + SourceRange SR(HightlightStartLoc, End); + + DocumentHighlightKind Kind = DocumentHighlightKind::Text; + if (static_cast(index::SymbolRole::Write) & Roles) + Kind = DocumentHighlightKind::Write; + else if (static_cast(index::SymbolRole::Read) & Roles) + Kind = DocumentHighlightKind::Read; + + DocumentHighlights.push_back(getDocumentHighlight(SR, Kind)); + return true; + } + + DocumentHighlight getDocumentHighlight(SourceRange SR, + DocumentHighlightKind Kind) { + const SourceManager &SourceMgr = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getLangOpts(); + SourceLocation LocStart = SR.getBegin(); + SourceLocation LocEnd = + Lexer::getLocForEndOfToken(SR.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}; + DocumentHighlight DH; + DH.range = R; + DH.kind = Kind; + return DH; + } +}; + } // 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(); @@ -1040,7 +1118,47 @@ SourceLocation SourceLocationBeg = getBeginningOfIdentifier(AST, Pos, FE); - auto DeclLocationsFinder = std::make_shared( + auto DeclMacrosFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor()); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DeclMacrosFinder, IndexOpts); + + std::vector Decls = DeclMacrosFinder->takeDecls(); + std::vector MacroInfos = + DeclMacrosFinder->takeMacroInfos(); + std::vector Result; + + for (auto Item : Decls) { + Location L = getDeclarationLocation(AST, Item->getSourceRange()); + Result.push_back(L); + } + + for (auto Item : MacroInfos) { + SourceRange SR(Item->getDefinitionLoc(), Item->getDefinitionEndLoc()); + Location L = getDeclarationLocation(AST, SR); + Result.push_back(L); + } + + return Result; +} + +std::vector +clangd::findDocumentHighlights(ParsedAST &AST, Position Pos, + clangd::Logger &Logger) { + 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( llvm::errs(), SourceLocationBeg, AST.getASTContext(), AST.getPreprocessor()); index::IndexingOptions IndexOpts; @@ -1049,9 +1167,17 @@ IndexOpts.IndexFunctionLocals = true; indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), - DeclLocationsFinder, IndexOpts); + DeclMacrosFinder, IndexOpts); + + std::vector SelectedDecls = DeclMacrosFinder->takeDecls(); + + auto DocHighlightsFinder = std::make_shared( + llvm::errs(), AST.getASTContext(), AST.getPreprocessor(), SelectedDecls); + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DocHighlightsFinder, IndexOpts); - return DeclLocationsFinder->takeLocations(); + return DocHighlightsFinder->takeHighlights(); } void ParsedAST::ensurePreambleDeclsDeserialized() { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -615,6 +615,38 @@ clangd::Logger &Logger); }; +enum class DocumentHighlightKind { Text = 1, Read = 2, Write = 3 }; + +/** + * A document highlight is a range inside a text document which deserves + * special attention. Usually a document highlight is visualized by changing + * the background color of its range. + * + */ +struct DocumentHighlight { + /* + * + * The range this highlight applies to. + */ + Range range; + + /** + * The highlight kind, default is DocumentHighlightKind.Text. + */ + DocumentHighlightKind kind = DocumentHighlightKind::Text; + + friend bool operator<(const DocumentHighlight &LHS, + const DocumentHighlight &RHS) { + return std::tie(LHS.range) < std::tie(RHS.range); + } + + friend bool operator==(const DocumentHighlight &LHS, + const DocumentHighlight &RHS) { + return LHS.kind == RHS.kind && LHS.range == RHS.range; + } + + static json::Expr unparse(const DocumentHighlight &DH); +}; } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -1128,3 +1128,11 @@ } return Result; } + +json::Expr DocumentHighlight::unparse(const DocumentHighlight &DH) { + + return json::obj{ + {"range", Range::unparse(DH.range)}, + {"kind", (int) DH.kind}, + }; +} Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -54,6 +54,8 @@ 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 onDocumentHighlight(Ctx C, + TextDocumentPositionParams &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -74,4 +74,6 @@ Register("textDocument/rename", &ProtocolCallbacks::onRename); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); + Register("textDocument/documentHighlight", + &ProtocolCallbacks::onDocumentHighlight); } Index: test/clangd/documenthighlight.test =================================================================== --- /dev/null +++ test/clangd/documenthighlight.test @@ -0,0 +1,37 @@ +# 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: 455 + +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#define MACRO 1\nnamespace ns1 {\nstruct MyClass {\nint xasd;\nvoid anotherOperation() {\n}\nstatic int foo(MyClass*) {\nreturn 0;\n}\n\n};\nstruct Foo {\nint xasd;\n};\n}\nint main() {\nint bonjour;\nbonjour = 2;\nns1::Foo bar = { xasd : 1};\nbar.xasd = 3;\nns1::MyClass* Params;\nParams->anotherOperation();}\n"}}} + +Content-Length: 156 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":2}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":12,"line":16},"start":{"character":4,"line":16}}},{"kind":0,"range":{"end":{"character":7,"line":17},"start":{"character":0,"line":17}}}]} + + +Content-Length: 157 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":18,"character":17}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":9,"line":12},"start":{"character":4,"line":12}}},{"kind":1,"range":{"end":{"character":21,"line":18},"start":{"character":17,"line":18}}},{"kind":216,"range":{"end":{"character":21,"line":18},"start":{"character":17,"line":18}}},{"kind":220,"range":{"end":{"character":8,"line":19},"start":{"character":4,"line":19}}}]} + +Content-Length: 157 + +{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":21,"character":10}}} +# Go to local variable +# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":22,"line":4},"start":{"character":5,"line":4}}},{"kind":0,"range":{"end":{"character":25,"line":21},"start":{"character":8,"line":21}}}]} + +Content-Length: 48 + +{"jsonrpc":"2.0","id":10000,"method":"shutdown"} + +Content-Length: 33 + +{"jsonrpc":"2.0":"method":"exit"} \ No newline at end of file Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -20,6 +20,7 @@ # CHECK-NEXT: }, # CHECK-NEXT: "definitionProvider": true, # CHECK-NEXT: "documentFormattingProvider": true, +# CHECK-NEXT: "documentHighlightProvider": true, # CHECK-NEXT: "documentOnTypeFormattingProvider": { # CHECK-NEXT: "firstTriggerCharacter": "}", # CHECK-NEXT: "moreTriggerCharacter": [] Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -20,6 +20,7 @@ # CHECK-NEXT: }, # CHECK-NEXT: "definitionProvider": true, # CHECK-NEXT: "documentFormattingProvider": true, +# CHECK-NEXT: "documentHighlightProvider": true, # CHECK-NEXT: "documentOnTypeFormattingProvider": { # CHECK-NEXT: "firstTriggerCharacter": "}", # CHECK-NEXT: "moreTriggerCharacter": []