//===--- SemanticSelection.cpp -----------------------------------*- C++-*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "SemanticSelection.h" #include "FindSymbols.h" #include "ParsedAST.h" #include "Protocol.h" #include "Selection.h" #include "SourceCode.h" #include "clang/AST/DeclBase.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.h" #include "clang/Tooling/Syntax/BuildTree.h" #include "clang/Tooling/Syntax/Nodes.h" #include "clang/Tooling/Syntax/Tree.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" #include #include namespace clang { namespace clangd { namespace { // Adds Range \p R to the Result if it is distinct from the last added Range. // Assumes that only consecutive ranges can coincide. void addIfDistinct(const Range &R, std::vector &Result) { if (Result.empty() || Result.back() != R) { Result.push_back(R); } } llvm::Optional toFoldingRange(SourceRange SR, const SourceManager &SM) { const auto Begin = SM.getDecomposedLoc(SR.getBegin()), End = SM.getDecomposedLoc(SR.getEnd()); // Do not produce folding ranges if either range ends is not within the main // file. Macros have their own FileID so this also checks if locations are not // within the macros. if ((Begin.first != SM.getMainFileID()) || (End.first != SM.getMainFileID())) return llvm::None; FoldingRange Range; Range.startCharacter = SM.getColumnNumber(Begin.first, Begin.second) - 1; Range.startLine = SM.getLineNumber(Begin.first, Begin.second) - 1; Range.endCharacter = SM.getColumnNumber(End.first, End.second) - 1; Range.endLine = SM.getLineNumber(End.first, End.second) - 1; return Range; } llvm::Optional extractFoldingRange(const syntax::Node *Node, const SourceManager &SM) { if (const auto *Stmt = dyn_cast(Node)) { const auto *LBrace = cast_or_null( Stmt->findChild(syntax::NodeRole::OpenParen)); // FIXME(kirillbobyrev): This should find the last child. Compound // statements have only one pair of braces so this is valid but for other // node kinds it might not be correct. const auto *RBrace = cast_or_null( Stmt->findChild(syntax::NodeRole::CloseParen)); if (!LBrace || !RBrace) return llvm::None; // Fold the entire range within braces, including whitespace. const SourceLocation LBraceLocInfo = LBrace->getToken()->endLocation(), RBraceLocInfo = RBrace->getToken()->location(); auto Range = toFoldingRange(SourceRange(LBraceLocInfo, RBraceLocInfo), SM); // Do not generate folding range for compound statements without any // nodes and newlines. if (Range && Range->startLine != Range->endLine) return Range; } return llvm::None; } // Traverse the tree and collect folding ranges along the way. std::vector collectFoldingRanges(const syntax::Node *Root, const SourceManager &SM) { std::queue Nodes; Nodes.push(Root); std::vector Result; while (!Nodes.empty()) { const syntax::Node *Node = Nodes.front(); Nodes.pop(); const auto Range = extractFoldingRange(Node, SM); if (Range) Result.push_back(*Range); if (const auto *T = dyn_cast(Node)) for (const auto *NextNode = T->getFirstChild(); NextNode; NextNode = NextNode->getNextSibling()) Nodes.push(NextNode); } return Result; } } // namespace llvm::Expected getSemanticRanges(ParsedAST &AST, Position Pos) { std::vector Ranges; const auto &SM = AST.getSourceManager(); const auto &LangOpts = AST.getLangOpts(); auto FID = SM.getMainFileID(); auto Offset = positionToOffset(SM.getBufferData(FID), Pos); if (!Offset) { return Offset.takeError(); } // Get node under the cursor. SelectionTree ST = SelectionTree::createRight( AST.getASTContext(), AST.getTokens(), *Offset, *Offset); for (const auto *Node = ST.commonAncestor(); Node != nullptr; Node = Node->Parent) { if (const Decl *D = Node->ASTNode.get()) { if (llvm::isa(D)) { break; } } auto SR = toHalfOpenFileRange(SM, LangOpts, Node->ASTNode.getSourceRange()); if (!SR.hasValue() || SM.getFileID(SR->getBegin()) != SM.getMainFileID()) { continue; } Range R; R.start = sourceLocToPosition(SM, SR->getBegin()); R.end = sourceLocToPosition(SM, SR->getEnd()); addIfDistinct(R, Ranges); } if (Ranges.empty()) { // LSP provides no way to signal "the point is not within a semantic range". // Return an empty range at the point. SelectionRange Empty; Empty.range.start = Empty.range.end = Pos; return std::move(Empty); } // Convert to the LSP linked-list representation. SelectionRange Head; Head.range = std::move(Ranges.front()); SelectionRange *Tail = &Head; for (auto &Range : llvm::makeMutableArrayRef(Ranges.data(), Ranges.size()).drop_front()) { Tail->parent = std::make_unique(); Tail = Tail->parent.get(); Tail->range = std::move(Range); } return std::move(Head); } // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and // other code regions (e.g. public/private/protected sections of classes, // control flow statement bodies). // Related issue: https://github.com/clangd/clangd/issues/310 llvm::Expected> getFoldingRanges(ParsedAST &AST) { syntax::Arena A(AST.getSourceManager(), AST.getLangOpts(), AST.getTokens()); const auto *SyntaxTree = syntax::buildSyntaxTree(A, AST.getASTContext()); return collectFoldingRanges(SyntaxTree, AST.getSourceManager()); } } // namespace clangd } // namespace clang