CCOpts.IncludeFixIts = Params.capabilities.CompletionFixes;
if (!CCOpts.BundleOverloads.hasValue())
CCOpts.BundleOverloads = Params.capabilities.HasSignatureHelp;
+ CCOpts.DocumentationFormat =
+ Params.capabilities.CompletionDocumentationFormat;
DiagOpts.EmbedFixesInDiagnostics = Params.capabilities.DiagnosticFixes;
DiagOpts.SendDiagnosticCategory = Params.capabilities.DiagnosticCategory;
DiagOpts.EmitRelatedLocations =
#include "FileDistance.h"
#include "FuzzyMatch.h"
#include "Headers.h"
+#include "Hover.h"
#include "Preamble.h"
#include "Protocol.h"
#include "Quality.h"
S.SnippetSuffix = std::string(C.IndexResult->CompletionSnippetSuffix);
S.ReturnType = std::string(C.IndexResult->ReturnType);
}
- if (ExtractDocumentation && Completion.Documentation.empty()) {
- if (C.IndexResult)
- Completion.Documentation = std::string(C.IndexResult->Documentation);
- else if (C.SemaResult)
- Completion.Documentation = getDocComment(*ASTCtx, *C.SemaResult,
- /*CommentsFromHeader=*/false);
+ if (ExtractDocumentation && !Completion.Documentation) {
+ auto SetDoc = [&](llvm::StringRef Doc) {
+ if (!Doc.empty()) {
+ Completion.Documentation.emplace();
+ parseDocumentation(Doc, *Completion.Documentation);
+ }
+ };
+ if (C.IndexResult) {
+ SetDoc(C.IndexResult->Documentation);
+ } else if (C.SemaResult) {
+ SetDoc(getDocComment(*ASTCtx, *C.SemaResult,
+ /*CommentsFromHeader=*/false));
+ }
}
}
LSP.deprecated = Deprecated;
if (InsertInclude)
LSP.detail += "\n" + InsertInclude->Header;
- LSP.documentation = Documentation;
+ if (Documentation) {
+ LSP.documentation.emplace();
+ switch (Opts.DocumentationFormat) {
+ case MarkupKind::PlainText:
+ LSP.documentation->value = Documentation->asPlainText();
+ break;
+ case MarkupKind::Markdown:
+ LSP.documentation->value = Documentation->asMarkdown();
+ break;
+ }
+ LSP.documentation->kind = Opts.DocumentationFormat;
+ }
LSP.sortText = sortText(Score.Total, Name);
LSP.filterText = Name;
LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name};
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H
+#include "FormattedString.h"
#include "Headers.h"
#include "Protocol.h"
#include "Quality.h"
/// If more results are available, we set CompletionList.isIncomplete.
size_t Limit = 0;
+ /// Whether to present doc comments as plain-text or markdown.
+ MarkupKind DocumentationFormat = MarkupKind::PlainText;
+
enum IncludeInsertion {
IWYU,
NeverInsert,
std::string SnippetSuffix;
// Type to be displayed for this completion.
std::string ReturnType;
- std::string Documentation;
+ // The parsed documentation comment.
+ llvm::Optional<markup::Document> Documentation;
CompletionItemKind Kind = CompletionItemKind::Missing;
// This completion item may represent several symbols that can be inserted in
// the same way, such as function overloads. In this case BundleSize > 1, and
OS << "\n---\n";
}
void renderPlainText(llvm::raw_ostream &OS) const override { OS << '\n'; }
+ std::unique_ptr<Block> clone() const override {
+ return std::make_unique<Ruler>(*this);
+ }
bool isRuler() const override { return true; }
};
OS << '\n' << Contents << "\n\n";
}
+ std::unique_ptr<Block> clone() const override {
+ return std::make_unique<CodeBlock>(*this);
+ }
+
CodeBlock(std::string Contents, std::string Language)
: Contents(std::move(Contents)), Language(std::move(Language)) {}
OS << " \n";
}
+std::unique_ptr<Block> Paragraph::clone() const {
+ return std::make_unique<Paragraph>(*this);
+}
+
void Paragraph::renderPlainText(llvm::raw_ostream &OS) const {
llvm::StringRef Sep = "";
for (auto &C : Chunks) {
return *this;
}
+std::unique_ptr<Block> BulletList::clone() const {
+ return std::make_unique<BulletList>(*this);
+}
+
class Document &BulletList::addItem() {
Items.emplace_back();
return Items.back();
}
+Document &Document::operator=(const Document &Other) {
+ Children.clear();
+ for (const auto &C : Other.Children)
+ Children.push_back(C->clone());
+ return *this;
+}
+
Paragraph &Document::addParagraph() {
Children.push_back(std::make_unique<Paragraph>());
return *static_cast<Paragraph *>(Children.back().get());
public:
virtual void renderMarkdown(llvm::raw_ostream &OS) const = 0;
virtual void renderPlainText(llvm::raw_ostream &OS) const = 0;
+ virtual std::unique_ptr<Block> clone() const = 0;
std::string asMarkdown() const;
std::string asPlainText() const;
public:
void renderMarkdown(llvm::raw_ostream &OS) const override;
void renderPlainText(llvm::raw_ostream &OS) const override;
+ std::unique_ptr<Block> clone() const override;
/// Append plain text to the end of the string.
Paragraph &appendText(llvm::StringRef Text);
public:
void renderMarkdown(llvm::raw_ostream &OS) const override;
void renderPlainText(llvm::raw_ostream &OS) const override;
+ std::unique_ptr<Block> clone() const override;
class Document &addItem();
/// markdown and plaintext.
class Document {
public:
+ Document() = default;
+ Document(const Document &Other) { *this = Other; }
+ Document &operator=(const Document &);
+ Document(Document &&) = default;
+ Document &operator=(Document &&) = default;
+
/// Adds a semantical block that will be separate from others.
Paragraph &addParagraph();
/// Inserts a horizontal separator to the document.
}
bool isHardLineBreakAfter(llvm::StringRef Line, llvm::StringRef Rest) {
+ // Should we also consider whether Line is short?
return punctuationIndicatesLineBreak(Line) || isHardLineBreakIndicator(Rest);
}
};
// Try to infer structure of a documentation comment (e.g. line breaks).
+// FIXME: move to another file so CodeComplete doesn't depend on Hover.
void parseDocumentation(llvm::StringRef Input, markup::Document &Output);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const HoverInfo::Param &);
if (auto *Item = Completion->getObject("completionItem")) {
if (auto SnippetSupport = Item->getBoolean("snippetSupport"))
R.CompletionSnippets = *SnippetSupport;
+ if (auto DocumentationFormat = Item->getArray("documentationFormat")) {
+ for (const auto &Format : *DocumentationFormat) {
+ if (fromJSON(Format, R.CompletionDocumentationFormat))
+ break;
+ }
+ }
}
if (auto *ItemKind = Completion->getObject("completionItemKind")) {
if (auto *ValueSet = ItemKind->get("valueSet")) {
if (auto *Hover = TextDocument->getObject("hover")) {
if (auto *ContentFormat = Hover->getArray("contentFormat")) {
for (const auto &Format : *ContentFormat) {
- MarkupKind K = MarkupKind::PlainText;
- if (fromJSON(Format, K)) {
- R.HoverContentFormat = K;
+ if (fromJSON(Format, R.HoverContentFormat))
break;
- }
}
}
}
Result["kind"] = static_cast<int>(CI.kind);
if (!CI.detail.empty())
Result["detail"] = CI.detail;
- if (!CI.documentation.empty())
+ if (CI.documentation)
Result["documentation"] = CI.documentation;
if (!CI.sortText.empty())
Result["sortText"] = CI.sortText;
/// textDocument.completion.completionItemKind.valueSet
llvm::Optional<CompletionItemKindBitset> CompletionItemKinds;
+ /// The documentation format that should be used for textDocument/completion.
+ /// textDocument.completion.completionItem.documentationFormat
+ MarkupKind CompletionDocumentationFormat = MarkupKind::PlainText;
+
/// Client supports CodeAction return value for textDocument/codeAction.
/// textDocument.codeAction.codeActionLiteralSupport.
bool CodeActionStructure = false;
std::string detail;
/// A human-readable string that represents a doc-comment.
- std::string documentation;
+ llvm::Optional<MarkupContent> documentation;
/// A string that should be used when comparing this item with other items.
/// When `falsy` the label is used.
}
MATCHER_P(SigHelpLabeled, Label, "") { return arg.label == Label; }
MATCHER_P(Kind, K, "") { return arg.Kind == K; }
-MATCHER_P(Doc, D, "") { return arg.Documentation == D; }
+MATCHER_P(Doc, D, "") {
+ return arg.Documentation && arg.Documentation->asPlainText() == D;
+}
MATCHER_P(ReturnType, D, "") { return arg.ReturnType == D; }
MATCHER_P(HasInclude, IncludeHeader, "") {
return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader;
CompletionItemKind K) {
return Contains(AllOf(Named(std::move(Name)), Kind(K)));
}
-MATCHER(IsDocumented, "") { return !arg.Documentation.empty(); }
+MATCHER(IsDocumented, "") { return arg.Documentation.hasValue(); }
MATCHER(Deprecated, "") { return arg.Deprecated; }
std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) {
Results.Completions,
Contains(AllOf(Named("bar"), Doc("Doxygen comment.\n\\param int a"))));
EXPECT_THAT(Results.Completions,
- Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment"))));
+ Contains(AllOf(Named("baz"), Doc("Multi-line block comment"))));
}
TEST(CompletionTest, CommentsFromSystemHeaders) {
EXPECT_EQ(A.Kind, CompletionItemKind::Method);
EXPECT_EQ(A.ReturnType, "int"); // All overloads return int.
// For now we just return one of the doc strings arbitrarily.
- EXPECT_THAT(A.Documentation, AnyOf(HasSubstr("Overload with int"),
- HasSubstr("Overload with bool")));
+ ASSERT_TRUE(A.Documentation);
+ EXPECT_THAT(
+ A.Documentation->asPlainText(),
+ AnyOf(HasSubstr("Overload with int"), HasSubstr("Overload with bool")));
EXPECT_EQ(A.SnippetSuffix, "($0)");
}
C.ReturnType = "int";
C.RequiredQualifier = "Foo::";
C.Scope = "ns::Foo::";
- C.Documentation = "This is x().";
+ C.Documentation.emplace();
+ C.Documentation->addParagraph().appendText("This is ").appendCode("x()");
C.Includes.emplace_back();
auto &Include = C.Includes.back();
Include.Header = "\"foo.h\"";
EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText);
EXPECT_EQ(R.filterText, "x");
EXPECT_EQ(R.detail, "int\n\"foo.h\"");
- EXPECT_EQ(R.documentation, "This is x().");
+ EXPECT_EQ(R.documentation->value, "This is x()");
EXPECT_THAT(R.additionalTextEdits, IsEmpty());
EXPECT_EQ(R.sortText, sortText(1.0, "x"));
EXPECT_FALSE(R.deprecated);
C.Deprecated = true;
R = C.render(Opts);
EXPECT_TRUE(R.deprecated);
+
+ Opts.DocumentationFormat = MarkupKind::Markdown;
+ R = C.render(Opts);
+ EXPECT_EQ(R.documentation->value, "This is `x()`");
}
TEST(CompletionTest, IgnoreRecoveryResults) {