Merge pull request #303 from InterplanetaryEngineer/master

Improve testing infrastructure, refactor makeScopeInternal and resolveTypeOfNodeInternal, and support "catch" scopes
This commit is contained in:
Alexandros Naskos 2021-04-08 07:59:58 -07:00 committed by GitHub
commit 09e6d9a4c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 600 additions and 719 deletions

File diff suppressed because it is too large Load Diff

View File

@ -138,8 +138,8 @@ fn fullIf(tree: Tree, info: full.If.Ast) full.If {
} }
pub fn ifFull(tree: Tree, node: Node.Index) full.If { pub fn ifFull(tree: Tree, node: Node.Index) full.If {
assert(tree.nodes.items(.tag)[node] == .@"if");
const data = tree.nodes.items(.data)[node]; const data = tree.nodes.items(.data)[node];
if (tree.nodes.items(.tag)[node] == .@"if") {
const extra = tree.extraData(data.rhs, Node.If); const extra = tree.extraData(data.rhs, Node.If);
return fullIf(tree, .{ return fullIf(tree, .{
.cond_expr = data.lhs, .cond_expr = data.lhs,
@ -147,17 +147,15 @@ pub fn ifFull(tree: Tree, node: Node.Index) full.If {
.else_expr = extra.else_expr, .else_expr = extra.else_expr,
.if_token = tree.nodes.items(.main_token)[node], .if_token = tree.nodes.items(.main_token)[node],
}); });
} } else {
pub fn ifSimple(tree: Tree, node: Node.Index) full.If {
assert(tree.nodes.items(.tag)[node] == .if_simple); assert(tree.nodes.items(.tag)[node] == .if_simple);
const data = tree.nodes.items(.data)[node];
return fullIf(tree, .{ return fullIf(tree, .{
.cond_expr = data.lhs, .cond_expr = data.lhs,
.then_expr = data.rhs, .then_expr = data.rhs,
.else_expr = 0, .else_expr = 0,
.if_token = tree.nodes.items(.main_token)[node], .if_token = tree.nodes.items(.main_token)[node],
}); });
}
} }
fn fullWhile(tree: Tree, info: full.While.Ast) full.While { fn fullWhile(tree: Tree, info: full.While.Ast) full.While {

View File

@ -38,7 +38,7 @@ pub fn log(
} }
// After shutdown, pipe output to stderr // After shutdown, pipe output to stderr
if (!keep_running) { if (!keep_running) {
std.debug.print("[{s}-{s}] " ++ format, .{ @tagName(message_level), @tagName(scope) } ++ args); std.debug.print("[{s}-{s}] " ++ format ++ "\n", .{ @tagName(message_level), @tagName(scope) } ++ args);
return; return;
} }
@ -619,37 +619,32 @@ fn hoverSymbol(
const tree = handle.tree; const tree = handle.tree;
const hover_kind: types.MarkupContent.Kind = if (client_capabilities.hover_supports_md) .Markdown else .PlainText; const hover_kind: types.MarkupContent.Kind = if (client_capabilities.hover_supports_md) .Markdown else .PlainText;
const md_string = switch (decl_handle.decl.*) { var doc_str: ?[]const u8 = null;
.ast_node => |node| ast_node: {
const def_str = switch (decl_handle.decl.*) {
.ast_node => |node| def: {
if (try analysis.resolveVarDeclAlias(&document_store, arena, .{ .node = node, .handle = handle })) |result| { if (try analysis.resolveVarDeclAlias(&document_store, arena, .{ .node = node, .handle = handle })) |result| {
return try hoverSymbol(id, arena, result); return try hoverSymbol(id, arena, result);
} }
doc_str = try analysis.getDocComments(&arena.allocator, tree, node, hover_kind);
const doc_str = if (try analysis.getDocComments(&arena.allocator, tree, node, hover_kind)) |str|
str
else
"";
var buf: [1]std.zig.ast.Node.Index = undefined; var buf: [1]std.zig.ast.Node.Index = undefined;
const signature_str = if (analysis.varDecl(tree, node)) |var_decl| blk: {
break :blk analysis.getVariableSignature(tree, var_decl);
} else if (analysis.fnProto(tree, node, &buf)) |fn_proto| blk: {
break :blk analysis.getFunctionSignature(tree, fn_proto);
} else if (analysis.containerField(tree, node)) |field| blk: {
break :blk analysis.getContainerFieldSignature(tree, field);
} else analysis.nodeToString(tree, node) orelse
return try respondGeneric(id, null_result_response);
break :ast_node if (hover_kind == .Markdown) if (analysis.varDecl(tree, node)) |var_decl| {
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```\n{s}", .{ signature_str, doc_str }) break :def analysis.getVariableSignature(tree, var_decl);
else } else if (analysis.fnProto(tree, node, &buf)) |fn_proto| {
try std.fmt.allocPrint(&arena.allocator, "{s}\n{s}", .{ signature_str, doc_str }); break :def analysis.getFunctionSignature(tree, fn_proto);
} else if (analysis.containerField(tree, node)) |field| {
break :def analysis.getContainerFieldSignature(tree, field);
} else {
break :def analysis.nodeToString(tree, node) orelse
return try respondGeneric(id, null_result_response);
}
}, },
.param_decl => |param| param_decl: { .param_decl => |param| def: {
const doc_str = if (param.first_doc_comment) |doc_comments| if (param.first_doc_comment) |doc_comments| {
try analysis.collectDocComments(&arena.allocator, handle.tree, doc_comments, hover_kind) doc_str = try analysis.collectDocComments(&arena.allocator, handle.tree, doc_comments, hover_kind);
else }
"";
const first_token = param.first_doc_comment orelse const first_token = param.first_doc_comment orelse
param.comptime_noalias orelse param.comptime_noalias orelse
@ -659,42 +654,35 @@ fn hoverSymbol(
const start = offsets.tokenLocation(tree, first_token).start; const start = offsets.tokenLocation(tree, first_token).start;
const end = offsets.tokenLocation(tree, last_token).end; const end = offsets.tokenLocation(tree, last_token).end;
const signature_str = tree.source[start..end]; break :def tree.source[start..end];
break :param_decl if (hover_kind == .Markdown)
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```\n{s}", .{ signature_str, doc_str })
else
try std.fmt.allocPrint(&arena.allocator, "{s}\n{s}", .{ signature_str, doc_str });
},
.pointer_payload => |payload| if (hover_kind == .Markdown)
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```", .{tree.tokenSlice(payload.name)})
else
try std.fmt.allocPrint(&arena.allocator, "{s}", .{tree.tokenSlice(payload.name)}),
.array_payload => |payload| if (hover_kind == .Markdown)
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```", .{handle.tree.tokenSlice(payload.identifier)})
else
try std.fmt.allocPrint(&arena.allocator, "{s}", .{handle.tree.tokenSlice(payload.identifier)}),
.array_index => |payload| if (hover_kind == .Markdown)
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```", .{handle.tree.tokenSlice(payload)})
else
try std.fmt.allocPrint(&arena.allocator, "{s}", .{handle.tree.tokenSlice(payload)}),
.switch_payload => |payload| if (hover_kind == .Markdown)
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```", .{tree.tokenSlice(payload.node)})
else
try std.fmt.allocPrint(&arena.allocator, "{s}", .{tree.tokenSlice(payload.node)}),
.label_decl => |label_decl| block: {
const source = tree.tokenSlice(label_decl);
break :block if (hover_kind == .Markdown)
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```", .{source})
else
try std.fmt.allocPrint(&arena.allocator, "```{s}```", .{source});
}, },
.pointer_payload => |payload| tree.tokenSlice(payload.name),
.array_payload => |payload| handle.tree.tokenSlice(payload.identifier),
.array_index => |payload| handle.tree.tokenSlice(payload),
.switch_payload => |payload| tree.tokenSlice(payload.node),
.label_decl => |label_decl| tree.tokenSlice(label_decl),
}; };
var hover_text: []const u8 = undefined;
if (hover_kind == .Markdown) {
hover_text =
if (doc_str) |doc|
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```\n{s}", .{ def_str, doc })
else
try std.fmt.allocPrint(&arena.allocator, "```zig\n{s}\n```", .{def_str});
} else {
hover_text =
if (doc_str) |doc|
try std.fmt.allocPrint(&arena.allocator, "{s}\n{s}", .{ def_str, doc })
else
def_str;
}
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
.result = .{ .result = .{
.Hover = .{ .Hover = .{
.contents = .{ .value = md_string }, .contents = .{ .value = hover_text },
}, },
}, },
}); });
@ -739,11 +727,13 @@ fn hoverDefinitionBuiltin(arena: *std.heap.ArenaAllocator, id: types.RequestId,
.id = id, .id = id,
.result = .{ .result = .{
.Hover = .{ .Hover = .{
.contents = .{ .value = try std.fmt.allocPrint( .contents = .{
.value = try std.fmt.allocPrint(
&arena.allocator, &arena.allocator,
"```zig\n{s}\n```\n{s}", "```zig\n{s}\n```\n{s}",
.{ builtin.signature, builtin.documentation }, .{ builtin.signature, builtin.documentation },
) }, ),
},
}, },
}, },
}); });
@ -1161,6 +1151,7 @@ fn completeError(
) !void { ) !void {
const completions = try document_store.errorCompletionItems(arena, handle); const completions = try document_store.errorCompletionItems(arena, handle);
truncateCompletions(completions, config.max_detail_length); truncateCompletions(completions, config.max_detail_length);
logger.debug("Completing error:", .{});
try send(arena, types.Response{ try send(arena, types.Response{
.id = id, .id = id,
@ -1348,6 +1339,7 @@ fn openDocumentHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req
const handle = try document_store.openDocument(req.params.textDocument.uri, req.params.textDocument.text); const handle = try document_store.openDocument(req.params.textDocument.uri, req.params.textDocument.text);
try publishDiagnostics(arena, handle.*, config); try publishDiagnostics(arena, handle.*, config);
if (client_capabilities.supports_semantic_tokens)
try semanticTokensFullHandler(arena, id, .{ .params = .{ .textDocument = .{ .uri = req.params.textDocument.uri } } }, config); try semanticTokensFullHandler(arena, id, .{ .params = .{ .textDocument = .{ .uri = req.params.textDocument.uri } } }, config);
} }
@ -1374,10 +1366,10 @@ fn closeDocumentHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, re
} }
fn semanticTokensFullHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.SemanticTokensFull, config: Config) (error{OutOfMemory} || std.fs.File.WriteError)!void { fn semanticTokensFullHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.SemanticTokensFull, config: Config) (error{OutOfMemory} || std.fs.File.WriteError)!void {
if (config.enable_semantic_tokens and client_capabilities.supports_semantic_tokens) { if (config.enable_semantic_tokens) blk: {
const handle = document_store.getHandle(req.params.textDocument.uri) orelse { const handle = document_store.getHandle(req.params.textDocument.uri) orelse {
logger.warn("Trying to get semantic tokens of non existent document {s}", .{req.params.textDocument.uri}); logger.warn("Trying to get semantic tokens of non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(id, no_semantic_tokens_response); break :blk;
}; };
const token_array = try semantic_tokens.writeAllSemanticTokens(arena, &document_store, handle, offset_encoding); const token_array = try semantic_tokens.writeAllSemanticTokens(arena, &document_store, handle, offset_encoding);
@ -1388,6 +1380,7 @@ fn semanticTokensFullHandler(arena: *std.heap.ArenaAllocator, id: types.RequestI
.result = .{ .SemanticTokensFull = .{ .data = token_array } }, .result = .{ .SemanticTokensFull = .{ .data = token_array } },
}); });
} }
return try respondGeneric(id, no_semantic_tokens_response);
} }
fn completionHandler( fn completionHandler(
@ -1444,11 +1437,13 @@ fn signatureHelpHandler(
)) |sig_info| { )) |sig_info| {
return try send(arena, types.Response{ return try send(arena, types.Response{
.id = id, .id = id,
.result = .{ .SignatureHelp = .{ .result = .{
.SignatureHelp = .{
.signatures = &[1]types.SignatureInformation{sig_info}, .signatures = &[1]types.SignatureInformation{sig_info},
.activeSignature = 0, .activeSignature = 0,
.activeParameter = sig_info.activeParameter, .activeParameter = sig_info.activeParameter,
} }, },
},
}); });
} }
return try respondGeneric(id, no_signatures_response); return try respondGeneric(id, no_signatures_response);
@ -1717,12 +1712,16 @@ var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_
pub fn main() anyerror!void { pub fn main() anyerror!void {
defer _ = gpa_state.deinit(); defer _ = gpa_state.deinit();
defer keep_running = false;
allocator = &gpa_state.allocator; allocator = &gpa_state.allocator;
analysis.init(allocator);
defer analysis.deinit();
// Check arguments. // Check arguments.
var args_it = std.process.args(); var args_it = std.process.args();
defer args_it.deinit(); defer args_it.deinit();
const prog_name = try args_it.next(allocator) orelse unreachable; const prog_name = try args_it.next(allocator) orelse @panic("Could not find self argument");
allocator.free(prog_name); allocator.free(prog_name);
while (args_it.next(allocator)) |maybe_arg| { while (args_it.next(allocator)) |maybe_arg| {
@ -1732,7 +1731,6 @@ pub fn main() anyerror!void {
actual_log_level = .debug; actual_log_level = .debug;
std.debug.print("Enabled debug logging\n", .{}); std.debug.print("Enabled debug logging\n", .{});
} else if (std.mem.eql(u8, arg, "config")) { } else if (std.mem.eql(u8, arg, "config")) {
keep_running = false;
try setup.wizard(allocator); try setup.wizard(allocator);
return; return;
} else { } else {

View File

@ -244,7 +244,7 @@ fn symbolReferencesInternal(
.@"if", .@"if",
.if_simple, .if_simple,
=> { => {
const if_node: ast.full.If = if (node_tags[node] == .@"if") ifFull(tree, node) else ifSimple(tree, node); const if_node = ifFull(tree, node);
try symbolReferencesInternal(arena, store, .{ .node = if_node.ast.cond_expr, .handle = handle }, decl, encoding, context, handler); try symbolReferencesInternal(arena, store, .{ .node = if_node.ast.cond_expr, .handle = handle }, decl, encoding, context, handler);
try symbolReferencesInternal(arena, store, .{ .node = if_node.ast.then_expr, .handle = handle }, decl, encoding, context, handler); try symbolReferencesInternal(arena, store, .{ .node = if_node.ast.then_expr, .handle = handle }, decl, encoding, context, handler);

View File

@ -696,7 +696,7 @@ fn writeNodeTokens(
.@"if", .@"if",
.if_simple, .if_simple,
=> { => {
const if_node: ast.full.If = if (tag == .@"if") ifFull(tree, node) else ifSimple(tree, node); const if_node = ifFull(tree, node);
try writeToken(builder, if_node.ast.if_token, .keyword); try writeToken(builder, if_node.ast.if_token, .keyword);
try await @asyncCall(child_frame, {}, writeNodeTokens, .{ builder, arena, store, if_node.ast.cond_expr }); try await @asyncCall(child_frame, {}, writeNodeTokens, .{ builder, arena, store, if_node.ast.cond_expr });

View File

@ -4,72 +4,142 @@ const headerPkg = @import("header");
const suffix = if (std.builtin.os.tag == .windows) ".exe" else ""; const suffix = if (std.builtin.os.tag == .windows) ".exe" else "";
const allocator = std.heap.page_allocator; const allocator = std.heap.page_allocator;
const initialize_message = const initialize_msg =
\\{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":[{"uri":"file://./tests", "name":"root"}]}} \\{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":[{"uri":"file://./tests", "name":"root"}]}
;
const initialize_msg_offs =
\\{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"offsetEncoding":["utf-16", "utf-8"],"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":null}
; ;
const initialized_message = const Server = struct {
\\{"jsonrpc":"2.0","method":"initialized","params":{}} process: *std.ChildProcess,
; request_id: u32 = 1,
const shutdown_message = fn start(initialization: []const u8, expect: ?[]const u8) !Server {
\\{"jsonrpc":"2.0", "id":"STDWN", "method":"shutdown","params":{}} var server = Server{ .process = try startZls() };
;
fn sendRequest(req: []const u8, process: *std.ChildProcess) !void { try server.request("initialize", initialization, expect);
try process.stdin.?.writer().print("Content-Length: {}\r\n\r\n", .{req.len}); try server.request("initialized", "{}", null);
try process.stdin.?.writeAll(req); return server;
} }
fn request(
self: *Server,
method: []const u8,
params: []const u8,
expect: ?[]const u8,
) !void {
self.request_id += 1;
const req = try std.fmt.allocPrint(allocator,
\\{{"jsonrpc":"2.0","id":{},"method":"{s}","params":{s}}}
, .{ self.request_id, method, params });
const to_server = self.process.stdin.?.writer();
try to_server.print("Content-Length: {}\r\n\r\n", .{req.len});
try to_server.writeAll(req);
const expected = expect orelse return;
var from_server = self.process.stdout.?.reader();
fn readResponses(process: *std.ChildProcess, expected_responses: anytype) !void {
var seen = std.mem.zeroes([expected_responses.len]bool);
while (true) { while (true) {
const header = headerPkg.readRequestHeader(allocator, process.stdout.?.reader()) catch |err| { const header = headerPkg.readRequestHeader(allocator, from_server) catch |err| {
switch (err) { switch (err) {
error.EndOfStream => break, error.EndOfStream => break,
else => return err, else => return err,
} }
}; };
defer header.deinit(allocator); defer header.deinit(allocator);
var resonse_bytes = try allocator.alloc(u8, header.content_length);
var stdout_mem = try allocator.alloc(u8, header.content_length); defer allocator.free(resonse_bytes);
defer allocator.free(stdout_mem); if ((try from_server.readAll(resonse_bytes)) != header.content_length) {
return error.InvalidResponse;
const stdout_bytes = stdout_mem[0..try process.stdout.?.reader().readAll(stdout_mem)];
inline for (expected_responses) |resp, idx| {
if (std.mem.eql(u8, resp, stdout_bytes)) {
if (seen[idx]) @panic("Expected response already received.");
seen[idx] = true;
} }
} // std.debug.print("{s}\n", .{resonse_bytes});
std.debug.print("GOT MESSAGE: {s}\n", .{stdout_bytes});
const json_fmt = "{\"jsonrpc\":\"2.0\",\"id\":";
if (!std.mem.startsWith(u8, resonse_bytes, json_fmt)) {
try extractError(resonse_bytes);
continue;
} }
comptime var idx = 0; const rest = resonse_bytes[json_fmt.len..];
inline while (idx < expected_responses.len) : (idx += 1) { const id_end = std.mem.indexOfScalar(u8, rest, ',') orelse return error.InvalidResponse;
if (!seen[idx]) {
std.debug.print("Response `{s}` not received.", .{expected_responses[idx]});
return error.ExpectedResponse;
}
}
}
const id = try std.fmt.parseInt(u32, rest[0..id_end], 10);
if (id != self.request_id) {
continue;
}
const result = ",\"result\":";
const msg = rest[id_end + result.len .. rest.len - 1];
if (std.mem.eql(u8, msg, expected)) {
return;
} else {
const mismatch = std.mem.indexOfDiff(u8, expected, msg) orelse 0;
std.debug.print("==> Expected:\n{s}\n==> Got: (Mismatch in position {})\n{s}\n", .{ expected, mismatch, msg });
return error.InvalidResponse;
}
}
}
fn extractError(msg: []const u8) !void {
const log_request =
\\"method":"window/logMessage","params":{"type":
;
if (std.mem.indexOf(u8, msg, log_request)) |log_msg| {
const rest = msg[log_msg + log_request.len ..];
const level = rest[0];
if (level <= '2') {
std.debug.print("{s}\n", .{rest[13 .. rest.len - 3]});
if (level <= '1') {
return error.ServerError;
}
}
}
}
fn shutdown(self: *Server) void {
self.request("shutdown", "{}", null) catch @panic("Could not send shutdown request");
waitNoError(self.process) catch |err| @panic("Server error");
self.process.deinit();
}
};
fn startZls() !*std.ChildProcess { fn startZls() !*std.ChildProcess {
std.debug.print("\n", .{});
var process = try std.ChildProcess.init(&[_][]const u8{"zig-cache/bin/zls" ++ suffix}, allocator); var process = try std.ChildProcess.init(&[_][]const u8{"zig-cache/bin/zls" ++ suffix}, allocator);
process.stdin_behavior = .Pipe; process.stdin_behavior = .Pipe;
process.stdout_behavior = .Pipe; process.stdout_behavior = .Pipe;
process.stderr_behavior = std.ChildProcess.StdIo.Inherit; process.stderr_behavior = .Pipe; //std.ChildProcess.StdIo.Inherit;
process.spawn() catch |err| { process.spawn() catch |err| {
std.log.debug("Failed to spawn zls process, error: {}\n", .{err}); std.debug.print("Failed to spawn zls process, error: {}\n", .{err});
return err; return err;
}; };
return process; return process;
} }
fn waitNoError(process: *std.ChildProcess) !void { fn waitNoError(process: *std.ChildProcess) !void {
const stderr = std.io.getStdErr().writer();
const err_in = process.stderr.?.reader();
var buf: [4096]u8 = undefined;
while (true) {
const line = err_in.readUntilDelimiterOrEof(&buf, '\n') catch |err| switch (err) {
error.StreamTooLong => {
std.debug.print("skipping very long line\n", .{});
continue;
},
else => return err,
} orelse break;
if (std.mem.startsWith(u8, line, "[debug")) continue;
try stderr.writeAll(line);
try stderr.writeByte('\n');
}
const result = try process.wait(); const result = try process.wait();
switch (result) { switch (result) {
.Exited => |code| if (code == 0) { .Exited => |code| if (code == 0) {
return; return;
@ -79,80 +149,49 @@ fn waitNoError(process: *std.ChildProcess) !void {
return error.ShutdownWithError; return error.ShutdownWithError;
} }
fn consumeOutputAndWait(process: *std.ChildProcess, expected_responses: anytype) !void {
process.stdin.?.close();
process.stdin = null;
try readResponses(process, expected_responses);
try waitNoError(process);
process.deinit();
}
test "Open file, ask for semantic tokens" { test "Open file, ask for semantic tokens" {
var process = try startZls(); var server = try Server.start(initialize_msg, null);
try sendRequest(initialize_message, process); defer server.shutdown();
try sendRequest(initialized_message, process);
const new_file_req = try server.request("textDocument/didOpen",
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file://./tests/test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");"}}} \\{"textDocument":{"uri":"file://./tests/test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");"}}
; , null);
try sendRequest(new_file_req, process); try server.request("textDocument/semanticTokens/full",
const sem_toks_req = \\{"textDocument":{"uri":"file://./tests/test.zig"}}
\\{"jsonrpc":"2.0","id":2,"method":"textDocument/semanticTokens/full","params":{"textDocument":{"uri":"file://./tests/test.zig"}}} ,
; \\{"data":[0,0,5,7,0,0,6,3,0,33,0,4,1,11,0,0,2,7,12,0,0,8,5,9,0]}
try sendRequest(sem_toks_req, process); );
try sendRequest(shutdown_message, process);
try consumeOutputAndWait(process, .{
\\{"jsonrpc":"2.0","id":0,"result":{"data":[0,0,5,7,0,0,6,3,0,33,0,4,1,11,0,0,2,7,12,0,0,8,5,9,0]}}
});
} }
test "Requesting a completion in an empty file" { test "Request completion in an empty file" {
var process = try startZls(); var server = try Server.start(initialize_msg, null);
try sendRequest(initialize_message, process); defer server.shutdown();
try sendRequest(initialized_message, process);
const new_file_req = try server.request("textDocument/didOpen",
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":""}}} \\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":""}}}
; , null);
try sendRequest(new_file_req, process); try server.request("textDocument/completion",
const completion_req = \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}}
\\{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}}} , null);
;
try sendRequest(completion_req, process);
try sendRequest(shutdown_message, process);
try consumeOutputAndWait(process, .{});
} }
test "Requesting a completion with no trailing whitespace" { test "Request completion with no trailing whitespace" {
var process = try startZls(); var server = try Server.start(initialize_msg, null);
try sendRequest(initialize_message, process); defer server.shutdown();
try sendRequest(initialized_message, process);
const new_file_req = try server.request("textDocument/didOpen",
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}}} \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}}
; , null);
try sendRequest(new_file_req, process); try server.request("textDocument/completion",
const completion_req = \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
\\{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}} ,
; \\{"isIncomplete":false,"items":[{"label":"std","kind":21,"textEdit":null,"filterText":null,"insertText":"std","insertTextFormat":1,"detail":"const std = @import(\"std\")","documentation":null}]}
try sendRequest(completion_req, process); );
try sendRequest(shutdown_message, process);
try consumeOutputAndWait(process, .{
\\{"jsonrpc":"2.0","id":2,"result":{"isIncomplete":false,"items":[{"label":"std","kind":21,"textEdit":null,"filterText":null,"insertText":"std","insertTextFormat":1,"detail":"const std = @import(\"std\")","documentation":null}]}}
});
} }
const initialize_message_offs = test "Request utf-8 offset encoding" {
\\{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"offsetEncoding":["utf-16", "utf-8"],"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":null}} var server = try Server.start(initialize_msg_offs,
; \\{"offsetEncoding":"utf-8","capabilities":{"signatureHelpProvider":{"triggerCharacters":["("],"retriggerCharacters":[","]},"textDocumentSync":1,"renameProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"]},"documentHighlightProvider":false,"hoverProvider":true,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"implementationProvider":false,"referencesProvider":true,"documentSymbolProvider":true,"colorProvider":false,"documentFormattingProvider":true,"documentRangeFormattingProvider":false,"foldingRangeProvider":false,"selectionRangeProvider":false,"workspaceSymbolProvider":false,"rangeProvider":false,"documentProvider":true,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"semanticTokensProvider":{"full":true,"range":false,"legend":{"tokenTypes":["type","parameter","variable","enumMember","field","errorTag","function","keyword","comment","string","number","operator","builtin","label","keywordLiteral"],"tokenModifiers":["namespace","struct","enum","union","opaque","declaration","async","documentation","generic"]}}},"serverInfo":{"name":"zls","version":"0.1.0"}}
);
test "Requesting utf-8 offset encoding" { server.shutdown();
var process = try startZls();
try sendRequest(initialize_message_offs, process);
try sendRequest(initialized_message, process);
try sendRequest(shutdown_message, process);
try consumeOutputAndWait(process, .{
\\{"jsonrpc":"2.0","id":0,"result":{"offsetEncoding":"utf-8","capabilities":{"signatureHelpProvider":{"triggerCharacters":["("],"retriggerCharacters":[","]},"textDocumentSync":1,"renameProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"]},"documentHighlightProvider":false,"hoverProvider":true,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"implementationProvider":false,"referencesProvider":true,"documentSymbolProvider":true,"colorProvider":false,"documentFormattingProvider":true,"documentRangeFormattingProvider":false,"foldingRangeProvider":false,"selectionRangeProvider":false,"workspaceSymbolProvider":false,"rangeProvider":false,"documentProvider":true,"workspace":{"workspaceFolders":{"supported":false,"changeNotifications":false}},"semanticTokensProvider":{"full":true,"range":false,"legend":{"tokenTypes":["type","parameter","variable","enumMember","field","errorTag","function","keyword","comment","string","number","operator","builtin","label","keywordLiteral"],"tokenModifiers":["namespace","struct","enum","union","opaque","declaration","async","documentation","generic"]}}},"serverInfo":{"name":"zls","version":"0.1.0"}}}
});
} }