Skip to content
Closed
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
4 changes: 2 additions & 2 deletions docs/en/features/hover.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@

## Special Hover Targets

- [ ] Show struct/enum members on type-level hover ([clangd#959](https://github.com/clangd/clangd/issues/959))
- [x] Show struct/enum members on type-level hover ([clangd#959](https://github.com/clangd/clangd/issues/959))

```cpp
enum Color { Red, Green, Blue };
Expand Down Expand Up @@ -266,7 +266,7 @@
// hover on "nodiscard" → description of the attribute
```

- [ ] `#include` directive hover showing resolved header path
- [x] `#include` directive hover showing resolved header path
- [x] `this` expression hover showing pointed-to type
- [x] `__func__` and related predefined identifier hover

Expand Down
4 changes: 2 additions & 2 deletions docs/zh/features/hover.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@

## 特殊悬停目标

- [ ] 类型级悬停显示结构体/枚举成员([clangd#959](https://github.com/clangd/clangd/issues/959))
- [x] 类型级悬停显示结构体/枚举成员([clangd#959](https://github.com/clangd/clangd/issues/959))

```cpp
enum Color { Red, Green, Blue };
Expand Down Expand Up @@ -266,7 +266,7 @@
// 悬停 "nodiscard" → 显示该属性的说明
```

- [ ] `#include` 指令悬停显示解析后的头文件路径
- [x] `#include` 指令悬停显示解析后的头文件路径
- [x] `this` 表达式悬停显示指向的类型
- [x] `__func__` 及相关预定义标识符悬停

Expand Down
117 changes: 117 additions & 0 deletions src/feature/hover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "semantic/ast_utility.h"
#include "semantic/find_target.h"
#include "semantic/selection.h"
#include "syntax/lexer.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
Expand Down Expand Up @@ -117,6 +118,58 @@ auto namespace_scope(const clang::Decl* decl) -> std::string {
return "";
}

void print_tag_name(std::string& definition,
const clang::TagDecl& decl,
clang::PrintingPolicy policy) {
/// We only need the full tag name, so truncate the body at the opening brace for later fill-in.
llvm::raw_string_ostream os(definition);
decl.print(os, policy);
auto body = definition.rfind('{');
if(body == std::string::npos) {
return;
}
definition.resize(body);
}

auto print_record_definition(const clang::RecordDecl& decl, clang::PrintingPolicy policy)
-> std::string {
std::string definition;

print_tag_name(definition, decl, policy);

llvm::raw_string_ostream os(definition);
os << " {";
Comment thread
zaragoza-xu marked this conversation as resolved.
for(const clang::FieldDecl* field: decl.fields()) {
Comment thread
zaragoza-xu marked this conversation as resolved.
os << '\n';
field->print(os, policy);
os << ';';
Comment on lines +141 to +145
Comment thread
zaragoza-xu marked this conversation as resolved.
}
os << "\n}";

return definition;
}

auto print_enum_definition(const clang::EnumDecl& decl, clang::PrintingPolicy policy)
-> std::string {
std::string definition;

print_tag_name(definition, decl, policy);

llvm::raw_string_ostream os(definition);
os << " {";
for(const clang::EnumConstantDecl* enumerator: decl.enumerators()) {
os << '\n';
enumerator->print(os, policy);
if(!enumerator->getInitExpr() && !enumerator->getType()->isDependentType()) {
os << " = " << llvm::toString(enumerator->getInitVal(), 10);
}
os << ',';
}
os << "\n}";

return definition;
}

auto print_definition(const clang::Decl* decl,
clang::PrintingPolicy policy,
const clang::syntax::TokenBuffer& tb) -> std::string {
Expand All @@ -129,6 +182,14 @@ auto print_definition(const clang::Decl* decl,
policy.SuppressInitializers = true;
}
}
} else if(auto* record = llvm::dyn_cast<clang::RecordDecl>(decl)) {
if(auto* definition = record->getDefinition()) {
return print_record_definition(*definition, policy);
}
} else if(auto* enum_decl = llvm::dyn_cast<clang::EnumDecl>(decl)) {
if(auto* definition = enum_decl->getDefinition()) {
return print_enum_definition(*definition, policy);
}
}

std::string definition;
Expand Down Expand Up @@ -1044,6 +1105,57 @@ auto attr_hover(const clang::Attr* attr, clang::ASTContext& context) -> std::opt
return info;
}

auto include_hover(CompilationUnitRef unit, std::uint32_t offset) -> std::optional<HoverInfo> {
auto interested = unit.interested_file();
auto directives_it = unit.directives().find(interested);
if(directives_it == unit.directives().end()) {
return std::nullopt;
}

auto content = unit.interested_content();
auto* lang_opts = &unit.lang_options();

auto header_name = [&](LocalSourceRange range) -> std::string {
auto arg = content.substr(range.begin, range.end - range.begin).trim();
if(arg.size() >= 2 && ((arg.front() == '"' && arg.back() == '"') ||
(arg.front() == '<' && arg.back() == '>'))) {
arg = arg.drop_front().drop_back();
}
return arg.str();
};

auto try_directive = [&](clang::SourceLocation loc,
clang::FileID target) -> std::optional<HoverInfo> {
if(!target.isValid())
return std::nullopt;
auto [fid, directive_offset] = unit.decompose_location(loc);
if(fid != interested || directive_offset >= content.size())
return std::nullopt;
auto range = find_directive_argument(content, directive_offset, lang_opts);
if(!range || !range->contains(offset))
Comment on lines +1127 to +1135
return std::nullopt;

HoverInfo info;
info.name = header_name(*range);
info.kind = SymbolKind::Header;
Comment thread
zaragoza-xu marked this conversation as resolved.
info.definition = unit.file_path(target);
info.symbol_range = *range;
return info;
};

for(const auto& include: directives_it->second.includes) {
if(auto info = try_directive(include.location, include.fid)) {
return info;
}
}
for(const auto& has_include: directives_it->second.has_includes) {
if(auto info = try_directive(has_include.location, has_include.fid)) {
return info;
}
}
return std::nullopt;
}

void add_layout_info(const clang::NamedDecl& decl, HoverInfo& info) {
if(decl.isInvalidDecl()) {
return;
Expand Down Expand Up @@ -1504,6 +1616,11 @@ llvm::raw_ostream& operator<<(llvm::raw_ostream& os, const HoverInfo::Param& par

auto hover_info(CompilationUnitRef unit, std::uint32_t offset, const HoverOptions& options)
-> std::optional<HoverInfo> {
/// The hover is over an include directive.
if(auto info = include_hover(unit, offset)) {
return info;
}

auto& context = unit.context();
auto policy = hover_printing_policy(context.getPrintingPolicy());

Expand Down
76 changes: 76 additions & 0 deletions tests/data/hover/tag_decls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,79 @@ namespace nested_templates_class {
template <class T> struct cls {};
$(17_nested_template_class)cls<cls<cls<int>>> foo;
}

namespace enum_explicit {
enum Color {
RED = -123,
GREEN,
BLUE = 5,
};
void foo() {
$(18_enum_explicit)Color color = GREEN;
}
}

namespace struct_fields {
struct Point {
int x;
int y;
};
void foo() {
$(19_struct_fields)Point point;
}
}

namespace inherited_struct {
struct base {
int a;
};

struct Point : base {
int x;
int y;
};
void foo() {
$(20_inherited_struct)Point point;
}
}

namespace inherited_class {
struct first {};
struct second {};

class Point : public first, virtual protected second {
int value;
};
void foo() {
$(21_inherited_class)Point point;
}
}

namespace template_fields {
template <typename T>
struct Box {
T value;
};
void foo() {
$(22_template_fields)Box<int> box;
}
}

namespace inline_method_body {
struct Widget {
int value;
int get() const { return value; }
};
void foo() {
$(23_inline_method_body)Widget widget;
}
}

namespace lambda_field_body {
struct Widget {
int value = [] { return 42; }();
};
void foo() {
$(24_lambda_field_body)Widget widget;
}
}
Loading