670 lines · cpp
1//===--- FindSymbols.cpp ------------------------------------*- C++-*------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "FindSymbols.h"9 10#include "AST.h"11#include "FuzzyMatch.h"12#include "ParsedAST.h"13#include "Quality.h"14#include "SourceCode.h"15#include "index/Index.h"16#include "support/Logger.h"17#include "clang/AST/DeclFriend.h"18#include "clang/AST/DeclTemplate.h"19#include "clang/Index/IndexSymbol.h"20#include "llvm/ADT/ArrayRef.h"21#include "llvm/ADT/STLExtras.h"22#include "llvm/ADT/SmallVector.h"23#include "llvm/ADT/StringRef.h"24#include <limits>25#include <optional>26#include <tuple>27 28#define DEBUG_TYPE "FindSymbols"29 30namespace clang {31namespace clangd {32 33namespace {34using ScoredSymbolInfo = std::pair<float, SymbolInformation>;35struct ScoredSymbolGreater {36 bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {37 if (L.first != R.first)38 return L.first > R.first;39 return L.second.name < R.second.name; // Earlier name is better.40 }41};42 43// Returns true if \p Query can be found as a sub-sequence inside \p Scope.44bool approximateScopeMatch(llvm::StringRef Scope, llvm::StringRef Query) {45 assert(Scope.empty() || Scope.ends_with("::"));46 assert(Query.empty() || Query.ends_with("::"));47 while (!Scope.empty() && !Query.empty()) {48 auto Colons = Scope.find("::");49 assert(Colons != llvm::StringRef::npos);50 51 llvm::StringRef LeadingSpecifier = Scope.slice(0, Colons + 2);52 Scope = Scope.slice(Colons + 2, llvm::StringRef::npos);53 Query.consume_front(LeadingSpecifier);54 }55 return Query.empty();56}57 58} // namespace59 60llvm::Expected<Location> indexToLSPLocation(const SymbolLocation &Loc,61 llvm::StringRef TUPath) {62 auto Path = URI::resolve(Loc.FileURI, TUPath);63 if (!Path)64 return error("Could not resolve path for file '{0}': {1}", Loc.FileURI,65 Path.takeError());66 Location L;67 L.uri = URIForFile::canonicalize(*Path, TUPath);68 Position Start, End;69 Start.line = Loc.Start.line();70 Start.character = Loc.Start.column();71 End.line = Loc.End.line();72 End.character = Loc.End.column();73 L.range = {Start, End};74 return L;75}76 77llvm::Expected<Location> symbolToLocation(const Symbol &Sym,78 llvm::StringRef TUPath) {79 // Prefer the definition over e.g. a function declaration in a header80 return indexToLSPLocation(81 Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration, TUPath);82}83 84llvm::Expected<std::vector<SymbolInformation>>85getWorkspaceSymbols(llvm::StringRef Query, int Limit,86 const SymbolIndex *const Index, llvm::StringRef HintPath) {87 std::vector<SymbolInformation> Result;88 if (!Index)89 return Result;90 91 // Lookup for qualified names are performed as:92 // - Exact namespaces are boosted by the index.93 // - Approximate matches are (sub-scope match) included via AnyScope logic.94 // - Non-matching namespaces (no sub-scope match) are post-filtered.95 auto Names = splitQualifiedName(Query);96 97 FuzzyFindRequest Req;98 Req.Query = std::string(Names.second);99 100 // FuzzyFind doesn't want leading :: qualifier.101 auto HasLeadingColons = Names.first.consume_front("::");102 // Limit the query to specific namespace if it is fully-qualified.103 Req.AnyScope = !HasLeadingColons;104 // Boost symbols from desired namespace.105 if (HasLeadingColons || !Names.first.empty())106 Req.Scopes = {std::string(Names.first)};107 if (Limit) {108 Req.Limit = Limit;109 // If we are boosting a specific scope allow more results to be retrieved,110 // since some symbols from preferred namespaces might not make the cut.111 if (Req.AnyScope && !Req.Scopes.empty())112 *Req.Limit *= 5;113 }114 TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(115 Req.Limit.value_or(std::numeric_limits<size_t>::max()));116 FuzzyMatcher Filter(Req.Query);117 118 Index->fuzzyFind(Req, [HintPath, &Top, &Filter, AnyScope = Req.AnyScope,119 ReqScope = Names.first](const Symbol &Sym) {120 llvm::StringRef Scope = Sym.Scope;121 // Fuzzyfind might return symbols from irrelevant namespaces if query was122 // not fully-qualified, drop those.123 if (AnyScope && !approximateScopeMatch(Scope, ReqScope))124 return;125 126 auto Loc = symbolToLocation(Sym, HintPath);127 if (!Loc) {128 log("Workspace symbols: {0}", Loc.takeError());129 return;130 }131 132 SymbolQualitySignals Quality;133 Quality.merge(Sym);134 SymbolRelevanceSignals Relevance;135 Relevance.Name = Sym.Name;136 Relevance.Query = SymbolRelevanceSignals::Generic;137 // If symbol and request scopes do not match exactly, apply a penalty.138 Relevance.InBaseClass = AnyScope && Scope != ReqScope;139 if (auto NameMatch = Filter.match(Sym.Name))140 Relevance.NameMatch = *NameMatch;141 else {142 log("Workspace symbol: {0} didn't match query {1}", Sym.Name,143 Filter.pattern());144 return;145 }146 Relevance.merge(Sym);147 auto QualScore = Quality.evaluateHeuristics();148 auto RelScore = Relevance.evaluateHeuristics();149 auto Score = evaluateSymbolAndRelevance(QualScore, RelScore);150 dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score,151 Quality, Relevance);152 153 SymbolInformation Info;154 Info.name = (Sym.Name + Sym.TemplateSpecializationArgs).str();155 Info.kind = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);156 Info.location = *Loc;157 Scope.consume_back("::");158 Info.containerName = Scope.str();159 160 // Exposed score excludes fuzzy-match component, for client-side re-ranking.161 Info.score = Relevance.NameMatch > std::numeric_limits<float>::epsilon()162 ? Score / Relevance.NameMatch163 : QualScore;164 Top.push({Score, std::move(Info)});165 });166 for (auto &R : std::move(Top).items())167 Result.push_back(std::move(R.second));168 return Result;169}170 171namespace {172std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) {173 // Print `MyClass(Category)` instead of `Category` and `MyClass()` instead174 // of `anonymous`.175 if (const auto *Container = dyn_cast<ObjCContainerDecl>(&ND))176 return printObjCContainer(*Container);177 // Differentiate between class and instance methods: print `-foo` instead of178 // `foo` and `+sharedInstance` instead of `sharedInstance`.179 if (const auto *Method = dyn_cast<ObjCMethodDecl>(&ND)) {180 std::string Name;181 llvm::raw_string_ostream OS(Name);182 183 OS << (Method->isInstanceMethod() ? '-' : '+');184 Method->getSelector().print(OS);185 186 return Name;187 }188 return printName(Ctx, ND);189}190 191std::string getSymbolDetail(ASTContext &Ctx, const NamedDecl &ND) {192 PrintingPolicy P(Ctx.getPrintingPolicy());193 P.SuppressScope = true;194 P.SuppressUnwrittenScope = true;195 P.AnonymousTagLocations = false;196 P.PolishForDeclaration = true;197 std::string Detail;198 llvm::raw_string_ostream OS(Detail);199 if (ND.getDescribedTemplateParams()) {200 OS << "template ";201 }202 if (const auto *VD = dyn_cast<ValueDecl>(&ND)) {203 // FIXME: better printing for dependent type204 if (isa<CXXConstructorDecl>(VD)) {205 std::string ConstructorType = VD->getType().getAsString(P);206 // Print constructor type as "(int)" instead of "void (int)".207 llvm::StringRef WithoutVoid = ConstructorType;208 WithoutVoid.consume_front("void ");209 OS << WithoutVoid;210 } else if (!isa<CXXDestructorDecl>(VD)) {211 VD->getType().print(OS, P);212 }213 } else if (const auto *TD = dyn_cast<TagDecl>(&ND)) {214 OS << TD->getKindName();215 } else if (isa<TypedefNameDecl>(&ND)) {216 OS << "type alias";217 } else if (isa<ConceptDecl>(&ND)) {218 OS << "concept";219 }220 return std::move(OS.str());221}222 223std::optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {224 auto &SM = Ctx.getSourceManager();225 226 SourceLocation BeginLoc = ND.getBeginLoc();227 SourceLocation EndLoc = ND.getEndLoc();228 const auto SymbolRange =229 toHalfOpenFileRange(SM, Ctx.getLangOpts(), {BeginLoc, EndLoc});230 if (!SymbolRange)231 return std::nullopt;232 233 index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);234 // FIXME: This is not classifying constructors, destructors and operators235 // correctly.236 SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);237 238 DocumentSymbol SI;239 SI.name = getSymbolName(Ctx, ND);240 SI.kind = SK;241 SI.deprecated = ND.isDeprecated();242 SI.range = Range{sourceLocToPosition(SM, SymbolRange->getBegin()),243 sourceLocToPosition(SM, SymbolRange->getEnd())};244 SI.detail = getSymbolDetail(Ctx, ND);245 246 SourceLocation NameLoc = ND.getLocation();247 SourceLocation FallbackNameLoc;248 if (NameLoc.isMacroID()) {249 if (isSpelledInSource(NameLoc, SM)) {250 // Prefer the spelling loc, but save the expansion loc as a fallback.251 FallbackNameLoc = SM.getExpansionLoc(NameLoc);252 NameLoc = SM.getSpellingLoc(NameLoc);253 } else {254 NameLoc = SM.getExpansionLoc(NameLoc);255 }256 }257 auto ComputeSelectionRange = [&](SourceLocation L) -> Range {258 Position NameBegin = sourceLocToPosition(SM, L);259 Position NameEnd = sourceLocToPosition(260 SM, Lexer::getLocForEndOfToken(L, 0, SM, Ctx.getLangOpts()));261 return Range{NameBegin, NameEnd};262 };263 264 SI.selectionRange = ComputeSelectionRange(NameLoc);265 if (!SI.range.contains(SI.selectionRange) && FallbackNameLoc.isValid()) {266 // 'selectionRange' must be contained in 'range'. In cases where clang267 // reports unrelated ranges, we first try falling back to the expansion268 // loc for the selection range.269 SI.selectionRange = ComputeSelectionRange(FallbackNameLoc);270 }271 if (!SI.range.contains(SI.selectionRange)) {272 // If the containment relationship still doesn't hold, throw away273 // 'range' and use 'selectionRange' for both.274 SI.range = SI.selectionRange;275 }276 return SI;277}278 279/// A helper class to build an outline for the parse AST. It traverses the AST280/// directly instead of using RecursiveASTVisitor (RAV) for three main reasons:281/// - there is no way to keep RAV from traversing subtrees we are not282/// interested in. E.g. not traversing function locals or implicit template283/// instantiations.284/// - it's easier to combine results of recursive passes,285/// - visiting decls is actually simple, so we don't hit the complicated286/// cases that RAV mostly helps with (types, expressions, etc.)287class DocumentOutline {288 // A DocumentSymbol we're constructing.289 // We use this instead of DocumentSymbol directly so that we can keep track290 // of the nodes we insert for macros.291 class SymBuilder {292 std::vector<SymBuilder> Children;293 DocumentSymbol Symbol; // Symbol.children is empty, use Children instead.294 // Macro expansions that this node or its parents are associated with.295 // (Thus we will never create further children for these expansions).296 llvm::SmallVector<SourceLocation> EnclosingMacroLoc;297 298 public:299 DocumentSymbol build() && {300 for (SymBuilder &C : Children) {301 Symbol.children.push_back(std::move(C).build());302 // Expand range to ensure children nest properly, which editors expect.303 // This can fix some edge-cases in the AST, but is vital for macros.304 // A macro expansion "contains" AST node if it covers the node's primary305 // location, but it may not span the node's whole range.306 Symbol.range.start =307 std::min(Symbol.range.start, Symbol.children.back().range.start);308 Symbol.range.end =309 std::max(Symbol.range.end, Symbol.children.back().range.end);310 }311 return std::move(Symbol);312 }313 314 // Add a symbol as a child of the current one.315 SymBuilder &addChild(DocumentSymbol S) {316 Children.emplace_back();317 Children.back().EnclosingMacroLoc = EnclosingMacroLoc;318 Children.back().Symbol = std::move(S);319 return Children.back();320 }321 322 // Get an appropriate container for children of this symbol that were323 // expanded from a macro (whose spelled name is Tok).324 //325 // This may return:326 // - a macro symbol child of this (either new or previously created)327 // - this scope itself, if it *is* the macro symbol or is nested within it328 SymBuilder &inMacro(const syntax::Token &Tok, const SourceManager &SM,329 std::optional<syntax::TokenBuffer::Expansion> Exp) {330 if (llvm::is_contained(EnclosingMacroLoc, Tok.location()))331 return *this;332 // If there's an existing child for this macro, we expect it to be last.333 if (!Children.empty() && !Children.back().EnclosingMacroLoc.empty() &&334 Children.back().EnclosingMacroLoc.back() == Tok.location())335 return Children.back();336 337 DocumentSymbol Sym;338 Sym.name = Tok.text(SM).str();339 Sym.kind = SymbolKind::Null; // There's no suitable kind!340 Sym.range = Sym.selectionRange =341 halfOpenToRange(SM, Tok.range(SM).toCharRange(SM));342 343 // FIXME: Exp is currently unavailable for nested expansions.344 if (Exp) {345 // Full range covers the macro args.346 Sym.range = halfOpenToRange(SM, CharSourceRange::getCharRange(347 Exp->Spelled.front().location(),348 Exp->Spelled.back().endLocation()));349 // Show macro args as detail.350 llvm::raw_string_ostream OS(Sym.detail);351 const syntax::Token *Prev = nullptr;352 for (const auto &Tok : Exp->Spelled.drop_front()) {353 // Don't dump arbitrarily long macro args.354 if (OS.tell() > 80) {355 OS << " ...)";356 break;357 }358 if (Prev && Prev->endLocation() != Tok.location())359 OS << ' ';360 OS << Tok.text(SM);361 Prev = &Tok;362 }363 }364 SymBuilder &Child = addChild(std::move(Sym));365 Child.EnclosingMacroLoc.push_back(Tok.location());366 return Child;367 }368 };369 370public:371 DocumentOutline(ParsedAST &AST) : AST(AST) {}372 373 /// Builds the document outline for the generated AST.374 std::vector<DocumentSymbol> build() {375 SymBuilder Root;376 for (auto &TopLevel : AST.getLocalTopLevelDecls())377 traverseDecl(TopLevel, Root);378 return std::move(std::move(Root).build().children);379 }380 381private:382 enum class VisitKind { No, OnlyDecl, OnlyChildren, DeclAndChildren };383 384 void traverseDecl(Decl *D, SymBuilder &Parent) {385 // Skip symbols which do not originate from the main file.386 if (!isInsideMainFile(D->getLocation(), AST.getSourceManager()))387 return;388 389 if (auto *Templ = llvm::dyn_cast<TemplateDecl>(D)) {390 // TemplatedDecl might be null, e.g. concepts.391 if (auto *TD = Templ->getTemplatedDecl())392 D = TD;393 }394 395 // FriendDecls don't act as DeclContexts, but they might wrap a function396 // definition that won't be visible through other means in the AST. Hence397 // unwrap it here instead.398 if (auto *Friend = llvm::dyn_cast<FriendDecl>(D)) {399 if (auto *Func =400 llvm::dyn_cast_or_null<FunctionDecl>(Friend->getFriendDecl())) {401 if (Func->isThisDeclarationADefinition())402 D = Func;403 }404 }405 406 VisitKind Visit = shouldVisit(D);407 if (Visit == VisitKind::No)408 return;409 410 if (Visit == VisitKind::OnlyChildren)411 return traverseChildren(D, Parent);412 413 auto *ND = llvm::cast<NamedDecl>(D);414 auto Sym = declToSym(AST.getASTContext(), *ND);415 if (!Sym)416 return;417 SymBuilder &MacroParent = possibleMacroContainer(D->getLocation(), Parent);418 SymBuilder &Child = MacroParent.addChild(std::move(*Sym));419 420 if (Visit == VisitKind::OnlyDecl)421 return;422 423 assert(Visit == VisitKind::DeclAndChildren && "Unexpected VisitKind");424 traverseChildren(ND, Child);425 }426 427 // Determines where a decl should appear in the DocumentSymbol hierarchy.428 //429 // This is usually a direct child of the relevant AST parent.430 // But we may also insert nodes for macros. Given:431 // #define DECLARE_INT(V) int v;432 // namespace a { DECLARE_INT(x) }433 // We produce:434 // Namespace a435 // Macro DECLARE_INT(x)436 // Variable x437 //438 // In the absence of macros, this method simply returns Parent.439 // Otherwise it may return a macro expansion node instead.440 // Each macro only has at most one node in the hierarchy, even if it expands441 // to multiple decls.442 SymBuilder &possibleMacroContainer(SourceLocation TargetLoc,443 SymBuilder &Parent) {444 const auto &SM = AST.getSourceManager();445 // Look at the path of macro-callers from the token to the main file.446 // Note that along these paths we see the "outer" macro calls first.447 SymBuilder *CurParent = &Parent;448 for (SourceLocation Loc = TargetLoc; Loc.isMacroID();449 Loc = SM.getImmediateMacroCallerLoc(Loc)) {450 // Find the virtual macro body that our token is being substituted into.451 FileID MacroBody;452 if (SM.isMacroArgExpansion(Loc)) {453 // Loc is part of a macro arg being substituted into a macro body.454 MacroBody = SM.getFileID(SM.getImmediateExpansionRange(Loc).getBegin());455 } else {456 // Loc is already in the macro body.457 MacroBody = SM.getFileID(Loc);458 }459 // The macro body is being substituted for a macro expansion, whose460 // first token is the name of the macro.461 SourceLocation MacroName =462 SM.getSLocEntry(MacroBody).getExpansion().getExpansionLocStart();463 // Only include the macro expansion in the outline if it was written464 // directly in the main file, rather than expanded from another macro.465 if (!MacroName.isValid() || !MacroName.isFileID())466 continue;467 // All conditions satisfied, add the macro.468 if (auto *Tok = AST.getTokens().spelledTokenContaining(MacroName))469 CurParent = &CurParent->inMacro(470 *Tok, SM, AST.getTokens().expansionStartingAt(Tok));471 }472 return *CurParent;473 }474 475 void traverseChildren(Decl *D, SymBuilder &Builder) {476 auto *Scope = llvm::dyn_cast<DeclContext>(D);477 if (!Scope)478 return;479 for (auto *C : Scope->decls())480 traverseDecl(C, Builder);481 }482 483 VisitKind shouldVisit(Decl *D) {484 if (D->isImplicit())485 return VisitKind::No;486 487 if (llvm::isa<LinkageSpecDecl>(D) || llvm::isa<ExportDecl>(D))488 return VisitKind::OnlyChildren;489 490 if (!llvm::isa<NamedDecl>(D))491 return VisitKind::No;492 493 if (auto *Func = llvm::dyn_cast<FunctionDecl>(D)) {494 // Some functions are implicit template instantiations, those should be495 // ignored.496 if (auto *Info = Func->getTemplateSpecializationInfo()) {497 if (!Info->isExplicitInstantiationOrSpecialization())498 return VisitKind::No;499 }500 // Only visit the function itself, do not visit the children (i.e.501 // function parameters, etc.)502 return VisitKind::OnlyDecl;503 }504 // Handle template instantiations. We have three cases to consider:505 // - explicit instantiations, e.g. 'template class std::vector<int>;'506 // Visit the decl itself (it's present in the code), but not the507 // children.508 // - implicit instantiations, i.e. not written by the user.509 // Do not visit at all, they are not present in the code.510 // - explicit specialization, e.g. 'template <> class vector<bool> {};'511 // Visit both the decl and its children, both are written in the code.512 if (auto *TemplSpec = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D)) {513 if (TemplSpec->isExplicitInstantiationOrSpecialization())514 return TemplSpec->isExplicitSpecialization()515 ? VisitKind::DeclAndChildren516 : VisitKind::OnlyDecl;517 return VisitKind::No;518 }519 if (auto *TemplSpec = llvm::dyn_cast<VarTemplateSpecializationDecl>(D)) {520 if (TemplSpec->isExplicitInstantiationOrSpecialization())521 return TemplSpec->isExplicitSpecialization()522 ? VisitKind::DeclAndChildren523 : VisitKind::OnlyDecl;524 return VisitKind::No;525 }526 // For all other cases, visit both the children and the decl.527 return VisitKind::DeclAndChildren;528 }529 530 ParsedAST &AST;531};532 533struct PragmaMarkSymbol {534 DocumentSymbol DocSym;535 bool IsGroup;536};537 538/// Merge in `PragmaMarkSymbols`, sorted ascending by range, into the given539/// `DocumentSymbol` tree.540void mergePragmas(DocumentSymbol &Root, ArrayRef<PragmaMarkSymbol> Pragmas) {541 while (!Pragmas.empty()) {542 // We'll figure out where the Pragmas.front() should go.543 PragmaMarkSymbol P = std::move(Pragmas.front());544 Pragmas = Pragmas.drop_front();545 DocumentSymbol *Cur = &Root;546 while (Cur->range.contains(P.DocSym.range)) {547 bool Swapped = false;548 for (auto &C : Cur->children) {549 // We assume at most 1 child can contain the pragma (as pragmas are on550 // a single line, and children have disjoint ranges).551 if (C.range.contains(P.DocSym.range)) {552 Cur = &C;553 Swapped = true;554 break;555 }556 }557 // Cur is the parent of P since none of the children contain P.558 if (!Swapped)559 break;560 }561 // Pragma isn't a group so we can just insert it and we are done.562 if (!P.IsGroup) {563 Cur->children.emplace_back(std::move(P.DocSym));564 continue;565 }566 // Pragma is a group, so we need to figure out where it terminates:567 // - If the next Pragma is not contained in Cur, P owns all of its568 // parent's children which occur after P.569 // - If the next pragma is contained in Cur but actually belongs to one570 // of the parent's children, we temporarily skip over it and look at571 // the next pragma to decide where we end.572 // - Otherwise nest all of its parent's children which occur after P but573 // before the next pragma.574 bool TerminatedByNextPragma = false;575 for (auto &NextPragma : Pragmas) {576 // If we hit a pragma outside of Cur, the rest will be outside as well.577 if (!Cur->range.contains(NextPragma.DocSym.range))578 break;579 580 // NextPragma cannot terminate P if it is nested inside a child, look for581 // the next one.582 if (llvm::any_of(Cur->children, [&NextPragma](const auto &Child) {583 return Child.range.contains(NextPragma.DocSym.range);584 }))585 continue;586 587 // Pragma owns all the children between P and NextPragma588 auto It = llvm::partition(Cur->children,589 [&P, &NextPragma](const auto &S) -> bool {590 return !(P.DocSym.range < S.range &&591 S.range < NextPragma.DocSym.range);592 });593 P.DocSym.children.assign(make_move_iterator(It),594 make_move_iterator(Cur->children.end()));595 Cur->children.erase(It, Cur->children.end());596 TerminatedByNextPragma = true;597 break;598 }599 if (!TerminatedByNextPragma) {600 // P is terminated by the end of current symbol, hence it owns all the601 // children after P.602 auto It = llvm::partition(Cur->children, [&P](const auto &S) -> bool {603 return !(P.DocSym.range < S.range);604 });605 P.DocSym.children.assign(make_move_iterator(It),606 make_move_iterator(Cur->children.end()));607 Cur->children.erase(It, Cur->children.end());608 }609 // Update the range for P to cover children and append to Cur.610 for (DocumentSymbol &Sym : P.DocSym.children)611 unionRanges(P.DocSym.range, Sym.range);612 Cur->children.emplace_back(std::move(P.DocSym));613 }614}615 616PragmaMarkSymbol markToSymbol(const PragmaMark &P) {617 StringRef Name = StringRef(P.Trivia).trim();618 bool IsGroup = false;619 // "-\s+<group name>" or "<name>" after an initial trim. The former is620 // considered a group, the latter just a mark. Like Xcode, we don't consider621 // `-Foo` to be a group (space(s) after the `-` is required).622 //623 // We need to include a name here, otherwise editors won't properly render the624 // symbol.625 StringRef MaybeGroupName = Name;626 if (MaybeGroupName.consume_front("-") &&627 (MaybeGroupName.ltrim() != MaybeGroupName || MaybeGroupName.empty())) {628 Name = MaybeGroupName.empty() ? "(unnamed group)" : MaybeGroupName.ltrim();629 IsGroup = true;630 } else if (Name.empty()) {631 Name = "(unnamed mark)";632 }633 DocumentSymbol Sym;634 Sym.name = Name.str();635 Sym.kind = SymbolKind::File;636 Sym.range = P.Rng;637 Sym.selectionRange = P.Rng;638 return {Sym, IsGroup};639}640 641std::vector<DocumentSymbol> collectDocSymbols(ParsedAST &AST) {642 std::vector<DocumentSymbol> Syms = DocumentOutline(AST).build();643 644 const auto &PragmaMarks = AST.getMarks();645 if (PragmaMarks.empty())646 return Syms;647 648 std::vector<PragmaMarkSymbol> Pragmas;649 Pragmas.reserve(PragmaMarks.size());650 for (const auto &P : PragmaMarks)651 Pragmas.push_back(markToSymbol(P));652 Range EntireFile = {653 {0, 0},654 {std::numeric_limits<int>::max(), std::numeric_limits<int>::max()}};655 DocumentSymbol Root;656 Root.children = std::move(Syms);657 Root.range = EntireFile;658 mergePragmas(Root, llvm::ArrayRef(Pragmas));659 return Root.children;660}661 662} // namespace663 664llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST) {665 return collectDocSymbols(AST);666}667 668} // namespace clangd669} // namespace clang670