Skip to content

Commit dd5659e

Browse files
committed
[clangd] Escaping adaption due to whitespace preservation
1 parent 39b891d commit dd5659e

File tree

7 files changed

+103
-15
lines changed

7 files changed

+103
-15
lines changed

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/Hover.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1569,9 +1569,14 @@ std::optional<llvm::StringRef> getBacktickQuoteRange(llvm::StringRef Line,
15691569
return std::nullopt;
15701570

15711571
// The quoted string must be nonempty and usually has no leading/trailing ws.
1572-
auto Next = Line.find('`', Offset + 1);
1572+
auto Next = Line.find_first_of("`\n", Offset + 1);
15731573
if (Next == llvm::StringRef::npos)
15741574
return std::nullopt;
1575+
1576+
// There should be no newline in the quoted string.
1577+
if (Line[Next] == '\n')
1578+
return std::nullopt;
1579+
15751580
llvm::StringRef Contents = Line.slice(Offset + 1, Next);
15761581
if (Contents.empty() || isWhitespace(Contents.front()) ||
15771582
isWhitespace(Contents.back()))

clang-tools-extra/clangd/support/Markup.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,14 +203,39 @@ bool needsLeadingEscape(char C, llvm::StringRef Before, llvm::StringRef After,
203203
std::string renderText(llvm::StringRef Input, bool StartsLine,
204204
bool EscapeMarkdown = false) {
205205
std::string R;
206-
for (unsigned I = 0; I < Input.size(); ++I) {
207-
if (Input.substr(0, I).take_while(llvm::isSpace).empty() &&
208-
!isWhitespace(Input[I]) &&
209-
needsLeadingEscape(Input[I], Input.substr(0, I), Input.substr(I + 1),
210-
StartsLine, EscapeMarkdown))
211-
R.push_back('\\');
212-
R.push_back(Input[I]);
206+
207+
// split the input into lines, and escape each line separately.
208+
llvm::StringRef Line, Rest;
209+
210+
bool StartsLineIntern = StartsLine;
211+
bool IsFirstLine = true;
212+
213+
for (std::tie(Line, Rest) = Input.split('\n');
214+
!(Line.empty() && Rest.empty());
215+
std::tie(Line, Rest) = Rest.split('\n')) {
216+
217+
StartsLineIntern = IsFirstLine ? StartsLine : true;
218+
219+
// Ignore leading spaces for the escape logic, but preserve them in the
220+
// output.
221+
StringRef LeadingSpaces = Line.take_while(llvm::isSpace);
222+
if (!LeadingSpaces.empty()) {
223+
R.append(LeadingSpaces);
224+
}
225+
226+
for (unsigned I = LeadingSpaces.size(); I < Line.size(); ++I) {
227+
if (needsLeadingEscape(Line[I], Line.substr(LeadingSpaces.size(), I),
228+
Line.substr(I + 1), StartsLineIntern,
229+
EscapeMarkdown))
230+
R.push_back('\\');
231+
R.push_back(Line[I]);
232+
}
233+
234+
IsFirstLine = false;
235+
if (!Rest.empty())
236+
R.push_back('\n');
213237
}
238+
214239
return R;
215240
}
216241

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# RUN: mkdir -p %t/clangd
2+
3+
# Create a config file that configures to use CommentFormat Markdown.
4+
# RUN: echo 'Documentation:' > %t/clangd/config.yaml
5+
# RUN: echo ' CommentFormat: Markdown' >> %t/clangd/config.yaml
6+
# RUN: env XDG_CONFIG_HOME=%t clangd -lit-test -enable-config < %s | FileCheck -strict-whitespace %s
7+
# Start a session.
8+
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument": {"signatureHelp": {"signatureInformation": {"documentationFormat": ["markdown", "plaintext"]}}}},"trace":"off"}}
9+
---
10+
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"// comment `markdown` _noescape_\nvoid x(int);\nint main(){\nx("}}}
11+
---
12+
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":3,"character":2}}}
13+
# CHECK: "id": 1,
14+
# CHECK-NEXT: "jsonrpc": "2.0",
15+
# CHECK-NEXT: "result": {
16+
# CHECK-NEXT: "activeParameter": 0,
17+
# CHECK-NEXT: "activeSignature": 0,
18+
# CHECK-NEXT: "signatures": [
19+
# CHECK-NEXT: {
20+
# CHECK-NEXT: "documentation": {
21+
# CHECK-NEXT: "kind": "markdown",
22+
# CHECK-NEXT: "value": "comment `markdown` _noescape_"
23+
# CHECK-NEXT: },
24+
# CHECK-NEXT: "label": "x(int) -> void",
25+
# CHECK-NEXT: "parameters": [
26+
# CHECK-NEXT: {
27+
# CHECK-NEXT: "label": "int"
28+
# CHECK-NEXT: }
29+
# CHECK-NEXT: ]
30+
# CHECK-NEXT: }
31+
# CHECK-NEXT: ]
32+
# CHECK-NEXT: }
33+
---
34+
{"jsonrpc":"2.0","id":100000,"method":"shutdown"}
35+
---
36+
{"jsonrpc":"2.0","method":"exit"}

clang-tools-extra/clangd/test/signature-help.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Start a session.
33
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument": {"signatureHelp": {"signatureInformation": {"documentationFormat": ["markdown", "plaintext"]}}}},"trace":"off"}}
44
---
5-
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"// comment `markdown` _noescape_\nvoid x(int);\nint main(){\nx("}}}
5+
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"// comment `markdown` _escape_\nvoid x(int);\nint main(){\nx("}}}
66
---
77
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":3,"character":2}}}
88
# CHECK: "id": 1,
@@ -14,7 +14,7 @@
1414
# CHECK-NEXT: {
1515
# CHECK-NEXT: "documentation": {
1616
# CHECK-NEXT: "kind": "markdown",
17-
# CHECK-NEXT: "value": "comment `markdown` _noescape_"
17+
# CHECK-NEXT: "value": "comment `markdown` \\_escape\\_"
1818
# CHECK-NEXT: },
1919
# CHECK-NEXT: "label": "x(int) -> void",
2020
# CHECK-NEXT: "parameters": [

clang-tools-extra/clangd/unittests/HoverTests.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3765,24 +3765,29 @@ provides Foo, Bar, Baz, Foobar, Qux and 1 more)"}};
37653765
TEST(Hover, ParseDocumentation) {
37663766
struct Case {
37673767
llvm::StringRef Documentation;
3768+
llvm::StringRef ExpectedRenderEscapedMarkdown;
37683769
llvm::StringRef ExpectedRenderMarkdown;
37693770
llvm::StringRef ExpectedRenderPlainText;
37703771
} Cases[] = {{
37713772
" \n foo\nbar",
37723773
"foo\nbar",
3774+
"foo\nbar",
37733775
"foo bar",
37743776
},
37753777
{
37763778
"foo\nbar \n ",
37773779
"foo\nbar",
3780+
"foo\nbar",
37783781
"foo bar",
37793782
},
37803783
{
3784+
"foo \nbar",
37813785
"foo \nbar",
37823786
"foo \nbar",
37833787
"foo\nbar",
37843788
},
37853789
{
3790+
"foo \nbar",
37863791
"foo \nbar",
37873792
"foo \nbar",
37883793
"foo\nbar",
@@ -3791,43 +3796,52 @@ TEST(Hover, ParseDocumentation) {
37913796
"foo\n\n\nbar",
37923797
"foo\n\nbar",
37933798
"foo\n\nbar",
3799+
"foo\n\nbar",
37943800
},
37953801
{
37963802
"foo\n\n\n\tbar",
37973803
"foo\n\n\tbar",
3804+
"foo\n\n\tbar",
37983805
"foo\n\nbar",
37993806
},
38003807
{
38013808
"foo\n\n\n bar",
38023809
"foo\n\n bar",
3810+
"foo\n\n bar",
38033811
"foo\n\nbar",
38043812
},
38053813
{
38063814
"foo\n\n\n bar",
38073815
"foo\n\n bar",
3816+
"foo\n\n bar",
38083817
"foo\n\nbar",
38093818
},
38103819
{
38113820
"foo\n\n\n bar",
38123821
"foo\n\n bar",
3822+
"foo\n\n bar",
38133823
"foo\n\nbar",
38143824
},
38153825
{
38163826
"foo.\nbar",
38173827
"foo.\nbar",
38183828
"foo.\nbar",
3829+
"foo.\nbar",
38193830
},
38203831
{
3832+
"foo. \nbar",
38213833
"foo. \nbar",
38223834
"foo. \nbar",
38233835
"foo.\nbar",
38243836
},
38253837
{
38263838
"foo\n*bar",
3839+
"foo\n\\*bar",
38273840
"foo\n*bar",
38283841
"foo\n*bar",
38293842
},
38303843
{
3844+
"foo\nbar",
38313845
"foo\nbar",
38323846
"foo\nbar",
38333847
"foo bar",
@@ -3836,22 +3850,26 @@ TEST(Hover, ParseDocumentation) {
38363850
"Tests primality of `p`.",
38373851
"Tests primality of `p`.",
38383852
"Tests primality of `p`.",
3853+
"Tests primality of `p`.",
38393854
},
38403855
{
38413856
"'`' should not occur in `Code`",
3857+
"'\\`' should not occur in `Code`",
38423858
"'`' should not occur in `Code`",
38433859
"'`' should not occur in `Code`",
38443860
},
38453861
{
38463862
"`not\nparsed`",
3847-
"`not parsed`",
3863+
"\\`not\nparsed\\`",
3864+
"`not\nparsed`",
38483865
"`not parsed`",
38493866
}};
38503867

38513868
for (const auto &C : Cases) {
38523869
markup::Document Output;
38533870
parseDocumentation(C.Documentation, Output);
38543871

3872+
EXPECT_EQ(Output.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
38553873
EXPECT_EQ(Output.asMarkdown(), C.ExpectedRenderMarkdown);
38563874
EXPECT_EQ(Output.asPlainText(), C.ExpectedRenderPlainText);
38573875
}

clang-tools-extra/clangd/unittests/support/MarkupTests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,17 +371,17 @@ TEST(Paragraph, SeparationOfChunks3) {
371371
EXPECT_EQ(P.asPlainText(), "after\nfoobar");
372372

373373
P.appendText("- bat\n");
374-
EXPECT_EQ(P.asEscapedMarkdown(), "after \n foobar\n- bat");
374+
EXPECT_EQ(P.asEscapedMarkdown(), "after \n foobar\n\\- bat");
375375
EXPECT_EQ(P.asMarkdown(), "after \n foobar\n- bat");
376376
EXPECT_EQ(P.asPlainText(), "after\nfoobar\n- bat");
377377

378378
P.appendText("- baz");
379-
EXPECT_EQ(P.asEscapedMarkdown(), "after \n foobar\n- bat\n- baz");
379+
EXPECT_EQ(P.asEscapedMarkdown(), "after \n foobar\n\\- bat\n\\- baz");
380380
EXPECT_EQ(P.asMarkdown(), "after \n foobar\n- bat\n- baz");
381381
EXPECT_EQ(P.asPlainText(), "after\nfoobar\n- bat\n- baz");
382382

383383
P.appendText(" faz ");
384-
EXPECT_EQ(P.asEscapedMarkdown(), "after \n foobar\n- bat\n- baz faz");
384+
EXPECT_EQ(P.asEscapedMarkdown(), "after \n foobar\n\\- bat\n\\- baz faz");
385385
EXPECT_EQ(P.asMarkdown(), "after \n foobar\n- bat\n- baz faz");
386386
EXPECT_EQ(P.asPlainText(), "after\nfoobar\n- bat\n- baz faz");
387387
}

0 commit comments

Comments
 (0)