1 //===--- SemanticSelection.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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 #include "SemanticSelection.h"
9 #include "FindSymbols.h"
10 #include "ParsedAST.h"
11 #include "Protocol.h"
12 #include "Selection.h"
13 #include "SourceCode.h"
14 #include "clang/AST/DeclBase.h"
15 #include "clang/Basic/SourceLocation.h"
16 #include "llvm/ADT/ArrayRef.h"
17 #include "llvm/Support/Error.h"
18 
19 namespace clang {
20 namespace clangd {
21 namespace {
22 
23 // Adds Range \p R to the Result if it is distinct from the last added Range.
24 // Assumes that only consecutive ranges can coincide.
addIfDistinct(const Range & R,std::vector<Range> & Result)25 void addIfDistinct(const Range &R, std::vector<Range> &Result) {
26   if (Result.empty() || Result.back() != R) {
27     Result.push_back(R);
28   }
29 }
30 
31 // Recursively collects FoldingRange from a symbol and its children.
collectFoldingRanges(DocumentSymbol Symbol,std::vector<FoldingRange> & Result)32 void collectFoldingRanges(DocumentSymbol Symbol,
33                           std::vector<FoldingRange> &Result) {
34   FoldingRange Range;
35   Range.startLine = Symbol.range.start.line;
36   Range.startCharacter = Symbol.range.start.character;
37   Range.endLine = Symbol.range.end.line;
38   Range.endCharacter = Symbol.range.end.character;
39   Result.push_back(Range);
40   for (const auto &Child : Symbol.children)
41     collectFoldingRanges(Child, Result);
42 }
43 
44 } // namespace
45 
getSemanticRanges(ParsedAST & AST,Position Pos)46 llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
47   std::vector<Range> Ranges;
48   const auto &SM = AST.getSourceManager();
49   const auto &LangOpts = AST.getLangOpts();
50 
51   auto FID = SM.getMainFileID();
52   auto Offset = positionToOffset(SM.getBufferData(FID), Pos);
53   if (!Offset) {
54     return Offset.takeError();
55   }
56 
57   // Get node under the cursor.
58   SelectionTree ST = SelectionTree::createRight(
59       AST.getASTContext(), AST.getTokens(), *Offset, *Offset);
60   for (const auto *Node = ST.commonAncestor(); Node != nullptr;
61        Node = Node->Parent) {
62     if (const Decl *D = Node->ASTNode.get<Decl>()) {
63       if (llvm::isa<TranslationUnitDecl>(D)) {
64         break;
65       }
66     }
67 
68     auto SR = toHalfOpenFileRange(SM, LangOpts, Node->ASTNode.getSourceRange());
69     if (!SR.hasValue() || SM.getFileID(SR->getBegin()) != SM.getMainFileID()) {
70       continue;
71     }
72     Range R;
73     R.start = sourceLocToPosition(SM, SR->getBegin());
74     R.end = sourceLocToPosition(SM, SR->getEnd());
75     addIfDistinct(R, Ranges);
76   }
77 
78   if (Ranges.empty()) {
79     // LSP provides no way to signal "the point is not within a semantic range".
80     // Return an empty range at the point.
81     SelectionRange Empty;
82     Empty.range.start = Empty.range.end = Pos;
83     return std::move(Empty);
84   }
85 
86   // Convert to the LSP linked-list representation.
87   SelectionRange Head;
88   Head.range = std::move(Ranges.front());
89   SelectionRange *Tail = &Head;
90   for (auto &Range :
91        llvm::makeMutableArrayRef(Ranges.data(), Ranges.size()).drop_front()) {
92     Tail->parent = std::make_unique<SelectionRange>();
93     Tail = Tail->parent.get();
94     Tail->range = std::move(Range);
95   }
96 
97   return std::move(Head);
98 }
99 
100 // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and
101 // other code regions (e.g. public/private/protected sections of classes,
102 // control flow statement bodies).
103 // Related issue:
104 // https://github.com/clangd/clangd/issues/310
getFoldingRanges(ParsedAST & AST)105 llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) {
106   // FIXME(kirillbobyrev): getDocumentSymbols() is conveniently available but
107   // limited (e.g. doesn't yield blocks inside functions and provides ranges for
108   // nodes themselves instead of their contents which is less useful). Replace
109   // this with a more general RecursiveASTVisitor implementation instead.
110   auto DocumentSymbols = getDocumentSymbols(AST);
111   if (!DocumentSymbols)
112     return DocumentSymbols.takeError();
113   std::vector<FoldingRange> Result;
114   for (const auto &Symbol : *DocumentSymbols)
115     collectFoldingRanges(Symbol, Result);
116   return Result;
117 }
118 
119 } // namespace clangd
120 } // namespace clang
121