zls/src/features/hover.zig
2023-05-16 19:41:46 +02:00

257 lines
9.4 KiB
Zig

const std = @import("std");
const Ast = std.zig.Ast;
const log = std.log.scoped(.zls_hover);
const ast = @import("../ast.zig");
const types = @import("../lsp.zig");
const offsets = @import("../offsets.zig");
const URI = @import("../uri.zig");
const tracy = @import("../tracy.zig");
const Server = @import("../Server.zig");
const Analyser = @import("../analysis.zig");
const DocumentStore = @import("../DocumentStore.zig");
const data = @import("../data/data.zig");
pub fn hoverSymbol(server: *Server, decl_handle: Analyser.DeclWithHandle, markup_kind: types.MarkupKind) error{OutOfMemory}!?[]const u8 {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const handle = decl_handle.handle;
const tree = handle.tree;
var doc_str: ?[]const u8 = null;
const def_str = switch (decl_handle.decl.*) {
.ast_node => |node| def: {
if (try server.analyser.resolveVarDeclAlias(.{ .node = node, .handle = handle })) |result| {
return try hoverSymbol(server, result, markup_kind);
}
doc_str = try Analyser.getDocComments(server.arena.allocator(), tree, node, markup_kind);
var buf: [1]Ast.Node.Index = undefined;
if (tree.fullVarDecl(node)) |var_decl| {
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| {
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.first_doc_comment) |doc_comments| {
doc_str = try Analyser.collectDocComments(server.arena.allocator(), handle.tree, doc_comments, markup_kind, false);
}
const first_token = ast.paramFirstToken(tree, param);
const last_token = ast.paramLastToken(tree, param);
const start = offsets.tokenToIndex(tree, first_token);
const end = offsets.tokenToLoc(tree, last_token).end;
break :def tree.source[start..end];
},
.pointer_payload,
.array_payload,
.array_index,
.switch_payload,
.label_decl,
.error_token,
=> 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]) {
.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 hover_text: []const u8 = undefined;
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 });
} 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 });
}
return hover_text;
}
pub fn hoverDefinitionLabel(server: *Server, pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}!?types.Hover {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const markup_kind: types.MarkupKind = if (server.client_capabilities.hover_supports_md) .markdown else .plaintext;
const decl = (try Server.getLabelGlobal(pos_index, handle)) orelse return null;
return .{
.contents = .{
.MarkupContent = .{
.kind = markup_kind,
.value = (try hoverSymbol(server, decl, markup_kind)) orelse return null,
},
},
};
}
pub fn hoverDefinitionBuiltin(server: *Server, pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}!?types.Hover {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const name = Server.identifierFromPosition(pos_index, handle.*);
if (name.len == 0) return null;
const builtin = for (data.builtins) |builtin| {
if (std.mem.eql(u8, builtin.name[1..], name)) {
break builtin;
}
} else return null;
var contents: std.ArrayListUnmanaged(u8) = .{};
var writer = contents.writer(server.arena.allocator());
if (std.mem.eql(u8, name, "cImport")) blk: {
const index = for (handle.cimports.items(.node), 0..) |cimport_node, index| {
const main_token = handle.tree.nodes.items(.main_token)[cimport_node];
const cimport_loc = offsets.tokenToLoc(handle.tree, main_token);
if (cimport_loc.start <= pos_index and pos_index <= cimport_loc.end) break index;
} else break :blk;
const source = handle.cimports.items(.source)[index];
try writer.print(
\\```c
\\{s}
\\```
\\
, .{source});
}
try writer.print(
\\```zig
\\{s}
\\```
\\{s}
, .{ builtin.signature, builtin.documentation });
return types.Hover{
.contents = .{
.MarkupContent = .{
.kind = .markdown,
.value = contents.items,
},
},
};
}
pub fn hoverDefinitionGlobal(server: *Server, pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}!?types.Hover {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const markup_kind: types.MarkupKind = if (server.client_capabilities.hover_supports_md) .markdown else .plaintext;
const decl = (try server.getSymbolGlobal(pos_index, handle)) orelse return null;
return .{
.contents = .{
.MarkupContent = .{
.kind = markup_kind,
.value = (try hoverSymbol(server, decl, markup_kind)) orelse return null,
},
},
};
}
pub fn hoverDefinitionFieldAccess(
server: *Server,
handle: *const DocumentStore.Handle,
source_index: usize,
loc: offsets.Loc,
) error{OutOfMemory}!?types.Hover {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const markup_kind: types.MarkupKind = if (server.client_capabilities.hover_supports_md) .markdown else .plaintext;
const decls = (try server.getSymbolFieldAccesses(handle, source_index, loc)) orelse return null;
var content = std.ArrayListUnmanaged(types.MarkedString){};
for (decls) |decl| {
try content.append(server.arena.allocator(), .{
.string = (try hoverSymbol(server, decl, markup_kind)) orelse continue,
});
}
// Yes, this is deprecated; the issue is that there's no better
// solution for multiple hover entries :(
return .{
.contents = .{
.array_of_MarkedString = try content.toOwnedSlice(server.arena.allocator()),
},
};
}
pub fn hover(server: *Server, source_index: usize, handle: *const DocumentStore.Handle) !?types.Hover {
const pos_context = try Analyser.getPositionContext(server.arena.allocator(), handle.text, source_index, true);
const response = switch (pos_context) {
.builtin => try hoverDefinitionBuiltin(server, source_index, handle),
.var_access => try hoverDefinitionGlobal(server, source_index, handle),
.field_access => |loc| try hoverDefinitionFieldAccess(server, handle, source_index, loc),
.label => try hoverDefinitionLabel(server, source_index, handle),
else => null,
};
return response;
}