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 9 #include "SemanticSelection.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 "clang/Basic/SourceManager.h" 17 #include "clang/Tooling/Syntax/BuildTree.h" 18 #include "clang/Tooling/Syntax/Nodes.h" 19 #include "clang/Tooling/Syntax/TokenBufferTokenManager.h" 20 #include "clang/Tooling/Syntax/Tree.h" 21 #include "llvm/ADT/ArrayRef.h" 22 #include "llvm/ADT/StringRef.h" 23 #include "llvm/Support/Casting.h" 24 #include "llvm/Support/Error.h" 25 #include "support/Bracket.h" 26 #include "support/DirectiveTree.h" 27 #include "support/Token.h" 28 #include <optional> 29 #include <queue> 30 #include <vector> 31 32 namespace clang { 33 namespace clangd { 34 namespace { 35 36 // Adds Range \p R to the Result if it is distinct from the last added Range. 37 // Assumes that only consecutive ranges can coincide. 38 void addIfDistinct(const Range &R, std::vector<Range> &Result) { 39 if (Result.empty() || Result.back() != R) { 40 Result.push_back(R); 41 } 42 } 43 44 std::optional<FoldingRange> toFoldingRange(SourceRange SR, 45 const SourceManager &SM) { 46 const auto Begin = SM.getDecomposedLoc(SR.getBegin()), 47 End = SM.getDecomposedLoc(SR.getEnd()); 48 // Do not produce folding ranges if either range ends is not within the main 49 // file. Macros have their own FileID so this also checks if locations are not 50 // within the macros. 51 if ((Begin.first != SM.getMainFileID()) || (End.first != SM.getMainFileID())) 52 return std::nullopt; 53 FoldingRange Range; 54 Range.startCharacter = SM.getColumnNumber(Begin.first, Begin.second) - 1; 55 Range.startLine = SM.getLineNumber(Begin.first, Begin.second) - 1; 56 Range.endCharacter = SM.getColumnNumber(End.first, End.second) - 1; 57 Range.endLine = SM.getLineNumber(End.first, End.second) - 1; 58 return Range; 59 } 60 61 std::optional<FoldingRange> 62 extractFoldingRange(const syntax::Node *Node, 63 const syntax::TokenBufferTokenManager &TM) { 64 if (const auto *Stmt = dyn_cast<syntax::CompoundStatement>(Node)) { 65 const auto *LBrace = cast_or_null<syntax::Leaf>( 66 Stmt->findChild(syntax::NodeRole::OpenParen)); 67 // FIXME(kirillbobyrev): This should find the last child. Compound 68 // statements have only one pair of braces so this is valid but for other 69 // node kinds it might not be correct. 70 const auto *RBrace = cast_or_null<syntax::Leaf>( 71 Stmt->findChild(syntax::NodeRole::CloseParen)); 72 if (!LBrace || !RBrace) 73 return std::nullopt; 74 // Fold the entire range within braces, including whitespace. 75 const SourceLocation LBraceLocInfo = 76 TM.getToken(LBrace->getTokenKey())->endLocation(), 77 RBraceLocInfo = 78 TM.getToken(RBrace->getTokenKey())->location(); 79 auto Range = toFoldingRange(SourceRange(LBraceLocInfo, RBraceLocInfo), 80 TM.sourceManager()); 81 // Do not generate folding range for compound statements without any 82 // nodes and newlines. 83 if (Range && Range->startLine != Range->endLine) 84 return Range; 85 } 86 return std::nullopt; 87 } 88 89 // Traverse the tree and collect folding ranges along the way. 90 std::vector<FoldingRange> 91 collectFoldingRanges(const syntax::Node *Root, 92 const syntax::TokenBufferTokenManager &TM) { 93 std::queue<const syntax::Node *> Nodes; 94 Nodes.push(Root); 95 std::vector<FoldingRange> Result; 96 while (!Nodes.empty()) { 97 const syntax::Node *Node = Nodes.front(); 98 Nodes.pop(); 99 const auto Range = extractFoldingRange(Node, TM); 100 if (Range) 101 Result.push_back(*Range); 102 if (const auto *T = dyn_cast<syntax::Tree>(Node)) 103 for (const auto *NextNode = T->getFirstChild(); NextNode; 104 NextNode = NextNode->getNextSibling()) 105 Nodes.push(NextNode); 106 } 107 return Result; 108 } 109 110 } // namespace 111 112 llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) { 113 std::vector<Range> Ranges; 114 const auto &SM = AST.getSourceManager(); 115 const auto &LangOpts = AST.getLangOpts(); 116 117 auto FID = SM.getMainFileID(); 118 auto Offset = positionToOffset(SM.getBufferData(FID), Pos); 119 if (!Offset) { 120 return Offset.takeError(); 121 } 122 123 // Get node under the cursor. 124 SelectionTree ST = SelectionTree::createRight( 125 AST.getASTContext(), AST.getTokens(), *Offset, *Offset); 126 for (const auto *Node = ST.commonAncestor(); Node != nullptr; 127 Node = Node->Parent) { 128 if (const Decl *D = Node->ASTNode.get<Decl>()) { 129 if (llvm::isa<TranslationUnitDecl>(D)) { 130 break; 131 } 132 } 133 134 auto SR = toHalfOpenFileRange(SM, LangOpts, Node->ASTNode.getSourceRange()); 135 if (!SR || SM.getFileID(SR->getBegin()) != SM.getMainFileID()) { 136 continue; 137 } 138 Range R; 139 R.start = sourceLocToPosition(SM, SR->getBegin()); 140 R.end = sourceLocToPosition(SM, SR->getEnd()); 141 addIfDistinct(R, Ranges); 142 } 143 144 if (Ranges.empty()) { 145 // LSP provides no way to signal "the point is not within a semantic range". 146 // Return an empty range at the point. 147 SelectionRange Empty; 148 Empty.range.start = Empty.range.end = Pos; 149 return std::move(Empty); 150 } 151 152 // Convert to the LSP linked-list representation. 153 SelectionRange Head; 154 Head.range = std::move(Ranges.front()); 155 SelectionRange *Tail = &Head; 156 for (auto &Range : 157 llvm::MutableArrayRef(Ranges.data(), Ranges.size()).drop_front()) { 158 Tail->parent = std::make_unique<SelectionRange>(); 159 Tail = Tail->parent.get(); 160 Tail->range = std::move(Range); 161 } 162 163 return std::move(Head); 164 } 165 166 // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and 167 // other code regions (e.g. public/private/protected sections of classes, 168 // control flow statement bodies). 169 // Related issue: https://github.com/clangd/clangd/issues/310 170 llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) { 171 syntax::Arena A; 172 syntax::TokenBufferTokenManager TM(AST.getTokens(), AST.getLangOpts(), 173 AST.getSourceManager()); 174 const auto *SyntaxTree = syntax::buildSyntaxTree(A, TM, AST.getASTContext()); 175 return collectFoldingRanges(SyntaxTree, TM); 176 } 177 178 // FIXME( usaxena95): Collect PP conditional regions, includes and other code 179 // regions (e.g. public/private/protected sections of classes, control flow 180 // statement bodies). 181 // Related issue: https://github.com/clangd/clangd/issues/310 182 llvm::Expected<std::vector<FoldingRange>> 183 getFoldingRanges(const std::string &Code, bool LineFoldingOnly) { 184 auto OrigStream = lex(Code, genericLangOpts()); 185 186 auto DirectiveStructure = DirectiveTree::parse(OrigStream); 187 chooseConditionalBranches(DirectiveStructure, OrigStream); 188 189 // FIXME: Provide ranges in the disabled-PP regions as well. 190 auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream); 191 192 auto ParseableStream = cook(Preprocessed, genericLangOpts()); 193 pairBrackets(ParseableStream); 194 195 std::vector<FoldingRange> Result; 196 auto AddFoldingRange = [&](Position Start, Position End, 197 llvm::StringLiteral Kind) { 198 if (Start.line >= End.line) 199 return; 200 FoldingRange FR; 201 FR.startLine = Start.line; 202 FR.startCharacter = Start.character; 203 FR.endLine = End.line; 204 FR.endCharacter = End.character; 205 FR.kind = Kind.str(); 206 Result.push_back(FR); 207 }; 208 auto OriginalToken = [&](const Token &T) { 209 return OrigStream.tokens()[T.OriginalIndex]; 210 }; 211 auto StartOffset = [&](const Token &T) { 212 return OriginalToken(T).text().data() - Code.data(); 213 }; 214 auto StartPosition = [&](const Token &T) { 215 return offsetToPosition(Code, StartOffset(T)); 216 }; 217 auto EndOffset = [&](const Token &T) { 218 return StartOffset(T) + OriginalToken(T).Length; 219 }; 220 auto EndPosition = [&](const Token &T) { 221 return offsetToPosition(Code, EndOffset(T)); 222 }; 223 auto Tokens = ParseableStream.tokens(); 224 // Brackets. 225 for (const auto &Tok : Tokens) { 226 if (auto *Paired = Tok.pair()) { 227 // Process only token at the start of the range. Avoid ranges on a single 228 // line. 229 if (Tok.Line < Paired->Line) { 230 Position Start = offsetToPosition(Code, 1 + StartOffset(Tok)); 231 Position End = StartPosition(*Paired); 232 if (LineFoldingOnly) 233 End.line--; 234 AddFoldingRange(Start, End, FoldingRange::REGION_KIND); 235 } 236 } 237 } 238 auto IsBlockComment = [&](const Token &T) { 239 assert(T.Kind == tok::comment); 240 return OriginalToken(T).Length >= 2 && 241 Code.substr(StartOffset(T), 2) == "/*"; 242 }; 243 // Multi-line comments. 244 for (auto *T = Tokens.begin(); T != Tokens.end();) { 245 if (T->Kind != tok::comment) { 246 T++; 247 continue; 248 } 249 Token *FirstComment = T; 250 // Show starting sentinals (// and /*) of the comment. 251 Position Start = offsetToPosition(Code, 2 + StartOffset(*FirstComment)); 252 Token *LastComment = T; 253 Position End = EndPosition(*T); 254 while (T != Tokens.end() && T->Kind == tok::comment && 255 StartPosition(*T).line <= End.line + 1) { 256 End = EndPosition(*T); 257 LastComment = T; 258 T++; 259 } 260 if (IsBlockComment(*FirstComment)) { 261 if (LineFoldingOnly) 262 // Show last line of a block comment. 263 End.line--; 264 if (IsBlockComment(*LastComment)) 265 // Show ending sentinal "*/" of the block comment. 266 End.character -= 2; 267 } 268 AddFoldingRange(Start, End, FoldingRange::COMMENT_KIND); 269 } 270 return Result; 271 } 272 273 } // namespace clangd 274 } // namespace clang 275