| //===--- FindSymbols.cpp ------------------------------------*- C++-*------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| #include "FindSymbols.h" |
| |
| #include "AST.h" |
| #include "ClangdUnit.h" |
| #include "FuzzyMatch.h" |
| #include "Logger.h" |
| #include "Quality.h" |
| #include "SourceCode.h" |
| #include "index/Index.h" |
| #include "clang/Index/IndexDataConsumer.h" |
| #include "clang/Index/IndexSymbol.h" |
| #include "clang/Index/IndexingAction.h" |
| #include "llvm/Support/FormatVariadic.h" |
| #include "llvm/Support/Path.h" |
| |
| #define DEBUG_TYPE "FindSymbols" |
| |
| namespace clang { |
| namespace clangd { |
| |
| namespace { |
| |
| // Convert a index::SymbolKind to clangd::SymbolKind (LSP) |
| // Note, some are not perfect matches and should be improved when this LSP |
| // issue is addressed: |
| // https://github.com/Microsoft/language-server-protocol/issues/344 |
| SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) { |
| switch (Kind) { |
| case index::SymbolKind::Unknown: |
| return SymbolKind::Variable; |
| case index::SymbolKind::Module: |
| return SymbolKind::Module; |
| case index::SymbolKind::Namespace: |
| return SymbolKind::Namespace; |
| case index::SymbolKind::NamespaceAlias: |
| return SymbolKind::Namespace; |
| case index::SymbolKind::Macro: |
| return SymbolKind::String; |
| case index::SymbolKind::Enum: |
| return SymbolKind::Enum; |
| case index::SymbolKind::Struct: |
| return SymbolKind::Struct; |
| case index::SymbolKind::Class: |
| return SymbolKind::Class; |
| case index::SymbolKind::Protocol: |
| return SymbolKind::Interface; |
| case index::SymbolKind::Extension: |
| return SymbolKind::Interface; |
| case index::SymbolKind::Union: |
| return SymbolKind::Class; |
| case index::SymbolKind::TypeAlias: |
| return SymbolKind::Class; |
| case index::SymbolKind::Function: |
| return SymbolKind::Function; |
| case index::SymbolKind::Variable: |
| return SymbolKind::Variable; |
| case index::SymbolKind::Field: |
| return SymbolKind::Field; |
| case index::SymbolKind::EnumConstant: |
| return SymbolKind::EnumMember; |
| case index::SymbolKind::InstanceMethod: |
| case index::SymbolKind::ClassMethod: |
| case index::SymbolKind::StaticMethod: |
| return SymbolKind::Method; |
| case index::SymbolKind::InstanceProperty: |
| case index::SymbolKind::ClassProperty: |
| case index::SymbolKind::StaticProperty: |
| return SymbolKind::Property; |
| case index::SymbolKind::Constructor: |
| case index::SymbolKind::Destructor: |
| return SymbolKind::Method; |
| case index::SymbolKind::ConversionFunction: |
| return SymbolKind::Function; |
| case index::SymbolKind::Parameter: |
| return SymbolKind::Variable; |
| case index::SymbolKind::Using: |
| return SymbolKind::Namespace; |
| } |
| llvm_unreachable("invalid symbol kind"); |
| } |
| |
| using ScoredSymbolInfo = std::pair<float, SymbolInformation>; |
| struct ScoredSymbolGreater { |
| bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) { |
| if (L.first != R.first) |
| return L.first > R.first; |
| return L.second.name < R.second.name; // Earlier name is better. |
| } |
| }; |
| |
| } // namespace |
| |
| llvm::Expected<std::vector<SymbolInformation>> |
| getWorkspaceSymbols(StringRef Query, int Limit, const SymbolIndex *const Index, |
| StringRef HintPath) { |
| std::vector<SymbolInformation> Result; |
| if (Query.empty() || !Index) |
| return Result; |
| |
| auto Names = splitQualifiedName(Query); |
| |
| FuzzyFindRequest Req; |
| Req.Query = Names.second; |
| |
| // FuzzyFind doesn't want leading :: qualifier |
| bool IsGlobalQuery = Names.first.consume_front("::"); |
| // Restrict results to the scope in the query string if present (global or |
| // not). |
| if (IsGlobalQuery || !Names.first.empty()) |
| Req.Scopes = {Names.first}; |
| if (Limit) |
| Req.MaxCandidateCount = Limit; |
| TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(Req.MaxCandidateCount); |
| FuzzyMatcher Filter(Req.Query); |
| Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) { |
| // Prefer the definition over e.g. a function declaration in a header |
| auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; |
| auto Uri = URI::parse(CD.FileURI); |
| if (!Uri) { |
| log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", |
| CD.FileURI, Sym.Name); |
| return; |
| } |
| auto Path = URI::resolve(*Uri, HintPath); |
| if (!Path) { |
| log("Workspace symbol: Could not resolve path for URI '{0}' for symbol " |
| "'{1}'.", |
| Uri->toString(), Sym.Name); |
| return; |
| } |
| Location L; |
| L.uri = URIForFile((*Path)); |
| Position Start, End; |
| Start.line = CD.Start.Line; |
| Start.character = CD.Start.Column; |
| End.line = CD.End.Line; |
| End.character = CD.End.Column; |
| L.range = {Start, End}; |
| SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind); |
| std::string Scope = Sym.Scope; |
| StringRef ScopeRef = Scope; |
| ScopeRef.consume_back("::"); |
| SymbolInformation Info = {Sym.Name, SK, L, ScopeRef}; |
| |
| SymbolQualitySignals Quality; |
| Quality.merge(Sym); |
| SymbolRelevanceSignals Relevance; |
| Relevance.Query = SymbolRelevanceSignals::Generic; |
| if (auto NameMatch = Filter.match(Sym.Name)) |
| Relevance.NameMatch = *NameMatch; |
| else { |
| log("Workspace symbol: {0} didn't match query {1}", Sym.Name, |
| Filter.pattern()); |
| return; |
| } |
| Relevance.merge(Sym); |
| auto Score = |
| evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate()); |
| dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score, |
| Quality, Relevance); |
| |
| Top.push({Score, std::move(Info)}); |
| }); |
| for (auto &R : std::move(Top).items()) |
| Result.push_back(std::move(R.second)); |
| return Result; |
| } |
| |
| namespace { |
| /// Finds document symbols in the main file of the AST. |
| class DocumentSymbolsConsumer : public index::IndexDataConsumer { |
| ASTContext &AST; |
| std::vector<SymbolInformation> Symbols; |
| // We are always list document for the same file, so cache the value. |
| llvm::Optional<URIForFile> MainFileUri; |
| |
| public: |
| DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {} |
| std::vector<SymbolInformation> takeSymbols() { return std::move(Symbols); } |
| |
| void initialize(ASTContext &Ctx) override { |
| // Compute the absolute path of the main file which we will use for all |
| // results. |
| const SourceManager &SM = AST.getSourceManager(); |
| const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID()); |
| if (!F) |
| return; |
| auto FilePath = getAbsoluteFilePath(F, SM); |
| if (FilePath) |
| MainFileUri = URIForFile(*FilePath); |
| } |
| |
| bool shouldIncludeSymbol(const NamedDecl *ND) { |
| if (!ND || ND->isImplicit()) |
| return false; |
| // Skip anonymous declarations, e.g (anonymous enum/class/struct). |
| if (ND->getDeclName().isEmpty()) |
| return false; |
| return true; |
| } |
| |
| bool |
| handleDeclOccurence(const Decl *, index::SymbolRoleSet Roles, |
| ArrayRef<index::SymbolRelation> Relations, |
| SourceLocation Loc, |
| index::IndexDataConsumer::ASTNodeInfo ASTNode) override { |
| assert(ASTNode.OrigD); |
| // No point in continuing the index consumer if we could not get the |
| // absolute path of the main file. |
| if (!MainFileUri) |
| return false; |
| // We only want declarations and definitions, i.e. no references. |
| if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) || |
| Roles & static_cast<unsigned>(index::SymbolRole::Definition))) |
| return true; |
| SourceLocation NameLoc = findNameLoc(ASTNode.OrigD); |
| const SourceManager &SourceMgr = AST.getSourceManager(); |
| // We should be only be looking at "local" decls in the main file. |
| if (!SourceMgr.isWrittenInMainFile(NameLoc)) { |
| // Even thought we are visiting only local (non-preamble) decls, |
| // we can get here when in the presense of "extern" decls. |
| return true; |
| } |
| const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(ASTNode.OrigD); |
| if (!shouldIncludeSymbol(ND)) |
| return true; |
| |
| SourceLocation EndLoc = |
| Lexer::getLocForEndOfToken(NameLoc, 0, SourceMgr, AST.getLangOpts()); |
| Position Begin = sourceLocToPosition(SourceMgr, NameLoc); |
| Position End = sourceLocToPosition(SourceMgr, EndLoc); |
| Range R = {Begin, End}; |
| Location L; |
| L.uri = *MainFileUri; |
| L.range = R; |
| |
| std::string QName = printQualifiedName(*ND); |
| StringRef Scope, Name; |
| std::tie(Scope, Name) = splitQualifiedName(QName); |
| Scope.consume_back("::"); |
| |
| index::SymbolInfo SymInfo = index::getSymbolInfo(ND); |
| SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); |
| |
| SymbolInformation SI; |
| SI.name = Name; |
| SI.kind = SK; |
| SI.location = L; |
| SI.containerName = Scope; |
| Symbols.push_back(std::move(SI)); |
| return true; |
| } |
| }; |
| } // namespace |
| |
| llvm::Expected<std::vector<SymbolInformation>> |
| getDocumentSymbols(ParsedAST &AST) { |
| DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext()); |
| |
| index::IndexingOptions IndexOpts; |
| IndexOpts.SystemSymbolFilter = |
| index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; |
| IndexOpts.IndexFunctionLocals = false; |
| indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(), |
| DocumentSymbolsCons, IndexOpts); |
| |
| return DocumentSymbolsCons.takeSymbols(); |
| } |
| |
| } // namespace clangd |
| } // namespace clang |