diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -25,8 +25,9 @@ #include "clang/AST/PrettyPrinter.h" #include "clang/Index/IndexSymbol.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/iterator_range.h" -#include "llvm/Support/Casting.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" namespace clang { @@ -224,8 +225,8 @@ // Populates Type, ReturnType, and Parameters for function-like decls. void fillFunctionTypeAndParams(HoverInfo &HI, const Decl *D, - const FunctionDecl *FD, - const PrintingPolicy &Policy) { + const FunctionDecl *FD, + const PrintingPolicy &Policy) { HI.Parameters.emplace(); for (const ParmVarDecl *PVD : FD->parameters()) { HI.Parameters->emplace_back(); @@ -250,11 +251,11 @@ } } - if (const auto* CCD = llvm::dyn_cast(FD)) { + if (const auto *CCD = llvm::dyn_cast(FD)) { // Constructor's "return type" is the class type. HI.ReturnType = declaredType(CCD->getParent()).getAsString(Policy); // Don't provide any type for the constructor itself. - } else if (llvm::isa(FD)){ + } else if (llvm::isa(FD)) { HI.ReturnType = "void"; } else { HI.ReturnType = FD->getReturnType().getAsString(Policy); @@ -414,6 +415,20 @@ } return HI; } + +// Converts a string of the form `word1-word2-...` into `Word1 Word2 ...`. +std::string beautify(llvm::StringRef Input) { + std::string Res; + llvm::SmallVector Out; + llvm::SplitString(Input, Out, "-"); + llvm::for_each(Out, [&Res](llvm::StringRef Word) { + if (!Res.empty()) + Res += ' '; + Res += llvm::toUpper(Word.front()); + Res.append(Word.drop_front()); + }); + return Res; +} } // namespace llvm::Optional getHover(ParsedAST &AST, Position Pos, @@ -460,34 +475,73 @@ tooling::applyAllReplacements(HI->Definition, Replacements)) HI->Definition = *Formatted; - HI->SymRange = getTokenRange(AST.getSourceManager(), - AST.getLangOpts(), SourceLocationBeg); + HI->SymRange = getTokenRange(AST.getSourceManager(), AST.getLangOpts(), + SourceLocationBeg); return HI; } markup::Document HoverInfo::present() const { markup::Document Output; - if (NamespaceScope) { - auto &P = Output.addParagraph(); - P.appendText("Declared in"); - // Drop trailing "::". - if (!LocalScope.empty()) - P.appendCode(llvm::StringRef(LocalScope).drop_back(2)); - else if (NamespaceScope->empty()) - P.appendCode("global namespace"); - else - P.appendCode(llvm::StringRef(*NamespaceScope).drop_back(2)); + // Header contains a text of the form: + // Variable `var` : `int` + // or + // `class X` + markup::Paragraph &Header = Output.addParagraph(); + Header.appendText(beautify(index::getSymbolKindString(Kind))); + if (!Name.empty()) { + Header.appendCode(Name); + if (Type && !ReturnType) { + Header.appendText(":"); + Header.appendCode(*Type); + } } - if (!Definition.empty()) { - Output.addCodeBlock(Definition); - } else { - // Builtin types - Output.addCodeBlock(Name); + // For functions we display signature in a list form, e.g.: + // Returns: `int` + // - `bool param1` + // - `int param2 = 5` + if (ReturnType) { + markup::Paragraph &P = Output.addParagraph(); + P.appendText("Returns: "); + P.appendCode(*ReturnType); + } + if (Parameters && !Parameters->empty()) { + markup::BulletList &L = Output.addBulletList(); + for (const auto &Param : *Parameters) { + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + OS << Param; + L.addItem().addParagraph().appendCode(std::move(OS.str())); + } + } + + if (Value) { + markup::Paragraph &P = Output.addParagraph(); + P.appendText("Value: "); + P.appendCode(*Value); } if (!Documentation.empty()) Output.addParagraph().appendText(Documentation); + + if (!Definition.empty()) { + std::string Code; + llvm::raw_string_ostream OS(Code); + // Drop trailing "::". + if (!LocalScope.empty()) { + // Container name, e.g. class, method, function. + // We might want to propogate some info about container type to print + // function foo, class X, method X::bar, etc. + OS << "// In " << llvm::StringRef(LocalScope).rtrim(':') << '\n'; + } else if (NamespaceScope && !NamespaceScope->empty()) { + OS << "// In namespace " << llvm::StringRef(*NamespaceScope).rtrim(':') + << '\n'; + } + // Note that we don't print anything for global namespace, to not annoy + // non-c++ projects or projects that are not making use of namespaces. + OS << Definition; + Output.addCodeBlock(OS.str()); + } return Output; } diff --git a/clang-tools-extra/clangd/test/hover.test b/clang-tools-extra/clangd/test/hover.test --- a/clang-tools-extra/clangd/test/hover.test +++ b/clang-tools-extra/clangd/test/hover.test @@ -9,7 +9,7 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "contents": { # CHECK-NEXT: "kind": "plaintext", -# CHECK-NEXT: "value": "Declared in global namespace\n\nvoid foo()" +# CHECK-NEXT: "value": "Function foo\nReturns: void\n\nvoid foo()" # CHECK-NEXT: }, # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { @@ -37,7 +37,7 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "contents": { # CHECK-NEXT: "kind": "plaintext", -# CHECK-NEXT: "value": "Declared in global namespace\n\nenum foo {}" +# CHECK-NEXT: "value": "Enum foo\n\nenum foo {}" # CHECK-NEXT: }, # CHECK-NEXT: "range": { # CHECK-NEXT: "end": { diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -1606,6 +1606,100 @@ } } } +TEST(Hover, Present) { + struct { + const std::function Builder; + llvm::StringRef ExpectedRender; + } Cases[] = { + { + [](HoverInfo &HI) { HI.Kind = index::SymbolKind::Unknown; }, + R"()", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.Name = "foo"; + }, + R"(Class foo)", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::NamespaceAlias; + HI.Name = "foo"; + }, + R"(Namespace Alias foo)", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.TemplateParameters = { + {std::string("typename"), std::string("T"), llvm::None}, + {std::string("typename"), std::string("C"), + std::string("bool")}, + }; + HI.Documentation = "documentation"; + HI.Definition = + "template class Foo {}"; + HI.Name = "foo"; + HI.NamespaceScope.emplace(); + }, + R"(Class foo +documentation + +template class Foo {})", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Function; + HI.Name = "foo"; + HI.Type = "type"; + HI.ReturnType = "ret_type"; + HI.Parameters.emplace(); + HoverInfo::Param P; + HI.Parameters->push_back(P); + P.Type = "type"; + HI.Parameters->push_back(P); + P.Name = "foo"; + HI.Parameters->push_back(P); + P.Default = "default"; + HI.Parameters->push_back(P); + HI.NamespaceScope = "ns::"; + HI.Definition = "ret_type foo(params) {}"; + }, + R"(Function foo +Returns: ret_type +- +- type +- type foo +- type foo = default + +// In namespace ns +ret_type foo(params) {})", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Variable; + HI.LocalScope = "test::bar::"; + HI.Value = "value"; + HI.Name = "foo"; + HI.Type = "type"; + HI.Definition = "def"; + }, + R"(Variable foo : type +Value: value + +// In test::bar +def)", + }, + }; + + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + EXPECT_EQ(HI.present().asPlainText(), C.ExpectedRender); + } +} + } // namespace } // namespace clangd } // namespace clang