Improve testing infrastructure
It should now be a bit easier to set up a test and see how it failed.
This commit is contained in:
parent
b59db79a05
commit
0e4f4c1e04
@ -6,6 +6,17 @@ const offsets = @import("offsets.zig");
|
||||
const log = std.log.scoped(.analysis);
|
||||
usingnamespace @import("ast.zig");
|
||||
|
||||
var using_trail: std.ArrayList([*]const u8) = undefined;
|
||||
var resolve_trail: std.ArrayList(NodeWithHandle) = undefined;
|
||||
pub fn init(allocator: *std.mem.Allocator) void {
|
||||
using_trail = std.ArrayList([*]const u8).init(allocator);
|
||||
resolve_trail = std.ArrayList(NodeWithHandle).init(allocator);
|
||||
}
|
||||
pub fn deinit() void {
|
||||
using_trail.deinit();
|
||||
resolve_trail.deinit();
|
||||
}
|
||||
|
||||
/// Gets a declaration's doc comments, caller must free memory when a value is returned
|
||||
/// Like:
|
||||
///```zig
|
||||
@ -681,19 +692,16 @@ pub fn resolveTypeOfNodeInternal(
|
||||
node_handle: NodeWithHandle,
|
||||
bound_type_params: *BoundTypeParams,
|
||||
) error{OutOfMemory}!?TypeWithHandle {
|
||||
const state = struct {
|
||||
var resolve_trail = std.ArrayListUnmanaged(NodeWithHandle){};
|
||||
};
|
||||
// If we were asked to resolve this node before,
|
||||
// it is self-referential and we cannot resolve it.
|
||||
for (state.resolve_trail.items) |i| {
|
||||
for (resolve_trail.items) |i| {
|
||||
if (std.meta.eql(i, node_handle))
|
||||
return null;
|
||||
}
|
||||
// We use the backing allocator here because the ArrayList expects its
|
||||
// allocated memory to persist while it is empty.
|
||||
try state.resolve_trail.append(arena.child_allocator, node_handle);
|
||||
defer _ = state.resolve_trail.pop();
|
||||
try resolve_trail.append(node_handle);
|
||||
defer _ = resolve_trail.pop();
|
||||
|
||||
const node = node_handle.node;
|
||||
const handle = node_handle.handle;
|
||||
@ -2259,17 +2267,14 @@ fn resolveUse(
|
||||
symbol: []const u8,
|
||||
handle: *DocumentStore.Handle,
|
||||
) error{OutOfMemory}!?DeclWithHandle {
|
||||
const state = struct {
|
||||
var using_trail = std.ArrayListUnmanaged([*]const u8){};
|
||||
};
|
||||
// If we were asked to resolve this symbol before,
|
||||
// it is self-referential and we cannot resolve it.
|
||||
if (std.mem.indexOfScalar([*]const u8, state.using_trail.items, symbol.ptr) != null)
|
||||
if (std.mem.indexOfScalar([*]const u8, using_trail.items, symbol.ptr) != null)
|
||||
return null;
|
||||
// We use the backing allocator here because the ArrayList expects its
|
||||
// allocated memory to persist while it is empty.
|
||||
try state.using_trail.append(arena.child_allocator, symbol.ptr);
|
||||
defer _ = state.using_trail.pop();
|
||||
try using_trail.append(symbol.ptr);
|
||||
defer _ = using_trail.pop();
|
||||
|
||||
for (uses) |use| {
|
||||
const expr = .{ .node = handle.tree.nodes.items(.data)[use.*].lhs, .handle = handle };
|
||||
|
122
src/main.zig
122
src/main.zig
@ -38,7 +38,7 @@ pub fn log(
|
||||
}
|
||||
// After shutdown, pipe output to stderr
|
||||
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;
|
||||
}
|
||||
|
||||
@ -619,37 +619,32 @@ fn hoverSymbol(
|
||||
const tree = handle.tree;
|
||||
|
||||
const hover_kind: types.MarkupContent.Kind = if (client_capabilities.hover_supports_md) .Markdown else .PlainText;
|
||||
const md_string = switch (decl_handle.decl.*) {
|
||||
.ast_node => |node| ast_node: {
|
||||
var doc_str: ?[]const u8 = null;
|
||||
|
||||
const def_str = switch (decl_handle.decl.*) {
|
||||
.ast_node => |node| def: {
|
||||
if (try analysis.resolveVarDeclAlias(&document_store, arena, .{ .node = node, .handle = handle })) |result| {
|
||||
return try hoverSymbol(id, arena, result);
|
||||
}
|
||||
|
||||
const doc_str = if (try analysis.getDocComments(&arena.allocator, tree, node, hover_kind)) |str|
|
||||
str
|
||||
else
|
||||
"";
|
||||
doc_str = try analysis.getDocComments(&arena.allocator, tree, node, hover_kind);
|
||||
|
||||
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)
|
||||
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 });
|
||||
if (analysis.varDecl(tree, node)) |var_decl| {
|
||||
break :def analysis.getVariableSignature(tree, var_decl);
|
||||
} else if (analysis.fnProto(tree, node, &buf)) |fn_proto| {
|
||||
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: {
|
||||
const doc_str = if (param.first_doc_comment) |doc_comments|
|
||||
try analysis.collectDocComments(&arena.allocator, handle.tree, doc_comments, hover_kind)
|
||||
else
|
||||
"";
|
||||
.param_decl => |param| def: {
|
||||
if (param.first_doc_comment) |doc_comments| {
|
||||
doc_str = try analysis.collectDocComments(&arena.allocator, handle.tree, doc_comments, hover_kind);
|
||||
}
|
||||
|
||||
const first_token = param.first_doc_comment orelse
|
||||
param.comptime_noalias orelse
|
||||
@ -659,42 +654,35 @@ fn hoverSymbol(
|
||||
|
||||
const start = offsets.tokenLocation(tree, first_token).start;
|
||||
const end = offsets.tokenLocation(tree, last_token).end;
|
||||
const signature_str = 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});
|
||||
break :def tree.source[start..end];
|
||||
},
|
||||
.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{
|
||||
.id = id,
|
||||
.result = .{
|
||||
.Hover = .{
|
||||
.contents = .{ .value = md_string },
|
||||
.contents = .{ .value = hover_text },
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -739,11 +727,13 @@ fn hoverDefinitionBuiltin(arena: *std.heap.ArenaAllocator, id: types.RequestId,
|
||||
.id = id,
|
||||
.result = .{
|
||||
.Hover = .{
|
||||
.contents = .{ .value = try std.fmt.allocPrint(
|
||||
.contents = .{
|
||||
.value = try std.fmt.allocPrint(
|
||||
&arena.allocator,
|
||||
"```zig\n{s}\n```\n{s}",
|
||||
.{ builtin.signature, builtin.documentation },
|
||||
) },
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -1161,6 +1151,7 @@ fn completeError(
|
||||
) !void {
|
||||
const completions = try document_store.errorCompletionItems(arena, handle);
|
||||
truncateCompletions(completions, config.max_detail_length);
|
||||
logger.debug("Completing error:", .{});
|
||||
|
||||
try send(arena, types.Response{
|
||||
.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);
|
||||
try publishDiagnostics(arena, handle.*, config);
|
||||
|
||||
if (client_capabilities.supports_semantic_tokens)
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
@ -1388,6 +1380,7 @@ fn semanticTokensFullHandler(arena: *std.heap.ArenaAllocator, id: types.RequestI
|
||||
.result = .{ .SemanticTokensFull = .{ .data = token_array } },
|
||||
});
|
||||
}
|
||||
return try respondGeneric(id, no_semantic_tokens_response);
|
||||
}
|
||||
|
||||
fn completionHandler(
|
||||
@ -1444,11 +1437,13 @@ fn signatureHelpHandler(
|
||||
)) |sig_info| {
|
||||
return try send(arena, types.Response{
|
||||
.id = id,
|
||||
.result = .{ .SignatureHelp = .{
|
||||
.result = .{
|
||||
.SignatureHelp = .{
|
||||
.signatures = &[1]types.SignatureInformation{sig_info},
|
||||
.activeSignature = 0,
|
||||
.activeParameter = sig_info.activeParameter,
|
||||
} },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
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 {
|
||||
defer _ = gpa_state.deinit();
|
||||
defer keep_running = false;
|
||||
allocator = &gpa_state.allocator;
|
||||
|
||||
analysis.init(allocator);
|
||||
defer analysis.deinit();
|
||||
|
||||
// Check arguments.
|
||||
var args_it = std.process.args();
|
||||
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);
|
||||
|
||||
while (args_it.next(allocator)) |maybe_arg| {
|
||||
@ -1732,7 +1731,6 @@ pub fn main() anyerror!void {
|
||||
actual_log_level = .debug;
|
||||
std.debug.print("Enabled debug logging\n", .{});
|
||||
} else if (std.mem.eql(u8, arg, "config")) {
|
||||
keep_running = false;
|
||||
try setup.wizard(allocator);
|
||||
return;
|
||||
} else {
|
||||
|
@ -4,72 +4,142 @@ const headerPkg = @import("header");
|
||||
const suffix = if (std.builtin.os.tag == .windows) ".exe" else "";
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const initialize_message =
|
||||
\\{"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"}]}}
|
||||
const initialize_msg =
|
||||
\\{"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 =
|
||||
\\{"jsonrpc":"2.0","method":"initialized","params":{}}
|
||||
;
|
||||
const Server = struct {
|
||||
process: *std.ChildProcess,
|
||||
request_id: u32 = 1,
|
||||
|
||||
const shutdown_message =
|
||||
\\{"jsonrpc":"2.0", "id":"STDWN", "method":"shutdown","params":{}}
|
||||
;
|
||||
fn start(initialization: []const u8, expect: ?[]const u8) !Server {
|
||||
var server = Server{ .process = try startZls() };
|
||||
|
||||
fn sendRequest(req: []const u8, process: *std.ChildProcess) !void {
|
||||
try process.stdin.?.writer().print("Content-Length: {}\r\n\r\n", .{req.len});
|
||||
try process.stdin.?.writeAll(req);
|
||||
try server.request("initialize", initialization, expect);
|
||||
try server.request("initialized", "{}", null);
|
||||
return server;
|
||||
}
|
||||
|
||||
fn readResponses(process: *std.ChildProcess, expected_responses: anytype) !void {
|
||||
var seen = std.mem.zeroes([expected_responses.len]bool);
|
||||
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();
|
||||
|
||||
while (true) {
|
||||
const header = headerPkg.readRequestHeader(allocator, process.stdout.?.reader()) catch |err| {
|
||||
const header = headerPkg.readRequestHeader(allocator, from_server) catch |err| {
|
||||
switch (err) {
|
||||
error.EndOfStream => break,
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
defer header.deinit(allocator);
|
||||
|
||||
var stdout_mem = try allocator.alloc(u8, header.content_length);
|
||||
defer allocator.free(stdout_mem);
|
||||
|
||||
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;
|
||||
var resonse_bytes = try allocator.alloc(u8, header.content_length);
|
||||
defer allocator.free(resonse_bytes);
|
||||
if ((try from_server.readAll(resonse_bytes)) != header.content_length) {
|
||||
return error.InvalidResponse;
|
||||
}
|
||||
}
|
||||
std.debug.print("GOT MESSAGE: {s}\n", .{stdout_bytes});
|
||||
// std.debug.print("{s}\n", .{resonse_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;
|
||||
inline while (idx < expected_responses.len) : (idx += 1) {
|
||||
if (!seen[idx]) {
|
||||
std.debug.print("Response `{s}` not received.", .{expected_responses[idx]});
|
||||
return error.ExpectedResponse;
|
||||
const rest = resonse_bytes[json_fmt.len..];
|
||||
const id_end = std.mem.indexOfScalar(u8, rest, ',') orelse return error.InvalidResponse;
|
||||
|
||||
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 {
|
||||
std.debug.print("\n", .{});
|
||||
|
||||
var process = try std.ChildProcess.init(&[_][]const u8{"zig-cache/bin/zls" ++ suffix}, allocator);
|
||||
process.stdin_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| {
|
||||
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 process;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
switch (result) {
|
||||
.Exited => |code| if (code == 0) {
|
||||
return;
|
||||
@ -79,80 +149,49 @@ fn waitNoError(process: *std.ChildProcess) !void {
|
||||
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" {
|
||||
var process = try startZls();
|
||||
try sendRequest(initialize_message, process);
|
||||
try sendRequest(initialized_message, process);
|
||||
var server = try Server.start(initialize_msg, null);
|
||||
defer server.shutdown();
|
||||
|
||||
const new_file_req =
|
||||
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file://./tests/test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");"}}}
|
||||
;
|
||||
try sendRequest(new_file_req, process);
|
||||
const sem_toks_req =
|
||||
\\{"jsonrpc":"2.0","id":2,"method":"textDocument/semanticTokens/full","params":{"textDocument":{"uri":"file://./tests/test.zig"}}}
|
||||
;
|
||||
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]}}
|
||||
});
|
||||
try server.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file://./tests/test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");"}}
|
||||
, null);
|
||||
try server.request("textDocument/semanticTokens/full",
|
||||
\\{"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]}
|
||||
);
|
||||
}
|
||||
|
||||
test "Requesting a completion in an empty file" {
|
||||
var process = try startZls();
|
||||
try sendRequest(initialize_message, process);
|
||||
try sendRequest(initialized_message, process);
|
||||
test "Request completion in an empty file" {
|
||||
var server = try Server.start(initialize_msg, null);
|
||||
defer server.shutdown();
|
||||
|
||||
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":""}}}
|
||||
;
|
||||
try sendRequest(new_file_req, process);
|
||||
const completion_req =
|
||||
\\{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}}}
|
||||
;
|
||||
try sendRequest(completion_req, process);
|
||||
try sendRequest(shutdown_message, process);
|
||||
try consumeOutputAndWait(process, .{});
|
||||
, null);
|
||||
try server.request("textDocument/completion",
|
||||
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}}
|
||||
, null);
|
||||
}
|
||||
|
||||
test "Requesting a completion with no trailing whitespace" {
|
||||
var process = try startZls();
|
||||
try sendRequest(initialize_message, process);
|
||||
try sendRequest(initialized_message, process);
|
||||
test "Request completion with no trailing whitespace" {
|
||||
var server = try Server.start(initialize_msg, null);
|
||||
defer server.shutdown();
|
||||
|
||||
const new_file_req =
|
||||
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}}}
|
||||
;
|
||||
try sendRequest(new_file_req, process);
|
||||
const completion_req =
|
||||
\\{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}}
|
||||
;
|
||||
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}]}}
|
||||
});
|
||||
try server.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}}
|
||||
, null);
|
||||
try server.request("textDocument/completion",
|
||||
\\{"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}]}
|
||||
);
|
||||
}
|
||||
|
||||
const initialize_message_offs =
|
||||
\\{"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}}
|
||||
;
|
||||
|
||||
test "Requesting utf-8 offset encoding" {
|
||||
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"}}}
|
||||
});
|
||||
test "Request utf-8 offset encoding" {
|
||||
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"}}
|
||||
);
|
||||
server.shutdown();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user