Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -66,9 +66,11 @@ void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) override; void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override; void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override; + void onDocumentHighlight(Ctx C, TextDocumentPositionParams &Params) override; std::vector - getFixIts(StringRef File, const clangd::Diagnostic &D); + getFixIts(StringRef File, const clangd::Diagnostic &D); + /// Function that will be called on a separate thread when diagnostics are /// ready. Sends the Dianostics to LSP client via Out.writeMessage and caches 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, + "documentHighlightProvider": true }})"); if (Params.rootUri && !Params.rootUri->file.empty()) Server.setRootPath(Params.rootUri->file); @@ -187,6 +188,25 @@ C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")"); } +void ClangdLSPServer::onDocumentHighlight(Ctx C, TextDocumentPositionParams &Params) { + + auto Items = Server + .findDocumentHighlights(Params.textDocument.uri.file, + Position{Params.position.line, + Params.position.character}) + .Value; + + std::string Highlights; + + for (const auto &Item : Items) { + Highlights += DocumentHighlight::unparse(Item); + Highlights += ","; + } + if (!Highlights.empty()) + Highlights.pop_back(); + C.reply("[" + Highlights + "]"); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount, bool SnippetCompletions, llvm::Optional ResourceDir, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -271,6 +271,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 @@ -408,6 +408,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 @@ -271,6 +271,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 @@ -876,6 +876,8 @@ Preprocessor &PP; public: + std::vector Decls; + std::vector MacroInfos; DeclarationLocationsFinder(raw_ostream &OS, const SourceLocation &SearchedLocation, ASTContext &AST, Preprocessor &PP) @@ -896,8 +898,10 @@ ArrayRef Relations, FileID FID, unsigned Offset, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + if (isSearchedLocation(FID, Offset)) { addDeclarationLocation(D->getSourceRange()); + Decls.push_back(D); } return true; } @@ -953,6 +957,125 @@ if (MacroInf) { addDeclarationLocation(SourceRange(MacroInf->getDefinitionLoc(), MacroInf->getDefinitionEndLoc())); + MacroInfos.push_back(MacroInf); + } + } + } + } +}; + +/// Finds document highlights that a given FileID and file offset refers to. +class DocumentHighlightsFinder : public index::IndexDataConsumer { + std::vector DeclarationLocations; + + const SourceLocation &SearchedLocation; + const ASTContext &AST; + Preprocessor &PP; + +public: + std::vector Decls; + std::vector MacroInfos; + + DocumentHighlightsFinder(raw_ostream &OS, + const SourceLocation &SearchedLocation, + ASTContext &AST, Preprocessor &PP, + std::vector Decls, + std::vector MacroInfos) + : SearchedLocation(SearchedLocation), AST(AST), PP(PP), Decls(Decls), + MacroInfos(MacroInfos) {} + + std::vector takeHighlights() { + // 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()); + auto last = + std::unique(DeclarationLocations.begin(), DeclarationLocations.end()); + DeclarationLocations.erase(last, DeclarationLocations.end()); + return std::move(DeclarationLocations); + } + + 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 (std::find(Decls.begin(), Decls.end(), D) != Decls.end()) { + 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; + 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; + } + addHighlightLocation(SR, Kind); + } + return true; + } + +private: + + void addHighlightLocation(const SourceRange &ValSourceRange, + DocumentHighlightKind Kind) { + 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}; + DocumentHighlight DH; + DH.range = R; + DH.kind = Kind; + DeclarationLocations.push_back(DH); + } + + void finish() override { + // Also handle possible macro at the searched location. + Token Result; + if (!Lexer::getRawToken(SearchedLocation, Result, AST.getSourceManager(), + AST.getLangOpts(), false)) { + if (Result.is(tok::raw_identifier)) { + PP.LookUpIdentifierInfo(Result); + } + IdentifierInfo *IdentifierInfo = Result.getIdentifierInfo(); + if (IdentifierInfo && IdentifierInfo->hadMacroDefinition()) { + std::pair DecLoc = + AST.getSourceManager().getDecomposedExpansionLoc(SearchedLocation); + // Get the definition just before the searched location so that a macro + // referenced in a '#undef MACRO' can still be found. + SourceLocation BeforeSearchedLocation = getMacroArgExpandedLocation( + AST.getSourceManager(), + AST.getSourceManager().getFileEntryForID(DecLoc.first), + DecLoc.second - 1); + MacroDefinition MacroDef = + PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); + MacroInfo *MacroInf = MacroDef.getMacroInfo(); + if (MacroInf) { + if (std::find(MacroInfos.begin(), MacroInfos.end(), MacroInf) != + MacroInfos.end()) { + addHighlightLocation(SourceRange(MacroInf->getDefinitionLoc(), + MacroInf->getDefinitionEndLoc()), + DocumentHighlightKind::Text); + } } } } @@ -1022,6 +1145,42 @@ return DeclLocationsFinder->takeLocations(); } +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); + + auto DocHighlightsFinder = std::make_shared( + llvm::errs(), SourceLocationBeg, AST.getASTContext(), + AST.getPreprocessor(), DeclLocationsFinder->Decls, + DeclLocationsFinder->MacroInfos); + + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + + indexTopLevelDecls(AST.getASTContext(), AST.getTopLevelDecls(), + DocHighlightsFinder, IndexOpts); + + return DocHighlightsFinder->takeHighlights(); +} + void ParsedAST::ensurePreambleDeclsDeserialized() { if (PendingTopLevelDecls.empty()) return; Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -530,6 +530,38 @@ static std::string unparse(const SignatureHelp &); }; +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 std::string unparse(const DocumentHighlight &DH); +}; } // namespace clangd } // namespace clang Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -973,3 +973,13 @@ Result.push_back('}'); return Result; } + +std::string DocumentHighlight::unparse(const DocumentHighlight &DH) { + std::string Result; + std::string ref = Range::unparse(DH.range); + + llvm::raw_string_ostream(Result) + << llvm::formatv("{\"range\": {0}", ref) << "," + << llvm::format(R"( "kind": %d)", DH.kind) << "}"; + return Result; +} 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 onDocumentDidOpen(Ctx C, DidOpenTextDocumentParams &Params) = 0; @@ -51,6 +50,8 @@ 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 onDocumentHighlight(Ctx C, + TextDocumentPositionParams &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -42,12 +42,12 @@ JSONOutput *Out; ProtocolCallbacks *Callbacks; }; - } // namespace void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, ProtocolCallbacks &Callbacks) { + HandlerRegisterer Register{Dispatcher, &Out, &Callbacks}; Register("initialize", &ProtocolCallbacks::onInitialize); @@ -67,4 +67,6 @@ Register("textDocument/switchSourceHeader", &ProtocolCallbacks::onSwitchSourceHeader); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); + Register("textDocument/documentHighlight", + &ProtocolCallbacks::onDocumentHighlight); } Index: test/clangd/documenthighlight.test =================================================================== --- /dev/null +++ test/clangd/documenthighlight.test @@ -0,0 +1,42 @@ +# 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"}} +# CHECK: Content-Length: 580 +# 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: "documentHighlightProvider": true +# CHECK: }}} +# + +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: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 16, "character": 4}, "end": {"line": 16, "character": 12}}, "kind": 1},{"range": {"start": {"line": 17, "character": 0}, "end": {"line": 17, "character": 7}}, "kind": 1}]} + +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: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 12, "character": 4}, "end": {"line": 12, "character": 9}}, "kind": 1},{"range": {"start": {"line": 18, "character": 17}, "end": {"line": 18, "character": 21}}, "kind": 1},{"range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 8}}, "kind": 1}]} + +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: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 4, "character": 5}, "end": {"line": 4, "character": 22}}, "kind": 1},{"range": {"start": {"line": 21, "character": 8}, "end": {"line": 21, "character": 25}}, "kind": 1}]} + Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -5,7 +5,7 @@ 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: 580 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ # CHECK: "textDocumentSync": 1, # CHECK: "documentFormattingProvider": true, @@ -14,7 +14,8 @@ # CHECK: "codeActionProvider": true, # CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, # CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, -# CHECK: "definitionProvider": true +# CHECK: "definitionProvider": true, +# CHECK: "documentHighlightProvider": true # CHECK: }}} # Content-Length: 44 Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -5,7 +5,7 @@ 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: 580 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ # CHECK: "textDocumentSync": 1, # CHECK: "documentFormattingProvider": true, @@ -14,9 +14,12 @@ # CHECK: "codeActionProvider": true, # CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, # CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]}, -# CHECK: "definitionProvider": true +# CHECK: "definitionProvider": true, +# CHECK: "documentHighlightProvider": true # CHECK: }}} # Content-Length: 44 {"jsonrpc":"2.0","id":3,"method":"shutdown"} + +