| 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(x: R); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | std::optional<FoldingRange> toFoldingRange(SourceRange SR, |
| 45 | const SourceManager &SM) { |
| 46 | const auto Begin = SM.getDecomposedLoc(Loc: SR.getBegin()), |
| 47 | End = SM.getDecomposedLoc(Loc: 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(FID: Begin.first, FilePos: Begin.second) - 1; |
| 55 | Range.startLine = SM.getLineNumber(FID: Begin.first, FilePos: Begin.second) - 1; |
| 56 | Range.endCharacter = SM.getColumnNumber(FID: End.first, FilePos: End.second) - 1; |
| 57 | Range.endLine = SM.getLineNumber(FID: End.first, FilePos: End.second) - 1; |
| 58 | return Range; |
| 59 | } |
| 60 | |
| 61 | std::optional<FoldingRange> |
| 62 | (const syntax::Node *Node, |
| 63 | const syntax::TokenBufferTokenManager &TM) { |
| 64 | if (const auto *Stmt = dyn_cast<syntax::CompoundStatement>(Val: Node)) { |
| 65 | const auto *LBrace = cast_or_null<syntax::Leaf>( |
| 66 | Val: Stmt->findChild(R: 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 | Val: Stmt->findChild(R: 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(I: LBrace->getTokenKey())->endLocation(), |
| 77 | RBraceLocInfo = |
| 78 | TM.getToken(I: RBrace->getTokenKey())->location(); |
| 79 | auto Range = toFoldingRange(SR: SourceRange(LBraceLocInfo, RBraceLocInfo), |
| 80 | SM: 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(x: 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(x: *Range); |
| 102 | if (const auto *T = dyn_cast<syntax::Tree>(Val: Node)) |
| 103 | for (const auto *NextNode = T->getFirstChild(); NextNode; |
| 104 | NextNode = NextNode->getNextSibling()) |
| 105 | Nodes.push(x: 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(Code: SM.getBufferData(FID), P: Pos); |
| 119 | if (!Offset) { |
| 120 | return Offset.takeError(); |
| 121 | } |
| 122 | |
| 123 | // Get node under the cursor. |
| 124 | SelectionTree ST = SelectionTree::createRight( |
| 125 | AST&: AST.getASTContext(), Tokens: AST.getTokens(), Begin: *Offset, End: *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>(Val: 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, Result&: 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, Code: 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(x: 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, Offset: 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, Offset: 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, Offset: 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 = [&](const Token &T) { |
| 239 | assert(T.Kind == tok::comment); |
| 240 | return OriginalToken(T).Length >= 2 && |
| 241 | Code.substr(pos: StartOffset(T), n: 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 * = T; |
| 250 | // Show starting sentinals (// and /*) of the comment. |
| 251 | Position Start = offsetToPosition(Code, Offset: 2 + StartOffset(*FirstComment)); |
| 252 | Token * = 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 | |