Skip to content
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
29 changes: 28 additions & 1 deletion src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,32 @@ pub fn fmtEscapedSnippet(raw_text: []const u8) std.fmt.Alt([]const u8, SnippetEs
return .{ .data = raw_text };
}

pub fn renderBuiltinFunctionSignature(
arena: std.mem.Allocator,
name: []const u8,
builtin_data: version_data.Builtin,
multi_line: bool,
) error{OutOfMemory}![]u8 {
var signature: std.ArrayList(u8) = .empty;
try signature.appendSlice(arena, name);
try signature.append(arena, '(');
if (multi_line) try signature.append(arena, '\n');
for (builtin_data.parameters, 0..) |parameter, i| {
if (multi_line) {
try signature.appendSlice(arena, " ");
} else if (i != 0) {
try signature.appendSlice(arena, ", ");
}
try signature.appendSlice(arena, parameter.signature);
if (multi_line) {
try signature.appendSlice(arena, ",\n");
}
}
try signature.appendSlice(arena, ") ");
try signature.appendSlice(arena, builtin_data.return_type);
return signature.items;
}

pub fn isInstanceCall(
analyser: *Analyser,
call_handle: *DocumentStore.Handle,
Expand Down Expand Up @@ -6338,7 +6364,8 @@ pub fn resolveExpressionTypeFromAncestors(
const index = std.mem.findScalar(Ast.Node.Index, params, node) orelse return null;
if (index >= data.parameters.len) return null;
const parameter = data.parameters[index];
const type_str = parameter.type orelse return null;
const colon_index = std.mem.findScalar(u8, parameter.signature, ':') orelse return null;
const type_str = parameter.signature[colon_index + 2 ..];
return analyser.instanceStdBuiltinType(type_str);
}
},
Expand Down
24 changes: 20 additions & 4 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -540,28 +540,43 @@ fn completeBuiltin(builder: *Builder) error{OutOfMemory}!void {
// The generated snippet is not being escaped here because we can
// assume that the builtin name has no characters that need to be escaped.
if (builtin.parameters.len == 0) break :snippet try std.fmt.allocPrint(builder.arena, "{s}()", .{name});
if (use_placeholders) break :snippet builtin.snippet;
if (use_placeholders) {
var snippet: std.ArrayList(u8) = .empty;
try snippet.print(builder.arena, "{s}(", .{name});
for (builtin.parameters, 1..) |param, i| {
if (i != 1) try snippet.appendSlice(builder.arena, ", ");
try snippet.print(builder.arena, "${{{d}:{f}}}", .{ i, Analyser.fmtEscapedSnippet(param.signature) });
}
try snippet.append(builder.arena, ')');
break :snippet snippet.items;
}
break :snippet try std.fmt.allocPrint(builder.arena, "{s}(${{1:}})", .{name});
},
};
const insert_text_format: types.InsertTextFormat = switch (new_text_format) {
.only_name => .PlainText,
.snippet => .Snippet,
};
const detail = try Analyser.renderBuiltinFunctionSignature(
builder.arena,
name,
builtin,
builtin.parameters.len > 3,
);

builder.completions.appendAssumeCapacity(.{
.label = name,
.kind = .Function,
.filterText = name[1..],
.detail = builtin.signature,
.detail = detail,
.insertTextFormat = insert_text_format,
.textEdit = if (builder.server.client_capabilities.supports_completion_insert_replace_support)
.{ .insert_replace_edit = .{ .newText = new_text[1..], .insert = insert_range, .replace = replace_range } }
else
.{ .text_edit = .{ .newText = new_text[1..], .range = insert_range } },
.documentation = .{
.markup_content = .{
.kind = .markdown,
.kind = if (builder.server.client_capabilities.completion_doc_supports_md) .markdown else .plaintext,
.value = builtin.documentation,
},
},
Expand Down Expand Up @@ -1618,7 +1633,8 @@ fn resolveBuiltinFnArg(
const builtin = version_data.builtins.get(name) orelse return null;
if (arg_index >= builtin.parameters.len) return null;
const param = builtin.parameters[arg_index];
const builtin_name = param.type orelse return null;
const colon_index = std.mem.findScalar(u8, param.signature, ':') orelse return null;
const builtin_name = param.signature[colon_index + 2 ..];
return analyser.instanceStdBuiltinType(builtin_name);
}

Expand Down
10 changes: 8 additions & 2 deletions src/features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -250,21 +250,27 @@ fn hoverDefinitionBuiltin(
}

const builtin = data.builtins.get(name) orelse return null;
const signature = try Analyser.renderBuiltinFunctionSignature(
arena,
name,
builtin,
builtin.parameters.len > 3,
);

switch (markup_kind) {
.plaintext, .unknown_value => {
try contents.print(arena,
\\{s}
\\{s}
, .{ builtin.signature, builtin.documentation });
, .{ signature, builtin.documentation });
},
.markdown => {
try contents.print(arena,
\\```zig
\\{s}
\\```
\\{s}
, .{ builtin.signature, builtin.documentation });
, .{ signature, builtin.documentation });
},
}

Expand Down
66 changes: 31 additions & 35 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -162,20 +162,24 @@ const Builder = struct {
node_tag: Ast.Node.Tag,
token_index: Ast.TokenIndex,
label: []const u8,
tooltip: []const u8,
tooltip_text: []const u8,
tooltip_noalias: bool,
tooltip_comptime: bool,
) !void {
// adding tooltip_noalias & tooltip_comptime to InlayHint should be enough
const tooltip_text = blk: {
if (tooltip.len == 0) break :blk "";
const prefix = if (tooltip_noalias) if (tooltip_comptime) "noalias comptime " else "noalias " else if (tooltip_comptime) "comptime " else "";
const tooltip: ?types.MarkupContent = tooltip: {
if (tooltip_text.len == 0) break :tooltip null;
const prefix = if (tooltip_noalias) "noalias " else if (tooltip_comptime) "comptime " else "";

if (self.hover_kind == .markdown) {
break :blk try std.fmt.allocPrint(self.arena, "```zig\n{s}{s}\n```", .{ prefix, tooltip });
}
const text = switch (self.hover_kind) {
.markdown => try std.fmt.allocPrint(self.arena, "```zig\n{s}{s}\n```", .{ prefix, tooltip_text }),
.plaintext, .unknown_value => try std.fmt.allocPrint(self.arena, "{s}{s}", .{ prefix, tooltip_text }),
};

break :blk try std.fmt.allocPrint(self.arena, "{s}{s}", .{ prefix, tooltip });
break :tooltip .{
.kind = self.hover_kind,
.value = text,
};
};

try self.hints.append(self.arena, .{
Expand All @@ -185,10 +189,7 @@ const Builder = struct {
self.handle.tree.tokenStart(token_index),
.label = try std.fmt.allocPrint(self.arena, "{s}:", .{label}),
.kind = .Parameter,
.tooltip = .{
.kind = self.hover_kind,
.value = tooltip_text,
},
.tooltip = tooltip,
});
}

Expand Down Expand Up @@ -292,36 +293,31 @@ fn writeBuiltinHint(builder: *Builder, parameters: []const Ast.Node.Index, param

const len = @min(params.len, parameters.len);
for (params[0..len], parameters[0..len]) |param, parameter| {
const signature = param.signature;
if (signature.len == 0) continue;

const colonIndex = std.mem.findScalar(u8, signature, ':');
const type_expr = param.type orelse "";

// TODO: parse noalias/comptime/label in config_gen.zig
var maybe_label: ?[]const u8 = null;
var no_alias = false;
var comp_time = false;

var it = std.mem.splitScalar(u8, signature[0 .. colonIndex orelse signature.len], ' ');
while (it.next()) |item| {
if (item.len == 0) continue;
maybe_label = item;

no_alias = no_alias or std.mem.eql(u8, item, "noalias");
comp_time = comp_time or std.mem.eql(u8, item, "comptime");
var signature = param.signature;
if (std.mem.eql(u8, signature, "...")) return;

var is_comptime = false;
var is_noalias = false;
if (std.mem.cutPrefix(u8, signature, "comptime")) |rest| {
signature = rest;
is_comptime = true;
} else if (std.mem.cutPrefix(u8, signature, "noalias")) |rest| {
signature = rest;
is_noalias = true;
}
signature = std.mem.trimStart(u8, signature, &std.ascii.whitespace);

const label = maybe_label orelse return;
if (label.len == 0 or std.mem.eql(u8, label, "...")) return;
const colon_index = std.mem.findScalar(u8, signature, ':');
const label = signature[0 .. colon_index orelse signature.len];
const tooltip = if (colon_index) |i| signature[i + 1 ..] else "";

try builder.appendParameterHint(
tree.nodeTag(parameter),
tree.firstToken(parameter),
label,
std.mem.trim(u8, type_expr, " \t\n"),
no_alias,
comp_time,
tooltip,
is_noalias,
is_comptime,
);
}
}
Expand Down
27 changes: 18 additions & 9 deletions src/features/signature_help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -193,21 +193,30 @@ pub fn getSignatureInfo(

const expr_last_token = curr_token - 1;
if (tree.tokenTag(expr_last_token) == .builtin) {
// Builtin token, find the builtin and construct signature information.
const builtin = data.builtins.get(tree.tokenSlice(expr_last_token)) orelse return null;
const param_infos = try arena.alloc(
types.SignatureHelp.Signature.Parameter,
builtin.parameters.len,
);
const builtin_name = tree.tokenSlice(expr_last_token);
const builtin = data.builtins.get(builtin_name) orelse return null;

const param_infos = try arena.alloc(types.SignatureHelp.Signature.Parameter, builtin.parameters.len);
for (param_infos, builtin.parameters) |*info, parameter| {
info.* = .{
.label = .{ .string = parameter.signature },
.documentation = null,
.documentation = if (parameter.documentation) |doc|
.{ .markup_content = .{ .kind = markup_kind, .value = doc } }
else
null,
};
}
return types.SignatureHelp.Signature{
.label = builtin.signature,
.documentation = .{ .string = builtin.documentation },
.label = try Analyser.renderBuiltinFunctionSignature(
arena,
builtin_name,
builtin,
false,
),
.documentation = .{ .markup_content = .{
.kind = markup_kind,
.value = builtin.documentation,
} },
.parameters = param_infos,
.activeParameter = paren_commas,
};
Expand Down
Loading