Skip to content

[clangd] extend and rearrange doxygen hover documentation #152918

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ toCompletionItemKind(index::SymbolKind Kind,
const llvm::StringRef *Signature = nullptr) {
using SK = index::SymbolKind;
switch (Kind) {
// FIXME: for backwards compatibility, the include directive kind is treated
// the same as Unknown
case SK::IncludeDirective:
case SK::Unknown:
return CompletionItemKind::Missing;
case SK::Module:
Expand Down
8 changes: 6 additions & 2 deletions clang-tools-extra/clangd/CodeCompletionStrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
std::string Doc;

if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen &&
isa<ParmVarDecl>(Decl)) {
isa<ParmVarDecl, TemplateTypeParmDecl>(Decl)) {
// Parameters are documented in their declaration context (function or
// template function).
const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext());
Expand All @@ -135,7 +135,11 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
std::string RawDoc;
llvm::raw_string_ostream OS(RawDoc);

V.parameterDocToString(dyn_cast<ParmVarDecl>(&Decl)->getName(), OS);
if (auto *PVD = dyn_cast<ParmVarDecl>(&Decl))
V.parameterDocToString(PVD->getName(), OS);
else
V.templateTypeParmDocToString(
cast<TemplateTypeParmDecl>(&Decl)->getName(), OS);

Doc = StringRef(RawDoc).trim().str();
} else {
Expand Down
111 changes: 82 additions & 29 deletions clang-tools-extra/clangd/Hover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1272,10 +1272,10 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
HoverCountMetric.record(1, "include");
HoverInfo HI;
HI.Name = std::string(llvm::sys::path::filename(Inc.Resolved));
// FIXME: We don't have a fitting value for Kind.
HI.Definition =
URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str();
HI.DefinitionLanguage = "";
HI.Kind = index::SymbolKind::IncludeDirective;
maybeAddUsedSymbols(AST, HI, Inc);
return HI;
}
Expand Down Expand Up @@ -1481,10 +1481,6 @@ void HoverInfo::sizeToMarkupParagraph(markup::Paragraph &P) const {
}

markup::Document HoverInfo::presentDoxygen() const {
// NOTE: this function is currently almost identical to presentDefault().
// This is to have a minimal change when introducing the doxygen parser.
// This function will be changed when rearranging the output for doxygen
// parsed documentation.

markup::Document Output;
// Header contains a text of the form:
Expand All @@ -1500,45 +1496,108 @@ markup::Document HoverInfo::presentDoxygen() const {
// level 1 and 2 headers in a huge font, see
// https://github.com/microsoft/vscode/issues/88417 for details.
markup::Paragraph &Header = Output.addHeading(3);
if (Kind != index::SymbolKind::Unknown)
if (Kind != index::SymbolKind::Unknown &&
Kind != index::SymbolKind::IncludeDirective)
Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
assert(!Name.empty() && "hover triggered on a nameless symbol");

Header.appendCode(Name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW the NOTE: comment above should be removed now I think?

if (Kind == index::SymbolKind::IncludeDirective) {
Header.appendCode(Name);

if (!Definition.empty())
Output.addParagraph().appendCode(Definition);

if (!UsedSymbolNames.empty()) {
Output.addRuler();
usedSymbolNamesToMarkup(Output);
}

return Output;
}

if (!Definition.empty()) {
Output.addRuler();
definitionScopeToMarkup(Output);
} else {
Header.appendCode(Name);
}

if (!Provider.empty()) {
providerToMarkupParagraph(Output);
}

// Put a linebreak after header to increase readability.
Output.addRuler();
// Print Types on their own lines to reduce chances of getting line-wrapped by
// editor, as they might be long.
if (ReturnType) {
// For functions we display signature in a list form, e.g.:
// → `x`
// Parameters:
// - `bool param1`
// - `int param2 = 5`
Output.addParagraph().appendText("→ ").appendCode(
llvm::to_string(*ReturnType));
}

SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts);

if (SymbolDoc.hasBriefCommand()) {
SymbolDoc.briefToMarkup(Output.addParagraph());
Output.addRuler();
}

// For functions we display signature in a list form, e.g.:
// Template Parameters:
// - `typename T` - description
// Parameters:
// - `bool param1` - description
// - `int param2 = 5` - description
// Returns
// `type` - description
if (TemplateParameters && !TemplateParameters->empty()) {
Output.addParagraph().appendBoldText("Template Parameters:");
markup::BulletList &L = Output.addBulletList();
for (const auto &Param : *TemplateParameters) {
markup::Paragraph &P = L.addItem().addParagraph();
P.appendCode(llvm::to_string(Param));
if (SymbolDoc.isTemplateTypeParmDocumented(llvm::to_string(Param.Name))) {
P.appendText(" - ");
SymbolDoc.templateTypeParmDocToMarkup(llvm::to_string(Param.Name), P);
}
}
Output.addRuler();
}

if (Parameters && !Parameters->empty()) {
Output.addParagraph().appendText("Parameters:");
Output.addParagraph().appendBoldText("Parameters:");
markup::BulletList &L = Output.addBulletList();
for (const auto &Param : *Parameters) {
markup::Paragraph &P = L.addItem().addParagraph();
P.appendCode(llvm::to_string(Param));

if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) {
P.appendText(" -");
P.appendText(" - ");
SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P);
}
}
Output.addRuler();
}

// Print Types on their own lines to reduce chances of getting line-wrapped by
// editor, as they might be long.
if (ReturnType &&
((ReturnType->Type != "void" && !ReturnType->AKA.has_value()) ||
(ReturnType->AKA.has_value() && ReturnType->AKA != "void"))) {
Output.addParagraph().appendBoldText("Returns:");
markup::Paragraph &P = Output.addParagraph();
P.appendCode(llvm::to_string(*ReturnType));

if (SymbolDoc.hasReturnCommand()) {
P.appendText(" - ");
SymbolDoc.returnToMarkup(P);
}
Output.addRuler();
}

// add specially handled doxygen commands.
SymbolDoc.warningsToMarkup(Output);
SymbolDoc.notesToMarkup(Output);

// add any other documentation.
SymbolDoc.docToMarkup(Output);

Output.addRuler();

// Don't print Type after Parameters or ReturnType as this will just duplicate
// the information
if (Type && !ReturnType && !Parameters)
Expand All @@ -1559,13 +1618,6 @@ markup::Document HoverInfo::presentDoxygen() const {
calleeArgInfoToMarkupParagraph(Output.addParagraph());
}

SymbolDoc.docToMarkup(Output);

if (!Definition.empty()) {
Output.addRuler();
definitionScopeToMarkup(Output);
}

if (!UsedSymbolNames.empty()) {
Output.addRuler();
usedSymbolNamesToMarkup(Output);
Expand All @@ -1589,7 +1641,8 @@ markup::Document HoverInfo::presentDefault() const {
// level 1 and 2 headers in a huge font, see
// https://github.com/microsoft/vscode/issues/88417 for details.
markup::Paragraph &Header = Output.addHeading(3);
if (Kind != index::SymbolKind::Unknown)
if (Kind != index::SymbolKind::Unknown &&
Kind != index::SymbolKind::IncludeDirective)
Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
assert(!Name.empty() && "hover triggered on a nameless symbol");
Header.appendCode(Name);
Expand All @@ -1613,7 +1666,7 @@ markup::Document HoverInfo::presentDefault() const {
}

if (Parameters && !Parameters->empty()) {
Output.addParagraph().appendText("Parameters: ");
Output.addParagraph().appendText("Parameters:");
markup::BulletList &L = Output.addBulletList();
for (const auto &Param : *Parameters)
L.addItem().addParagraph().appendCode(llvm::to_string(Param));
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/Quality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ categorize(const index::SymbolInfo &D) {
case index::SymbolKind::Parameter:
case index::SymbolKind::NonTypeTemplateParm:
return SymbolQualitySignals::Variable;
// FIXME: for backwards compatibility, the include directive kind is treated
// the same as Unknown
case index::SymbolKind::IncludeDirective:
case index::SymbolKind::Using:
case index::SymbolKind::Module:
case index::SymbolKind::Unknown:
Expand Down
103 changes: 96 additions & 7 deletions clang-tools-extra/clangd/SymbolDocumentation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ void commandToMarkup(markup::Paragraph &Out, StringRef Command,
comments::CommandMarkerKind CommandMarker,
StringRef Args) {
Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str());
Out.appendSpace();
if (!Args.empty()) {
Out.appendSpace();
Out.appendEmphasizedText(Args.str());
}
}
Expand Down Expand Up @@ -132,7 +132,8 @@ class ParagraphToMarkupDocument
const comments::CommandTraits &Traits;

/// If true, the next leading space after a new line is trimmed.
bool LastChunkEndsWithNewline = false;
/// Initially set it to true, to always trim the first text line.
bool LastChunkEndsWithNewline = true;
};

class ParagraphToString
Expand Down Expand Up @@ -263,8 +264,76 @@ class BlockCommentToMarkupDocument
StringRef CommentEscapeMarker;
};

void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName,
markup::Paragraph &Out) {
void SymbolDocCommentVisitor::visitBlockCommandComment(
const comments::BlockCommandComment *B) {
switch (B->getCommandID()) {
case comments::CommandTraits::KCI_brief: {
if (!BriefParagraph) {
BriefParagraph = B->getParagraph();
return;
}
break;
}
case comments::CommandTraits::KCI_return:
case comments::CommandTraits::KCI_returns:
if (!ReturnParagraph) {
ReturnParagraph = B->getParagraph();
return;
}
break;
case comments::CommandTraits::KCI_retval:
RetvalParagraphs.push_back(B->getParagraph());
return;
case comments::CommandTraits::KCI_warning:
WarningParagraphs.push_back(B->getParagraph());
return;
case comments::CommandTraits::KCI_note:
NoteParagraphs.push_back(B->getParagraph());
return;
default:
break;
}

// For all other commands, we store them in the UnhandledCommands map.
// This allows us to keep the order of the comments.
UnhandledCommands[CommentPartIndex] = B;
CommentPartIndex++;
}

void SymbolDocCommentVisitor::paragraphsToMarkup(
markup::Document &Out,
const llvm::SmallVectorImpl<const comments::ParagraphComment *> &Paragraphs)
const {
if (Paragraphs.empty())
return;

for (const auto *P : Paragraphs) {
ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
}
}

void SymbolDocCommentVisitor::briefToMarkup(markup::Paragraph &Out) const {
if (!BriefParagraph)
return;
ParagraphToMarkupDocument(Out, Traits).visit(BriefParagraph);
}

void SymbolDocCommentVisitor::returnToMarkup(markup::Paragraph &Out) const {
if (!ReturnParagraph)
return;
ParagraphToMarkupDocument(Out, Traits).visit(ReturnParagraph);
}

void SymbolDocCommentVisitor::notesToMarkup(markup::Document &Out) const {
paragraphsToMarkup(Out, NoteParagraphs);
}

void SymbolDocCommentVisitor::warningsToMarkup(markup::Document &Out) const {
paragraphsToMarkup(Out, WarningParagraphs);
}

void SymbolDocCommentVisitor::parameterDocToMarkup(
StringRef ParamName, markup::Paragraph &Out) const {
if (ParamName.empty())
return;

Expand All @@ -274,7 +343,7 @@ void SymbolDocCommentVisitor::parameterDocToMarkup(StringRef ParamName,
}

void SymbolDocCommentVisitor::parameterDocToString(
StringRef ParamName, llvm::raw_string_ostream &Out) {
StringRef ParamName, llvm::raw_string_ostream &Out) const {
if (ParamName.empty())
return;

Expand All @@ -283,15 +352,35 @@ void SymbolDocCommentVisitor::parameterDocToString(
}
}

void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) {
void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) const {
for (unsigned I = 0; I < CommentPartIndex; ++I) {
if (const auto *BC = BlockCommands.lookup(I)) {
if (const auto *BC = UnhandledCommands.lookup(I)) {
BlockCommentToMarkupDocument(Out, Traits).visit(BC);
} else if (const auto *P = FreeParagraphs.lookup(I)) {
ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
}
}
}

void SymbolDocCommentVisitor::templateTypeParmDocToMarkup(
StringRef TemplateParamName, markup::Paragraph &Out) const {
if (TemplateParamName.empty())
return;

if (const auto *TP = TemplateParameters.lookup(TemplateParamName)) {
ParagraphToMarkupDocument(Out, Traits).visit(TP->getParagraph());
}
}

void SymbolDocCommentVisitor::templateTypeParmDocToString(
StringRef TemplateParamName, llvm::raw_string_ostream &Out) const {
if (TemplateParamName.empty())
return;

if (const auto *P = TemplateParameters.lookup(TemplateParamName)) {
ParagraphToString(Out, Traits).visit(P->getParagraph());
}
}

} // namespace clangd
} // namespace clang
Loading
Loading