FoldingRanges: Handle LineFoldingsOnly clients.

Do not fold the endline which contains tokens after the end of range.

Differential Revision: https://reviews.llvm.org/D131154
This commit is contained in:
Utkarsh Saxena 2022-08-04 12:41:15 +02:00
parent ba916c0cf6
commit a11ec00afe
8 changed files with 158 additions and 30 deletions

View File

@ -514,6 +514,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
Params.capabilities.HierarchicalDocumentSymbol;
SupportFileStatus = Params.initializationOptions.FileStatus;
HoverContentFormat = Params.capabilities.HoverContentFormat;
Opts.LineFoldingOnly = Params.capabilities.LineFoldingOnly;
SupportsOffsetsInSignatureHelp = Params.capabilities.OffsetsInSignatureHelp;
if (Params.capabilities.WorkDoneProgress)
BackgroundIndexProgressState = BackgroundIndexProgress::Empty;

View File

@ -177,6 +177,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB,
DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
ClangTidyProvider(Opts.ClangTidyProvider),
UseDirtyHeaders(Opts.UseDirtyHeaders),
LineFoldingOnly(Opts.LineFoldingOnly),
PreambleParseForwardingFunctions(Opts.PreambleParseForwardingFunctions),
WorkspaceRoot(Opts.WorkspaceRoot),
Transient(Opts.ImplicitCancellation ? TUScheduler::InvalidateOnUpdate
@ -855,8 +856,9 @@ void ClangdServer::foldingRanges(llvm::StringRef File,
return CB(llvm::make_error<LSPError>(
"trying to compute folding ranges for non-added document",
ErrorCode::InvalidParams));
auto Action = [CB = std::move(CB), Code = std::move(*Code)]() mutable {
CB(clangd::getFoldingRanges(Code));
auto Action = [LineFoldingOnly = LineFoldingOnly, CB = std::move(CB),
Code = std::move(*Code)]() mutable {
CB(clangd::getFoldingRanges(Code, LineFoldingOnly));
};
// We want to make sure folding ranges are always available for all the open
// files, hence prefer runQuick to not wait for operations on other files.

View File

@ -164,6 +164,9 @@ public:
/// Enable preview of FoldingRanges feature.
bool FoldingRanges = false;
// Whether the client supports folding only complete lines.
bool LineFoldingOnly = false;
FeatureModuleSet *FeatureModules = nullptr;
/// If true, use the dirty buffer contents when building Preambles.
bool UseDirtyHeaders = false;
@ -429,6 +432,9 @@ private:
bool UseDirtyHeaders = false;
// Whether the client supports folding only complete lines.
bool LineFoldingOnly = false;
bool PreambleParseForwardingFunctions = false;
// GUARDED_BY(CachedCompletionFuzzyFindRequestMutex)

View File

@ -391,6 +391,10 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R,
}
}
}
if (auto *Folding = TextDocument->getObject("foldingRange")) {
if (auto LineFolding = Folding->getBoolean("lineFoldingOnly"))
R.LineFoldingOnly = *LineFolding;
}
if (auto *Rename = TextDocument->getObject("rename")) {
if (auto RenameSupport = Rename->getBoolean("prepareSupport"))
R.RenamePrepareSupport = *RenameSupport;

View File

@ -437,6 +437,12 @@ struct ClientCapabilities {
/// textDocument.signatureHelp
bool HasSignatureHelp = false;
/// Client signals that it only supports folding complete lines.
/// Client will ignore specified `startCharacter` and `endCharacter`
/// properties in a FoldingRange.
/// textDocument.foldingRange.lineFoldingOnly
bool LineFoldingOnly = false;
/// Client supports processing label offsets instead of a simple label string.
/// textDocument.signatureHelp.signatureInformation.parameterInformation.labelOffsetSupport
bool OffsetsInSignatureHelp = false;

View File

@ -179,7 +179,7 @@ llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) {
// statement bodies).
// Related issue: https://github.com/clangd/clangd/issues/310
llvm::Expected<std::vector<FoldingRange>>
getFoldingRanges(const std::string &Code) {
getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
auto OrigStream = pseudo::lex(Code, clang::pseudo::genericLangOpts());
auto DirectiveStructure = pseudo::DirectiveTree::parse(OrigStream);
@ -192,15 +192,17 @@ getFoldingRanges(const std::string &Code) {
pseudo::pairBrackets(ParseableStream);
std::vector<FoldingRange> Result;
auto ToFoldingRange = [](Position Start, Position End,
llvm::StringLiteral Kind) {
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();
return FR;
Result.push_back(FR);
};
auto OriginalToken = [&](const pseudo::Token &T) {
return OrigStream.tokens()[T.OriginalIndex];
@ -211,8 +213,11 @@ getFoldingRanges(const std::string &Code) {
auto StartPosition = [&](const pseudo::Token &T) {
return offsetToPosition(Code, StartOffset(T));
};
auto EndOffset = [&](const pseudo::Token &T) {
return StartOffset(T) + OriginalToken(T).Length;
};
auto EndPosition = [&](const pseudo::Token &T) {
return offsetToPosition(Code, StartOffset(T) + OriginalToken(T).Length);
return offsetToPosition(Code, EndOffset(T));
};
auto Tokens = ParseableStream.tokens();
// Brackets.
@ -223,26 +228,43 @@ getFoldingRanges(const std::string &Code) {
if (Tok.Line < Paired->Line) {
Position Start = offsetToPosition(Code, 1 + StartOffset(Tok));
Position End = StartPosition(*Paired);
Result.push_back(ToFoldingRange(Start, End, FoldingRange::REGION_KIND));
if (LineFoldingOnly)
End.line--;
AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
}
}
}
auto IsBlockComment = [&](const pseudo::Token &T) {
assert(T.Kind == tok::comment);
return OriginalToken(T).Length >= 2 &&
Code.substr(StartOffset(T), 2) == "/*";
};
// Multi-line comments.
for (const auto *T = Tokens.begin(); T != Tokens.end();) {
for (auto *T = Tokens.begin(); T != Tokens.end();) {
if (T->Kind != tok::comment) {
T++;
continue;
}
Position Start = StartPosition(*T);
Position LastCommentEnd = EndPosition(*T);
pseudo::Token *FirstComment = T;
// Show starting sentinals (// and /*) of the comment.
Position Start = offsetToPosition(Code, 2 + StartOffset(*FirstComment));
pseudo::Token *LastComment = T;
Position End = EndPosition(*T);
while (T != Tokens.end() && T->Kind == tok::comment &&
StartPosition(*T).line <= LastCommentEnd.line + 1) {
LastCommentEnd = EndPosition(*T);
StartPosition(*T).line <= End.line + 1) {
End = EndPosition(*T);
LastComment = T;
T++;
}
if (Start.line < LastCommentEnd.line)
Result.push_back(
ToFoldingRange(Start, LastCommentEnd, FoldingRange::COMMENT_KIND));
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);
}
return Result;
}

View File

@ -33,7 +33,7 @@ llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST);
/// Returns a list of ranges whose contents might be collapsible in an editor.
/// This version uses the pseudoparser which does not require the AST.
llvm::Expected<std::vector<FoldingRange>>
getFoldingRanges(const std::string &Code);
getFoldingRanges(const std::string &Code, bool LineFoldingOnly);
} // namespace clangd
} // namespace clang

View File

@ -196,7 +196,7 @@ TEST(SemanticSelection, RunViaClangdServer) {
ElementsAre(SourceAnnotations.range("empty")));
}
TEST(FoldingRanges, All) {
TEST(FoldingRanges, ASTAll) {
const char *Tests[] = {
R"cpp(
#define FOO int foo() {\
@ -265,7 +265,7 @@ TEST(FoldingRanges, All) {
}
}
TEST(FoldingRangesPseudoParser, All) {
TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
const char *Tests[] = {
R"cpp(
#define FOO int foo() {\
@ -336,36 +336,123 @@ TEST(FoldingRangesPseudoParser, All) {
]]};
)cpp",
R"cpp(
[[/* Multi
/*[[ Multi
* line
* comment
*/]]
]]*/
)cpp",
R"cpp(
[[// Comment
//[[ Comment
// 1]]
[[// Comment
//[[ Comment
// 2]]
// No folding for single line comment.
[[/* comment 3
*/]]
/*[[ comment 3
]]*/
[[/* comment 4
*/]]
/*[[ comment 4
]]*/
/*[[ foo */
/* bar ]]*/
/*[[ foo */
// baz
/* bar ]]*/
/*[[ foo */
/* bar*/
// baz]]
//[[ foo
/* bar */]]
)cpp",
};
for (const char *Test : Tests) {
auto T = Annotations(Test);
EXPECT_THAT(
gatherFoldingRanges(llvm::cantFail(getFoldingRanges(T.code().str()))),
UnorderedElementsAreArray(T.ranges()))
EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(
T.code().str(), /*LineFoldingsOnly=*/false))),
UnorderedElementsAreArray(T.ranges()))
<< Test;
}
}
TEST(FoldingRanges, PseudoParserLineFoldingsOnly) {
const char *Tests[] = {
R"cpp(
void func(int a) {[[
a++;]]
}
)cpp",
R"cpp(
// Always exclude last line for brackets.
void func(int a) {[[
if(a == 1) {[[
a++;]]
} else if (a == 2){[[
a--;]]
} else { // No folding for 2 line bracketed ranges.
}]]
}
)cpp",
R"cpp(
/*[[ comment
* comment]]
*/
/* No folding for this comment.
*/
// No folding for this comment.
//[[ 2 single line comment.
// 2 single line comment.]]
//[[ >=2 line comments.
// >=2 line comments.
// >=2 line comments.]]
//[[ foo\
bar\
baz]]
/*[[ foo */
/* bar */]]
/* baz */
/*[[ foo */
/* bar]]
* This does not fold me */
//[[ foo
/* bar */]]
)cpp",
// FIXME: Support folding template arguments.
// R"cpp(
// template <[[typename foo, class bar]]> struct baz {};
// )cpp",
};
auto StripColumns = [](const std::vector<Range> &Ranges) {
std::vector<Range> Res;
for (Range R : Ranges) {
R.start.character = R.end.character = 0;
Res.push_back(R);
}
return Res;
};
for (const char *Test : Tests) {
auto T = Annotations(Test);
EXPECT_THAT(
StripColumns(gatherFoldingRanges(llvm::cantFail(
getFoldingRanges(T.code().str(), /*LineFoldingsOnly=*/true)))),
UnorderedElementsAreArray(StripColumns(T.ranges())))
<< Test;
}
}
} // namespace
} // namespace clangd
} // namespace clang