Skip to content
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

Hover: Add links to referenced types when possible #1281

Merged
merged 7 commits into from
Jun 29, 2023
Merged
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
163 changes: 160 additions & 3 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1095,9 +1095,9 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e

var either = std.ArrayListUnmanaged(Type.EitherEntry){};
if (try analyser.resolveTypeOfNodeInternal(.{ .handle = handle, .node = if_node.ast.then_expr })) |t|
try either.append(analyser.arena.allocator(), .{ .type_with_handle = t, .descriptor = tree.getNodeSource(if_node.ast.cond_expr) });
try either.append(analyser.arena.allocator(), .{ .type_with_handle = t, .descriptor = offsets.nodeToSlice(tree, if_node.ast.cond_expr) });
if (try analyser.resolveTypeOfNodeInternal(.{ .handle = handle, .node = if_node.ast.else_expr })) |t|
try either.append(analyser.arena.allocator(), .{ .type_with_handle = t, .descriptor = try std.fmt.allocPrint(analyser.arena.allocator(), "!({s})", .{tree.getNodeSource(if_node.ast.cond_expr)}) });
try either.append(analyser.arena.allocator(), .{ .type_with_handle = t, .descriptor = try std.fmt.allocPrint(analyser.arena.allocator(), "!({s})", .{offsets.nodeToSlice(tree, if_node.ast.cond_expr)}) });

return TypeWithHandle{
.type = .{ .data = .{ .either = try either.toOwnedSlice(analyser.arena.allocator()) }, .is_type_val = false },
Expand All @@ -1117,7 +1117,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
var descriptor = std.ArrayListUnmanaged(u8){};

for (switch_case.ast.values, 0..) |values, index| {
try descriptor.appendSlice(analyser.arena.allocator(), tree.getNodeSource(values));
try descriptor.appendSlice(analyser.arena.allocator(), offsets.nodeToSlice(tree, values));
if (index != switch_case.ast.values.len - 1) try descriptor.appendSlice(analyser.arena.allocator(), ", ");
}

Expand Down Expand Up @@ -3107,3 +3107,160 @@ fn makeScopeInternal(context: ScopeContext, tree: Ast, node_idx: Ast.Node.Index)
},
}
}

pub const ReferencedType = struct {
str: []const u8,
handle: *const DocumentStore.Handle,
token: Ast.TokenIndex,

fn init(str: []const u8, handle: *const DocumentStore.Handle, token: Ast.TokenIndex) ReferencedType {
return .{ .str = str, .handle = handle, .token = token };
}
};

pub fn referencedTypes(
analyser: *Analyser,
allocator: std.mem.Allocator,
type_str: ?[]const u8,
resolved_type: TypeWithHandle,
resolved_type_str: *[]const u8,
referenced_types: *[]const ReferencedType,
Techatrix marked this conversation as resolved.
Show resolved Hide resolved
) error{OutOfMemory}!void {
var list = std.ArrayList(ReferencedType).init(allocator);
if (resolved_type.type.is_type_val) {
_ = try analyser.addReferencedTypes(type_str, resolved_type, false, &list);
resolved_type_str.* = switch (resolved_type.type.data) {
.@"comptime" => |co| try std.fmt.allocPrint(allocator, "{}", .{co.value.index.fmt(co.interpreter.ip.*)}),
else => "type",
};
} else if (try analyser.addReferencedTypes(type_str, resolved_type, true, &list)) |str| {
resolved_type_str.* = str;
}
referenced_types.* = list.items;
}

fn addReferencedTypesFromNode(
analyser: *Analyser,
node_handle: NodeWithHandle,
referenced_types: *std.ArrayList(ReferencedType),
) error{OutOfMemory}!void {
const type_handle = try analyser.resolveTypeOfNode(node_handle) orelse return;
const type_str = offsets.nodeToSlice(node_handle.handle.tree, node_handle.node);
_ = try analyser.addReferencedTypes(type_str, type_handle, true, referenced_types);
}

fn addReferencedTypes(
analyser: *Analyser,
type_str: ?[]const u8,
type_handle: TypeWithHandle,
is_referenced_type: bool,
referenced_types: *std.ArrayList(ReferencedType),
) error{OutOfMemory}!?[]const u8 {
const allocator = referenced_types.allocator;

const handle = type_handle.handle;
const tree = handle.tree;

const node_tags = tree.nodes.items(.tag);
const token_tags = tree.tokens.items(.tag);
const main_tokens = tree.nodes.items(.main_token);

switch (type_handle.type.data) {
.pointer,
.slice,
.error_union,
.primitive,
=> |p| return offsets.nodeToSlice(tree, p),

.other => |p| switch (node_tags[p]) {
.root => {
const path = URI.parse(allocator, handle.uri) catch return null;
const str = std.fs.path.stem(path);
if (is_referenced_type)
try referenced_types.append(ReferencedType.init(type_str orelse str, handle, tree.firstToken(p)));
return str;
},

.container_decl,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
=> {
// NOTE: This is a hacky nightmare but it works :P
const token = main_tokens[p] - 2;
if (token_tags[token] != .identifier) return null;
const str = tree.tokenSlice(token);
if (is_referenced_type)
try referenced_types.append(ReferencedType.init(type_str orelse str, handle, token));
return str;
},

.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> {
var buffer: [1]Ast.Node.Index = undefined;
const fn_proto = tree.fullFnProto(&buffer, p).?;

var it = fn_proto.iterate(&tree);
while (ast.nextFnParam(&it)) |param| {
try analyser.addReferencedTypesFromNode(
.{ .node = param.type_expr, .handle = handle },
referenced_types,
);
}

try analyser.addReferencedTypesFromNode(
.{ .node = fn_proto.ast.return_type, .handle = handle },
referenced_types,
);

return "fn";
},

.array_type,
.array_type_sentinel,
=> {
const array_type = tree.fullArrayType(p).?;

try analyser.addReferencedTypesFromNode(
.{ .node = array_type.ast.elem_type, .handle = handle },
referenced_types,
);

return offsets.nodeToSlice(tree, p);
},

.ptr_type,
.ptr_type_aligned,
.ptr_type_bit_range,
.ptr_type_sentinel,
=> {
const ptr_type = tree.fullPtrType(p).?;

try analyser.addReferencedTypesFromNode(
.{ .node = ptr_type.ast.child_type, .handle = handle },
referenced_types,
);

return offsets.nodeToSlice(tree, p);
},

else => {}, // TODO: Implement more "other" type expressions; better safe than sorry
},

else => {},
}

return null;
}
90 changes: 32 additions & 58 deletions src/features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup
const handle = decl_handle.handle;
const tree = handle.tree;

var type_str: ?[]const u8 = null;
var doc_str: ?[]const u8 = null;

const def_str = switch (decl_handle.decl.*) {
Expand All @@ -33,17 +34,27 @@ pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup
var buf: [1]Ast.Node.Index = undefined;

if (tree.fullVarDecl(node)) |var_decl| {
if (var_decl.ast.type_node != 0)
type_str = offsets.nodeToSlice(tree, var_decl.ast.type_node);

break :def Analyser.getVariableSignature(tree, var_decl);
} else if (tree.fullFnProto(&buf, node)) |fn_proto| {
break :def Analyser.getFunctionSignature(tree, fn_proto);
} else if (tree.fullContainerField(node)) |field| {
std.debug.assert(field.ast.type_expr != 0);
type_str = offsets.nodeToSlice(tree, field.ast.type_expr);

break :def Analyser.getContainerFieldSignature(tree, field);
} else {
break :def Analyser.nodeToString(tree, node) orelse return null;
}
},
.param_payload => |pay| def: {
const param = pay.param;

if (param.type_expr != 0) // zero for `anytype` and extern C varargs `...`
type_str = offsets.nodeToSlice(tree, param.type_expr);

if (param.first_doc_comment) |doc_comments| {
doc_str = try Analyser.collectDocComments(server.arena.allocator(), handle.tree, doc_comments, markup_kind, false);
}
Expand All @@ -59,69 +70,32 @@ pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup
=> tree.tokenSlice(decl_handle.nameToken()),
};

const resolved_type = try decl_handle.resolveType(&server.analyser);

const resolved_type_str = if (resolved_type) |rt|
if (rt.type.is_type_val) switch (rt.type.data) {
.@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.index.fmt(co.interpreter.ip.*)}),
else => "type",
} else switch (rt.type.data) {
.pointer,
.slice,
.error_union,
.primitive,
=> |p| offsets.nodeToSlice(rt.handle.tree, p),
.other => |p| switch (rt.handle.tree.nodes.items(.tag)[p]) {
.root => if (URI.parse(server.arena.allocator(), rt.handle.uri)) |path| std.fs.path.stem(path) else |_| "unknown",
.container_decl,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
=> rt.handle.tree.tokenSlice(rt.handle.tree.nodes.items(.main_token)[p] - 2), // NOTE: This is a hacky nightmare but it works :P
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> "fn", // TODO:(?) Add more info?
.array_type,
.array_type_sentinel,
.ptr_type,
.ptr_type_aligned,
.ptr_type_bit_range,
.ptr_type_sentinel,
=> offsets.nodeToSlice(rt.handle.tree, p),
else => "unknown", // TODO: Implement more "other" type expressions; better safe than sorry
},
else => "unknown",
}
else
"unknown";
var resolved_type_str: []const u8 = "unknown";
var referenced_types: []const Analyser.ReferencedType = &.{};
if (try decl_handle.resolveType(&server.analyser)) |resolved_type|
try server.analyser.referencedTypes(server.arena.allocator(), type_str, resolved_type, &resolved_type_str, &referenced_types);

var hover_text: []const u8 = undefined;
var hover_text = std.ArrayList(u8).init(server.arena.allocator());
const writer = hover_text.writer();
if (markup_kind == .markdown) {
hover_text =
if (doc_str) |doc|
try std.fmt.allocPrint(server.arena.allocator(), "```zig\n{s}\n```\n```zig\n({s})\n```\n{s}", .{ def_str, resolved_type_str, doc })
else
try std.fmt.allocPrint(server.arena.allocator(), "```zig\n{s}\n```\n```zig\n({s})\n```", .{ def_str, resolved_type_str });
try writer.print("```zig\n{s}\n```\n```zig\n({s})\n```", .{ def_str, resolved_type_str });
if (doc_str) |doc|
try writer.print("\n{s}", .{doc});
if (referenced_types.len > 0)
try writer.print("\n\n", .{});
for (referenced_types, 0..) |ref, index| {
if (index > 0)
try writer.print(" | ", .{});
const loc = offsets.tokenToPosition(ref.handle.tree, ref.token, server.offset_encoding);
try writer.print("Go to [{s}]({s}#L{d})", .{ ref.str, ref.handle.uri, loc.line + 1 });
}
} else {
hover_text =
if (doc_str) |doc|
try std.fmt.allocPrint(server.arena.allocator(), "{s} ({s})\n{s}", .{ def_str, resolved_type_str, doc })
else
try std.fmt.allocPrint(server.arena.allocator(), "{s} ({s})", .{ def_str, resolved_type_str });
try writer.print("{s} ({s})", .{ def_str, resolved_type_str });
if (doc_str) |doc|
try writer.print("\n{s}", .{doc});
}

return hover_text;
return hover_text.items;
}

pub fn hoverDefinitionLabel(server: *Server, pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}!?types.Hover {
Expand Down