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 mode = b.standardReleaseOptions();
|
||||||
const exe = b.addExecutable("zls", "src/main.zig");
|
const exe = b.addExecutable("zls", "src/main.zig");
|
||||||
|
|
||||||
exe.addBuildOption(
|
exe.addBuildOption(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
"data_version",
|
"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(
|
fn findReturnStatementInternal(
|
||||||
tree: ast.Tree,
|
tree: ast.Tree,
|
||||||
fn_decl: ast.full.FnProto,
|
fn_decl: ast.full.FnProto,
|
||||||
@ -3094,19 +3078,7 @@ fn makeScopeInternal(
|
|||||||
.async_call_one_comma,
|
.async_call_one_comma,
|
||||||
=> {
|
=> {
|
||||||
var buf: [1]ast.Node.Index = undefined;
|
var buf: [1]ast.Node.Index = undefined;
|
||||||
const call: ast.full.Call = switch (node_tag) {
|
const call = callFull(tree, node_idx, &buf).?;
|
||||||
.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,
|
|
||||||
};
|
|
||||||
|
|
||||||
try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, call.ast.fn_expr);
|
try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, call.ast.fn_expr);
|
||||||
for (call.ast.params) |param|
|
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 {
|
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]) {
|
return switch (tree.nodes.items(.tag)[node]) {
|
||||||
.fn_proto => tree.fnProto(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,
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
68
src/main.zig
68
src/main.zig
@ -124,6 +124,9 @@ const edit_not_applied_response =
|
|||||||
const no_completions_response =
|
const no_completions_response =
|
||||||
\\,"result":{"isIncomplete":false,"items":[]}}
|
\\,"result":{"isIncomplete":false,"items":[]}}
|
||||||
;
|
;
|
||||||
|
const no_signatures_response =
|
||||||
|
\\,"result":{"signatures":[]}
|
||||||
|
;
|
||||||
const no_semantic_tokens_response =
|
const no_semantic_tokens_response =
|
||||||
\\,"result":{"data":[]}}
|
\\,"result":{"data":[]}}
|
||||||
;
|
;
|
||||||
@ -1280,7 +1283,8 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
|
|||||||
},
|
},
|
||||||
.capabilities = .{
|
.capabilities = .{
|
||||||
.signatureHelpProvider = .{
|
.signatureHelpProvider = .{
|
||||||
.triggerCharacters = &[_][]const u8{ "(", "," },
|
.triggerCharacters = &.{"("},
|
||||||
|
.retriggerCharacters = &.{","},
|
||||||
},
|
},
|
||||||
.textDocumentSync = .Full,
|
.textDocumentSync = .Full,
|
||||||
.renameProvider = true,
|
.renameProvider = true,
|
||||||
@ -1398,13 +1402,20 @@ 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 {
|
const handle = document_store.getHandle(req.params.textDocument.uri) orelse {
|
||||||
logger.warn("Trying to complete in non existent document {s}", .{req.params.textDocument.uri});
|
logger.warn("Trying to complete in non existent document {s}", .{req.params.textDocument.uri});
|
||||||
return try respondGeneric(id, no_completions_response);
|
return try respondGeneric(id, no_completions_response);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (req.params.position.character >= 0) {
|
if (req.params.position.character == 0)
|
||||||
|
return try respondGeneric(id, no_completions_response);
|
||||||
|
|
||||||
const doc_position = try offsets.documentPosition(handle.document, req.params.position, offset_encoding);
|
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 pos_context = try analysis.documentPositionContext(arena, handle.document, doc_position);
|
||||||
const use_snippets = config.enable_snippets and client_capabilities.supports_snippets;
|
const use_snippets = config.enable_snippets and client_capabilities.supports_snippets;
|
||||||
@ -1418,19 +1429,58 @@ fn completionHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
|
|||||||
.label => try completeLabel(arena, id, doc_position.absolute_index, 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),
|
||||||
}
|
}
|
||||||
} 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
|
// TODO Implement this
|
||||||
try respondGeneric(id,
|
try respondGeneric(id,
|
||||||
\\,"result":{"signatures":[]}}
|
\\,"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 {
|
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});
|
logger.warn("Trying to go to definition in non existent document {s}", .{req.params.textDocument.uri});
|
||||||
return try respondGeneric(id, null_result_response);
|
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/didClose", requests.CloseDocument, closeDocumentHandler },
|
||||||
.{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler },
|
.{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler },
|
||||||
.{ "textDocument/completion", requests.Completion, completionHandler },
|
.{ "textDocument/completion", requests.Completion, completionHandler },
|
||||||
.{ "textDocument/signatureHelp", void, signatureHelperHandler },
|
.{ "textDocument/signatureHelp", requests.SignatureHelp, signatureHelperHandler },
|
||||||
.{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler },
|
.{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler },
|
||||||
.{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler },
|
.{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler },
|
||||||
.{ "textDocument/implementation", 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 {
|
fn Transform(comptime Original: type, comptime transform_fn: anytype) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub const original_type = Original;
|
pub const original_type = Original;
|
||||||
pub const transform = transform_fn;
|
pub const transform = transform_fn;
|
||||||
|
|
||||||
value: @TypeOf(transform(@as(Original, undefined))),
|
value: ErrorUnwrappedReturnOf(transform_fn),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,16 +111,25 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu
|
|||||||
}
|
}
|
||||||
} else if (T == std.json.Value) {
|
} else if (T == std.json.Value) {
|
||||||
out.* = value;
|
out.* = value;
|
||||||
} else {
|
} else if (comptime std.meta.trait.is(.Enum)(T)) {
|
||||||
switch (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 => {
|
bool => {
|
||||||
if (value != .Bool) return error.MalformedJson;
|
if (value != .Bool) return error.MalformedJson;
|
||||||
out.* = value.Bool;
|
out.* = value.Bool;
|
||||||
},
|
},
|
||||||
i64 => {
|
|
||||||
if (value != .Integer) return error.MalformedJson;
|
|
||||||
out.* = value.Integer;
|
|
||||||
},
|
|
||||||
f64 => {
|
f64 => {
|
||||||
if (value != .Float) return error.MalformedJson;
|
if (value != .Float) return error.MalformedJson;
|
||||||
out.* = value.Float;
|
out.* = value.Float;
|
||||||
@ -121,7 +140,6 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu
|
|||||||
},
|
},
|
||||||
else => @compileError("Invalid type " ++ @typeName(T)),
|
else => @compileError("Invalid type " ++ @typeName(T)),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fromDynamicTree(arena: *std.heap.ArenaAllocator, comptime T: type, value: std.json.Value) error{ MalformedJson, OutOfMemory }!T {
|
pub fn fromDynamicTree(arena: *std.heap.ArenaAllocator, comptime T: type, value: std.json.Value) error{ MalformedJson, OutOfMemory }!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 Completion = TextDocumentIdentifierPositionRequest;
|
||||||
pub const GotoDefinition = TextDocumentIdentifierPositionRequest;
|
pub const GotoDefinition = TextDocumentIdentifierPositionRequest;
|
||||||
pub const GotoDeclaration = TextDocumentIdentifierPositionRequest;
|
pub const GotoDeclaration = TextDocumentIdentifierPositionRequest;
|
||||||
|
@ -289,12 +289,32 @@ pub const WorkspaceFolder = struct {
|
|||||||
name: []const u8,
|
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.
|
// Only includes options we set in our initialize result.
|
||||||
const InitializeResult = struct {
|
const InitializeResult = struct {
|
||||||
offsetEncoding: []const u8,
|
offsetEncoding: []const u8,
|
||||||
capabilities: struct {
|
capabilities: struct {
|
||||||
signatureHelpProvider: struct {
|
signatureHelpProvider: struct {
|
||||||
triggerCharacters: []const []const u8,
|
triggerCharacters: []const []const u8,
|
||||||
|
retriggerCharacters: []const []const u8,
|
||||||
},
|
},
|
||||||
textDocumentSync: enum {
|
textDocumentSync: enum {
|
||||||
None = 0,
|
None = 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user