diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -66,6 +66,7 @@ RIFF.cpp Selection.cpp SemanticHighlighting.cpp + SemanticSelection.cpp SourceCode.cpp QueryDriverDatabase.cpp Threading.cpp diff --git a/clang-tools-extra/clangd/SemanticSelection.h b/clang-tools-extra/clangd/SemanticSelection.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/SemanticSelection.h @@ -0,0 +1,33 @@ +//===--- SemanticSelection.h -------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Features for giving interesting semantic ranges around the cursor. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICSELECTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICSELECTION_H +#include "ParsedAST.h" +#include "Protocol.h" +#include +namespace clang { +namespace clangd { + +struct SemanticSelectionResult { + // The list of interesting selections around the cursor. + std::vector Ranges; +}; + +// Returns the list of all interesting ranges around the Position \p Pos. +// Constructs the +// Any range in the result strictly contains all the previous ranges in the result. +SemanticSelectionResult getSemanticRanges(ParsedAST &AST, Position Pos); +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SEMANTICSELECTION_H \ No newline at end of file diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/SemanticSelection.cpp @@ -0,0 +1,66 @@ +//===--- 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 "clang/Basic/SourceLocation.h" + +namespace clang { +namespace clangd { +namespace { +void addIfUnique(const Range &R, SemanticSelectionResult *Result) { + if (Result->Ranges.empty() || Result->Ranges.back() != R) { + Result->Ranges.push_back(R); + } +} +} // namespace + +SemanticSelectionResult getSemanticRanges(ParsedAST &AST, Position Pos) { + SemanticSelectionResult Result; + const auto &SM = AST.getSourceManager(); + const auto &LangOpts = AST.getASTContext().getLangOpts(); + + auto FID = SM.getMainFileID(); + auto Offset = positionToOffset(SM.getBufferData(FID), Pos); + if (!Offset) { + llvm::errs() << "Unable to convert postion to offset"; + return {}; + } + + // Get node under the cursor. + SelectionTree ST(AST.getASTContext(), AST.getTokens(), *Offset); + if(ST.commonAncestor() == nullptr){ + llvm::errs() << "No AST node associated with the position."; + } + for (const auto *Node = ST.commonAncestor(); Node != nullptr; + Node = Node->Parent) { + auto SR = Node->ASTNode.getSourceRange(); + if (SR.isInvalid()) { + continue; + } + SourceLocation BeginLoc = SR.getBegin(); + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(SR.getEnd(), 0, SM, LangOpts); + + llvm::errs() << BeginLoc.printToString(SM) << ":" + << EndLoc.printToString(SM) << "\n"; + Range R; + R.start = sourceLocToPosition(SM, BeginLoc); + R.end = sourceLocToPosition(SM, EndLoc); + if (isValidFileRange(SM, {BeginLoc, EndLoc})) { + addIfUnique(R, &Result); + llvm::errs() << toSourceCode(SM, {BeginLoc, EndLoc}) << "\n"; + } + } + return Result; +} + +} // namespace clangd +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -56,6 +56,7 @@ RIFFTests.cpp SelectionTests.cpp SemanticHighlightingTests.cpp + SemanticSelectionTests.cpp SerializationTests.cpp SourceCodeTests.cpp SymbolCollectorTests.cpp diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -0,0 +1,133 @@ +//===-- SemanticSelectionTests.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 "Annotations.h" +#include "Matchers.h" +#include "Protocol.h" +#include "SemanticSelection.h" +#include "SourceCode.h" +#include "TestTU.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +namespace clang { +namespace clangd { +namespace { +using ::testing::ElementsAreArray; + +TEST(SemanticSelection, All) { + const char *Tests[] = { + R"cpp( // Single statement in a function body. + [[void func() [[{ + [[[[int v = [[1^00]]]];]] + }]]]] + )cpp", + R"cpp( // Expression + [[void func() [[{ + int a = 1; + // int v = (10 + 2) * (a + a); + [[[[int v = [[[[([[[[10^]] + 2]])]] * (a + a)]]]];]] + }]]]] + )cpp", + R"cpp( // Function call. + int add(int x, int y) { return x + y; } + [[void callee() [[{ + // int res = add(11, 22); + [[[[int res = [[add([[1^1]], 22)]]]];]] + }]]]] + )cpp", + R"cpp( // Tricky macros. + #define MUL ) * ( + [[void func() [[{ + // int var = (4 + 15 MUL 6 + 10); + [[[[int var = [[([[4 + [[1^5]]]] MUL 6 + 10)]]]];]] + }]]]] + )cpp", + R"cpp( // Cursor inside a macro. + #define HASH(x) ((x) % 10) + [[void func() [[{ + [[[[int a = HASH(2^3)]];]] + }]]]] + )cpp", + R"cpp( // Cursor on a macro. + #define HASH(x) ((x) % 10) + [[void func() [[{ + [[[[int a = HA^SH(23)]];]] + }]]]] + )cpp", + R"cpp( // Multiple declaration. + [[void func() [[{ + [[[[int var1, var^2]], var3;]] + }]]]] + )cpp", + R"cpp( // Before comment. + [[void func() [[{ + int var1 = 1; + [[[[int var2 = [[[[var1]]^ /*some comment*/ + 41]]]];]] + }]]]] + )cpp", + // Empty file. + "^", + // FIXME: We should get the whole DeclStmt as a range. + R"cpp( // Single statement in TU. + [[int v = [[1^00]]]]; + )cpp", + // FIXME: No node found associated to the position. + R"cpp( // Cursor at end of VarDecl. + void func() { + int v = 100 + 100^; + } + )cpp", + // FIXME: No node found associated to the position. + R"cpp( // Cursor at end of VarDecl. + void func() { + int v = 100 + ^ 100; + } + )cpp", + // Structs. + R"cpp( + struct AAA { struct BBB { static int ccc(); };}; + [[void func() [[{ + // int x = AAA::BBB::ccc(); + [[[[int x = [[[[AAA::BBB::c^cc]]()]]]];]] + }]]]] + )cpp", + R"cpp( + struct AAA { struct BBB { static int ccc(); };}; + [[void func() [[{ + // int x = AAA::BBB::ccc(); + [[[[int x = [[[[[[[[[[AA^A]]::]]BBB::]]ccc]]()]]]];]] + }]]]] + )cpp", + // Namespaces. + R"cpp( + [[namespace nsa { + [[namespace nsb { + static int ccc(); + [[void func() [[{ + // int x = nsa::nsb::ccc(); + [[[[int x = [[[[nsa::nsb::cc^c]]()]]]];]] + }]]]] + }]] + }]] + )cpp", + + }; + + for (const char *Test : Tests) { + auto T = Annotations(Test); + auto AST = TestTU::withCode(T.code()).build(); + auto Ranges = getSemanticRanges(AST, T.point()).Ranges; + EXPECT_THAT(Ranges, ElementsAreArray(T.ranges())) << Test; + } +} +} // namespace +} // namespace clangd +} // namespace clang \ No newline at end of file