169 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- 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 <queue>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
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<Range> &Result) {
 | 
						|
  if (Result.empty() || Result.back() != R) {
 | 
						|
    Result.push_back(R);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
llvm::Optional<FoldingRange> 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<FoldingRange> extractFoldingRange(const syntax::Node *Node,
 | 
						|
                                                 const SourceManager &SM) {
 | 
						|
  if (const auto *Stmt = dyn_cast<syntax::CompoundStatement>(Node)) {
 | 
						|
    const auto *LBrace = cast_or_null<syntax::Leaf>(
 | 
						|
        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<syntax::Leaf>(
 | 
						|
        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<FoldingRange> collectFoldingRanges(const syntax::Node *Root,
 | 
						|
                                               const SourceManager &SM) {
 | 
						|
  std::queue<const syntax::Node *> Nodes;
 | 
						|
  Nodes.push(Root);
 | 
						|
  std::vector<FoldingRange> 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<syntax::Tree>(Node))
 | 
						|
      for (const auto *NextNode = T->getFirstChild(); NextNode;
 | 
						|
           NextNode = NextNode->getNextSibling())
 | 
						|
        Nodes.push(NextNode);
 | 
						|
  }
 | 
						|
  return Result;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
 | 
						|
  std::vector<Range> 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<Decl>()) {
 | 
						|
      if (llvm::isa<TranslationUnitDecl>(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<SelectionRange>();
 | 
						|
    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<std::vector<FoldingRange>> 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
 |