Started signature help implementation
This commit is contained in:
parent
f45a934f50
commit
9cc8085699
@ -10,7 +10,6 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
|
||||
const mode = b.standardReleaseOptions();
|
||||
const exe = b.addExecutable("zls", "src/main.zig");
|
||||
|
||||
exe.addBuildOption(
|
||||
[]const u8,
|
||||
"data_version",
|
||||
|
@ -376,22 +376,6 @@ fn isBlock(tree: ast.Tree, node: ast.Node.Index) bool {
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns `true` when the given `node` is one of the call tags
|
||||
fn isCall(tree: ast.Tree, node: ast.Node.Index) bool {
|
||||
return switch (tree.nodes.items(.tag)[node]) {
|
||||
.call,
|
||||
.call_comma,
|
||||
.call_one,
|
||||
.call_one_comma,
|
||||
.async_call,
|
||||
.async_call_comma,
|
||||
.async_call_one,
|
||||
.async_call_one_comma,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn findReturnStatementInternal(
|
||||
tree: ast.Tree,
|
||||
fn_decl: ast.full.FnProto,
|
||||
@ -3094,19 +3078,7 @@ fn makeScopeInternal(
|
||||
.async_call_one_comma,
|
||||
=> {
|
||||
var buf: [1]ast.Node.Index = undefined;
|
||||
const call: ast.full.Call = switch (node_tag) {
|
||||
.async_call,
|
||||
.async_call_comma,
|
||||
.call,
|
||||
.call_comma,
|
||||
=> tree.callFull(node_idx),
|
||||
.async_call_one,
|
||||
.async_call_one_comma,
|
||||
.call_one,
|
||||
.call_one_comma,
|
||||
=> tree.callOne(&buf, node_idx),
|
||||
else => unreachable,
|
||||
};
|
||||
const call = callFull(tree, node_idx, &buf).?;
|
||||
|
||||
try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, call.ast.fn_expr);
|
||||
for (call.ast.params) |param|
|
||||
|
31
src/ast.zig
31
src/ast.zig
@ -857,6 +857,21 @@ pub fn isBuiltinCall(tree: ast.Tree, node: ast.Node.Index) bool {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isCall(tree: ast.Tree, node: ast.Node.Index) bool {
|
||||
return switch (tree.nodes.items(.tag)[node]) {
|
||||
.call,
|
||||
.call_comma,
|
||||
.call_one,
|
||||
.call_one_comma,
|
||||
.async_call,
|
||||
.async_call_comma,
|
||||
.async_call_one,
|
||||
.async_call_one_comma,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fnProto(tree: ast.Tree, node: ast.Node.Index, buf: *[1]ast.Node.Index) ?ast.full.FnProto {
|
||||
return switch (tree.nodes.items(.tag)[node]) {
|
||||
.fn_proto => tree.fnProto(node),
|
||||
@ -867,3 +882,19 @@ pub fn fnProto(tree: ast.Tree, node: ast.Node.Index, buf: *[1]ast.Node.Index) ?a
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn callFull(tree: ast.Tree, node: ast.Node.Index, buf: *[1]ast.Node.Index) ?ast.full.Call {
|
||||
return switch (tree.nodes.items(.tag)[node]) {
|
||||
.async_call,
|
||||
.async_call_comma,
|
||||
.call,
|
||||
.call_comma,
|
||||
=> tree.callFull(node),
|
||||
.async_call_one,
|
||||
.async_call_one_comma,
|
||||
.call_one,
|
||||
.call_one_comma,
|
||||
=> tree.callOne(buf, node),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
90
src/main.zig
90
src/main.zig
@ -124,6 +124,9 @@ const edit_not_applied_response =
|
||||
const no_completions_response =
|
||||
\\,"result":{"isIncomplete":false,"items":[]}}
|
||||
;
|
||||
const no_signatures_response =
|
||||
\\,"result":{"signatures":[]}
|
||||
;
|
||||
const no_semantic_tokens_response =
|
||||
\\,"result":{"data":[]}}
|
||||
;
|
||||
@ -1280,7 +1283,8 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
|
||||
},
|
||||
.capabilities = .{
|
||||
.signatureHelpProvider = .{
|
||||
.triggerCharacters = &[_][]const u8{ "(", "," },
|
||||
.triggerCharacters = &.{"("},
|
||||
.retriggerCharacters = &.{","},
|
||||
},
|
||||
.textDocumentSync = .Full,
|
||||
.renameProvider = true,
|
||||
@ -1398,39 +1402,85 @@ fn semanticTokensFullHandler(arena: *std.heap.ArenaAllocator, id: types.RequestI
|
||||
}
|
||||
}
|
||||
|
||||
fn completionHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.Completion, config: Config) !void {
|
||||
fn completionHandler(
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
id: types.RequestId,
|
||||
req: requests.Completion,
|
||||
config: Config,
|
||||
) !void {
|
||||
const handle = document_store.getHandle(req.params.textDocument.uri) orelse {
|
||||
logger.warn("Trying to complete in non existent document {s}", .{req.params.textDocument.uri});
|
||||
return try respondGeneric(id, no_completions_response);
|
||||
};
|
||||
|
||||
if (req.params.position.character >= 0) {
|
||||
const doc_position = try offsets.documentPosition(handle.document, req.params.position, offset_encoding);
|
||||
const pos_context = try analysis.documentPositionContext(arena, handle.document, doc_position);
|
||||
const use_snippets = config.enable_snippets and client_capabilities.supports_snippets;
|
||||
if (req.params.position.character == 0)
|
||||
return try respondGeneric(id, no_completions_response);
|
||||
|
||||
switch (pos_context) {
|
||||
.builtin => try completeBuiltin(arena, id, config),
|
||||
.var_access, .empty => try completeGlobal(arena, id, doc_position.absolute_index, handle, config),
|
||||
.field_access => |range| try completeFieldAccess(arena, id, handle, doc_position, range, config),
|
||||
.global_error_set => try completeError(arena, id, handle, config),
|
||||
.enum_literal => try completeDot(arena, id, handle, config),
|
||||
.label => try completeLabel(arena, id, doc_position.absolute_index, handle, config),
|
||||
else => try respondGeneric(id, no_completions_response),
|
||||
}
|
||||
} else {
|
||||
try respondGeneric(id, no_completions_response);
|
||||
const doc_position = try offsets.documentPosition(handle.document, req.params.position, offset_encoding);
|
||||
const pos_context = try analysis.documentPositionContext(arena, handle.document, doc_position);
|
||||
const use_snippets = config.enable_snippets and client_capabilities.supports_snippets;
|
||||
|
||||
switch (pos_context) {
|
||||
.builtin => try completeBuiltin(arena, id, config),
|
||||
.var_access, .empty => try completeGlobal(arena, id, doc_position.absolute_index, handle, config),
|
||||
.field_access => |range| try completeFieldAccess(arena, id, handle, doc_position, range, config),
|
||||
.global_error_set => try completeError(arena, id, handle, config),
|
||||
.enum_literal => try completeDot(arena, id, handle, config),
|
||||
.label => try completeLabel(arena, id, doc_position.absolute_index, handle, config),
|
||||
else => try respondGeneric(id, no_completions_response),
|
||||
}
|
||||
}
|
||||
|
||||
fn signatureHelperHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, config: Config) !void {
|
||||
fn signatureHelperHandler(
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
id: types.RequestId,
|
||||
req: requests.SignatureHelp,
|
||||
config: Config,
|
||||
) !void {
|
||||
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);
|
||||
};
|
||||
|
||||
if (req.params.position.character == 0)
|
||||
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 => {},
|
||||
}
|
||||
|
||||
// TODO Implement this
|
||||
try respondGeneric(id,
|
||||
\\,"result":{"signatures":[]}}
|
||||
);
|
||||
}
|
||||
|
||||
fn gotoHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.GotoDefinition, config: Config, resolve_alias: bool) !void {
|
||||
fn gotoHandler(
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
id: types.RequestId,
|
||||
req: requests.GotoDefinition,
|
||||
config: Config,
|
||||
resolve_alias: bool,
|
||||
) !void {
|
||||
const handle = document_store.getHandle(req.params.textDocument.uri) orelse {
|
||||
logger.warn("Trying to go to definition in non existent document {s}", .{req.params.textDocument.uri});
|
||||
return try respondGeneric(id, null_result_response);
|
||||
@ -1612,7 +1662,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", void, signatureHelperHandler },
|
||||
.{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelperHandler },
|
||||
.{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler },
|
||||
.{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler },
|
||||
.{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler },
|
||||
|
@ -18,12 +18,22 @@ fn Default(comptime T: type, comptime default_value: T) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ErrorUnwrappedReturnOf(comptime func: anytype) type {
|
||||
return switch (@typeInfo(@TypeOf(func))) {
|
||||
.Fn, .BoundFn => |fn_info| switch (@typeInfo(fn_info.return_type.?)) {
|
||||
.ErrorUnion => |err_union| err_union.payload,
|
||||
else => |T| return T,
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn Transform(comptime Original: type, comptime transform_fn: anytype) type {
|
||||
return struct {
|
||||
pub const original_type = Original;
|
||||
pub const transform = transform_fn;
|
||||
|
||||
value: @TypeOf(transform(@as(Original, undefined))),
|
||||
value: ErrorUnwrappedReturnOf(transform_fn),
|
||||
};
|
||||
}
|
||||
|
||||
@ -101,26 +111,34 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu
|
||||
}
|
||||
} else if (T == std.json.Value) {
|
||||
out.* = value;
|
||||
} else {
|
||||
switch (T) {
|
||||
bool => {
|
||||
if (value != .Bool) return error.MalformedJson;
|
||||
out.* = value.Bool;
|
||||
},
|
||||
i64 => {
|
||||
if (value != .Integer) return error.MalformedJson;
|
||||
out.* = value.Integer;
|
||||
},
|
||||
f64 => {
|
||||
if (value != .Float) return error.MalformedJson;
|
||||
out.* = value.Float;
|
||||
},
|
||||
[]const u8 => {
|
||||
if (value != .String) return error.MalformedJson;
|
||||
out.* = value.String;
|
||||
},
|
||||
else => @compileError("Invalid type " ++ @typeName(T)),
|
||||
}
|
||||
} else if (comptime std.meta.trait.is(.Enum)(T)) {
|
||||
const info = @typeInfo(T).Enum;
|
||||
if (info.layout != .Auto)
|
||||
@compileError("Only auto layout enums are allowed");
|
||||
|
||||
const TagType = info.tag_type;
|
||||
if (value != .Integer) return error.MalformedJson;
|
||||
out.* = std.meta.intToEnum(
|
||||
T,
|
||||
std.math.cast(TagType, value.Integer) catch return error.MalformedJson,
|
||||
) catch return error.MalformedJson;
|
||||
} else if (comptime std.meta.trait.is(.Int)(T)) {
|
||||
if (value != .Integer) return error.MalformedJson;
|
||||
out.* = std.math.cast(T, value.Integer) catch return error.MalformedJson;
|
||||
} else switch (T) {
|
||||
bool => {
|
||||
if (value != .Bool) return error.MalformedJson;
|
||||
out.* = value.Bool;
|
||||
},
|
||||
f64 => {
|
||||
if (value != .Float) return error.MalformedJson;
|
||||
out.* = value.Float;
|
||||
},
|
||||
[]const u8 => {
|
||||
if (value != .String) return error.MalformedJson;
|
||||
out.* = value.String;
|
||||
},
|
||||
else => @compileError("Invalid type " ++ @typeName(T)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,6 +222,23 @@ const TextDocumentIdentifierPositionRequest = struct {
|
||||
},
|
||||
};
|
||||
|
||||
pub const SignatureHelp = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
position: types.Position,
|
||||
context: ?struct {
|
||||
triggerKind: enum {
|
||||
invoked = 1,
|
||||
trigger_character = 2,
|
||||
content_change = 3,
|
||||
},
|
||||
triggerCharacter: ?[]const u8,
|
||||
isRetrigger: bool,
|
||||
activeSignatureHelp: ?types.SignatureHelp,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub const Completion = TextDocumentIdentifierPositionRequest;
|
||||
pub const GotoDefinition = TextDocumentIdentifierPositionRequest;
|
||||
pub const GotoDeclaration = TextDocumentIdentifierPositionRequest;
|
||||
|
@ -289,12 +289,32 @@ pub const WorkspaceFolder = struct {
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
pub const SignatureInformation = struct {
|
||||
pub const ParameterInformation = struct {
|
||||
// TODO Can also send a pair of encoded offsets
|
||||
label: []const u8,
|
||||
documentation: ?MarkupContent,
|
||||
};
|
||||
|
||||
label: []const u8,
|
||||
documentation: ?MarkupContent,
|
||||
parameters: ?[]const ParameterInformation,
|
||||
activeParameter: ?u32,
|
||||
};
|
||||
|
||||
pub const SignatureHelp = struct {
|
||||
signatures: ?[]const SignatureInformation,
|
||||
activeSignature: ?u32,
|
||||
activeParameter: ?u32,
|
||||
};
|
||||
|
||||
// Only includes options we set in our initialize result.
|
||||
const InitializeResult = struct {
|
||||
offsetEncoding: []const u8,
|
||||
capabilities: struct {
|
||||
signatureHelpProvider: struct {
|
||||
triggerCharacters: []const []const u8,
|
||||
retriggerCharacters: []const []const u8,
|
||||
},
|
||||
textDocumentSync: enum {
|
||||
None = 0,
|
||||
|
Loading…
Reference in New Issue
Block a user