kbobyrev updated this revision to Diff 277357.
kbobyrev marked 4 inline comments as done.
kbobyrev added a comment.
Add tests and fix DocumentSymbol ranges.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D82436/new/
https://reviews.llvm.org/D82436
Files:
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/ClangdServer.cpp
clang-tools-extra/clangd/ClangdServer.h
clang-tools-extra/clangd/FindSymbols.cpp
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/SemanticSelection.cpp
clang-tools-extra/clangd/SemanticSelection.h
clang-tools-extra/clangd/tool/ClangdMain.cpp
clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
Index: clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
+++ clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
@@ -17,15 +17,19 @@
#include "TestTU.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
+#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <vector>
+
namespace clang {
namespace clangd {
namespace {
+
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
+using ::testing::UnorderedElementsAreArray;
// front() is SR.range, back() is outermost range.
std::vector<Range> gatherRanges(const SelectionRange &SR) {
@@ -35,6 +39,23 @@
return Ranges;
}
+// front() is SR.range, back() is outermost range.
+std::vector<Range>
+gatherFoldingRanges(llvm::ArrayRef<FoldingRange> FoldingRanges) {
+ std::vector<Range> Ranges;
+ Range NextRange;
+ for (const auto &R : FoldingRanges) {
+ NextRange.start.line = R.startLine;
+ EXPECT_TRUE(R.startCharacter);
+ NextRange.start.character = *R.startCharacter;
+ NextRange.end.line = R.endLine;
+ EXPECT_TRUE(R.endCharacter);
+ NextRange.end.character = *R.endCharacter;
+ Ranges.push_back(NextRange);
+ }
+ return Ranges;
+}
+
TEST(SemanticSelection, All) {
const char *Tests[] = {
R"cpp( // Single statement in a function body.
@@ -118,16 +139,16 @@
)cpp",
R"cpp( // Inside struct.
struct A { static int a(); };
- [[struct B {
+ [[struct B {
[[static int b() [[{
[[return [[[[1^1]] + 2]]]];
}]]]]
}]];
)cpp",
// Namespaces.
- R"cpp(
- [[namespace nsa {
- [[namespace nsb {
+ R"cpp(
+ [[namespace nsa {
+ [[namespace nsb {
static int ccc();
[[void func() [[{
// int x = nsa::nsb::ccc();
@@ -181,6 +202,41 @@
EXPECT_THAT(gatherRanges(Ranges->back()),
ElementsAre(SourceAnnotations.range("empty")));
}
+
+TEST(SemanticSelection, FoldingRanges) {
+ const char *Tests[] = {
+ R"cpp(
+ int [[global_variable]];
+
+ [[void func() {
+ int v = 100;
+ }]]
+ )cpp",
+ R"cpp(
+ [[class Foo {
+ public:
+ [[Foo() {
+ int X = 1;
+ }]]
+
+ private:
+ [[int getBar() {
+ return 42;
+ }]]
+
+ [[void getFooBar() { }]]
+ }]];
+ )cpp",
+ };
+ for (const char *Test : Tests) {
+ auto T = Annotations(Test);
+ auto AST = TestTU::withCode(T.code()).build();
+ EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))),
+ UnorderedElementsAreArray(T.ranges()))
+ << Test;
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -8,6 +8,7 @@
#include "Annotations.h"
#include "ClangdServer.h"
#include "FindSymbols.h"
+#include "Protocol.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "gmock/gmock.h"
@@ -33,7 +34,7 @@
}
MATCHER_P(WithName, N, "") { return arg.name == N; }
MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; }
-MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; }
+MATCHER_P(SymRange, Range, "") { return arg.range == Range; }
// GMock helpers for matching DocumentSymbol.
MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; }
@@ -746,5 +747,71 @@
WithName("Foo_type::method3")));
}
+TEST_F(DocumentSymbolsTest, Ranges) {
+ std::string FilePath = testPath("foo.cpp");
+ Annotations Main(R"(
+ $foo[[int foo(bool Argument) {
+ return 42;
+ }]]
+
+ char $variable[[GLOBAL_VARIABLE]];
+
+ $ns[[namespace ns {
+ $bar[[class Bar {
+ public:
+ $ctor[[Bar() {}]]
+ $dtor[[~Bar()]];
+
+ private:
+ unsigned $field[[Baz]];
+
+ $getbaz[[unsigned getBaz() { return Baz; }]]
+ }]];
+ }]] // namespace ns
+
+ $forwardclass[[class ForwardClassDecl]];
+
+ $struct[[struct StructDefinition {
+ int *$structfield[[Pointer]];
+ }]];
+ $forwardstruct[[struct StructDeclaration]];
+
+ $forwardfunc[[void forwardFunctionDecl(int Something)]];
+ )");
+ addFile(FilePath, Main.code());
+ EXPECT_THAT(
+ getSymbols(FilePath),
+ UnorderedElementsAre(
+ AllOf(WithName("foo"), WithKind(SymbolKind::Function),
+ SymRange(Main.range("foo"))),
+ AllOf(WithName("GLOBAL_VARIABLE"), WithKind(SymbolKind::Variable),
+ SymRange(Main.range("variable"))),
+ AllOf(
+ WithName("ns"), WithKind(SymbolKind::Namespace),
+ SymRange(Main.range("ns")),
+ Children(AllOf(
+ WithName("Bar"), WithKind(SymbolKind::Class),
+ SymRange(Main.range("bar")),
+ Children(
+ AllOf(WithName("Bar"), WithKind(SymbolKind::Constructor),
+ SymRange(Main.range("ctor"))),
+ AllOf(WithName("~Bar"), WithKind(SymbolKind::Constructor),
+ SymRange(Main.range("dtor"))),
+ AllOf(WithName("Baz"), WithKind(SymbolKind::Field),
+ SymRange(Main.range("field"))),
+ AllOf(WithName("getBaz"), WithKind(SymbolKind::Method),
+ SymRange(Main.range("getbaz"))))))),
+ AllOf(WithName("ForwardClassDecl"), WithKind(SymbolKind::Class),
+ SymRange(Main.range("forwardclass"))),
+ AllOf(WithName("StructDefinition"), WithKind(SymbolKind::Struct),
+ SymRange(Main.range("struct")),
+ Children(AllOf(WithName("Pointer"), WithKind(SymbolKind::Field),
+ SymRange(Main.range("structfield"))))),
+ AllOf(WithName("StructDeclaration"), WithKind(SymbolKind::Struct),
+ SymRange(Main.range("forwardstruct"))),
+ AllOf(WithName("forwardFunctionDecl"), WithKind(SymbolKind::Function),
+ SymRange(Main.range("forwardfunc")))));
+}
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/tool/ClangdMain.cpp
===================================================================
--- clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -296,6 +296,14 @@
Hidden,
};
+opt<bool> FoldingRanges{
+ "folding-ranges",
+ cat(Features),
+ desc("Enable preview of FoldingRanges feature"),
+ init(false),
+ Hidden,
+};
+
opt<unsigned> WorkerThreadsCount{
"j",
cat(Misc),
@@ -659,6 +667,7 @@
Opts.AsyncThreadsCount = WorkerThreadsCount;
Opts.BuildRecoveryAST = RecoveryAST;
Opts.PreserveRecoveryASTType = RecoveryASTType;
+ Opts.FoldingRanges = FoldingRanges;
clangd::CodeCompleteOptions CCOpts;
CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
Index: clang-tools-extra/clangd/SemanticSelection.h
===================================================================
--- clang-tools-extra/clangd/SemanticSelection.h
+++ clang-tools-extra/clangd/SemanticSelection.h
@@ -25,6 +25,8 @@
/// If pos is not in any interesting range, return [Pos, Pos).
llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos);
+llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST);
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/SemanticSelection.cpp
===================================================================
--- clang-tools-extra/clangd/SemanticSelection.cpp
+++ clang-tools-extra/clangd/SemanticSelection.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "SemanticSelection.h"
+#include "FindSymbols.h"
#include "ParsedAST.h"
#include "Protocol.h"
#include "Selection.h"
@@ -18,6 +19,7 @@
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) {
@@ -25,6 +27,20 @@
Result.push_back(R);
}
}
+
+// Recursively collects FoldingRange from a symbol and its children.
+void collectFoldingRanges(DocumentSymbol Symbol,
+ std::vector<FoldingRange> &Result) {
+ FoldingRange Range;
+ Range.startLine = Symbol.range.start.line;
+ Range.startCharacter = Symbol.range.start.character;
+ Range.endLine = Symbol.range.end.line;
+ Range.endCharacter = Symbol.range.end.character;
+ Result.push_back(Range);
+ for (const auto &Child : Symbol.children)
+ collectFoldingRanges(Child, Result);
+}
+
} // namespace
llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
@@ -81,5 +97,24 @@
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) {
+ // FIXME(kirillbobyrev): getDocumentSymbols() is conveniently available but
+ // limited (e.g. doesn't yield blocks inside functions and provides ranges for
+ // nodes themselves instead of their contents which is less useful). Replace
+ // this with a more general RecursiveASTVisitor implementation instead.
+ auto DocumentSymbols = getDocumentSymbols(AST);
+ if (!DocumentSymbols)
+ return DocumentSymbols.takeError();
+ std::vector<FoldingRange> Result;
+ for (const auto &Symbol : *DocumentSymbols)
+ collectFoldingRanges(Symbol, Result);
+ return Result;
+}
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/Protocol.h
===================================================================
--- clang-tools-extra/clangd/Protocol.h
+++ clang-tools-extra/clangd/Protocol.h
@@ -1510,6 +1510,23 @@
};
llvm::json::Value toJSON(const DocumentLink &DocumentLink);
+// FIXME(kirillbobyrev): Add FoldingRangeClientCapabilities so we can support
+// per-line-folding editors.
+struct FoldingRangeParams {
+ TextDocumentIdentifier textDocument;
+};
+bool fromJSON(const llvm::json::Value &, FoldingRangeParams &);
+
+/// Stores information about a region of code that can be folded.
+struct FoldingRange {
+ unsigned startLine = 0;
+ llvm::Optional<unsigned> startCharacter;
+ unsigned endLine = 0;
+ llvm::Optional<unsigned> endCharacter;
+ llvm::Optional<std::string> kind;
+};
+llvm::json::Value toJSON(const FoldingRange &Range);
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/Protocol.cpp
===================================================================
--- clang-tools-extra/clangd/Protocol.cpp
+++ clang-tools-extra/clangd/Protocol.cpp
@@ -1241,5 +1241,24 @@
};
}
+bool fromJSON(const llvm::json::Value &Params, FoldingRangeParams &R) {
+ llvm::json::ObjectMapper O(Params);
+ return O && O.map("textDocument", R.textDocument);
+}
+
+llvm::json::Value toJSON(const FoldingRange &Range) {
+ llvm::json::Object Result{
+ {"startLine", Range.startLine},
+ {"endLine", Range.endLine},
+ };
+ if (Range.startCharacter)
+ Result["startCharacter"] = *Range.startCharacter;
+ if (Range.endCharacter)
+ Result["endCharacter"] = *Range.endCharacter;
+ if (Range.kind)
+ Result["kind"] = *Range.kind;
+ return Result;
+}
+
} // namespace clangd
} // namespace clang
Index: clang-tools-extra/clangd/FindSymbols.cpp
===================================================================
--- clang-tools-extra/clangd/FindSymbols.cpp
+++ clang-tools-extra/clangd/FindSymbols.cpp
@@ -10,6 +10,7 @@
#include "AST.h"
#include "FuzzyMatch.h"
#include "ParsedAST.h"
+#include "Protocol.h"
#include "Quality.h"
#include "SourceCode.h"
#include "index/Index.h"
@@ -164,6 +165,17 @@
SI.deprecated = ND.isDeprecated();
SI.range =
Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)};
+ if (SK == SymbolKind::Class || SK == SymbolKind::Struct ||
+ SK == SymbolKind::Function || SK == SymbolKind::Method ||
+ SK == SymbolKind::Constructor || SK == SymbolKind::Enum ||
+ SK == SymbolKind::Operator || SK == SymbolKind::Namespace) {
+ const auto HalfOpenRange =
+ toHalfOpenFileRange(SM, Ctx.getLangOpts(), {BeginLoc, EndLoc});
+ if (!HalfOpenRange)
+ return llvm::None;
+ SI.range = Range{sourceLocToPosition(SM, HalfOpenRange->getBegin()),
+ sourceLocToPosition(SM, HalfOpenRange->getEnd())};
+ }
SI.selectionRange = Range{NameBegin, NameEnd};
if (!SI.range.contains(SI.selectionRange)) {
// 'selectionRange' must be contained in 'range', so in cases where clang
Index: clang-tools-extra/clangd/ClangdServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdServer.h
+++ clang-tools-extra/clangd/ClangdServer.h
@@ -153,6 +153,9 @@
/// Enable notification-based semantic highlighting.
bool TheiaSemanticHighlighting = false;
+ /// Enable preview of FoldingRanges feature.
+ bool FoldingRanges = false;
+
/// Returns true if the tweak should be enabled.
std::function<bool(const Tweak &)> TweakFilter = [](const Tweak &T) {
return !T.hidden(); // only enable non-hidden tweaks.
@@ -242,6 +245,9 @@
void documentSymbols(StringRef File,
Callback<std::vector<DocumentSymbol>> CB);
+ /// Retrieve ranges that can be used to fold code within the specified file.
+ void foldingRanges(StringRef File, Callback<std::vector<FoldingRange>> CB);
+
/// Retrieve locations for symbol references.
void findReferences(PathRef File, Position Pos, uint32_t Limit,
Callback<ReferencesResult> CB);
Index: clang-tools-extra/clangd/ClangdServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdServer.cpp
+++ clang-tools-extra/clangd/ClangdServer.cpp
@@ -655,6 +655,18 @@
TUScheduler::InvalidateOnUpdate);
}
+void ClangdServer::foldingRanges(llvm::StringRef File,
+ Callback<std::vector<FoldingRange>> CB) {
+ auto Action =
+ [CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::getFoldingRanges(InpAST->AST));
+ };
+ WorkScheduler.runWithAST("foldingRanges", File, std::move(Action),
+ TUScheduler::InvalidateOnUpdate);
+}
+
void ClangdServer::findReferences(PathRef File, Position Pos, uint32_t Limit,
Callback<ReferencesResult> CB) {
auto Action = [Pos, Limit, CB = std::move(CB),
Index: clang-tools-extra/clangd/ClangdLSPServer.h
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.h
+++ clang-tools-extra/clangd/ClangdLSPServer.h
@@ -87,6 +87,8 @@
// otherwise.
void onDocumentSymbol(const DocumentSymbolParams &,
Callback<llvm::json::Value>);
+ void onFoldingRange(const FoldingRangeParams &,
+ Callback<std::vector<FoldingRange>>);
void onCodeAction(const CodeActionParams &, Callback<llvm::json::Value>);
void onCompletion(const CompletionParams &, Callback<CompletionList>);
void onSignatureHelp(const TextDocumentPositionParams &,
Index: clang-tools-extra/clangd/ClangdLSPServer.cpp
===================================================================
--- clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -637,6 +637,8 @@
->insert(
{"semanticHighlighting",
llvm::json::Object{{"scopes", buildHighlightScopeLookupTable()}}});
+ if (ClangdServerOpts.FoldingRanges)
+ Result.getObject("capabilities")->insert({"foldingRangeProvider", true});
Reply(std::move(Result));
}
@@ -922,7 +924,6 @@
static std::vector<SymbolInformation>
flattenSymbolHierarchy(llvm::ArrayRef<DocumentSymbol> Symbols,
const URIForFile &FileURI) {
-
std::vector<SymbolInformation> Results;
std::function<void(const DocumentSymbol &, llvm::StringRef)> Process =
[&](const DocumentSymbol &S, llvm::Optional<llvm::StringRef> ParentName) {
@@ -961,6 +962,12 @@
});
}
+void ClangdLSPServer::onFoldingRange(
+ const FoldingRangeParams &Params,
+ Callback<std::vector<FoldingRange>> Reply) {
+ Server->foldingRanges(Params.textDocument.uri.file(), std::move(Reply));
+}
+
static llvm::Optional<Command> asCommand(const CodeAction &Action) {
Command Cmd;
if (Action.command && Action.edit)
@@ -1388,6 +1395,8 @@
MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink);
MsgHandler->bind("textDocument/semanticTokens", &ClangdLSPServer::onSemanticTokens);
MsgHandler->bind("textDocument/semanticTokens/edits", &ClangdLSPServer::onSemanticTokensEdits);
+ if (Opts.FoldingRanges)
+ MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
// clang-format on
}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits