257 lines
9.4 KiB
Zig
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;
|
|
}
|