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 @@ -22,6 +22,10 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/PrettyPrinter.h" #include "clang/Index/IndexSymbol.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" namespace clang { @@ -200,8 +204,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(); @@ -226,11 +230,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); @@ -343,7 +347,7 @@ /// Generate a \p Hover object given the type \p T. HoverInfo getHoverContents(QualType T, const Decl *D, ASTContext &ASTCtx, - const SymbolIndex *Index) { + const SymbolIndex *Index) { HoverInfo HI; llvm::raw_string_ostream OS(HI.Name); PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); @@ -385,6 +389,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, @@ -437,34 +455,70 @@ 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)); + markup::Paragraph &P = Output.addParagraph(); + P.appendText(beautify(index::getSymbolKindString(Kind))); + if (!Name.empty()) { + P.appendCode(Name); + if (Type && !ReturnType) { + P.appendText(":"); + P.appendCode(*Type); + } + } else if (Type) { + P.appendCode(*Type); } - if (!Definition.empty()) { - Output.addCodeBlock(Definition); - } else { - // Builtin types - Output.addCodeBlock(Name); + 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); + if (NamespaceScope) { + // 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).drop_back(2); + } else if (!NamespaceScope->empty()) { + OS << "// namespace " << llvm::StringRef(*NamespaceScope).drop_back(2); + } else { + OS << "// global namespace"; + } + OS << '\n'; + } + OS << Definition; + Output.addCodeBlock(OS.str()); + } return Output; } diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -268,6 +268,90 @@ llvm_unreachable("invalid symbol kind"); } +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, SymbolKind Kind) { + switch (Kind) { + case SymbolKind::File: + OS << "File"; + break; + case SymbolKind::Module: + OS << "Module"; + break; + case SymbolKind::Namespace: + OS << "Namespace"; + break; + case SymbolKind::Package: + OS << "Package"; + break; + case SymbolKind::Class: + OS << "Class"; + break; + case SymbolKind::Method: + OS << "Method"; + break; + case SymbolKind::Property: + OS << "Property"; + break; + case SymbolKind::Field: + OS << "Field"; + break; + case SymbolKind::Constructor: + OS << "Constructor"; + break; + case SymbolKind::Enum: + OS << "Enum"; + break; + case SymbolKind::Interface: + OS << "Interface"; + break; + case SymbolKind::Function: + OS << "Function"; + break; + case SymbolKind::Variable: + OS << "Variable"; + break; + case SymbolKind::Constant: + OS << "Constant"; + break; + case SymbolKind::String: + OS << "String"; + break; + case SymbolKind::Number: + OS << "Number"; + break; + case SymbolKind::Boolean: + OS << "Boolean"; + break; + case SymbolKind::Array: + OS << "Array"; + break; + case SymbolKind::Object: + OS << "Object"; + break; + case SymbolKind::Key: + OS << "Key"; + break; + case SymbolKind::Null: + OS << "Null"; + break; + case SymbolKind::EnumMember: + OS << "EnumMember"; + break; + case SymbolKind::Struct: + OS << "Struct"; + break; + case SymbolKind::Event: + OS << "Event"; + break; + case SymbolKind::Operator: + OS << "Operator"; + break; + case SymbolKind::TypeParameter: + OS << "TypeParameter"; + break; + } + return OS; +} + bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) { const llvm::json::Object *O = Params.getAsObject(); if (!O) 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\n// global namespace\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\n// global namespace\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 @@ -1439,6 +1439,207 @@ } } +TEST(Hover, Present) { + struct { + const std::function Builder; + llvm::StringRef ExpectedRender; + } Cases[] = { + // Basic coverage tests. + { + [](HoverInfo &HI) { HI.Kind = index::SymbolKind::Unknown; }, + R"pt()pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.Name = "foo"; + }, + R"pt(Class foo)pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::NamespaceAlias; + HI.Name = "foo"; + }, + R"pt(Namespace Alias foo)pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.Type = "type"; + }, + R"pt(Class type)pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.Name = "foo"; + HI.Type = "type"; + }, + R"pt(Class foo : type)pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.Name = "foo"; + HI.Type = "type"; + HI.ReturnType = "ret_type"; + }, + R"pt(Class foo +Returns: ret_type)pt", + }, + { + [](HoverInfo &HI) { HI.Parameters.emplace(); }, + R"pt()pt", + }, + { + [](HoverInfo &HI) { + 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); + }, + R"pt( +- +- type +- type foo +- type foo = default)pt", + }, + { + [](HoverInfo &HI) { HI.Value = "value"; }, + R"pt( +Value: value)pt", + }, + { + [](HoverInfo &HI) { HI.Documentation = "doc"; }, + R"pt( +doc)pt", + }, + { + [](HoverInfo &HI) { HI.NamespaceScope = ""; }, + R"pt()pt", + }, + { + [](HoverInfo &HI) { HI.Definition = "def"; }, + R"pt( + +def)pt", + }, + { + [](HoverInfo &HI) { + HI.Definition = "def"; + HI.NamespaceScope = ""; + }, + R"pt( + +// global namespace +def)pt", + }, + { + [](HoverInfo &HI) { + HI.Definition = "def"; + HI.NamespaceScope = "ns::"; + }, + R"pt( + +// namespace ns +def)pt", + }, + { + [](HoverInfo &HI) { + HI.Definition = "def"; + HI.NamespaceScope = ""; + HI.LocalScope = "test::bar::"; + }, + R"pt( + +// In test::bar +def)pt", + }, + // Behaviour checks. + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Class; + HI.Name = "Foo"; + HI.NamespaceScope = ""; + HI.TemplateParameters = { + {std::string("typename"), std::string("T"), llvm::None}, + {std::string("typename"), std::string("C"), + std::string("bool")}, + }; + HI.Definition = + "template class Foo {}"; + }, + R"pt(Class Foo + +// global namespace +template class Foo {})pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::InstanceMethod; + HI.Name = "baz"; + HI.NamespaceScope = ""; + HI.LocalScope = "Foo::"; + HI.Parameters = { + {std::string("int"), std::string("foo"), std::string("3")}, + }; + HI.ReturnType = "int"; + HI.Definition = "constexpr int baz(int foo = 3)"; + }, + R"pt(Instance Method baz +Returns: int +- int foo = 3 + +// In Foo +constexpr int baz(int foo = 3))pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Parameter; + HI.Name = "foo"; + HI.NamespaceScope = ""; + HI.LocalScope = "Foo::baz"; + HI.Type = "int"; + HI.Value = "3"; + HI.Definition = "int foo = 3"; + }, + R"pt(Parameter foo : int +Value: 3 + +// In Foo::baz +int foo = 3)pt", + }, + { + [](HoverInfo &HI) { + HI.Kind = index::SymbolKind::Variable; + HI.Name = "x"; + HI.NamespaceScope = ""; + HI.LocalScope = "foo"; + HI.Type = "const int"; + HI.Value = "6"; + HI.Definition = "const int x = f.baz()"; + }, + R"pt(Variable x : const int +Value: 6 + +// In foo +const int x = f.baz())pt", + }, + }; + + for (const auto &C : Cases) { + HoverInfo HI; + C.Builder(HI); + EXPECT_EQ(HI.present().asPlainText(), C.ExpectedRender); + } +} + } // namespace } // namespace clangd } // namespace clang