Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -68,6 +68,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,18 @@ C.reply(Result ? URI::fromFile(*Result).uri : ""); } +void ClangdLSPServer::onDocumentHighlight(Ctx C, + TextDocumentPositionParams &Params) { + + auto Items = Server + .findDocumentHighlights(Params.textDocument.uri.file, + Position{Params.position.line, + Params.position.character}) + .Value; + + C.reply(json::ary(Items)); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool StorePreamblesInMemory, bool SnippetCompletions, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -287,6 +287,10 @@ /// given a header file and vice versa. llvm::Optional switchSourceHeader(PathRef Path); + /// Get document highlights for a symbol hovered on. + Tagged> 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 @@ -504,6 +504,26 @@ return llvm::None; } +Tagged> +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,10 +315,20 @@ SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, const FileEntry *FE); +Location addDeclarationLocation(ParsedAST &AST, SourceRange SR); + +DocumentHighlight addHighlightLocation(ParsedAST &AST, SourceRange SR, DocumentHighlightKind Kind); + /// Get definition of symbol at a specified \p Pos. std::vector findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger); + +Location getDeclarationLocation(ParsedAST &AST, const SourceRange &ValSourceRange); + +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 @@ -941,6 +941,9 @@ /// Finds declarations locations that a given source location refers to. class DeclarationLocationsFinder : public index::IndexDataConsumer { std::vector DeclarationLocations; + std::vector DeclarationDecls; + std::vector DeclarationMacroInfos; + std::vector Kinds; const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; @@ -951,6 +954,26 @@ ASTContext &AST, Preprocessor &PP) : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} + std::vector takeDecls() { + // Don't keep the same location multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(DeclarationDecls.begin(), DeclarationDecls.end()); + auto last = + std::unique(DeclarationDecls.begin(), DeclarationDecls.end()); + DeclarationDecls.erase(last, DeclarationDecls.end()); + return std::move(DeclarationDecls); + } + + std::vector takeDeclarationMacroInfos() { + // Don't keep the same location multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(DeclarationMacroInfos.begin(), DeclarationMacroInfos.end()); + auto last = + std::unique(DeclarationMacroInfos.begin(), DeclarationMacroInfos.end()); + DeclarationMacroInfos.erase(last, DeclarationMacroInfos.end()); + return std::move(DeclarationMacroInfos); + } + std::vector takeLocations() { // Don't keep the same location multiple times. // This can happen when nodes in the AST are visited twice. @@ -966,12 +989,31 @@ ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + if (isSearchedLocation(FID, Offset)) { addDeclarationLocation(D->getSourceRange()); + DeclarationDecls.push_back(D); + DocumentHighlightKind Kind; + switch (Roles) { + case (unsigned)index::SymbolRole::Read: + Kind = DocumentHighlightKind::Read; + break; + case (unsigned)index::SymbolRole::Write: + Kind = DocumentHighlightKind::Write; + break; + default: + Kind = DocumentHighlightKind::Text; + break; + } + Kinds.push_back(Kind); } return true; } + std::vector getDeclarationDecls() { return DeclarationDecls; }; + std::vector getDeclarationMacroInfos() { return DeclarationMacroInfos; }; + std::vector getKinds() { return Kinds; }; + private: bool isSearchedLocation(FileID FID, unsigned Offset) const { const SourceManager &SourceMgr = AST.getSourceManager(); @@ -984,7 +1026,7 @@ const LangOptions &LangOpts = AST.getLangOpts(); SourceLocation LocStart = ValSourceRange.getBegin(); SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), - 0, SourceMgr, LangOpts); + 0, SourceMgr, LangOpts); Position Begin; Begin.line = SourceMgr.getSpellingLineNumber(LocStart) - 1; Begin.character = SourceMgr.getSpellingColumnNumber(LocStart) - 1; @@ -994,15 +1036,15 @@ 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); - } - } + 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. @@ -1026,16 +1068,77 @@ PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); MacroInfo *MacroInf = MacroDef.getMacroInfo(); if (MacroInf) { - addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), - MacroInf->getDefinitionEndLoc())); + addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), MacroInf->getDefinitionEndLoc())); + DeclarationMacroInfos.push_back(MacroInf); } } } } }; +/// Finds document highlights that a given FileID and file offset refers to. +class DocumentHighlightsFinder : public index::IndexDataConsumer { + std::vector &DeclarationDecls; + std::vector Kinds; + std::vector SourceRanges; + const ASTContext &AST; + Preprocessor &PP; + +public: + DocumentHighlightsFinder(raw_ostream &OS, ASTContext &AST, Preprocessor &PP, + std::vector &Decls, + std::vector Kinds) + : DeclarationDecls(Decls), Kinds(Kinds), AST(AST), PP(PP) {} + + 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(DeclarationDecls.begin(), DeclarationDecls.end(), D) == DeclarationDecls.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); + SourceRanges.push_back(SR); + + return true; + } + + std::vector getDeclarationDecls() { return DeclarationDecls; }; + std::vector getKinds() { return Kinds; }; + std::vector getSourceRanges() { return SourceRanges; }; +}; + } // namespace +Location clangd::addDeclarationLocation(ParsedAST &AST, SourceRange SR) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().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}; + Location L; + L.uri = + URI::fromFile(SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart))); + L.range = R; + return L; +} + std::vector clangd::findDefinitions(ParsedAST &AST, Position Pos, clangd::Logger &Logger) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); @@ -1059,6 +1162,109 @@ return DeclLocationsFinder->takeLocations(); } +Location clangd::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; +} + +DocumentHighlight clangd::addHighlightLocation(ParsedAST &AST, SourceRange SR, + DocumentHighlightKind Kind) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().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; +} + +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 DeclLocationsFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor()); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DeclLocationsFinder, IndexOpts); + + std::vector TempDecls = DeclLocationsFinder->getDeclarationDecls(); + std::vector TempKinds = + DeclLocationsFinder->getKinds(); + + auto DocHighlightsFinder = std::make_shared( + llvm::errs(), AST.getASTContext(), AST.getPreprocessor(), TempDecls, + TempKinds); + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DocHighlightsFinder, IndexOpts); + + std::vector HighlightLocations; + + for (unsigned I = 0; I < DocHighlightsFinder->getSourceRanges().size(); I++) { + HighlightLocations.push_back( + addHighlightLocation(AST, DocHighlightsFinder->getSourceRanges()[I], + DocHighlightsFinder->getKinds()[I])); + } + + for (unsigned I = 0; I < DeclLocationsFinder->getDeclarationMacroInfos().size(); I++) { + HighlightLocations.push_back(addHighlightLocation( + AST, + SourceRange( + DeclLocationsFinder->getDeclarationMacroInfos()[I]->getDefinitionLoc(), + DeclLocationsFinder->getDeclarationMacroInfos()[I]->getDefinitionEndLoc()), + DocHighlightsFinder->getKinds()[I])); + } + + // Don't keep the same location multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(HighlightLocations.begin(), HighlightLocations.end()); + auto last = std::unique(HighlightLocations.begin(), HighlightLocations.end()); + HighlightLocations.erase(last, HighlightLocations.end()); + return std::move(HighlightLocations); +} + void ParsedAST::ensurePreambleDeclsDeserialized() { if (PendingTopLevelDecls.empty()) return; Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -615,6 +615,39 @@ 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; + int test = 3; + + 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 @@ -31,7 +31,6 @@ public: using Ctx = RequestContext; virtual ~ProtocolCallbacks() = default; - virtual void onInitialize(Ctx C, InitializeParams &Params) = 0; virtual void onShutdown(Ctx C, ShutdownParams &Params) = 0; virtual void onExit(Ctx C, ExitParams &Params) = 0; @@ -54,6 +53,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 @@ -46,7 +46,6 @@ JSONOutput *Out; ProtocolCallbacks *Callbacks; }; - } // namespace void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, @@ -74,4 +73,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": []