[clang-tools-extra] 7a514c9 - [clangd] Implement textDocument/foldingRange
Kirill Bobyrev via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 14 00:29:00 PDT 2020
Author: Kirill Bobyrev
Date: 2020-07-14T09:28:42+02:00
New Revision: 7a514c9bf8f2513b57aee685879dd2c104381d99
URL: https://github.com/llvm/llvm-project/commit/7a514c9bf8f2513b57aee685879dd2c104381d99
DIFF: https://github.com/llvm/llvm-project/commit/7a514c9bf8f2513b57aee685879dd2c104381d99.diff
LOG: [clangd] Implement textDocument/foldingRange
Summary:
This patch introduces basic textDocument/foldingRange support. It relies on
textDocument/documentSymbols to collect all symbols and uses takes ranges
to create folds.
The next steps for textDocument/foldingRange support would be:
* Implementing FoldingRangeClientCapabilities and respecting respect client
preferences
* Specifying folding range kind
* Migrating from DocumentSymbol implementation to custom RecursiveASTVisitor flow that will allow more flexibility
* Supporting more folding range types: comments, PP conditional regions, includes and other code regions (e.g. public/private/protected sections of classes, control flow statement bodies)
Tested: (Neo)Vim (coc-clangd) and VSCode.
Related issue: https://github.com/clangd/clangd/issues/310
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: nridge, ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits
Tags: #clang
Differential Revision: https://reviews.llvm.org/D82436
Added:
Modified:
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/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/SemanticSelectionTests.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index b0aba886edbe..0408b0498488 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -637,6 +637,8 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
->insert(
{"semanticHighlighting",
llvm::json::Object{{"scopes", buildHighlightScopeLookupTable()}}});
+ if (ClangdServerOpts.FoldingRanges)
+ Result.getObject("capabilities")->insert({"foldingRangeProvider", true});
Reply(std::move(Result));
}
@@ -929,7 +931,6 @@ void ClangdLSPServer::onDocumentFormatting(
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) {
@@ -968,6 +969,12 @@ void ClangdLSPServer::onDocumentSymbol(const DocumentSymbolParams &Params,
});
}
+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)
@@ -1395,6 +1402,8 @@ ClangdLSPServer::ClangdLSPServer(
MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink);
MsgHandler->bind("textDocument/semanticTokens/full", &ClangdLSPServer::onSemanticTokens);
MsgHandler->bind("textDocument/semanticTokens/full/delta", &ClangdLSPServer::onSemanticTokensDelta);
+ if (Opts.FoldingRanges)
+ MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange);
// clang-format on
}
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index a779e9036c4a..d0c0e814c641 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -87,6 +87,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
// 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 &,
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 5d99104dadaf..c33cdffcb0ca 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -674,6 +674,18 @@ void ClangdServer::documentSymbols(llvm::StringRef File,
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),
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index ea82081f2440..3529e5050aa3 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -157,6 +157,9 @@ class ClangdServer {
/// 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.
@@ -246,6 +249,9 @@ class ClangdServer {
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);
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 239603715785..b5dbee54f59d 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1241,5 +1241,24 @@ llvm::json::Value toJSON(const DocumentLink &DocumentLink) {
};
}
+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
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 77d402a6a9ba..2bb23e5ddd94 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1510,6 +1510,23 @@ struct DocumentLink {
};
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;
+ unsigned startCharacter;
+ unsigned endLine = 0;
+ unsigned endCharacter;
+ llvm::Optional<std::string> kind;
+};
+llvm::json::Value toJSON(const FoldingRange &Range);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp
index a6b1ebfb8327..cfce1520cd08 100644
--- a/clang-tools-extra/clangd/SemanticSelection.cpp
+++ b/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 @@ void addIfDistinct(const Range &R, std::vector<Range> &Result) {
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 @@ llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
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
diff --git a/clang-tools-extra/clangd/SemanticSelection.h b/clang-tools-extra/clangd/SemanticSelection.h
index 810cc21d9a58..2fe37871ec68 100644
--- a/clang-tools-extra/clangd/SemanticSelection.h
+++ b/clang-tools-extra/clangd/SemanticSelection.h
@@ -25,6 +25,10 @@ namespace clangd {
/// If pos is not in any interesting range, return [Pos, Pos).
llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos);
+/// Returns a list of ranges whose contents might be collapsible in an editor.
+/// This should include large scopes, preprocessor blocks etc.
+llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 6e3d6a231da1..12d3e299868e 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -296,6 +296,14 @@ opt<bool> RecoveryASTType{
Hidden,
};
+opt<bool> FoldingRanges{
+ "folding-ranges",
+ cat(Features),
+ desc("Enable preview of FoldingRanges feature"),
+ init(false),
+ Hidden,
+};
+
opt<unsigned> WorkerThreadsCount{
"j",
cat(Misc),
@@ -676,6 +684,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
Opts.AsyncThreadsCount = WorkerThreadsCount;
Opts.BuildRecoveryAST = RecoveryAST;
Opts.PreserveRecoveryASTType = RecoveryASTType;
+ Opts.FoldingRanges = FoldingRanges;
clangd::CodeCompleteOptions CCOpts;
CCOpts.IncludeIneligibleResults = IncludeIneligibleResults;
diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
index cd1d79834167..5c1a80aae7f9 100644
--- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
+++ b/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,20 @@ std::vector<Range> gatherRanges(const SelectionRange &SR) {
return Ranges;
}
+std::vector<Range>
+gatherFoldingRanges(llvm::ArrayRef<FoldingRange> FoldingRanges) {
+ std::vector<Range> Ranges;
+ Range NextRange;
+ for (const auto &R : FoldingRanges) {
+ NextRange.start.line = R.startLine;
+ NextRange.start.character = R.startCharacter;
+ NextRange.end.line = R.endLine;
+ 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 +136,16 @@ TEST(SemanticSelection, All) {
)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 +199,41 @@ TEST(SemanticSelection, RunViaClangdServer) {
EXPECT_THAT(gatherRanges(Ranges->back()),
ElementsAre(SourceAnnotations.range("empty")));
}
+
+TEST(FoldingRanges, All) {
+ 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
More information about the cfe-commits
mailing list