294 lines
9.0 KiB
C++
294 lines
9.0 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 "ParsedAST.h"
|
|
#include "Protocol.h"
|
|
#include "Selection.h"
|
|
#include "SourceCode.h"
|
|
#include "support/Bracket.h"
|
|
#include "support/DirectiveTree.h"
|
|
#include "support/Token.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/TokenBufferTokenManager.h"
|
|
#include "clang/Tooling/Syntax/Tree.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include <optional>
|
|
#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);
|
|
}
|
|
}
|
|
|
|
} // 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 || 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::MutableArrayRef(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);
|
|
}
|
|
|
|
class PragmaRegionFinder {
|
|
// Record the token range of a region:
|
|
//
|
|
// #pragma region name[[
|
|
// ...
|
|
// ]]#pragma endregion
|
|
std::vector<Token::Range> &Ranges;
|
|
const TokenStream &Code;
|
|
// Stack of starting token (the name of the region) indices for nested #pragma
|
|
// region.
|
|
std::vector<unsigned> Stack;
|
|
|
|
public:
|
|
PragmaRegionFinder(std::vector<Token::Range> &Ranges, const TokenStream &Code)
|
|
: Ranges(Ranges), Code(Code) {}
|
|
|
|
void walk(const DirectiveTree &T) {
|
|
for (const auto &C : T.Chunks)
|
|
std::visit(*this, C);
|
|
}
|
|
|
|
void operator()(const DirectiveTree::Code &C) {}
|
|
|
|
void operator()(const DirectiveTree::Directive &D) {
|
|
// Get the tokens that make up this directive.
|
|
auto Tokens = Code.tokens(D.Tokens);
|
|
if (Tokens.empty())
|
|
return;
|
|
const Token &HashToken = Tokens.front();
|
|
assert(HashToken.Kind == tok::hash);
|
|
const Token &Pragma = HashToken.nextNC();
|
|
if (Pragma.text() != "pragma")
|
|
return;
|
|
const Token &Value = Pragma.nextNC();
|
|
|
|
// Handle "#pragma region name"
|
|
if (Value.text() == "region") {
|
|
// Find the last token at the same line.
|
|
const Token *T = &Value.next();
|
|
while (T < Tokens.end() && T->Line == Pragma.Line)
|
|
T = &T->next();
|
|
--T;
|
|
Stack.push_back(T->OriginalIndex);
|
|
return;
|
|
}
|
|
|
|
// Handle "#pragma endregion"
|
|
if (Value.text() == "endregion") {
|
|
if (Stack.empty())
|
|
return; // unmatched end region; ignore.
|
|
|
|
unsigned StartIdx = Stack.back();
|
|
Stack.pop_back();
|
|
Ranges.push_back(Token::Range{StartIdx, HashToken.OriginalIndex});
|
|
}
|
|
}
|
|
|
|
void operator()(const DirectiveTree::Conditional &C) {
|
|
// C.Branches needs to see the DirectiveTree definition, otherwise build
|
|
// fails in C++20.
|
|
[[maybe_unused]] DirectiveTree Dummy;
|
|
for (const auto &[_, SubTree] : C.Branches)
|
|
walk(SubTree);
|
|
}
|
|
};
|
|
|
|
// FIXME( usaxena95): Collect 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(const std::string &Code, bool LineFoldingOnly) {
|
|
auto OrigStream = lex(Code, genericLangOpts());
|
|
|
|
auto DirectiveStructure = DirectiveTree::parse(OrigStream);
|
|
chooseConditionalBranches(DirectiveStructure, OrigStream);
|
|
|
|
std::vector<FoldingRange> Result;
|
|
auto AddFoldingRange = [&](Position Start, Position End,
|
|
llvm::StringLiteral Kind) {
|
|
if (Start.line >= End.line)
|
|
return;
|
|
FoldingRange FR;
|
|
FR.startLine = Start.line;
|
|
FR.startCharacter = Start.character;
|
|
FR.endLine = End.line;
|
|
FR.endCharacter = End.character;
|
|
FR.kind = Kind.str();
|
|
Result.push_back(FR);
|
|
};
|
|
auto OriginalToken = [&](const Token &T) {
|
|
return OrigStream.tokens()[T.OriginalIndex];
|
|
};
|
|
auto StartOffset = [&](const Token &T) {
|
|
return OriginalToken(T).text().data() - Code.data();
|
|
};
|
|
auto StartPosition = [&](const Token &T) {
|
|
return offsetToPosition(Code, StartOffset(T));
|
|
};
|
|
auto EndOffset = [&](const Token &T) {
|
|
return StartOffset(T) + OriginalToken(T).Length;
|
|
};
|
|
auto EndPosition = [&](const Token &T) {
|
|
return offsetToPosition(Code, EndOffset(T));
|
|
};
|
|
|
|
// Preprocessor directives
|
|
auto PPRanges = pairDirectiveRanges(DirectiveStructure, OrigStream);
|
|
for (const auto &R : PPRanges) {
|
|
auto BTok = OrigStream.tokens()[R.Begin];
|
|
auto ETok = OrigStream.tokens()[R.End];
|
|
if (ETok.Kind == tok::eof)
|
|
continue;
|
|
if (BTok.Line >= ETok.Line)
|
|
continue;
|
|
|
|
Position Start = EndPosition(BTok);
|
|
Position End = StartPosition(ETok);
|
|
if (LineFoldingOnly)
|
|
End.line--;
|
|
AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
|
|
}
|
|
|
|
// FIXME: Provide ranges in the disabled-PP regions as well.
|
|
auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);
|
|
|
|
auto ParseableStream = cook(Preprocessed, genericLangOpts());
|
|
pairBrackets(ParseableStream);
|
|
|
|
auto Tokens = ParseableStream.tokens();
|
|
|
|
// Brackets.
|
|
for (const auto &Tok : Tokens) {
|
|
if (auto *Paired = Tok.pair()) {
|
|
// Process only token at the start of the range. Avoid ranges on a single
|
|
// line.
|
|
if (Tok.Line < Paired->Line) {
|
|
Position Start = offsetToPosition(Code, 1 + StartOffset(Tok));
|
|
Position End = StartPosition(*Paired);
|
|
if (LineFoldingOnly)
|
|
End.line--;
|
|
AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
|
|
}
|
|
}
|
|
}
|
|
auto IsBlockComment = [&](const Token &T) {
|
|
assert(T.Kind == tok::comment);
|
|
return OriginalToken(T).Length >= 2 &&
|
|
Code.substr(StartOffset(T), 2) == "/*";
|
|
};
|
|
|
|
// Multi-line comments.
|
|
for (auto *T = Tokens.begin(); T != Tokens.end();) {
|
|
if (T->Kind != tok::comment) {
|
|
T++;
|
|
continue;
|
|
}
|
|
Token *FirstComment = T;
|
|
// Show starting sentinals (// and /*) of the comment.
|
|
Position Start = offsetToPosition(Code, 2 + StartOffset(*FirstComment));
|
|
Token *LastComment = T;
|
|
Position End = EndPosition(*T);
|
|
while (T != Tokens.end() && T->Kind == tok::comment &&
|
|
StartPosition(*T).line <= End.line + 1) {
|
|
End = EndPosition(*T);
|
|
LastComment = T;
|
|
T++;
|
|
}
|
|
if (IsBlockComment(*FirstComment)) {
|
|
if (LineFoldingOnly)
|
|
// Show last line of a block comment.
|
|
End.line--;
|
|
if (IsBlockComment(*LastComment))
|
|
// Show ending sentinal "*/" of the block comment.
|
|
End.character -= 2;
|
|
}
|
|
AddFoldingRange(Start, End, FoldingRange::COMMENT_KIND);
|
|
}
|
|
|
|
// #pragma region
|
|
std::vector<Token::Range> Ranges;
|
|
PragmaRegionFinder(Ranges, OrigStream).walk(DirectiveStructure);
|
|
auto Ts = OrigStream.tokens();
|
|
for (const auto &R : Ranges) {
|
|
auto End = StartPosition(Ts[R.End]);
|
|
if (LineFoldingOnly)
|
|
End.line--;
|
|
AddFoldingRange(EndPosition(Ts[R.Begin]), End, FoldingRange::REGION_KIND);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
} // namespace clangd
|
|
} // namespace clang
|