Implemented the signature help request.

Refreshes builtin data, added 0.7.1 builtins
This commit is contained in:
Alexandros Naskos 2021-04-02 20:49:01 +03:00
parent 9cc8085699
commit 7f432d8715
No known key found for this signature in database
GPG Key ID: 02BF2E72B0EA32D2
11 changed files with 3864 additions and 683 deletions

4
.github/README.md vendored
View File

@ -66,14 +66,14 @@ zig build config # Configure ZLS
<!-- If this table grows too large, then delete this one and move it all over to the Wiki page about building from source. -->
| Option | Type | Default Value | What it Does |
| --- | --- | --- | --- |
| `-Ddata_version` | `string` (master or 0.7.0) | master | The data file version. This selects the files in the `src/data` folder that correspond to the Zig version being served.|
| `-Ddata_version` | `string` (master, 0.7.0 or 0.7.1) | master | The data file version. This selects the files in the `src/data` folder that correspond to the Zig version being served.|
### Configuration Options
You can configure zls by providing a zls.json file.
zls will look for a zls.json configuration file in multiple locations with the following priority:
- In the local configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders#folder-list))
- In the same directory as the executable
- In the global configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders#folder-list))
The following options are currently available.

View File

@ -63,14 +63,14 @@ zig build config # Configure ZLS
<!-- If this table grows too large, then delete this one and move it all over to the Wiki page about building from source. -->
| Option | Type | Default Value | What it Does |
| --- | --- | --- | --- |
| `-Ddata_version` | `string` (master or 0.7.0) | master | The data file version. This selects the files in the `src/data` folder that correspond to the Zig version being served.|
| `-Ddata_version` | `string` (master, 0.7.0 or 0.7.1) | master | The data file version. This selects the files in the `src/data` folder that correspond to the Zig version being served.|
### Configuration Options
You can configure zls by running `zls config`.
zls will look for a zls.json configuration file in multiple locations with the following priority:
- In the local configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders#folder-list))
- In the same directory as the executable
- In the global configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders#folder-list))
The following options are currently available.

View File

@ -1,6 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
// const build_options = @import("build_options")
var builder: *std.build.Builder = undefined;
@ -13,7 +12,7 @@ pub fn build(b: *std.build.Builder) !void {
exe.addBuildOption(
[]const u8,
"data_version",
b.option([]const u8, "data_version", "The data version - either 0.7.0 or master.") orelse "master",
b.option([]const u8, "data_version", "The data version - 0.7.0, 0.7.1 or master.") orelse "master",
);
exe.addPackage(.{ .name = "known-folders", .path = "src/known-folders/known-folders.zig" });

View File

@ -2234,6 +2234,21 @@ pub fn iterateSymbolsGlobal(
return try iterateSymbolsGlobalInternal(store, arena, handle, source_index, callback, context, &use_trail);
}
pub fn innermostScope(handle: DocumentStore.Handle, source_index: usize) ast.Node.Index {
var current = handle.document_scope.scopes[0].data.container;
if (handle.document_scope.scopes.len == 1) return current;
for (handle.document_scope.scopes[1..]) |scope| {
if (source_index >= scope.range.start and source_index <= scope.range.end) {
switch (scope.data) {
.container, .function, .block => |node| current = node,
else => {},
}
}
}
return current;
}
pub fn innermostContainer(handle: *DocumentStore.Handle, source_index: usize) TypeWithHandle {
var current = handle.document_scope.scopes[0].data.container;
if (handle.document_scope.scopes.len == 1) return TypeWithHandle.typeVal(.{ .node = current, .handle = handle });

File diff suppressed because it is too large Load Diff

1861
src/data/0.7.1.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -41,6 +41,7 @@ console.log(
signature: []const u8,
snippet: []const u8,
documentation: []const u8,
arguments: []const []const u8,
};
pub const builtins = [_]Builtin{` +
@ -49,11 +50,13 @@ pub const builtins = [_]Builtin{` +
const first_paren_idx = builtin.code.indexOf('(');
var snippet = builtin.code.substr(0, first_paren_idx + 1);
var rest = builtin.code.substr(first_paren_idx + 1);
var args = [];
if (rest[0] == ')') {
snippet += ')';
} else {
snippet += "${1:"
args.push("");
var arg_idx = 2;
var paren_depth = 1;
@ -69,10 +72,12 @@ pub const builtins = [_]Builtin{` +
}
} else if (char == '"') {
snippet += "\\\"";
args[args.length - 1] += "\\\"";
continue;
} else if (char == ',' && paren_depth == 1) {
snippet += "}, ${" + arg_idx + ':';
arg_idx += 1;
args.push("");
skip_space = true;
continue;
} else if (char == ' ' && skip_space) {
@ -80,6 +85,7 @@ pub const builtins = [_]Builtin{` +
}
snippet += char;
args[args.length - 1] += char;
skip_space = false;
}
}
@ -89,6 +95,9 @@ pub const builtins = [_]Builtin{` +
.signature = "${builtin.code.replaceAll('"', "\\\"")}",
.snippet = "${snippet}",
.documentation =
\\\\${builtin.documentation.split('\n').join("\n \\\\") + '\n'} },`;
\\\\${builtin.documentation.split('\n').join("\n \\\\")}
,
.arguments = &.{${args.map(x => "\n \"" + x + "\"").join(",") + ((args.length > 0) ? ",\n " : "")}},
},`;
}).join('\n') + "\n};\n"
);

File diff suppressed because it is too large Load Diff

View File

@ -125,7 +125,7 @@ const no_completions_response =
\\,"result":{"isIncomplete":false,"items":[]}}
;
const no_signatures_response =
\\,"result":{"signatures":[]}
\\,"result":{"signatures":[]}}
;
const no_semantic_tokens_response =
\\,"result":{"data":[]}}
@ -570,7 +570,7 @@ fn nodeToCompletion(
}
}
fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []const u8 {
pub fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []const u8 {
const text = handle.document.text;
if (pos_index + 1 >= text.len) return &[0]u8{};
@ -757,7 +757,11 @@ fn hoverDefinitionBuiltin(arena: *std.heap.ArenaAllocator, id: types.RequestId,
.id = id,
.result = .{
.Hover = .{
.contents = .{ .value = try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```\n{s}", .{ builtin.signature, builtin.documentation }) },
.contents = .{ .value = try std.fmt.allocPrint(
&arena.allocator,
"```zig\n{s}\n```\n{s}",
.{ builtin.signature, builtin.documentation },
) },
},
},
});
@ -1431,12 +1435,13 @@ fn completionHandler(
}
}
fn signatureHelperHandler(
fn signatureHelpHandler(
arena: *std.heap.ArenaAllocator,
id: types.RequestId,
req: requests.SignatureHelp,
config: Config,
) !void {
const getSignatureInfo = @import("signature_help.zig").getSignatureInfo;
const handle = document_store.getHandle(req.params.textDocument.uri) orelse {
logger.warn("Trying to get signature help in non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(id, no_signatures_response);
@ -1446,32 +1451,23 @@ fn signatureHelperHandler(
return try respondGeneric(id, no_signatures_response);
const doc_position = try offsets.documentPosition(handle.document, req.params.position, offset_encoding);
const last_idx = doc_position.absolute_index;
const source = handle.document.text[0..last_idx];
// We use a text based method since we cannot rely on a valid call node being here
// or even that it is the innermost call if it is.
const trigger = source[last_idx - 1];
// Check for comment, multiline string literal lines and skip
// Tokenize line forwards, pull in previous line if we need it etc.
switch (trigger) {
'(' => {
// go backwards while char in 'a'...'z','A'...'Z','0'...'9', '_', check for keywords
// resolve function, send result
// then add support for @"..." in the identifiers
},
',' => {
// Go backwards, keep a stack for (), [], {}, "" (with \" support)
// first ( to be hit when
},
else => {},
if (try getSignatureInfo(
&document_store,
arena,
handle,
doc_position.absolute_index,
data,
)) |sig_info| {
return try send(arena, types.Response{
.id = id,
.result = .{ .SignatureHelp = .{
.signatures = &[1]types.SignatureInformation{sig_info},
.activeSignature = 0,
.activeParameter = sig_info.activeParameter,
} },
});
}
// TODO Implement this
try respondGeneric(id,
\\,"result":{"signatures":[]}}
);
return try respondGeneric(id, no_signatures_response);
}
fn gotoHandler(
@ -1662,7 +1658,7 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso
.{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler },
.{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler },
.{ "textDocument/completion", requests.Completion, completionHandler },
.{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelperHandler },
.{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelpHandler },
.{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler },
.{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler },
.{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler },

326
src/signature_help.zig Normal file
View File

@ -0,0 +1,326 @@
const std = @import("std");
const analysis = @import("analysis.zig");
const offsets = @import("offsets.zig");
const DocumentStore = @import("document_store.zig");
const types = @import("types.zig");
const ast = std.zig.ast;
const Token = std.zig.Token;
const identifierFromPosition = @import("main.zig").identifierFromPosition;
usingnamespace @import("ast.zig");
fn fnProtoToSignatureHelp(
arena: *std.heap.ArenaAllocator,
is_self_call: bool,
commas: u32,
tree: ast.Tree,
proto: ast.full.FnProto,
) !types.SignatureInformation {
const ParameterInformation = types.SignatureInformation.ParameterInformation;
const token_starts = tree.tokens.items(.start);
const alloc = &arena.allocator;
const arg_idx = commas + @boolToInt(is_self_call);
const label = analysis.getFunctionSignature(tree, proto);
const proto_comments = types.MarkupContent{ .value = if (try analysis.getDocComments(
alloc,
tree,
proto.ast.fn_token,
.Markdown,
)) |dc|
dc
else
"" };
var params = std.ArrayListUnmanaged(ParameterInformation){};
var param_it = proto.iterate(tree);
while (param_it.next()) |param| {
const param_comments = if (param.first_doc_comment) |dc|
types.MarkupContent{ .value = try analysis.collectDocComments(
alloc,
tree,
dc,
.Markdown,
) }
else
null;
var param_label_start: usize = 0;
var param_label_end: usize = 0;
if (param.comptime_noalias) |cn| {
param_label_start = token_starts[cn];
param_label_end = param_label_start + tree.tokenSlice(cn).len;
}
if (param.name_token) |nt| {
if (param_label_start == 0)
param_label_start = token_starts[nt];
param_label_end = token_starts[nt] + tree.tokenSlice(nt).len;
}
if (param.anytype_ellipsis3) |ae| {
if (param_label_start == 0)
param_label_start = token_starts[ae];
param_label_end = token_starts[ae] + tree.tokenSlice(ae).len;
}
if (param.type_expr != 0) {
if (param_label_start == 0)
param_label_start = token_starts[tree.firstToken(param.type_expr)];
const last_param_tok = lastToken(tree, param.type_expr);
param_label_end = token_starts[last_param_tok] + tree.tokenSlice(last_param_tok).len;
}
const param_label = tree.source[param_label_start..param_label_end];
try params.append(alloc, .{
.label = param_label,
.documentation = param_comments,
});
}
return types.SignatureInformation{
.label = label,
.documentation = proto_comments,
.parameters = params.items,
.activeParameter = arg_idx,
};
}
pub fn getSignatureInfo(
document_store: *DocumentStore,
arena: *std.heap.ArenaAllocator,
handle: *DocumentStore.Handle,
absolute_index: usize,
comptime data: type,
) !?types.SignatureInformation {
const innermost_block = analysis.innermostScope(handle.*, absolute_index);
const tree = handle.tree;
const token_tags = tree.tokens.items(.tag);
const token_starts = tree.tokens.items(.start);
const first_token = tree.firstToken(innermost_block);
const last_token = blk: {
if (token_starts[0] >= absolute_index)
return null;
var i: u32 = 1;
while (i < token_tags.len) : (i += 1) {
if (token_starts[i] >= absolute_index) {
break :blk i - 1;
}
}
break :blk @truncate(u32, token_tags.len - 1);
};
const StackSymbol = enum {
l_paren,
r_paren,
l_brace,
r_brace,
l_bracket,
r_bracket,
fn from(tag: Token.Tag) @This() {
return switch (tag) {
.l_paren => .l_paren,
.r_paren => .r_paren,
.l_brace => .l_brace,
.r_brace => .r_brace,
.l_bracket => .l_bracket,
.r_bracket => .r_bracket,
else => unreachable,
};
}
};
const alloc = &arena.allocator;
var symbol_stack = try std.ArrayListUnmanaged(StackSymbol).initCapacity(alloc, 8);
var curr_commas: u32 = 0;
var comma_stack = try std.ArrayListUnmanaged(u32).initCapacity(alloc, 4);
var curr_token = last_token;
while (curr_token >= first_token) : (curr_token -= 1) {
switch (token_tags[curr_token]) {
.comma => curr_commas += 1,
.l_brace => {
curr_commas = comma_stack.popOrNull() orelse 0;
if (symbol_stack.items.len != 0) {
const peek_sym = symbol_stack.items[symbol_stack.items.len - 1];
switch (peek_sym) {
.r_brace => {
_ = symbol_stack.pop();
continue;
},
.r_bracket, .r_paren => {
return null;
},
else => {},
}
}
try symbol_stack.append(alloc, .l_brace);
},
.l_bracket => {
curr_commas = comma_stack.popOrNull() orelse 0;
if (symbol_stack.items.len != 0) {
const peek_sym = symbol_stack.items[symbol_stack.items.len - 1];
switch (peek_sym) {
.r_bracket => {
_ = symbol_stack.pop();
continue;
},
.r_brace, .r_paren => {
return null;
},
else => {},
}
}
try symbol_stack.append(alloc, .l_bracket);
},
.l_paren => {
const paren_commas = curr_commas;
curr_commas = comma_stack.popOrNull() orelse 0;
if (symbol_stack.items.len != 0) {
const peek_sym = symbol_stack.items[symbol_stack.items.len - 1];
switch (peek_sym) {
.r_paren => {
_ = symbol_stack.pop();
continue;
},
.r_brace, .r_bracket => {
return null;
},
else => {},
}
}
// Try to find a function expression or a builtin identifier
// and send a response if found
if (curr_token == first_token)
return null;
const expr_last_token = curr_token - 1;
if (token_tags[expr_last_token] == .builtin) {
for (data.builtins) |builtin| {
if (std.mem.eql(u8, builtin.name, tree.tokenSlice(expr_last_token))) {
const param_infos = try alloc.alloc(
types.SignatureInformation.ParameterInformation,
builtin.arguments.len,
);
for (param_infos) |*info, i| {
info.* = .{
.label = builtin.arguments[i],
.documentation = null,
};
}
return types.SignatureInformation{
.label = builtin.signature,
.documentation = .{
.value = builtin.documentation,
},
.parameters = param_infos,
.activeParameter = paren_commas,
};
}
}
return null;
}
var state: union(enum) {
any,
in_bracket: u32,
in_paren: u32,
} = .any;
var i = expr_last_token;
const expr_first_token = while (i > first_token) : (i -= 1) {
switch (state) {
.in_bracket => |*count| if (token_tags[i] == .r_bracket) {
count.* += 1;
} else if (token_tags[i] == .l_bracket) {
count.* -= 1;
if (count.* == 0)
state = .any;
},
.in_paren => |*count| if (token_tags[i] == .r_paren) {
count.* += 1;
} else if (token_tags[i] == .l_paren) {
count.* -= 1;
if (count.* == 0)
state = .any;
},
.any => switch (token_tags[i]) {
.r_bracket => state = .{ .in_bracket = 1 },
.r_paren => state = .{ .in_paren = 1 },
.identifier,
.period,
.period_asterisk,
=> {},
else => break i + 1,
},
}
} else first_token + 1;
if (state != .any or expr_first_token > expr_last_token) {
try symbol_stack.append(alloc, .l_paren);
continue;
}
const expr_start = token_starts[expr_first_token];
const last_token_slice = tree.tokenSlice(expr_last_token);
const expr_end = token_starts[expr_last_token] + last_token_slice.len;
const expr_source = tree.source[expr_start..expr_end];
var tokenizer = std.zig.Tokenizer.init(expr_source);
if (try analysis.getFieldAccessType(
document_store,
arena,
handle,
expr_start,
&tokenizer,
)) |result| {
const type_handle = result.unwrapped orelse result.original;
var node = switch (type_handle.type.data) {
.other => |n| n,
else => return null,
};
// @@@ TODO Resolve alias
// TODO Better detection
var is_self_call = expr_last_token > expr_first_token and
token_tags[expr_last_token] == .identifier and
token_tags[expr_last_token - 1] == .period;
var buf: [1]ast.Node.Index = undefined;
if (fnProto(type_handle.handle.tree, node, &buf)) |proto| {
return try fnProtoToSignatureHelp(
arena,
is_self_call,
paren_commas,
type_handle.handle.tree,
proto,
);
}
const name = identifierFromPosition(expr_end - 1, handle.*);
if (name.len == 0) return null;
const decl_handle = (try analysis.lookupSymbolContainer(
document_store,
arena,
.{ .node = node, .handle = type_handle.handle },
name,
true,
)) orelse return null;
node = switch (decl_handle.decl.*) {
.ast_node => |n| n,
else => return null,
};
if (fnProto(type_handle.handle.tree, node, &buf)) |proto| {
return try fnProtoToSignatureHelp(
arena,
is_self_call,
paren_commas,
type_handle.handle.tree,
proto,
);
}
}
},
.r_brace, .r_paren, .r_bracket => |tag| {
try comma_stack.append(alloc, curr_commas);
curr_commas = 0;
try symbol_stack.append(alloc, StackSymbol.from(tag));
},
else => {},
}
}
return null;
}

View File

@ -31,6 +31,7 @@ pub const Hover = struct {
/// Params of a response (result)
pub const ResponseParams = union(enum) {
SignatureHelp: SignatureHelp,
CompletionList: CompletionList,
Location: Location,
Hover: Hover,