Skip to content

Commit

Permalink
[clangd] Implement textDocument/foldingRange
Browse files Browse the repository at this point in the history
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: clangd/clangd#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
  • Loading branch information
kirillbobyrev committed Jul 14, 2020
1 parent c06b7e2 commit 7a514c9
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 5 deletions.
11 changes: 10 additions & 1 deletion clang-tools-extra/clangd/ClangdLSPServer.cpp
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Expand Up @@ -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 &,
Expand Down
12 changes: 12 additions & 0 deletions clang-tools-extra/clangd/ClangdServer.cpp
Expand Up @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/ClangdServer.h
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Expand Up @@ -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
17 changes: 17 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions clang-tools-extra/clangd/SemanticSelection.cpp
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "SemanticSelection.h"
#include "FindSymbols.h"
#include "ParsedAST.h"
#include "Protocol.h"
#include "Selection.h"
Expand All @@ -18,13 +19,28 @@
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);
}
}

// 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) {
Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/SemanticSelection.h
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
61 changes: 57 additions & 4 deletions clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
Expand Up @@ -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) {
Expand All @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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

0 comments on commit 7a514c9

Please sign in to comment.