Skip to content

Commit 5199489

Browse files
authored
[clangd] Improve Markup Rendering (#140498)
This is a preparation for fixing clangd/clangd#529. It changes the Markup rendering to markdown and plaintext. - Properly separate paragraphs using an empty line between - Dont escape markdown syntax for markdown output except for HTML - Dont do any formatting for markdown because the client is handling the actual markdown rendering
1 parent 0d35e17 commit 5199489

File tree

14 files changed

+923
-192
lines changed

14 files changed

+923
-192
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,11 +1279,9 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
12791279
R.contents.kind = HoverContentFormat;
12801280
R.range = (*H)->SymRange;
12811281
switch (HoverContentFormat) {
1282-
case MarkupKind::PlainText:
1283-
R.contents.value = (*H)->present().asPlainText();
1284-
return Reply(std::move(R));
12851282
case MarkupKind::Markdown:
1286-
R.contents.value = (*H)->present().asMarkdown();
1283+
case MarkupKind::PlainText:
1284+
R.contents.value = (*H)->present(HoverContentFormat);
12871285
return Reply(std::move(R));
12881286
};
12891287
llvm_unreachable("unhandled MarkupKind");

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ MarkupContent renderDoc(const markup::Document &Doc, MarkupKind Kind) {
193193
Result.value.append(Doc.asPlainText());
194194
break;
195195
case MarkupKind::Markdown:
196-
Result.value.append(Doc.asMarkdown());
196+
if (Config::current().Documentation.CommentFormat ==
197+
Config::CommentFormatPolicy::PlainText)
198+
Result.value.append(Doc.asEscapedMarkdown());
199+
else
200+
Result.value.append(Doc.asMarkdown());
197201
break;
198202
}
199203
return Result;

clang-tools-extra/clangd/Config.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,19 @@ struct Config {
196196
/// Controls highlighting modifiers that are disabled.
197197
std::vector<std::string> DisabledModifiers;
198198
} SemanticTokens;
199+
200+
enum class CommentFormatPolicy {
201+
/// Treat comments as plain text.
202+
PlainText,
203+
/// Treat comments as Markdown.
204+
Markdown,
205+
/// Treat comments as doxygen.
206+
Doxygen,
207+
};
208+
209+
struct {
210+
CommentFormatPolicy CommentFormat = CommentFormatPolicy::PlainText;
211+
} Documentation;
199212
};
200213

201214
} // namespace clangd

clang-tools-extra/clangd/ConfigCompile.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ struct FragmentCompiler {
198198
compile(std::move(F.InlayHints));
199199
compile(std::move(F.SemanticTokens));
200200
compile(std::move(F.Style));
201+
compile(std::move(F.Documentation));
201202
}
202203

203204
void compile(Fragment::IfBlock &&F) {
@@ -793,6 +794,21 @@ struct FragmentCompiler {
793794
}
794795
}
795796

797+
void compile(Fragment::DocumentationBlock &&F) {
798+
if (F.CommentFormat) {
799+
if (auto Val =
800+
compileEnum<Config::CommentFormatPolicy>("CommentFormat",
801+
*F.CommentFormat)
802+
.map("Plaintext", Config::CommentFormatPolicy::PlainText)
803+
.map("Markdown", Config::CommentFormatPolicy::Markdown)
804+
.map("Doxygen", Config::CommentFormatPolicy::Doxygen)
805+
.value())
806+
Out.Apply.push_back([Val](const Params &, Config &C) {
807+
C.Documentation.CommentFormat = *Val;
808+
});
809+
}
810+
}
811+
796812
constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error;
797813
constexpr static llvm::SourceMgr::DiagKind Warning =
798814
llvm::SourceMgr::DK_Warning;

clang-tools-extra/clangd/ConfigFragment.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,17 @@ struct Fragment {
393393
std::vector<Located<std::string>> DisabledModifiers;
394394
};
395395
SemanticTokensBlock SemanticTokens;
396+
397+
/// Configures documentation style and behaviour.
398+
struct DocumentationBlock {
399+
/// Specifies the format of comments in the code.
400+
/// Valid values are enum Config::CommentFormatPolicy values:
401+
/// - Plaintext: Treat comments as plain text.
402+
/// - Markdown: Treat comments as Markdown.
403+
/// - Doxygen: Treat comments as doxygen.
404+
std::optional<Located<std::string>> CommentFormat;
405+
};
406+
DocumentationBlock Documentation;
396407
};
397408

398409
} // namespace config

clang-tools-extra/clangd/ConfigYAML.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class Parser {
6969
Dict.handle("Hover", [&](Node &N) { parse(F.Hover, N); });
7070
Dict.handle("InlayHints", [&](Node &N) { parse(F.InlayHints, N); });
7171
Dict.handle("SemanticTokens", [&](Node &N) { parse(F.SemanticTokens, N); });
72+
Dict.handle("Documentation", [&](Node &N) { parse(F.Documentation, N); });
7273
Dict.parse(N);
7374
return !(N.failed() || HadError);
7475
}
@@ -312,6 +313,15 @@ class Parser {
312313
Dict.parse(N);
313314
}
314315

316+
void parse(Fragment::DocumentationBlock &F, Node &N) {
317+
DictParser Dict("Documentation", this);
318+
Dict.handle("CommentFormat", [&](Node &N) {
319+
if (auto Value = scalarValue(N, "CommentFormat"))
320+
F.CommentFormat = *Value;
321+
});
322+
Dict.parse(N);
323+
}
324+
315325
// Helper for parsing mapping nodes (dictionaries).
316326
// We don't use YamlIO as we want to control over unknown keys.
317327
class DictParser {

clang-tools-extra/clangd/Hover.cpp

Lines changed: 49 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "Headers.h"
1616
#include "IncludeCleaner.h"
1717
#include "ParsedAST.h"
18+
#include "Protocol.h"
1819
#include "Selection.h"
1920
#include "SourceCode.h"
2021
#include "clang-include-cleaner/Analysis.h"
@@ -960,42 +961,6 @@ std::optional<HoverInfo> getHoverContents(const Attr *A, ParsedAST &AST) {
960961
return HI;
961962
}
962963

963-
bool isParagraphBreak(llvm::StringRef Rest) {
964-
return Rest.ltrim(" \t").starts_with("\n");
965-
}
966-
967-
bool punctuationIndicatesLineBreak(llvm::StringRef Line) {
968-
constexpr llvm::StringLiteral Punctuation = R"txt(.:,;!?)txt";
969-
970-
Line = Line.rtrim();
971-
return !Line.empty() && Punctuation.contains(Line.back());
972-
}
973-
974-
bool isHardLineBreakIndicator(llvm::StringRef Rest) {
975-
// '-'/'*' md list, '@'/'\' documentation command, '>' md blockquote,
976-
// '#' headings, '`' code blocks
977-
constexpr llvm::StringLiteral LinebreakIndicators = R"txt(-*@\>#`)txt";
978-
979-
Rest = Rest.ltrim(" \t");
980-
if (Rest.empty())
981-
return false;
982-
983-
if (LinebreakIndicators.contains(Rest.front()))
984-
return true;
985-
986-
if (llvm::isDigit(Rest.front())) {
987-
llvm::StringRef AfterDigit = Rest.drop_while(llvm::isDigit);
988-
if (AfterDigit.starts_with(".") || AfterDigit.starts_with(")"))
989-
return true;
990-
}
991-
return false;
992-
}
993-
994-
bool isHardLineBreakAfter(llvm::StringRef Line, llvm::StringRef Rest) {
995-
// Should we also consider whether Line is short?
996-
return punctuationIndicatesLineBreak(Line) || isHardLineBreakIndicator(Rest);
997-
}
998-
999964
void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) {
1000965
if (ND.isInvalidDecl())
1001966
return;
@@ -1570,6 +1535,26 @@ markup::Document HoverInfo::present() const {
15701535
return Output;
15711536
}
15721537

1538+
std::string HoverInfo::present(MarkupKind Kind) const {
1539+
if (Kind == MarkupKind::Markdown) {
1540+
const Config &Cfg = Config::current();
1541+
if ((Cfg.Documentation.CommentFormat ==
1542+
Config::CommentFormatPolicy::Markdown) ||
1543+
(Cfg.Documentation.CommentFormat ==
1544+
Config::CommentFormatPolicy::Doxygen))
1545+
// If the user prefers Markdown, we use the present() method to generate
1546+
// the Markdown output.
1547+
return present().asMarkdown();
1548+
if (Cfg.Documentation.CommentFormat ==
1549+
Config::CommentFormatPolicy::PlainText)
1550+
// If the user prefers plain text, we use the present() method to generate
1551+
// the plain text output.
1552+
return present().asEscapedMarkdown();
1553+
}
1554+
1555+
return present().asPlainText();
1556+
}
1557+
15731558
// If the backtick at `Offset` starts a probable quoted range, return the range
15741559
// (including the quotes).
15751560
std::optional<llvm::StringRef> getBacktickQuoteRange(llvm::StringRef Line,
@@ -1583,9 +1568,14 @@ std::optional<llvm::StringRef> getBacktickQuoteRange(llvm::StringRef Line,
15831568
return std::nullopt;
15841569

15851570
// The quoted string must be nonempty and usually has no leading/trailing ws.
1586-
auto Next = Line.find('`', Offset + 1);
1571+
auto Next = Line.find_first_of("`\n", Offset + 1);
15871572
if (Next == llvm::StringRef::npos)
15881573
return std::nullopt;
1574+
1575+
// There should be no newline in the quoted string.
1576+
if (Line[Next] == '\n')
1577+
return std::nullopt;
1578+
15891579
llvm::StringRef Contents = Line.slice(Offset + 1, Next);
15901580
if (Contents.empty() || isWhitespace(Contents.front()) ||
15911581
isWhitespace(Contents.back()))
@@ -1600,51 +1590,40 @@ std::optional<llvm::StringRef> getBacktickQuoteRange(llvm::StringRef Line,
16001590
return Line.slice(Offset, Next + 1);
16011591
}
16021592

1603-
void parseDocumentationLine(llvm::StringRef Line, markup::Paragraph &Out) {
1593+
void parseDocumentationParagraph(llvm::StringRef Text, markup::Paragraph &Out) {
16041594
// Probably this is appendText(Line), but scan for something interesting.
1605-
for (unsigned I = 0; I < Line.size(); ++I) {
1606-
switch (Line[I]) {
1595+
for (unsigned I = 0; I < Text.size(); ++I) {
1596+
switch (Text[I]) {
16071597
case '`':
1608-
if (auto Range = getBacktickQuoteRange(Line, I)) {
1609-
Out.appendText(Line.substr(0, I));
1598+
if (auto Range = getBacktickQuoteRange(Text, I)) {
1599+
Out.appendText(Text.substr(0, I));
16101600
Out.appendCode(Range->trim("`"), /*Preserve=*/true);
1611-
return parseDocumentationLine(Line.substr(I + Range->size()), Out);
1601+
return parseDocumentationParagraph(Text.substr(I + Range->size()), Out);
16121602
}
16131603
break;
16141604
}
16151605
}
1616-
Out.appendText(Line).appendSpace();
1606+
Out.appendText(Text);
16171607
}
16181608

16191609
void parseDocumentation(llvm::StringRef Input, markup::Document &Output) {
1620-
std::vector<llvm::StringRef> ParagraphLines;
1621-
auto FlushParagraph = [&] {
1622-
if (ParagraphLines.empty())
1623-
return;
1624-
auto &P = Output.addParagraph();
1625-
for (llvm::StringRef Line : ParagraphLines)
1626-
parseDocumentationLine(Line, P);
1627-
ParagraphLines.clear();
1628-
};
1629-
1630-
llvm::StringRef Line, Rest;
1631-
for (std::tie(Line, Rest) = Input.split('\n');
1632-
!(Line.empty() && Rest.empty());
1633-
std::tie(Line, Rest) = Rest.split('\n')) {
1634-
1635-
// After a linebreak remove spaces to avoid 4 space markdown code blocks.
1636-
// FIXME: make FlushParagraph handle this.
1637-
Line = Line.ltrim();
1638-
if (!Line.empty())
1639-
ParagraphLines.push_back(Line);
1640-
1641-
if (isParagraphBreak(Rest) || isHardLineBreakAfter(Line, Rest)) {
1642-
FlushParagraph();
1643-
}
1610+
// A documentation string is treated as a sequence of paragraphs,
1611+
// where the paragraphs are seperated by at least one empty line
1612+
// (meaning 2 consecutive newline characters).
1613+
// Possible leading empty lines (introduced by an odd number > 1 of
1614+
// empty lines between 2 paragraphs) will be removed later in the Markup
1615+
// renderer.
1616+
llvm::StringRef Paragraph, Rest;
1617+
for (std::tie(Paragraph, Rest) = Input.split("\n\n");
1618+
!(Paragraph.empty() && Rest.empty());
1619+
std::tie(Paragraph, Rest) = Rest.split("\n\n")) {
1620+
1621+
// The Paragraph will be empty if there is an even number of newline
1622+
// characters between two paragraphs, so we skip it.
1623+
if (!Paragraph.empty())
1624+
parseDocumentationParagraph(Paragraph, Output.addParagraph());
16441625
}
1645-
FlushParagraph();
16461626
}
1647-
16481627
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
16491628
const HoverInfo::PrintedType &T) {
16501629
OS << T.Type;

clang-tools-extra/clangd/Hover.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ struct HoverInfo {
120120

121121
/// Produce a user-readable information.
122122
markup::Document present() const;
123+
124+
std::string present(MarkupKind Kind) const;
123125
};
124126

125127
inline bool operator==(const HoverInfo::PrintedType &LHS,

0 commit comments

Comments
 (0)