diff --git a/include/Feature/Hover.h b/include/Feature/Hover.h index 911f62d9..a3e644af 100644 --- a/include/Feature/Hover.h +++ b/include/Feature/Hover.h @@ -1,95 +1,89 @@ #pragma once -#include "Basic/Document.h" #include "AST/SymbolKind.h" +#include "Basic/SourceCode.h" +#include "Index/Shared.h" namespace clice { class ASTInfo; -namespace proto { - -// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#hoverParams -struct HoverParams { - // The text document. - URI textDocument; - - // The position inside the text document. - Position position; -}; - -} // namespace proto - namespace config { -/// For a full memory layout infomation, the render kind decides how to display the value. By -/// default, show both decimal and hexadecimal. e.g: -/// size = 4 (0x4), align = 4 (0x4), offset: 0 (0x0) -/// while show decimal only: -/// size = 4, align = 4, offset: 0 -/// while show hexadecimal only: -/// size = 0x4, align = 0x4, offset: 0x0 -/// -/// And bit field is always displayed in decimal. -/// size = 1 bit (+5 bits padding), align = 1 byte, offset: 4 byte + 2 bit -enum class MemoryLayoutRenderKind : uint8_t { - Both = 0, - Decimal, - Hexadecimal, -}; - -struct HoverOption { - /// The maximum number of fields to show in the hover of a class/struct/enum. 0 means show all. - uint16_t maxFieldsCount = 0; - - /// TODO: - /// The maximum number of derived classes to show in the hover of a pure virtual class. - // uint16_t maxDerivedClassNum; - - /// Decide how to render the memory layout. - MemoryLayoutRenderKind memoryLayoutRenderKind = MemoryLayoutRenderKind::Both; - - /// Show associated document. - bool documentation : 1 = true; - - /// TODO: - /// Show overloaded virtual method for class/struct. - bool overloadVirtualMethod : 1 = true; - - /// Show documentation link for key words, this will link to corresponding page of - /// `https://en.cppreference.com/w/cpp/keyword/`. - bool keywords : 1 = true; - - /// TODO: - /// Show links instead of codeblock in hover information for mentioned symbols. - bool useLink : 1 = true; -}; +struct HoverOptions {}; } // namespace config -namespace feature::hover { +namespace feature { -/// TODO: -/// Implement the action for hovering over elements. -// struct HoverAction { -// // Goto type -// // Find reference -// }; +struct HoverItem { + enum class HoverKind : uint8_t { + /// The typename of a variable or a type alias. + Type, + /// Size of type or variable. + Size, + /// Align of type or variable. + Align, + /// Offset of field in a class/struct. + Offset, + /// Bit width of a bit field. + BitWidth, + /// The index of a field in a class/struct. + FieldIndex, + /// The value of an enum item. + EnumValue, + }; -struct Result { - std::string markdown; + using enum HoverKind; + + HoverKind kind; + + std::string value; }; -/// Get the hover information of a declaration with given option. -Result hover(const clang::Decl* decl, const config::HoverOption& option); +/// Hover information for a symbol. +struct Hover { + /// Title + SymbolKind kind; + std::string name; -/// Compute inlay hints for MainfileID in given param and config. -std::optional hover(const proto::HoverParams& param, - ASTInfo& AST, - const config::HoverOption& option); + /// Extra information. + std::vector items; -proto::MarkupContent toLspType(Result hover); + /// Raw document in the source code. + std::string document; -} // namespace feature::hover + /// The full qualified name of the declaration. + std::string qualifier; + + /// The source code of the declaration. + std::string source; +}; + +/// Hover information for all symbols in the file. +struct Hovers { + struct Occurrence { + LocalSourceRange range; + uint32_t index; + }; + + /// Hover information for all symbols in the file. + std::vector hovers; + + /// A map between the file offset and the index of the hover. + std::vector occurrences; +}; + +/// Generate the hover information for the given declaration(for test). +Hover hover(ASTInfo& AST, const clang::NamedDecl* decl); + +/// Generate the hover information for the symbol at the given offset. +Hover hover(ASTInfo& AST, uint32_t offset); + +/// Generate the hover information for all files in the given AST. +index::Shared indexHover(ASTInfo& AST); + +} // namespace feature } // namespace clice + diff --git a/include/Server/LSPConverter.h b/include/Server/LSPConverter.h index 664c7b61..4a9604bc 100644 --- a/include/Server/LSPConverter.h +++ b/include/Server/LSPConverter.h @@ -17,6 +17,8 @@ public: Result convert(llvm::StringRef path, llvm::ArrayRef tokens); + Result convert(const feature::Hover& hover); + private: proto::PositionEncodingKind kind; }; diff --git a/src/Feature/Hover.cpp b/src/Feature/Hover.cpp index 99ec43fb..b318f5cf 100644 --- a/src/Feature/Hover.cpp +++ b/src/Feature/Hover.cpp @@ -1,1504 +1,155 @@ +#include "AST/Selection.h" +#include "AST/Semantic.h" +#include "AST/Utility.h" +#include "Compiler/AST.h" +#include "Index/Shared.h" +#include "Support/Compare.h" +#include "Support/Ranges.h" #include "Feature/Hover.h" -#include "AST/Selection.h" -#include "Compiler/AST.h" -#include "Support/FileSystem.h" - -#include "clang/Lex/LiteralSupport.h" -#include "clang/AST/DeclVisitor.h" - -#ifdef _WIN32 -#include -#include -#endif - -template -struct Match : Ts... { - using Ts::operator()...; -}; - -namespace clice { +namespace clice::feature { namespace { -struct HoverBase { - /// The kind of the symbol. - SymbolKind symbol; +std::vector getHoverItems(ASTInfo& AST, const clang::NamedDecl* decl) { + clang::ASTContext& Ctx = AST.context(); + std::vector items; - /// The name of given item (declaration or variable name or something else). - std::string name; - - /// The text of source code of given declaration. - std::string source; - - size_t estimated_size() const { - return name.size() + source.size(); - } -}; - -struct WithDocument { - std::string document; - - size_t estimated_size() const { - return document.size(); - } -}; - -struct WithScope { - /// For class/struct/enum in some namespace, like "NamespaceA::NamespaceA::", maybe empty. - std::string namespac; - - /// For nested class, like "StructA::StructB::", maybe empty. - std::string local; - - size_t estimated_size() const { - return namespac.size() + local.size(); - } -}; - -struct Namespace : HoverBase, WithScope { - size_t estimated_size() const { - return HoverBase::estimated_size() + WithScope::estimated_size(); - } -}; - -/// Memory layout information of a class/struct/field. -struct MemoryLayout { - - bool isBitField = false; - - // bits if isBitField is true, otherwise in bytes - uint8_t padding; - - // bits if isBitField is true, otherwise in bytes - uint16_t size; - - // bits if isBitField is true, otherwise in bytes - uint16_t offset; - - // always in bytes - uint16_t align; -}; - -struct PrettyType { - /// The type of the item in a more human-readable format. - std::string type; - - /// Desugared type of the item. (aka `SomeDesugaredType`) - std::string akaType; - - size_t estimated_size() const { - return type.size() + akaType.size(); - } -}; - -struct Field : WithDocument, WithScope, MemoryLayout { - - PrettyType type; - - std::string name; - - clang::AccessSpecifier access; - - size_t estimated_size() const { - return WithDocument::estimated_size() + WithScope::estimated_size() + sizeof(MemoryLayout) + - type.estimated_size() + name.size(); - } -}; - -template -size_t estimated_size(const std::vector& xs) { - return std::ranges::fold_left(xs, 0, [](size_t acc, const T& x) { - return acc + x.estimated_size(); - }); -} - -struct Enum : HoverBase, WithDocument, WithScope { - - struct EnumItem { - std::string name; - std::string value; - - size_t estimated_size() const { - return name.size() + value.size(); - } + auto addItem = [&items](HoverItem::HoverKind kind, uint32_t value) { + items.emplace_back(kind, llvm::Twine(value).str()); }; - /// underlying type of the enum, e.g. "int", "unsigned int", "long long" - std::string implType; - - std::vector items; - - bool isScoped; - - size_t estimated_size() const { - return HoverBase::estimated_size() + WithDocument::estimated_size() + - WithScope::estimated_size() + implType.size() + estimated_size(items); - } -}; - -/// TODO: macro parameters ? -/// Represents parameters of a function, a template or a macro. -/// For example: -/// - void foo(ParamType Name = DefaultValue) -/// - #define FOO(Name) -/// - template class Foo {}; -struct Param { - /// In case the template parameter, kind is `Type` parameter. - SymbolKind kind; - - /// The printable parameter type, e.g. "int", or "typename" (in - /// TemplateParameters) - PrettyType type; - - /// Empty if is unnamed parameters. - std::string name; - - /// Empty if no default is provided. - std::string defaultParaName; - - size_t estimated_size() const { - return type.estimated_size() + name.size() + defaultParaName.size(); - } -}; - -struct Record : HoverBase, WithDocument, WithScope, MemoryLayout { - std::vector fields; - - std::vector templateParams; - - size_t estimated_size() const { - return HoverBase::estimated_size() + WithDocument::estimated_size() + - WithScope::estimated_size() + estimated_size(fields) + - estimated_size(templateParams); - } -}; - -struct Fn : HoverBase, WithDocument, WithScope { - PrettyType retType; - - std::vector params; - - std::vector templateParams; - - bool isConstexpr = false; - bool isConsteval = false; - bool isInlined = false; - bool isStatic = false; - bool isPureVirtual = false; - bool isOverloadedOperator = false; - bool isExternallyVisible = false; - bool isDeprecated = false; - - /// Non empty if isDeprecated is true. - std::string deprecateReason; - - size_t estimated_size() const { - return HoverBase::estimated_size() + WithDocument::estimated_size() + - WithScope::estimated_size() + retType.estimated_size() + - estimated_size(params) + estimated_size(templateParams); - } -}; - -struct Var : HoverBase, WithDocument, WithScope { - PrettyType type; - - uint16_t size; - uint16_t align; - - bool isConstexpr = false; - bool isFileScope = false; - bool isLocal = false; - bool isStaticLocal = false; - bool isExtern = false; - bool isDeprecated = false; - - /// Non empty if the variable has a constexpr evaluated value.; - // std::string value; - - // Non empty if isDeprecated is true. - std::string deprecateReason; - - size_t estimated_size() const { - return HoverBase::estimated_size() + WithDocument::estimated_size() + - WithScope::estimated_size() + type.estimated_size(); - } -}; - -/// Include directive. -struct Header : HoverBase { - - std::string absPath; - - /// TODO: symbols provided by the header - /// std::vector provides; - - size_t estimated_size() const { - return HoverBase::estimated_size() + absPath.size(); - } -}; - -struct Numeric { - SymbolKind symbol = SymbolKind::Number; - - llvm::StringRef rawText; - - std::variant value; - - size_t estimated_size() const { - return 16; - } -}; - -struct Keyword { - SymbolKind symbol = SymbolKind::Keyword; - - clang::tok::TokenKind tkkind; - - llvm::StringRef spelling; - - constexpr static std::string_view CppRefKeywordBaseUrl = - "https://en.cppreference.com/w/cpp/keyword/"; - - static std::string cpprefLink(clang::tok::TokenKind keyword) { - std::string url{CppRefKeywordBaseUrl}; - url += clang::tok::getTokenName(keyword); - return url; - } - - size_t estimated_size() const { - return 64; - } -}; - -struct Literal { - SymbolKind symbol = SymbolKind::String; - - clang::tok::TokenKind kind; - - std::string content; - - /// User-defined suffix of the literal, e.g. "_s" in "123"_s, maybe empty. - std::string udSuffix; - - size_t estimated_size() const { - return content.size() + 32; - } - - static std::string_view stringLiteralKindName(clang::tok::TokenKind kind) { - switch(kind) { - case clang::tok::char_constant: return "char_constant"; - case clang::tok::string_literal: return "string_literal"; - case clang::tok::wide_string_literal: return "wide_string_literal"; - case clang::tok::utf8_string_literal: return "utf8_string_literal"; - case clang::tok::utf16_string_literal: return "utf16_string_literal"; - case clang::tok::utf32_string_literal: return "utf32_string_literal"; - // case clang::tok::binary_data: return "binary_data"; - default: return "unknown"; + /// FIXME: Add other hover items. + if(auto FD = llvm::dyn_cast(decl)) { + addItem(HoverItem::FieldIndex, FD->getFieldIndex()); + addItem(HoverItem::Offset, Ctx.getFieldOffset(FD)); + addItem(HoverItem::Size, Ctx.getTypeSizeInChars(FD->getType()).getQuantity()); + addItem(HoverItem::Align, Ctx.getTypeAlignInChars(FD->getType()).getQuantity()); + if(FD->isBitField()) { + /// FIXME: + /// addItem(HoverItem::BitWidth, FD->getBitWidthValue()); } } -}; -struct Expression : HoverBase { + return items; +} - PrettyType type; +std::string getDocument(ASTInfo& AST, const clang::NamedDecl* decl) { + clang::ASTContext& Ctx = AST.context(); + const clang::RawComment* comment = Ctx.getRawCommentForAnyRedecl(decl); + if(!comment) { + return ""; + } - /// TODO: - std::string evaluated; + return comment->getRawText(Ctx.getSourceManager()).str(); +} - size_t estimated_size() const { - return HoverBase::estimated_size() + evaluated.size() + type.estimated_size(); +std::string getQualifier(ASTInfo& AST, const clang::NamedDecl* decl) { + std::string result; + llvm::raw_string_ostream os(result); + decl->printNestedNameSpecifier(os); + return result; +} + +std::string getSourceCode(ASTInfo& AST, const clang::NamedDecl* decl) { + clang::SourceRange range = decl->getSourceRange(); + auto& TB = AST.tokBuf(); + auto& SM = AST.srcMgr(); + auto tokens = TB.expandedTokens(range); + /// FIXME: How to cut off the tokens? + return ""; +} + +struct HoversStorage : Hovers { + llvm::DenseMap cache; + + void add(ASTInfo& AST, const clang::NamedDecl* decl, LocalSourceRange range) { + auto [iter, success] = cache.try_emplace(decl, hovers.size()); + if(success) { + hovers.emplace_back(hover(AST, decl)); + } + occurrences.emplace_back(range, iter->second); + } + + void sort() { + std::vector hoverMap(hovers.size()); + + { + std::vector new2old(hovers.size()); + for(uint32_t i = 0; i < hovers.size(); ++i) { + new2old[i] = i; + } + + ranges::sort(views::zip(hovers, new2old), refl::less, [](const auto& element) { + return std::get<0>(element); + }); + + for(uint32_t i = 0; i < hovers.size(); ++i) { + hoverMap[new2old[i]] = i; + } + } + + for(auto& occurrence: occurrences) { + occurrence.index = hoverMap[occurrence.index]; + } + + ranges::sort(occurrences, refl::less, [](const auto& item) { return item.range; }); } }; -/// Make HoverInfo default constructible. -struct Empty : std::monostate { - size_t estimated_size() const { - return 0; - } -}; +/// For index all hover information in the given AST. +class HoverCollector : public SemanticVisitor { +public: + HoverCollector(ASTInfo& AST) : SemanticVisitor(AST, false) {} -/// Use variant to store different types of hover information to reduce the memory usage. -struct HoverInfo : - public std::variant { - /// Return the estimated size of the hover information in bytes. - size_t estimated_size() const { - auto size_counter = Match{ - [](const auto& x) { return x.estimated_size(); }, - }; - return std::visit(size_counter, *this); - } -}; - -struct DeclHoverBuilder : public clang::ConstDeclVisitor { - using Base = clang::ConstDeclVisitor; - - const config::HoverOption option; - - /// State used to build bitfield memorylayout information. - uint32_t continousBitFieldBits = 0; - - HoverInfo hover; - - static void recFillScope(const clang::DeclContext* DC, WithScope& scope) { - if(DC->isTranslationUnit()) + void handleDeclOccurrence(const clang::NamedDecl* decl, + RelationKind kind, + clang::SourceLocation location) { + /// FIXME: Currently we only handle file loca1tion. + if(location.isMacroID()) { return; - - if(auto ND = llvm::dyn_cast(DC)) { - auto name = ND->getName(); - if(ND->isAnonymousNamespace()) { - recFillScope(ND->getDeclContext(), scope); - scope.namespac += "(anonymous)"; - scope.namespac += name; - } else if(ND->isInline()) { - /// FIXME: Should we add "(inline)" to the namespace name ? - recFillScope(ND->getDeclContext(), scope); - scope.namespac += "(inline)"; - scope.namespac = name; - } else { - recFillScope(ND->getDeclContext(), scope); - scope.namespac += name; - } - scope.namespac += "::"; - } else if(auto RD = llvm::dyn_cast(DC)) { - recFillScope(RD->getDeclContext(), scope); - scope.local += RD->getName(), scope.local += "::"; - } else if(auto FD = llvm::dyn_cast(DC)) { - recFillScope(FD->getDeclContext(), scope); - scope.local += FD->getNameAsString(), scope.local += "::"; - } - } - - static void fillScope(const clang::DeclContext* DC, WithScope& scope) { - recFillScope(DC, scope); - - for(auto& lens: {std::ref(scope.local), std::ref(scope.namespac)}) { - // remove tailing "::" - if(auto& ref = lens.get(); ref.ends_with("::")) - ref.pop_back(), ref.pop_back(); - } - } - - void VisitNamespaceDecl(const clang::NamespaceDecl* ND) { - Namespace ns; - ns.symbol = SymbolKind::Namespace; - fillScope(ND, ns); - - ns.source = ""; - hover.emplace(std::move(ns)); - } - - void fillMemoryLayout(const clang::RecordDecl* FD, MemoryLayout& lay) { - clang::ASTContext& ctx = FD->getASTContext(); - clang::QualType QT = ctx.getRecordType(FD); - - lay.align = ctx.getTypeAlignInChars(QT).getQuantity(); - lay.size = ctx.getTypeSizeInChars(QT).getQuantity(); - } - - void fillRecord(const clang::RecordDecl* RD, Record& rc) { - rc.symbol = RD->isStruct() ? SymbolKind::Struct : SymbolKind::Class; - rc.name = RD->getNameAsString(); - - fillMemoryLayout(RD, rc); - fillScope(RD->getDeclContext(), rc); - - rc.source = ""; - rc.document = ""; - - for(auto FD: RD->fields()) { - Field fd; - fillField(FD, fd); - rc.fields.push_back(std::move(fd)); - } - } - - void VisitRecordDecl(const clang::RecordDecl* RD) { - Record rc; - fillRecord(RD, rc); - - hover.emplace(std::move(rc)); - } - - void VisitClassTemplateDecl(const clang::ClassTemplateDecl* TD) { - Record rc; - for(const auto& tpara: *TD->getTemplateParameters()) { - Param p; - p.name = tpara->getName(); - rc.templateParams.push_back(std::move(p)); } - fillRecord(TD->getTemplatedDecl(), rc); - hover.emplace(std::move(rc)); - VisitRecordDecl(llvm::dyn_cast(TD->getTemplatedDecl())); + decl = normalize(decl); + + auto [fid, range] = AST.toLocalRange(location); + auto& file = files[fid]; + file.add(AST, decl, range); } - void VisitClassTemplateSpecializationDecl(const clang::ClassTemplateSpecializationDecl* TD) { - VisitClassTemplateDecl(TD->getSpecializedTemplate()); - } + auto build() { + index::Shared hovers; - void fillMemoryLayout(const clang::FieldDecl* FD, - const clang::RecordDecl* RD, - MemoryLayout& lay) { - clang::QualType QT = FD->getType(); - clang::ASTContext& ctx = RD->getASTContext(); + run(); - lay.isBitField = FD->isBitField(); - - auto bitsOffset = ctx.getFieldOffset(FD); - lay.offset = lay.isBitField ? bitsOffset : bitsOffset / 8; - lay.align = ctx.getTypeAlignInChars(QT).getQuantity(); - - if(lay.isBitField) { - /// lay.size = FD->getBitWidthValue(ctx); - continousBitFieldBits += lay.size; - if(continousBitFieldBits > 8) { - lay.padding = lay.align - continousBitFieldBits % lay.align; - } - } else { - lay.size = ctx.getTypeSizeInChars(QT).getQuantity(); - continousBitFieldBits = 0; - lay.padding = lay.align - (lay.offset % lay.align) % lay.align; - } - } - - void fillField(const clang::FieldDecl* FD, Field& field) { - field.access = FD->getAccess(); - field.name = FD->getNameAsString(); - - auto RD = llvm::dyn_cast(FD->getDeclContext()); - fillScope(RD, field); - fillMemoryLayout(FD, RD, field); - - auto ty = FD->getType(); - auto canonicalTy = ty.getCanonicalType(); // ignore typedef - - if(ty == canonicalTy) { - field.type.type = ty.getAsString(); - } else { - field.type.type = ty.getAsString(); - field.type.akaType = canonicalTy.getAsString(); - } - } - - void VisitFieldDecl(const clang::FieldDecl* FD) { - Field field; - fillField(FD, field); - hover.emplace(std::move(field)); - } - - void VisitEnumDecl(const clang::EnumDecl* ED) { - using RK = enum config::MemoryLayoutRenderKind; - - Enum enm; - enm.symbol = SymbolKind::Enum; - enm.name = ED->getName(); - enm.isScoped = ED->isScoped(); - enm.implType = ED->getIntegerType().getAsString(); - fillScope(ED->getDeclContext(), enm); - - enm.source = ""; - enm.document = ""; - - for(const auto ECD: ED->enumerators()) { - llvm::SmallString<64> buffer; - - auto val = ECD->getInitVal(); - auto kind = option.memoryLayoutRenderKind; - if(kind == RK::Both || kind == RK::Decimal) { - val.toString(buffer, 10); - } - - if(kind == RK::Both || kind == RK::Hexadecimal) { - bool parent = !buffer.empty(); - if(parent) - buffer += " ("; - buffer += "0x"; - val.toString(buffer, 16); - if(parent) - buffer += ")"; - } - - enm.items.push_back(Enum::EnumItem{ - .name = ECD->getName().str(), - .value = buffer.str().str(), - }); + for(auto& [fid, storage]: files) { + storage.sort(); + hovers[fid] = std::move(static_cast(storage)); } - hover.emplace(std::move(enm)); + return hovers; } - static void fillFunction(const clang::FunctionDecl* FD, Fn& fn) { - if(fn.symbol != SymbolKind::Method && fn.symbol != SymbolKind::Operator) { - fn.symbol = SymbolKind::Function; - } - - fn.name = FD->getNameAsString(); - fn.isConstexpr = FD->isConstexpr(); - fn.isConsteval = FD->isConsteval(); - fn.isInlined = FD->isInlined(); - fn.isStatic = FD->isStatic(); - fn.isOverloadedOperator = FD->isOverloadedOperator(); - fn.isPureVirtual = FD->isPureVirtual(); - fn.isExternallyVisible = FD->isExternallyVisible(); - fn.isDeprecated = FD->isDeprecated(&fn.deprecateReason); - - fillScope(FD->getDeclContext(), fn); - - auto retType = FD->getReturnType(); - auto canonicalRetType = retType.getCanonicalType(); - if(retType == canonicalRetType) { - fn.retType.type = retType.getAsString(); - } else { - fn.retType.type = retType.getAsString(); - fn.retType.akaType = canonicalRetType.getAsString(); - } - - for(auto param: FD->parameters()) { - Param p; - p.name = param->getName(); - - auto qty = param->getType(); - p.type.type = qty.getAsString(); - if(auto cqty = qty.getCanonicalType(); cqty != qty) { - p.type.akaType = cqty.getAsString(); - } - - fn.params.push_back(std::move(p)); - } - - fn.source = ""; - fn.document = ""; - } - - void VisitFunctionDecl(const clang::FunctionDecl* FD) { - Fn fn; - fillFunction(FD, fn); - hover.emplace(std::move(fn)); - } - - void VisitFunctionTemplateDecl(const clang::FunctionTemplateDecl* TD) { - Fn fn; - - for(auto tpara: *TD->getTemplateParameters()) { - Param p; - p.kind = SymbolKind::Type; - p.name = tpara->getName(); - fn.templateParams.push_back(std::move(p)); - } - - fillFunction(TD->getAsFunction(), fn); - hover.emplace(std::move(fn)); - } - - void VisitTemplateTypeParmDecl(const clang::TemplateTypeParmDecl* TTPD) { - Var var; - - var.symbol = SymbolKind::Type; - var.name = TTPD->getName(); - if(var.name.empty()) - var.name = "(unnamed)"; - - fillScope(TTPD->getDeclContext(), var); - var.type.type = TTPD->getNameAsString(); - - hover.emplace(std::move(var)); - } - - void VisitNonTypeTemplateParmDecl(const clang::NonTypeTemplateParmDecl* NTPD) { - Var var; - - var.symbol = SymbolKind::Parameter; - var.name = NTPD->getName(); - if(var.name.empty()) - var.name = "(unnamed)"; - - fillScope(NTPD->getDeclContext(), var); - - auto qty = NTPD->getType(); - var.type.type = qty.getAsString(); - if(auto cqty = qty.getCanonicalType().getAsString(); cqty != var.type.type) { - var.type.akaType = std::move(cqty); - } - - var.document = ""; - var.source = ""; - - hover.emplace(std::move(var)); - } - - void VisitTemplateTemplateParmDecl(const clang::TemplateTemplateParmDecl* TTPD) { - Var var; - - var.symbol = SymbolKind::Type; - var.name = TTPD->getName(); - if(var.name.empty()) - var.name = "(unnamed)"; - - fillScope(TTPD->getDeclContext(), var); - var.type.type = TTPD->getNameAsString(); - - hover.emplace(std::move(var)); - } - - void VisitCXXMethodDecl(const clang::CXXMethodDecl* MD) { - Fn fn; - fn.symbol = SymbolKind::Method; - fillFunction(MD, fn); - hover.emplace(std::move(fn)); - } - - static void fillVarInfo(const clang::VarDecl* VD, Var& var) { - var.name = VD->getName(); - if(var.name.empty()) - var.name = "(unnamed)"; - - var.isConstexpr = VD->isConstexpr(); - var.isFileScope = VD->isFileVarDecl(); - var.isLocal = VD->isLocalVarDecl(); - var.isStaticLocal = VD->isStaticLocal(); - var.isExtern = VD->isLocalExternDecl(); - var.isDeprecated = VD->isDeprecated(&var.deprecateReason); - if(!var.isDeprecated && var.name.starts_with("_")) { - var.isDeprecated = true; - var.deprecateReason = "Manually marked as throwaway variable"; - } - - fillScope(VD->getDeclContext(), var); - - auto qty = VD->getType(); - var.type.type = qty.getAsString(); - if(auto cqty = qty.getCanonicalType().getAsString(); cqty != var.type.type) { - var.type.akaType = std::move(cqty); - } - - if(!qty->isDependentType()) { - auto& ctx = VD->getASTContext(); - var.size = ctx.getTypeSizeInChars(qty).getQuantity(); - var.align = ctx.getTypeAlignInChars(qty).getQuantity(); - } - - var.document = ""; - var.source = ""; - } - - void VisitVarDecl(const clang::VarDecl* VD) { - Var var; - var.symbol = SymbolKind::Variable; - fillVarInfo(VD, var); - hover.emplace(std::move(var)); - } - - void VisitParmVarDecl(const clang::ParmVarDecl* PVD) { - Var var; - var.symbol = SymbolKind::Parameter; - fillVarInfo(PVD, var); - hover.emplace(std::move(var)); - } - - static HoverInfo build(const clang::Decl* decl, const config::HoverOption& option) { - assert(decl && "Must be non-null pointer"); - DeclHoverBuilder builder{.option = option}; - builder.Visit(decl); - return std::move(builder.hover); - } -}; - -using Token = clang::syntax::Token; -using namespace clang::tok; - -llvm::SmallVector pickBestToken(llvm::ArrayRef& touching) { - constexpr auto ranker = [](const Token& tk) -> uint32_t { - auto kind = tk.kind(); - if(isAnyIdentifier(kind)) - return 10; - - if(llvm::is_contained({kw_auto, kw_decltype}, kind)) - return 9; - - if(isStringLiteral(kind)) - return 7; - - auto prefix_ops = { - l_square, - r_square, - star, - minus, - exclaim, - numeric_constant, - clang::tok::pipe, - }; - if(llvm::is_contained(prefix_ops, kind)) - return 6; - - // keyword or function call. - if(getKeywordSpelling(kind) || llvm::is_contained({l_paren, r_paren}, kind)) - return 5; - - if(getPunctuatorSpelling(kind)) - return 0; - - return 1; - }; - - llvm::SmallVector ranked{touching}; - std::ranges::sort(ranked, [ranker](const Token& lhs, const Token& rhs) { - return ranker(lhs) > ranker(rhs); - }); - return ranked; -} - -struct ExprHoverBuilder { - - using Node = SelectionTree::Node; - - template - struct Accept : - public std::variant>...> { - - using Cases = std::variant>...>; - - const Node* deepest = nullptr; - - template - void accept(const Node* node) { - if(auto ptr = node->dynNode.get

()) { - this->template emplace>>(ptr); - deepest = node; - } - } - - bool accept(const Node* node) { - // Return true to get the deepest node. - return (accept(node), ...), true; - } - }; - - using PreciseExprMatcher = Accept; - - const SelectionTree& tree; - ASTInfo& AST; - const config::HoverOption& option; - - /// The final matched expression node by `PreciseExprMatcher`. - const Node* target = nullptr; - - using Res = std::optional; - - Res operator() (const clang::DeclRefExpr* DR) const { - return DeclHoverBuilder::build(DR->getDecl(), option); - } - - Res operator() (const clang::CXXMemberCallExpr* MC) const { - return DeclHoverBuilder::build(MC->getCalleeDecl(), option); - } - - Res operator() (const clang::CallExpr* C) const { - return DeclHoverBuilder::build(C->getCalleeDecl(), option); - } - - Res operator() (const clang::CXXNamedCastExpr* NC) const { - auto dest = NC->getTypeAsWritten(); - if(dest->isFundamentalType()) { - return std::nullopt; - } - - Expression expr; - expr.symbol = SymbolKind::Variable; - expr.name = NC->getStmtClassName(); - - expr.type.type = dest.getAsString(); - if(auto qty = dest.getCanonicalType(); qty != dest) { - expr.type.akaType = qty.getAsString(); - } - - expr.source = ""; - return HoverInfo{std::move(expr)}; - } - - // By default, return empty. - template - Res operator() (O) const { - return std::nullopt; - } - - static Res build(ASTInfo& AST, const SelectionTree& tree, const config::HoverOption& option) { - PreciseExprMatcher expr; - if(!tree.walkDfs([&expr](const Node* node) { return expr.accept(node); })) { - return std::nullopt; - } - - ExprHoverBuilder builder{tree, AST, option, expr.deepest}; - return std::visit(builder, expr); - } -}; - -namespace hit { - -std::optional header(llvm::ArrayRef includes, ASTInfo& AST, uint32_t line) { - auto lineof = [&SM = AST.srcMgr()](const Include& inc) { - return SM.getPresumedLineNumber(inc.location); - }; - - if(includes.empty() || lineof(includes.back()) < line) { - return std::nullopt; - } - - for(auto& inc: includes) { - if(lineof(inc) != line) - continue; - - Header ic; - ic.symbol = SymbolKind::Header; - ic.name = inc.fileName; - ic.absPath = path::join(inc.searchPath, inc.relativePath); - return HoverInfo{std::move(ic)}; - } - - return std::nullopt; -} - -std::optional numeric(const Token& token, ASTInfo& AST) { - if(auto kind = token.kind(); kind == numeric_constant) { - llvm::StringRef text = token.text(AST.srcMgr()); - auto& Ctx = AST.context(); - clang::NumericLiteralParser parser(text, - token.location(), - AST.srcMgr(), - Ctx.getLangOpts(), - Ctx.getTargetInfo(), - Ctx.getDiagnostics()); - llvm::APInt apint; - if(!parser.GetIntegerValue(apint)) { - return HoverInfo{ - Numeric{.rawText = text, .value = apint} - }; - } - - llvm::APFloat apfloat{0.0}; - if(parser.GetFloatValue(apfloat, llvm::RoundingMode::NearestTiesToEven)) { - return HoverInfo{ - Numeric{.rawText = text, .value = apfloat} - }; - } - - std::string reason = std::format("Parse numeric literal failed, text: {}", text); - llvm_unreachable(reason.c_str()); - } - return std::nullopt; -} - -std::optional keyword(const Token& token) { - if(auto spelling = getKeywordSpelling(token.kind())) { - return HoverInfo{ - Keyword{.tkkind = token.kind(), .spelling = spelling} - }; - } - return std::nullopt; -} - -std::optional literal(const Token& token, ASTInfo& AST) { - if(isStringLiteral(token.kind())) { - auto& Ctx = AST.context(); - clang::Token raw; - bool isFail = clang::Lexer::getRawToken(token.location(), - raw, - AST.srcMgr(), - Ctx.getLangOpts(), - /*IgnoreWhiteSpace=*/true); - if(!isFail) { - clang::StringLiteralParser parser(raw, - AST.srcMgr(), - Ctx.getLangOpts(), - Ctx.getTargetInfo()); - auto text = parser.GetString(); - auto udsuffix = parser.getUDSuffix(); - return HoverInfo{ - Literal{.kind = token.kind(), .content = text.str(), .udSuffix = udsuffix.str()} - }; - } - } - - return std::nullopt; -} - -std::optional deduced(const Token& token, - ASTInfo& AST, - const SelectionTree& tree, - const config::HoverOption& option) { - if(token.kind() != kw_auto && token.kind() != kw_decltype) { - return std::nullopt; - } - - const SelectionTree::Node* ctx = nullptr; - auto findDeclContext = [&AST, &ctx](const SelectionTree::Node* node) -> bool { - // `decltype(auto)` is `AutoTypeLoc`. - if(auto AT = node->dynNode.get()) { - ctx = node->parent; - return false; - } - if(auto DT = node->dynNode.get()) { - ctx = node->parent; - return false; - } - return true; - }; - - if(tree.walkDfs(findDeclContext)) - return std::nullopt; - - if(auto dynKind = ctx->dynNode.getNodeKind(); - dynKind.isSame(clang::ASTNodeKind::getFromNodeKind())) { - ctx = ctx->parent; - } - - const clang::Decl* decl = ctx->dynNode.get(); - assert(decl && "Selected Node must be a valid pointer"); - - return DeclHoverBuilder::build(decl, option); -} - -std::optional declaration(const SelectionTree& tree, const config::HoverOption& option) { - const clang::Decl* decl = nullptr; - - // Find the most inner declaration node. - tree.walkDfs([&decl](const SelectionTree::Node* node) { - if(auto D = node->dynNode.get()) - decl = D; - return true; - }); - - if(!decl) { - return std::nullopt; - } - - return DeclHoverBuilder::build(decl, option); -} - -std::optional expression(ASTInfo& AST, - const SelectionTree& tree, - const config::HoverOption& option) { - return ExprHoverBuilder::build(AST, tree, option); -} - -std::optional detect(const Token& token, - ASTInfo& AST, - const config::HoverOption& option) { - auto cheap = hit::numeric(token, AST).or_else([&]() { return hit::literal(token, AST); }); - auto expensive = [&]() -> std::optional { - const auto tree = SelectionTree::selectToken(token, AST.context(), AST.tokBuf()); - if(!tree.hasValue()) - return std::nullopt; - - return deduced(token, AST, tree, option) - .or_else([&]() { return hit::expression(AST, tree, option); }) - .or_else([&]() { return hit::declaration(tree, option); }) - .or_else([&]() { return hit::keyword(token); }); - }; - - // Try some cheap cases first to avoid construct a selection tree. - return cheap.or_else(expensive); -} - -} // namespace hit - -struct MarkdownPrinter { - - using Self = MarkdownPrinter; - - const config::HoverOption option; - - std::string buffer; - - /// Title - constexpr static std::string_view H3 = "###"; - - /// Horizontal Rules. - constexpr static std::string_view HLine = "\n___\n"; - - template - Self& v(std::format_string fmt, Args&&... args) { - std::format_to(std::back_inserter(buffer), fmt, std::forward(args)...); - return *this; - } - - template - Self& vif(bool cond, std::format_string fmt, Args&&... args) { - if(cond) - std::format_to(std::back_inserter(buffer), fmt, std::forward(args)...); - return *this; - } - - /// Optional print the text if it's not empty. - Self& o(std::format_string fmt, std::string_view text) { - if(!(text.empty() || text == "_")) - std::format_to(std::back_inserter(buffer), fmt, text); - return *this; - } - - /// Optional print the text if it's not empty. - Self& oln(std::format_string fmt, std::string_view text) { - if(!(text.empty() || text == "_")) { - return std::format_to(std::back_inserter(buffer), fmt, text), ln(); - } - return *this; - } - - Self& ln() { - buffer += '\n', buffer += '\n'; - return *this; - } - - template - Self& vln(std::format_string fmt, Args&&... args) { - return v(fmt, std::forward(args)...).ln(); - } - - Self& hln() { - while(buffer.ends_with("\n\n")) - buffer.pop_back(); - - if(!buffer.ends_with(HLine)) - buffer += HLine; - return *this; - } - - template - Self& vhln(std::format_string fmt, Args&&... args) { - return v(fmt, std::forward(args)...), hln(); - } - - template - Self& iter(const std::vector& items, llvm::function_ref op) { - for(auto& it: items) { - op(it); - } - return *this; - } - - void operator() (const Namespace& sp) { - // clang-format off - vhln("{} {} `{}{}`", H3, sp.symbol.name(), sp.namespac, sp.name) - .vln("{}", sp.source); - // clang-format on - } - - Self& title(const HoverBase& bs) { - return v("{} {} `{}`", H3, bs.symbol.name(), bs.name); - } - - Self& scope(const WithScope& sc) { - std::string_view mod = "(global)"; - if(!sc.namespac.empty()) - mod = sc.namespac; - return v("In namespace: `{}`", mod).o(", scope: `{}`", sc.local); - } - - template - Self& doc(const HasDocument& doc) { - if(!option.documentation) - return *this; - - return oln("{}", doc.document); - } - - Self& mem(const MemoryLayout& lay, bool isField) { - using RK = enum config::MemoryLayoutRenderKind; - - if(!lay.isBitField) { - auto kind = option.memoryLayoutRenderKind; - const char* name[] = {"size", "align", "offset", "padding"}; - uint32_t value[] = {lay.size, lay.align, lay.offset, lay.padding}; - - for(int i = 0; auto item: value) { - v("{}: ", name[i++]); - if(kind == RK::Both || kind == RK::Decimal) - v("{}", item); - - if(kind == RK::Both || kind == RK::Hexadecimal) - v(" (0x{:X})", item); - - if(i < 4) - v(" bytes, "); - - // Skip `padding` and `offset` for RecordDecl - if(!isField && i >= (4 - 2)) - break; - } - } else { - vln("BitField: size: {} bits, offset: {} bytes + {} bits, padding: {} bits, align: {} bytes", - lay.size, - lay.offset >> 3, - lay.offset & 0x7, - lay.padding, - lay.align); - } - - return *this; - } - - Self& temparms(const std::vector& params) { - return vln("{} template parameters:", params.size()) - .iter(params, [this](const Param& p) { - v("+ {}", p.name).o(" (default: `{}`)", p.defaultParaName).ln(); - }); - } - - Self& tags(const Fn& fn) { - v("`{}`", fn.isConstexpr ? "constexpr" : "") - .o(" `{}`", fn.isConsteval ? "consteval" : "") - .o(" `{}`", fn.isInlined ? "inline" : "") - .o(" `{}`", fn.isStatic ? "static" : "") - .o(" `{}`", fn.isPureVirtual ? "(pure virtual)" : "") - .o(" `{}`", fn.isOverloadedOperator ? "(overloaded operator)" : "") - .ln(); - - if(!fn.isExternallyVisible) - vln("__Visibility: hidden__"); - - if(fn.isDeprecated) - vln("__Deprecated: {}__", fn.deprecateReason); - - return *this; - } - - void operator() (const Record& rc) { - // clang-format off - - // Block 1: title and namespace - title(rc).ln() - .scope(rc).ln() - .hln() - - // Block 2: optional document - .doc(rc) - .hln(); - - // Block 3: memory layout or template parameters - (!rc.templateParams.empty() ? temparms(rc.templateParams) : mem(rc, /*isField=*/false)).hln(); - - // Block 4: fields - vif(!rc.fields.empty(), "{} fields:", rc.fields.size()).ln() - .iter(rc.fields, [this](const Field& f) { - // a short description in one line. - v("+ {}", f.name).v(": `{}`", f.type.type).o(" (aka `{}`)", f.type.akaType).ln(); - }) - .hln() - - // Block 5: source code - .vln("{}", rc.source); - - // clang-format on - } - - void operator() (const Field& fd) { - // clang-format off - - SymbolKind kind = SymbolKind::Field; - // Block 1: title and namespace - v("{} {} `{}`", H3, kind.name(), fd.name).ln() - .scope(fd).ln() - .hln() - - // Block 2: type - .v("Type: `{}`", fd.type.type).o(" (aka `{}`)", fd.type.akaType).ln() - .mem(fd, /*isField=*/true) - .hln() - - // Block 3: optional document - .doc(fd) - .hln(); - - // clang-format on - } - - void operator() (const Enum& em) { - // clang-format off - - // Block 1: title and namespace - title(em).v(" `({})`", em.implType).ln() - .scope(em).vif(!em.isScoped, ", (unscoped)").ln() - .hln() - - // Block 2: optional document - .doc(em) - .hln() - - // Block 3: items - .vif(!em.items.empty(), "{} items:", em.items.size()).ln() - .iter(em.items, [this](const Enum::EnumItem& it) { - v("+ {} = `{}`", it.name, it.value).ln(); - }) - .hln() - - // Block 4: source code - .vln("{}", em.source); - - // clang-format on - } - - void operator() (const Fn& fn) { - // clang-format off - - // Block 1: title and namespace - title(fn).ln() - .scope(fn).ln() - .hln() - - .tags(fn) - .hln() - - // Block 2: template parameters - .vif(!fn.templateParams.empty(), "{} template parameters:", fn.templateParams.size()).ln() - .iter(fn.templateParams, [this](const Param& p) { - v("+ {}", p.name).o(" (default: `{}`)", p.defaultParaName).ln(); - }) - .hln() - - // Block 3: return type - .v("-> `{}`", fn.retType.type) .o(" (aka `{}`)", fn.retType.akaType).ln() - .hln() - - // Block 4: parameters - .vif(!fn.params.empty(), "{} parameters:", fn.params.size()).ln() - .iter(fn.params, [this](const Param& p) { - v("+ {}", p.name).v(": `{}`", p.type.type).o(" (aka `{}`)", p.type.akaType).o("= `{}`", p.defaultParaName).ln(); - }) - .hln() - - // Block 5: optional document - .doc(fn) - .hln() - - - // Block 6: source code - .vln("{}", fn.source); - - // clang-format on - } - - Self& tags(const Var& var) { - v("`{}`", var.isConstexpr ? "constexpr" : "") - .o(" `{}`", var.isStaticLocal ? "static" : "") - .o(" `{}`", var.isExtern ? "extern" : "") - .o(" `{}`", var.isFileScope ? "(file variable)" : "") - .o(" `{}`", var.isLocal ? "(local variable)" : "") - .ln(); - - return *this; - } - - void operator() (const Var& var) { - // clang-format off - - // Block 1: title and namespace - title(var).ln() - .scope(var).ln() - .hln() - - // Block 2: type - .tags(var) - .v("Type: `{}`", var.type.type).o(" (aka `{}`)", var.type.akaType).ln() - .vif(var.size, "size = {} bytes, align = {} bytes", var.size, var.align).ln() - .hln() - - // Block 3: optional document - .doc(var) - .hln() - - // Block 4: source code - .vln("{}", var.source); - - // clang-format on - } - - void operator() (const Header& ic) { - // clang-format off - title(ic) - .hln() - - .vln("`{}`", ic.absPath); - // clang-format on - } - - void operator() (const Keyword& kw) { - // clang-format off - v("{} {} `{}`", H3, kw.symbol.name(), clang::tok::getTokenName(kw.tkkind)) - .hln() - - .vln("See: [{0}]({0})", kw.cpprefLink(kw.tkkind)); - // clang-format on - } - - void operator() (const Numeric& nm) { - bool isInteger = true; - llvm::SmallString<64> bin; - llvm::SmallString<32> dec; - llvm::SmallString<32> hex; - - auto fmtter = Match{ - [&](const llvm::APInt& apint) { - apint.toString(bin, 2, /*Signed=*/true); - apint.toString(dec, 10, /*Signed=*/true); - apint.toString(hex, 16, /*Signed=*/true); - }, - [&](const llvm::APFloat& apfloat) { - isInteger = false; - apfloat.toString(dec, 10); - hex.resize_for_overwrite( - apfloat.convertToHexString(hex.begin(), - 16, - /*UpperCase=*/true, - llvm::RoundingMode::NearestTiesToEven)); - }, - }; - std::visit(fmtter, nm.value); - - constexpr auto sv = [](llvm::StringRef str) -> std::string_view { - return std::string_view{str.data(), str.size()}; - }; - - if(isInteger) { - // clang-format off - v("{} {} `{}`", H3, nm.symbol.name(), sv(nm.rawText)) - .hln() - .vln("Binary: `{}`", sv(bin)) - .vln("Decimal: `{}`", sv(dec)) - .vln("Hexadecimal: `{}`", sv(hex)); - // clang-format on - } else { - // clang-format off - v("{} {} `{}`", H3, nm.symbol.name(), sv(nm.rawText)) - .hln() - .vln("Decimal: `{}`", sv(dec)) - .vln("Hexadecimal: `{}`", sv(hex)); - // clang-format on - } - } - - void operator() (const Literal& lit) { - // clang-format off - v("{} {} `{}`", H3, lit.symbol.name(), lit.stringLiteralKindName(lit.kind)) - .hln() - - .o("User Defined Suffix: `{}`", lit.udSuffix) - .hln() - - .vln("size: {} bytes", lit.content.size() + 1) // null-terminated - .hln() - - .vln("{}", lit.content); - // clang-format on - } - - void operator() (const Expression& expr) { - // clang-format off - v("{} Expression `{}`", H3, expr.name) - .hln() - - .v("type: `{}`", expr.type.type).o(" (aka `{}`)", expr.type.akaType).ln() - .hln() - - .vln("{}", expr.source); - // clang-format on - } - - void operator() (const Empty&) { - // Show nothing in release mode to avoid crash. -#ifndef NDEBUG - llvm_unreachable("Empty just used to defualt-construct a HoverInfo"); -#endif - } - - /// Render the hover information to markdown text. - static std::string print(const HoverInfo& hover, config::HoverOption option) { - constexpr size_t kMaxBufferSize = 512; - - MarkdownPrinter pv{.option = option}; - /// Reserve at most 512 bytes for the buffer. - pv.buffer.reserve(std::min(std::bit_ceil(hover.estimated_size()), kMaxBufferSize)); - - std::visit(pv, hover); - - while(pv.buffer.ends_with(HLine)) - pv.buffer.resize(pv.buffer.size() - HLine.size()); - - // Keep at most one \n in the tail. - while(pv.buffer.ends_with("\n\n")) - pv.buffer.pop_back(); - - pv.buffer.shrink_to_fit(); - return std::move(pv.buffer); - } +private: + index::Shared files; }; } // namespace -} // namespace clice - -namespace clice::feature::hover { - -namespace { - -Result toMarkdown(const HoverInfo& hover, const config::HoverOption& option) { - return {.markdown = MarkdownPrinter::print(hover, option)}; +Hover hover(ASTInfo& AST, const clang::NamedDecl* decl) { + return Hover{ + .kind = SymbolKind::from(decl), + .name = getDeclName(decl), + .items = getHoverItems(AST, decl), + .document = getDocument(AST, decl), + .qualifier = getQualifier(AST, decl), + .source = getSourceCode(AST, decl), + }; } -std::optional - hover(uint32_t line, uint32_t col, ASTInfo& AST, const config::HoverOption& option) { - - auto srcLoc = AST.srcMgr().translateLineCol(AST.getInterestedFile(), line, col); - if(srcLoc.isInvalid()) - return std::nullopt; - - auto tokens = clang::syntax::spelledTokensTouching(srcLoc, AST.tokBuf()); - if(tokens.empty()) - return std::nullopt; - - // Check if the position is in a include directive. - llvm::ArrayRef includes = AST.directives()[AST.getInterestedFile()].includes; - if(auto hit = hit::header(includes, AST, line)) - return hit; - - auto candidates = pickBestToken(tokens); - for(const auto& token: candidates) { - if(auto hit = hit::detect(token, AST, option)) - return hit; - } - - return std::nullopt; +index::Shared indexHover(ASTInfo& AST) { + HoverCollector collector(AST); + return collector.build(); } -} // namespace - -Result hover(const clang::Decl* decl, const config::HoverOption& option) { - return toMarkdown(DeclHoverBuilder::build(decl, option), option); -} - -std::optional hover(const proto::HoverParams& param, - ASTInfo& AST, - const config::HoverOption& option) { - // Convert 0-0 based lsp position to clang 1-1 based loction. - return hover(param.position.line + 1, param.position.character + 1, AST, option) - .transform([&option](HoverInfo&& hv) { return toMarkdown(hv, option); }); -} - -proto::MarkupContent toLspType(Result hover) { - return {.value = std::move(hover.markdown)}; -} - -} // namespace clice::feature::hover +} // namespace clice::feature diff --git a/src/Feature/SemanticTokens.cpp b/src/Feature/SemanticTokens.cpp index 75b076c8..6bbdac36 100644 --- a/src/Feature/SemanticTokens.cpp +++ b/src/Feature/SemanticTokens.cpp @@ -8,10 +8,11 @@ namespace clice::feature { namespace { -class HighlightBuilder : public SemanticVisitor { +class SemanticTokensCollector : public SemanticVisitor { public: - HighlightBuilder(ASTInfo& AST, bool interestedOnly) : - emitForIndex(!interestedOnly), SemanticVisitor(AST, interestedOnly) {} + SemanticTokensCollector(ASTInfo& AST, bool interestedOnly) : + emitForIndex(!interestedOnly), + SemanticVisitor(AST, interestedOnly) {} void handleDeclOccurrence(const clang::NamedDecl* decl, RelationKind kind, @@ -266,11 +267,11 @@ private: } // namespace std::vector semanticTokens(ASTInfo& AST) { - return HighlightBuilder(AST, true).buildForFile(); + return SemanticTokensCollector(AST, true).buildForFile(); } index::Shared> indexSemanticTokens(ASTInfo& AST) { - return HighlightBuilder(AST, false).buildForIndex(); + return SemanticTokensCollector(AST, false).buildForIndex(); } } // namespace clice::feature diff --git a/src/Index/SymbolIndex.cpp b/src/Index/SymbolIndex.cpp index e3653cba..2d06feb5 100644 --- a/src/Index/SymbolIndex.cpp +++ b/src/Index/SymbolIndex.cpp @@ -44,11 +44,6 @@ struct SymbolIndexStorage : memory::SymbolIndex { /// to make sure that the data is in the same order even they are in different /// files. - /// Polyfill ranges::iota for libc++ - auto iota = [](auto& r, auto init) { - ranges::generate(r, [init] mutable { return init++; }); - }; - /// Map the old index to new index. std::vector symbolMap(symbols.size()); std::vector locationMap(ranges.size()); @@ -56,7 +51,9 @@ struct SymbolIndexStorage : memory::SymbolIndex { { /// Sort symbols and update the symbolMap. std::vector new2old(symbols.size()); - iota(new2old, 0u); + for(uint32_t i = 0; i < symbols.size(); ++i) { + new2old[i] = i; + } ranges::sort(views::zip(symbols, new2old), refl::less, [](const auto& element) { auto& symbol = std::get<0>(element); @@ -71,7 +68,9 @@ struct SymbolIndexStorage : memory::SymbolIndex { { /// Sort locations and update the locationMap. std::vector new2old(ranges.size()); - iota(new2old, 0u); + for(uint32_t i = 0; i < ranges.size(); ++i) { + new2old[i] = i; + } ranges::sort(views::zip(ranges, new2old), refl::less, [](const auto& element) { return std::get<0>(element); diff --git a/src/Server/LSPConverter.cpp b/src/Server/LSPConverter.cpp index baf9ca3a..448db8d8 100644 --- a/src/Server/LSPConverter.cpp +++ b/src/Server/LSPConverter.cpp @@ -285,4 +285,9 @@ LSPConverter::Result LSPConverter::convert(llvm::StringRef path, co_return json::serialize(result); } +LSPConverter::Result LSPConverter::convert(const feature::Hover& hover) { + /// FIXME: Implement hover information render here. + co_return json::Value(""); +} + } // namespace clice diff --git a/unittests/Feature/Hover.cpp b/unittests/Feature/Hover.cpp index 5dbe2037..663c0b19 100644 --- a/unittests/Feature/Hover.cpp +++ b/unittests/Feature/Hover.cpp @@ -1,20 +1,15 @@ #include "Test/CTest.h" #include "Feature/Hover.h" -#include "src/Feature/Hover.cpp" - #include "clang/AST/RecursiveASTVisitor.h" namespace clice::testing { namespace { -constexpr config::HoverOption DefaultOption = {}; - -using namespace feature::hover; +using namespace feature; struct DeclCollector : public clang::RecursiveASTVisitor { - llvm::StringMap decls; bool VisitNamedDecl(const clang::NamedDecl* decl) { @@ -25,16 +20,13 @@ struct DeclCollector : public clang::RecursiveASTVisitor { }; struct Hover : public ::testing::Test { - using HoverChecker = llvm::function_ref&)>; protected: std::optional tester; llvm::StringMap decls; - void run(llvm::StringRef code, - proto::Range range = {}, - const config::HoverOption& option = DefaultOption) { + void run(llvm::StringRef code, proto::Range range = {}) { tester.emplace("main.cpp", code); tester->run(); @@ -44,81 +36,6 @@ protected: collector.TraverseTranslationUnitDecl(info->tu()); decls = std::move(collector.decls); } - - void runWithHeader(llvm::StringRef source, - llvm::StringRef header, - const config::HoverOption& option = DefaultOption) { - tester.emplace("main.cpp", source); - tester->addFile(path::join(".", "header.h"), header); - tester->run(); - - auto& info = tester->info; - DeclCollector collector; - collector.TraverseTranslationUnitDecl(info->tu()); - decls = std::move(collector.decls); - } - - const clang::Decl* getValidDeclPtr(llvm::StringRef name) { - auto ptr = decls.lookup(name); - EXPECT_TRUE(bool(ptr)); - return ptr; - } - - void EXPECT_HOVER(llvm::StringRef declName, - llvm::function_ref checker, - const config::HoverOption& option = DefaultOption) { - auto ptr = getValidDeclPtr(declName); - auto result = hover(ptr, option); - EXPECT_TRUE(checker(result)); - } - - void EXPECT_HOVER(llvm::StringRef declName, - llvm::StringRef mdText, - const config::HoverOption& option = DefaultOption) { - auto ptr = getValidDeclPtr(declName); - auto result = hover(ptr, option); - - // llvm::outs() << result.markdown << '\n'; - EXPECT_EQ(mdText, result.markdown); - } - - template - static bool is(std::optional& hover) { - EXPECT_TRUE(hover.has_value()); - if(hover.has_value()) { - return std::holds_alternative(*hover); - } - return false; - } - - static bool isNone(std::optional& hover) { - bool no = !hover.has_value(); - EXPECT_TRUE(no); - return no; - } - - void EXPECT_HOVER_TYPE(llvm::StringRef key, - HoverChecker checker, - config::HoverOption option = DefaultOption) { - auto pos = tester->locations.at(key); - auto hoverInfo = hover(pos.line + 1, pos.character + 1, *tester->info, option); - - bool checkResult = checker(hoverInfo); - // if(hoverInfo.has_value()) { - // llvm::outs() << "======[" << key << "]======\n"; - // llvm::outs() << toMarkdown(*hoverInfo, option).markdown << '\n'; - // } - EXPECT_TRUE(checkResult); - } - - using Fmtter = std::string(int index); - - void EXPECT_TYPES_N(Fmtter fmt, int n, HoverChecker checker) { - for(int i = 1; i <= n; i++) { - auto key = fmt(i); - EXPECT_HOVER_TYPE(key, checker); - } - }; }; TEST_F(Hover, Namespace) { @@ -245,7 +162,6 @@ ___ ___ )md"; - EXPECT_HOVER("M", M_TEXT); } TEST_F(Hover, EnumStyle) { @@ -285,11 +201,6 @@ ___ )md"; -#ifndef _WIN32 - // The underlying type of `Free` is `int` on Windows. - EXPECT_HOVER("Free", FREE_STYLE); -#endif - // EXPECT_HOVER("Scope", ""); } @@ -346,8 +257,6 @@ ___ // EXPECT_HOVER("t", FREE_STYLE); // EXPECT_HOVER("g", FREE_STYLE); // EXPECT_HOVER("h", FREE_STYLE); - - EXPECT_HOVER("m", FUNC_STYLE); } TEST_F(Hover, VariableStyle) { @@ -375,8 +284,6 @@ ___ ___ )md"; - - EXPECT_HOVER("x1", FREE_STYLE); } TEST_F(Hover, HeaderAndNamespace) { @@ -399,11 +306,6 @@ $(n1)names$(n2)pace$(n3) outt$(n4)er { }$(n10) )cpp"; - - runWithHeader(code, header); - - EXPECT_TYPES_N([](int i) { return std::format("h{}", i); }, 5, is

); - EXPECT_TYPES_N([](int i) { return std::format("n{}", i); }, 8, is); } TEST_F(Hover, VariableAndLiteral) { @@ -424,9 +326,6 @@ TEST_F(Hover, VariableAndLiteral) { auto l$(v7)4 = $(l10)"$(l11)udf_string$(l12)"_w; )cpp"; run(code); - - EXPECT_TYPES_N([](int i) { return std::format("v{}", i); }, 7, is); - EXPECT_TYPES_N([](int i) { return std::format("l{}", i); }, 12, is); } TEST_F(Hover, FunctionDeclAndParameter) { @@ -463,9 +362,6 @@ TEST_F(Hover, FunctionDeclAndParameter) { )cpp"; run(code); - - EXPECT_TYPES_N([](int i) { return std::format("f{}", i); }, 12, is); - EXPECT_TYPES_N([](int i) { return std::format("p{}", i); }, 18, is); } TEST_F(Hover, AutoAndDecltype) { @@ -489,13 +385,6 @@ int f3(au$(fn_para_auto)to x) {} run(code); - EXPECT_TYPES_N([](int i) { return std::format("a{}", i); }, 5, is); - EXPECT_TYPES_N([](int i) { return std::format("d{}", i); }, 3, is); - - EXPECT_HOVER_TYPE("fa", is); - EXPECT_HOVER_TYPE("fn_decltype", is); - EXPECT_HOVER_TYPE("fn_decltype_auto", is); - /// FIXME: It seems a bug of SelectionTree, which cannot select any node of `f3`; /// EXPECT_HOVER_TYPE("fn_para_auto", is); } @@ -517,16 +406,6 @@ struct A { )cpp"; run(code); - - EXPECT_HOVER_TYPE("e1", is); - EXPECT_HOVER_TYPE("e2", is); - EXPECT_HOVER_TYPE("e3", is); - EXPECT_HOVER_TYPE("e4", is); - EXPECT_HOVER_TYPE("e5", is); - EXPECT_HOVER_TYPE("e6", is); - EXPECT_HOVER_TYPE("e7", is); - EXPECT_HOVER_TYPE("e8", is); - EXPECT_HOVER_TYPE("e9", is); } } // namespace