From 671318730abbbdd27a2f5ee54b849a46cfcc2400 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Tue, 30 Jun 2020 15:46:43 +0300 Subject: [PATCH] Started adding session tests, finished (buggy) main loop rewrite --- build.zig | 7 +- src/analysis.zig | 6 +- src/main.zig | 640 +++++++++++++++++++++------------------------ src/requests.zig | 21 ++ src/types.zig | 4 +- tests/sessions.zig | 82 ++++++ 6 files changed, 404 insertions(+), 356 deletions(-) create mode 100644 tests/sessions.zig diff --git a/build.zig b/build.zig index e973a3e..4f04840 100644 --- a/build.zig +++ b/build.zig @@ -179,5 +179,10 @@ pub fn build(b: *std.build.Builder) !void { unit_tests.addPackage(.{ .name = "analysis", .path = "src/analysis.zig" }); unit_tests.addPackage(.{ .name = "types", .path = "src/types.zig" }); unit_tests.setBuildMode(.Debug); - test_step.dependOn(&unit_tests.step); + + var session_tests = b.addTest("tests/sessions.zig"); + session_tests.addPackage(.{ .name = "header", .path = "src/header.zig" }); + session_tests.setBuildMode(.Debug); + + test_step.dependOn(&session_tests.step); } diff --git a/src/analysis.zig b/src/analysis.zig index e042199..a6880d0 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1249,14 +1249,10 @@ fn tokenRangeAppend(prev: SourceRange, token: std.zig.Token) SourceRange { }; } -pub fn documentPositionContext(allocator: *std.mem.Allocator, document: types.TextDocument, position: types.Position) !PositionContext { +pub fn documentPositionContext(arena: *std.heap.ArenaAllocator, document: types.TextDocument, position: types.Position) !PositionContext { const line = try document.getLine(@intCast(usize, position.line)); const pos_char = @intCast(usize, position.character); const idx = if (pos_char > line.len) line.len else pos_char; - - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - var tokenizer = std.zig.Tokenizer.init(line[0..idx]); var stack = try std.ArrayList(StackState).initCapacity(&arena.allocator, 8); diff --git a/src/main.zig b/src/main.zig index b728f92..7faa0e4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -492,7 +492,7 @@ fn gotoDefinitionSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, de else => decl_handle.location(), }; - try send(types.Response{ + try send(arena, types.Response{ .id = id, .result = .{ .Location = .{ @@ -572,7 +572,7 @@ fn hoverSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle }, }; - try send(types.Response{ + try send(arena, types.Response{ .id = id, .result = .{ .Hover = .{ @@ -596,36 +596,24 @@ fn getSymbolGlobal(arena: *std.heap.ArenaAllocator, pos_index: usize, handle: *D return try analysis.lookupSymbolGlobal(&document_store, arena, handle, name, pos_index); } -fn gotoDefinitionLabel(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - +fn gotoDefinitionLabel(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { const decl = (try getLabelGlobal(pos_index, handle)) orelse return try respondGeneric(id, null_result_response); - return try gotoDefinitionSymbol(id, &arena, decl, false); + return try gotoDefinitionSymbol(id, arena, decl, false); } -fn gotoDefinitionGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config, resolve_alias: bool) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const decl = (try getSymbolGlobal(&arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); - return try gotoDefinitionSymbol(id, &arena, decl, resolve_alias); +fn gotoDefinitionGlobal(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config, resolve_alias: bool) !void { + const decl = (try getSymbolGlobal(arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); + return try gotoDefinitionSymbol(id, arena, decl, resolve_alias); } -fn hoverDefinitionLabel(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - +fn hoverDefinitionLabel(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { const decl = (try getLabelGlobal(pos_index, handle)) orelse return try respondGeneric(id, null_result_response); - return try hoverSymbol(id, &arena, decl); + return try hoverSymbol(id, arena, decl); } -fn hoverDefinitionGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const decl = (try getSymbolGlobal(&arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); - return try hoverSymbol(id, &arena, decl); +fn hoverDefinitionGlobal(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { + const decl = (try getSymbolGlobal(arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); + return try hoverSymbol(id, arena, decl); } fn getSymbolFieldAccess( @@ -659,6 +647,7 @@ fn getSymbolFieldAccess( } fn gotoDefinitionFieldAccess( + arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, position: types.Position, @@ -666,33 +655,25 @@ fn gotoDefinitionFieldAccess( config: Config, resolve_alias: bool, ) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const decl = (try getSymbolFieldAccess(handle, &arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); - return try gotoDefinitionSymbol(id, &arena, decl, resolve_alias); + const decl = (try getSymbolFieldAccess(handle, arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); + return try gotoDefinitionSymbol(id, arena, decl, resolve_alias); } fn hoverDefinitionFieldAccess( + arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, position: types.Position, range: analysis.SourceRange, config: Config, ) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const decl = (try getSymbolFieldAccess(handle, &arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); - return try hoverSymbol(id, &arena, decl); + const decl = (try getSymbolFieldAccess(handle, arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); + return try hoverSymbol(id, arena, decl); } -fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { +fn gotoDefinitionString(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { const tree = handle.tree; - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const import_str = analysis.getImportStr(tree, pos_index) orelse return try respondGeneric(id, null_result_response); const uri = (try document_store.uriFromImportStr( &arena.allocator, @@ -700,7 +681,7 @@ fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *Document import_str, )) orelse return try respondGeneric(id, null_result_response); - try send(types.Response{ + try send(arena, types.Response{ .id = id, .result = .{ .Location = .{ @@ -714,23 +695,21 @@ fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *Document }); } -fn renameDefinitionGlobal(id: types.RequestId, handle: *DocumentStore.Handle, pos_index: usize, new_name: []const u8) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const decl = (try getSymbolGlobal(&arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); +fn renameDefinitionGlobal(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, pos_index: usize, new_name: []const u8) !void { + const decl = (try getSymbolGlobal(arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); var workspace_edit = types.WorkspaceEdit{ .changes = std.StringHashMap([]types.TextEdit).init(&arena.allocator), }; - try rename.renameSymbol(&arena, &document_store, decl, new_name, &workspace_edit.changes.?); - try send(types.Response{ + try rename.renameSymbol(arena, &document_store, decl, new_name, &workspace_edit.changes.?); + try send(arena, types.Response{ .id = id, .result = .{ .WorkspaceEdit = workspace_edit }, }); } fn renameDefinitionFieldAccess( + arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, position: types.Position, @@ -738,32 +717,26 @@ fn renameDefinitionFieldAccess( new_name: []const u8, config: Config, ) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - const decl = (try getSymbolFieldAccess(handle, &arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); + const decl = (try getSymbolFieldAccess(handle, arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); var workspace_edit = types.WorkspaceEdit{ .changes = std.StringHashMap([]types.TextEdit).init(&arena.allocator), }; - try rename.renameSymbol(&arena, &document_store, decl, new_name, &workspace_edit.changes.?); - try send(types.Response{ + try rename.renameSymbol(arena, &document_store, decl, new_name, &workspace_edit.changes.?); + try send(arena, types.Response{ .id = id, .result = .{ .WorkspaceEdit = workspace_edit }, }); } -fn renameDefinitionLabel(id: types.RequestId, handle: *DocumentStore.Handle, pos_index: usize, new_name: []const u8) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - +fn renameDefinitionLabel(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, pos_index: usize, new_name: []const u8) !void { const decl = (try getLabelGlobal(pos_index, handle)) orelse return try respondGeneric(id, null_result_response); var workspace_edit = types.WorkspaceEdit{ .changes = std.StringHashMap([]types.TextEdit).init(&arena.allocator), }; - try rename.renameLabel(&arena, decl, new_name, &workspace_edit.changes.?); - try send(types.Response{ + try rename.renameLabel(arena, decl, new_name, &workspace_edit.changes.?); + try send(arena, types.Response{ .id = id, .result = .{ .WorkspaceEdit = workspace_edit }, }); @@ -825,22 +798,18 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl } } -fn completeLabel(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { - // We use a local arena allocator to deallocate all temporary data without iterating - var arena = std.heap.ArenaAllocator.init(allocator); +fn completeLabel(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); - // Deallocate all temporary data. - defer arena.deinit(); const context = DeclToCompletionContext{ .completions = &completions, .config = &config, - .arena = &arena, + .arena = arena, .orig_handle = handle, }; try analysis.iterateLabels(handle, pos_index, declToCompletion, context); - try send(types.Response{ + try send(arena, types.Response{ .id = id, .result = .{ .CompletionList = .{ @@ -851,22 +820,18 @@ fn completeLabel(id: types.RequestId, pos_index: usize, handle: *DocumentStore.H }); } -fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { - // We use a local arena allocator to deallocate all temporary data without iterating - var arena = std.heap.ArenaAllocator.init(allocator); +fn completeGlobal(arena: *std.heap.ArenaAllocator, id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); - // Deallocate all temporary data. - defer arena.deinit(); const context = DeclToCompletionContext{ .completions = &completions, .config = &config, - .arena = &arena, + .arena = arena, .orig_handle = handle, }; - try analysis.iterateSymbolsGlobal(&document_store, &arena, handle, pos_index, declToCompletion, context); + try analysis.iterateSymbolsGlobal(&document_store, arena, handle, pos_index, declToCompletion, context); - try send(types.Response{ + try send(arena, types.Response{ .id = id, .result = .{ .CompletionList = .{ @@ -877,21 +842,18 @@ fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore. }); } -fn completeFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, position: types.Position, range: analysis.SourceRange, config: Config) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - +fn completeFieldAccess(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle, position: types.Position, range: analysis.SourceRange, config: Config) !void { var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); const line = try handle.document.getLine(@intCast(usize, position.line)); var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); const pos_index = try handle.document.positionToIndex(position); - if (try analysis.getFieldAccessType(&document_store, &arena, handle, pos_index, &tokenizer)) |node| { - try typeToCompletion(&arena, &completions, node, handle, config); + if (try analysis.getFieldAccessType(&document_store, arena, handle, pos_index, &tokenizer)) |node| { + try typeToCompletion(arena, &completions, node, handle, config); } - try send(types.Response{ + try send(arena, types.Response{ .id = id, .result = .{ .CompletionList = .{ @@ -902,11 +864,8 @@ fn completeFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, posit }); } -fn documentSymbol(id: types.RequestId, handle: *DocumentStore.Handle) !void { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - - try send(types.Response{ +fn documentSymbol(arena: *std.heap.ArenaAllocator, id: types.RequestId, handle: *DocumentStore.Handle) !void { + try send(arena, types.Response{ .id = id, .result = .{ .DocumentSymbols = try analysis.getDocumentSymbols(&arena.allocator, handle.tree) }, }); @@ -1096,6 +1055,8 @@ fn saveDocumentHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req } fn closeDocumentHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.CloseDocument, config: Config) !void { + std.log.debug(.main, "CLOSING DOCUMENT!!!, id: {}\n", .{id}); + document_store.closeDocument(req.params.textDocument.uri); } @@ -1103,7 +1064,7 @@ fn semanticTokensHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, r const this_config = configFromUriOr(req.params.textDocument.uri, config); if (this_config.enable_semantic_tokens) { const handle = document_store.getHandle(req.params.textDocument.uri) orelse { - std.log.debug(.main, "Trying to complete in non existent document {}", .{req.params.textDocument.uri}); + std.log.debug(.main, "Trying to get semantic tokens of non existent document {}", .{req.params.textDocument.uri}); return try respondGeneric(id, no_semantic_tokens_response); }; @@ -1118,20 +1079,201 @@ fn semanticTokensHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, r return try respondGeneric(id, no_semantic_tokens_response); } +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 { + std.log.debug(.main, "Trying to complete in non existent document {}", .{req.params.textDocument.uri}); + return try respondGeneric(id, no_completions_response); + }; + + if (req.params.position.character >= 0) { + const pos_index = try handle.document.positionToIndex(req.params.position); + const pos_context = try analysis.documentPositionContext(arena, handle.document, req.params.position); + + const this_config = configFromUriOr(req.params.textDocument.uri, config); + const use_snippets = this_config.enable_snippets and client_capabilities.supports_snippets; + switch (pos_context) { + .builtin => try send(arena, types.Response{ + .id = id, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = builtin_completions[@boolToInt(use_snippets)][0..], + }, + }, + }), + .var_access, .empty => try completeGlobal(arena, id, pos_index, handle, this_config), + .field_access => |range| try completeFieldAccess(arena, id, handle, req.params.position, range, this_config), + .global_error_set => try send(arena, types.Response{ + .id = id, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = document_store.error_completions.completions.items, + }, + }, + }), + .enum_literal => try send(arena, types.Response{ + .id = id, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = document_store.enum_completions.completions.items, + }, + }, + }), + .label => try completeLabel(arena, id, pos_index, handle, this_config), + 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 { + // TODO Implement this + try respondGeneric(id, + \\,"result":{"signatures":[]}} + ); +} + +fn gotoHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.GotoDefinition, config: Config, resolve_alias: bool) !void { + const handle = document_store.getHandle(req.params.textDocument.uri) orelse { + std.log.debug(.main, "Trying to go to definition in non existent document {}", .{req.params.textDocument.uri}); + return try respondGeneric(id, null_result_response); + }; + + if (req.params.position.character >= 0) { + const pos_index = try handle.document.positionToIndex(req.params.position); + const pos_context = try analysis.documentPositionContext(arena, handle.document, req.params.position); + + const this_config = configFromUriOr(req.params.textDocument.uri, config); + switch (pos_context) { + .var_access => try gotoDefinitionGlobal(arena, id, pos_index, handle, this_config, resolve_alias), + .field_access => |range| try gotoDefinitionFieldAccess(arena, id, handle, req.params.position, range, this_config, resolve_alias), + .string_literal => try gotoDefinitionString(arena, id, pos_index, handle, config), + .label => try gotoDefinitionLabel(arena, id, pos_index, handle, this_config), + else => try respondGeneric(id, null_result_response), + } + } else { + try respondGeneric(id, null_result_response); + } +} + +fn gotoDefinitionHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.GotoDefinition, config: Config) !void { + try gotoHandler(arena, id, req, config, true); +} + +fn gotoDeclarationHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.GotoDeclaration, config: Config) !void { + try gotoHandler(arena, id, req, config, false); +} + +fn hoverHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.Hover, config: Config) !void { + const handle = document_store.getHandle(req.params.textDocument.uri) orelse { + std.log.debug(.main, "Trying to get hover in non existent document {}", .{req.params.textDocument.uri}); + return try respondGeneric(id, null_result_response); + }; + + if (req.params.position.character >= 0) { + const pos_index = try handle.document.positionToIndex(req.params.position); + const pos_context = try analysis.documentPositionContext(arena, handle.document, req.params.position); + + const this_config = configFromUriOr(req.params.textDocument.uri, config); + switch (pos_context) { + .var_access => try hoverDefinitionGlobal(arena, id, pos_index, handle, this_config), + .field_access => |range| try hoverDefinitionFieldAccess(arena, id, handle, req.params.position, range, this_config), + .label => try hoverDefinitionLabel(arena, id, pos_index, handle, this_config), + else => try respondGeneric(id, null_result_response), + } + } else { + try respondGeneric(id, null_result_response); + } +} + +fn documentSymbolsHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.DocumentSymbols, config: Config) !void { + const handle = document_store.getHandle(req.params.textDocument.uri) orelse { + std.log.debug(.main, "Trying to get document symbols in non existent document {}", .{req.params.textDocument.uri}); + return try respondGeneric(id, null_result_response); + }; + try documentSymbol(arena, id, handle); +} + +fn formattingHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.Formatting, config: Config) !void { + if (config.zig_exe_path) |zig_exe_path| { + const handle = document_store.getHandle(req.params.textDocument.uri) orelse { + std.log.debug(.main, "Trying to got to definition in non existent document {}", .{req.params.textDocument.uri}); + return try respondGeneric(id, null_result_response); + }; + + var process = try std.ChildProcess.init(&[_][]const u8{ zig_exe_path, "fmt", "--stdin" }, allocator); + defer process.deinit(); + process.stdin_behavior = .Pipe; + process.stdout_behavior = .Pipe; + + process.spawn() catch |err| { + std.log.debug(.main, "Failed to spawn zig fmt process, error: {}\n", .{err}); + return try respondGeneric(id, null_result_response); + }; + try process.stdin.?.writeAll(handle.document.text); + process.stdin.?.close(); + process.stdin = null; + + const stdout_bytes = try process.stdout.?.reader().readAllAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(stdout_bytes); + + switch (try process.wait()) { + .Exited => |code| if (code == 0) { + try send(arena, types.Response{ + .id = id, + .result = .{ + .TextEdits = &[1]types.TextEdit{ + .{ + .range = handle.document.range(), + .newText = stdout_bytes, + }, + }, + }, + }); + }, + else => {}, + } + } + return try respondGeneric(id, null_result_response); +} + +fn renameHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.Rename, config: Config) !void { + const handle = document_store.getHandle(req.params.textDocument.uri) orelse { + std.log.debug(.main, "Trying to got to definition in non existent document {}", .{req.params.textDocument.uri}); + return try respondGeneric(id, null_result_response); + }; + + if (req.params.position.character >= 0) { + const pos_index = try handle.document.positionToIndex(req.params.position); + const pos_context = try analysis.documentPositionContext(arena, handle.document, req.params.position); + + const this_config = configFromUriOr(req.params.textDocument.uri, config); + switch (pos_context) { + .var_access => try renameDefinitionGlobal(arena, id, handle, pos_index, req.params.newName), + .field_access => |range| try renameDefinitionFieldAccess(arena, id, handle, req.params.position, range, req.params.newName, this_config), + .label => try renameDefinitionLabel(arena, id, handle, pos_index, req.params.newName), + else => try respondGeneric(id, null_result_response), + } + } else { + try respondGeneric(id, null_result_response); + } +} + fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, json: []const u8, config: Config) !void { var tree = try parser.parse(json); defer tree.deinit(); - const root = tree.root; - - const id = if (root.Object.getValue("id")) |id| switch (id) { + const id = if (tree.root.Object.getValue("id")) |id| switch (id) { .Integer => |int| types.RequestId{ .Integer = int }, .String => |str| types.RequestId{ .String = str }, else => types.RequestId{ .Integer = 0 }, } else types.RequestId{ .Integer = 0 }; - std.debug.assert(root.Object.getValue("method") != null); - const method = root.Object.getValue("method").?.String; + std.debug.assert(tree.root.Object.getValue("method") != null); + const method = tree.root.Object.getValue("method").?.String; const start_time = std.time.milliTimestamp(); defer { @@ -1140,271 +1282,73 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso } const method_map = .{ - .{ "initialize", .{ requests.Initialize, initializeHandler } }, - .{ "shutdown", .{ void, shutdownHandler } }, - .{ "initialized", .{} }, - .{ "$/cancelRequest", .{} }, - .{ "workspace/didChangeWorkspaceFolders", .{ requests.WorkspaceFoldersChange, workspaceFoldersChangeHandler } }, - .{ "textDocument/didOpen", .{ requests.OpenDocument, openDocumentHandler } }, - .{ "textDocument/didChange", .{ requests.ChangeDocument, changeDocumentHandler } }, - .{ "textDocument/didSave", .{ requests.SaveDocument, saveDocumentHandler } }, - .{ "textDocument/willSave", .{} }, - .{ "textDocument/didClose", .{ requests.CloseDocument, closeDocumentHandler } }, - .{ "textDocument/semanticTokens", .{ requests.SemanticTokens, semanticTokensHandler } }, + .{"initialized"}, + .{"$/cancelRequest"}, + .{"textDocument/willSave"}, + .{ "initialize", requests.Initialize, initializeHandler }, + .{ "shutdown", void, shutdownHandler }, + .{ "workspace/didChangeWorkspaceFolders", requests.WorkspaceFoldersChange, workspaceFoldersChangeHandler }, + .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, + .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, + .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, + .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, + .{ "textDocument/semanticTokens", requests.SemanticTokens, semanticTokensHandler }, + .{ "textDocument/completion", requests.Completion, completionHandler }, + .{ "textDocument/signatureHelp", void, signatureHelperHandler }, + .{ "textDocument/definition", requests.GotoDefinition, gotoDefinitionHandler }, + .{ "textDocument/typeDefinition", requests.GotoDefinition, gotoDefinitionHandler }, + .{ "textDocument/implementation", requests.GotoDefinition, gotoDefinitionHandler }, + .{ "textDocument/declaration", requests.GotoDeclaration, gotoDeclarationHandler }, + .{ "textDocument/hover", requests.Hover, hoverHandler }, + .{ "textDocument/documentSymbol", requests.DocumentSymbols, documentSymbolsHandler }, + .{ "textDocument/formatting", requests.Formatting, formattingHandler }, + .{ "textDocument/rename", requests.Rename, renameHandler }, }; inline for (method_map) |method_info| { - if (std.mem.eql(u8, method_info[0], method)) { - if (method_info[1].len != 0) { - const info = method_info[1]; - if (info[0] != void) { - const request_obj = requests.fromDynamicTree(arena, info[0], tree.root) catch |err| { - switch (err) { - error.MalformedJson => { - std.log.debug(.main, "Could not create request type {} from JSON {}\n", .{ @typeName(info[0]), json }); - // @TODO What should we return to the client in this case? - return; - }, - error.OutOfMemory => return err, - } - }; - return try info[1](arena, id, request_obj, config); - } else { - return try info[1](arena, id, config); - } + if (std.mem.eql(u8, method, method_info[0])) { + if (method_info.len == 1) { + return; + } else if (method_info[1] != void) { + const request_obj = requests.fromDynamicTree(arena, method_info[1], tree.root) catch |err| { + switch (err) { + error.MalformedJson => { + std.log.debug(.main, "Could not create request type {} from JSON {}\n", .{ @typeName(method_info[1]), json }); + return try respondGeneric(id, null_result_response); + }, + error.OutOfMemory => return err, + } + }; + + std.log.debug(.TEMPORARY, "{} {}\n", .{method, method_info[0]}); + return try (method_info[2])(arena, id, request_obj, config); + } else { + return try (method_info[2])(arena, id, config); } } } - // if (std.mem.eql(u8, method, "textDocument/completion")) { - // const params = root.Object.getValue("params").?.Object; - // const text_document = params.getValue("textDocument").?.Object; - // const uri = text_document.getValue("uri").?.String; - // const position = params.getValue("position").?.Object; + const unimplemented_map = std.ComptimeStringMap(void, .{ + .{ "textDocument/references" }, + .{ "textDocument/documentHighlight" }, + .{ "textDocument/codeAction" }, + .{ "textDocument/codeLens" }, + .{ "textDocument/documentLink" }, + .{ "textDocument/rangeFormatting" }, + .{ "textDocument/onTypeFormatting" }, + .{ "textDocument/prepareRename" }, + .{ "textDocument/foldingRange" }, + .{ "textDocument/selectionRange" }, + }); - // const handle = document_store.getHandle(uri) orelse { - // std.log.debug(.main, "Trying to complete in non existent document {}", .{uri}); - // return try respondGeneric(id, no_completions_response); - // }; - - // const pos = types.Position{ - // .line = position.getValue("line").?.Integer, - // .character = position.getValue("character").?.Integer, - // }; - // if (pos.character >= 0) { - // const pos_index = try handle.document.positionToIndex(pos); - // const pos_context = try analysis.documentPositionContext(allocator, handle.document, pos); - - // const this_config = configFromUriOr(uri, config); - // const use_snippets = this_config.enable_snippets and client_capabilities.supports_snippets; - // switch (pos_context) { - // .builtin => try send(types.Response{ - // .id = id, - // .result = .{ - // .CompletionList = .{ - // .isIncomplete = false, - // .items = builtin_completions[@boolToInt(use_snippets)][0..], - // }, - // }, - // }), - // .var_access, .empty => try completeGlobal(id, pos_index, handle, this_config), - // .field_access => |range| try completeFieldAccess(id, handle, pos, range, this_config), - // .global_error_set => try send(types.Response{ - // .id = id, - // .result = .{ - // .CompletionList = .{ - // .isIncomplete = false, - // .items = document_store.error_completions.completions.items, - // }, - // }, - // }), - // .enum_literal => try send(types.Response{ - // .id = id, - // .result = .{ - // .CompletionList = .{ - // .isIncomplete = false, - // .items = document_store.enum_completions.completions.items, - // }, - // }, - // }), - // .label => try completeLabel(id, pos_index, handle, this_config), - // else => try respondGeneric(id, no_completions_response), - // } - // } else { - // try respondGeneric(id, no_completions_response); - // } - // } else if (std.mem.eql(u8, method, "textDocument/signatureHelp")) { - // // TODO: Implement this - // try respondGeneric(id, - // \\,"result":{"signatures":[]}} - // ); - // } else if (std.mem.eql(u8, method, "textDocument/definition") or - // std.mem.eql(u8, method, "textDocument/declaration") or - // std.mem.eql(u8, method, "textDocument/typeDefinition") or - // std.mem.eql(u8, method, "textDocument/implementation")) - // { - // const params = root.Object.getValue("params").?.Object; - // const document = params.getValue("textDocument").?.Object; - // const uri = document.getValue("uri").?.String; - // const position = params.getValue("position").?.Object; - - // const handle = document_store.getHandle(uri) orelse { - // std.log.debug(.main, "Trying to got to definition in non existent document {}", .{uri}); - // return try respondGeneric(id, null_result_response); - // }; - - // const pos = types.Position{ - // .line = position.getValue("line").?.Integer, - // .character = position.getValue("character").?.Integer, - // }; - // if (pos.character >= 0) { - // const resolve_alias = !std.mem.eql(u8, method, "textDocument/declaration"); - // const pos_index = try handle.document.positionToIndex(pos); - // const pos_context = try analysis.documentPositionContext(allocator, handle.document, pos); - - // switch (pos_context) { - // .var_access => try gotoDefinitionGlobal(id, pos_index, handle, configFromUriOr(uri, config), resolve_alias), - // .field_access => |range| try gotoDefinitionFieldAccess(id, handle, pos, range, configFromUriOr(uri, config), resolve_alias), - // .string_literal => try gotoDefinitionString(id, pos_index, handle, config), - // .label => try gotoDefinitionLabel(id, pos_index, handle, configFromUriOr(uri, config)), - // else => try respondGeneric(id, null_result_response), - // } - // } else { - // try respondGeneric(id, null_result_response); - // } - // } else if (std.mem.eql(u8, method, "textDocument/hover")) { - // const params = root.Object.getValue("params").?.Object; - // const document = params.getValue("textDocument").?.Object; - // const uri = document.getValue("uri").?.String; - // const position = params.getValue("position").?.Object; - - // const handle = document_store.getHandle(uri) orelse { - // std.log.debug(.main, "Trying to got to definition in non existent document {}", .{uri}); - // return try respondGeneric(id, null_result_response); - // }; - - // const pos = types.Position{ - // .line = position.getValue("line").?.Integer, - // .character = position.getValue("character").?.Integer, - // }; - // if (pos.character >= 0) { - // const pos_index = try handle.document.positionToIndex(pos); - // const pos_context = try analysis.documentPositionContext(allocator, handle.document, pos); - - // switch (pos_context) { - // .var_access => try hoverDefinitionGlobal(id, pos_index, handle, configFromUriOr(uri, config)), - // .field_access => |range| try hoverDefinitionFieldAccess(id, handle, pos, range, configFromUriOr(uri, config)), - // .label => try hoverDefinitionLabel(id, pos_index, handle, configFromUriOr(uri, config)), - // else => try respondGeneric(id, null_result_response), - // } - // } else { - // try respondGeneric(id, null_result_response); - // } - // } else if (std.mem.eql(u8, method, "textDocument/documentSymbol")) { - // const params = root.Object.getValue("params").?.Object; - // const document = params.getValue("textDocument").?.Object; - // const uri = document.getValue("uri").?.String; - - // const handle = document_store.getHandle(uri) orelse { - // std.log.debug(.main, "Trying to got to definition in non existent document {}", .{uri}); - // return try respondGeneric(id, null_result_response); - // }; - - // try documentSymbol(id, handle); - // } else if (std.mem.eql(u8, method, "textDocument/formatting")) { - // if (config.zig_exe_path) |zig_exe_path| { - // const params = root.Object.getValue("params").?.Object; - // const document = params.getValue("textDocument").?.Object; - // const uri = document.getValue("uri").?.String; - - // const handle = document_store.getHandle(uri) orelse { - // std.log.debug(.main, "Trying to got to definition in non existent document {}", .{uri}); - // return try respondGeneric(id, null_result_response); - // }; - - // var process = try std.ChildProcess.init(&[_][]const u8{ zig_exe_path, "fmt", "--stdin" }, allocator); - // defer process.deinit(); - // process.stdin_behavior = .Pipe; - // process.stdout_behavior = .Pipe; - - // process.spawn() catch |err| { - // std.log.debug(.main, "Failed to spawn zig fmt process, error: {}\n", .{err}); - // return try respondGeneric(id, null_result_response); - // }; - // try process.stdin.?.writeAll(handle.document.text); - // process.stdin.?.close(); - // process.stdin = null; - - // const stdout_bytes = try process.stdout.?.reader().readAllAlloc(allocator, std.math.maxInt(usize)); - // defer allocator.free(stdout_bytes); - - // switch (try process.wait()) { - // .Exited => |code| if (code == 0) { - // try send(types.Response{ - // .id = id, - // .result = .{ - // .TextEdits = &[1]types.TextEdit{ - // .{ - // .range = handle.document.range(), - // .newText = stdout_bytes, - // }, - // }, - // }, - // }); - // }, - // else => {}, - // } - // } - // return try respondGeneric(id, null_result_response); - // } else if (std.mem.eql(u8, method, "textDocument/rename")) { - // const params = root.Object.getValue("params").?.Object; - // const document = params.getValue("textDocument").?.Object; - // const uri = document.getValue("uri").?.String; - // const position = params.getValue("position").?.Object; - - // const handle = document_store.getHandle(uri) orelse { - // std.log.debug(.main, "Trying to got to definition in non existent document {}", .{uri}); - // return try respondGeneric(id, null_result_response); - // }; - - // const pos = types.Position{ - // .line = position.getValue("line").?.Integer, - // .character = position.getValue("character").?.Integer, - // }; - // if (pos.character >= 0) { - // const new_name = params.getValue("newName").?.String; - // const pos_index = try handle.document.positionToIndex(pos); - // const pos_context = try analysis.documentPositionContext(allocator, handle.document, pos); - - // const this_config = configFromUriOr(uri, config); - // switch (pos_context) { - // .var_access => try renameDefinitionGlobal(id, handle, pos_index, new_name), - // .field_access => |range| try renameDefinitionFieldAccess(id, handle, pos, range, new_name, this_config), - // .label => try renameDefinitionLabel(id, handle, pos_index, new_name), - // else => try respondGeneric(id, null_result_response), - // } - // } else { - // try respondGeneric(id, null_result_response); - // } - // } else if (std.mem.eql(u8, method, "textDocument/references") or - // std.mem.eql(u8, method, "textDocument/documentHighlight") or - // std.mem.eql(u8, method, "textDocument/codeAction") or - // std.mem.eql(u8, method, "textDocument/codeLens") or - // std.mem.eql(u8, method, "textDocument/documentLink") or - // std.mem.eql(u8, method, "textDocument/rangeFormatting") or - // std.mem.eql(u8, method, "textDocument/onTypeFormatting") or - // std.mem.eql(u8, method, "textDocument/prepareRename") or - // std.mem.eql(u8, method, "textDocument/foldingRange") or - // std.mem.eql(u8, method, "textDocument/selectionRange")) - // { - // // TODO: Unimplemented methods, implement them and add them to server capabilities. - // try respondGeneric(id, null_result_response); - // } else if (root.Object.getValue("id")) |_| { - // std.log.debug(.main, "Method with return value not implemented: {}", .{method}); - // try respondGeneric(id, not_implemented_response); - // } else { - // std.log.debug(.main, "Method without return value not implemented: {}", .{method}); - // } + if (unimplemented_map.has(method)) { + // TODO: Unimplemented methods, implement them and add them to server capabilities. + return try respondGeneric(id, null_result_response); + } + if (tree.root.Object.getValue("id")) |_| { + return try respondGeneric(id, not_implemented_response); + } + std.log.debug(.main, "Method without return value not implemented: {}", .{method}); } var debug_alloc_state: DebugAllocator = undefined; diff --git a/src/requests.zig b/src/requests.zig index efd4f06..e7891d1 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -186,3 +186,24 @@ const TextDocumentIdentifierRequest = struct { pub const SaveDocument = TextDocumentIdentifierRequest; pub const CloseDocument = TextDocumentIdentifierRequest; pub const SemanticTokens = TextDocumentIdentifierRequest; + +const TextDocumentIdentifierPositionRequest = struct { + params: struct { + textDocument: TextDocumentIdentifier, + position: types.Position, + }, +}; + +pub const Completion = TextDocumentIdentifierPositionRequest; +pub const GotoDefinition = TextDocumentIdentifierPositionRequest; +pub const GotoDeclaration = TextDocumentIdentifierPositionRequest; +pub const Hover = TextDocumentIdentifierPositionRequest; +pub const DocumentSymbols = TextDocumentIdentifierRequest; +pub const Formatting = TextDocumentIdentifierRequest; +pub const Rename = struct { + params: struct { + textDocument: TextDocumentIdentifier, + position: types.Position, + newName: types.String, + }, +}; diff --git a/src/types.zig b/src/types.zig index 8cbd937..41d2b7e 100644 --- a/src/types.zig +++ b/src/types.zig @@ -18,11 +18,11 @@ pub const Object = json.ObjectMap; pub const DocumentUri = String; pub const Position = struct { - line: Integer, character: Integer + line: Integer, character: Integer, }; pub const Range = struct { - start: Position, end: Position + start: Position, end: Position, }; pub const Location = struct { diff --git a/tests/sessions.zig b/tests/sessions.zig new file mode 100644 index 0000000..750c52c --- /dev/null +++ b/tests/sessions.zig @@ -0,0 +1,82 @@ +const std = @import("std"); +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":null}} +; + +const initialized_message = \\{"jsonrpc":"2.0","method":"initialized","params":{}} +; + +const shutdown_message = \\{"jsonrpc":"2.0", "id":"STDWN", "method":"shutdown","params":{}} +; + +fn sendRequest(req: []const u8, process: var) !void { + try process.stdin.?.writer().print("Content-Length: {}\r\n\r\n", .{req.len}); + try process.stdin.?.writeAll(req); +} + +fn readResponses(process: var) !void { + while (true) { + const header = headerPkg.readRequestHeader(allocator, process.stdout.?.reader()) 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)]; + std.debug.print("GOT MESSAGE: {}\n", .{stdout_bytes}); + } +} + +test "Open file, ask for semantic tokens" { + var process = try std.ChildProcess.init(&[_][]const u8{ "zig-cache/bin/zls" ++ suffix }, allocator); + defer process.deinit(); + process.stdin_behavior = .Pipe; + process.stdout_behavior = .Pipe; + process.stderr_behavior = std.ChildProcess.StdIo.Inherit; + + process.spawn() catch |err| { + std.log.debug(.main, "Failed to spawn zls process, error: {}\n", .{err}); + return err; + }; + + try sendRequest(initialize_message, process); + try sendRequest(initialized_message, process); + + 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\");"}}} + ; + try sendRequest(new_file_req, process); + const sem_toks_req = \\{"jsonrpc":"2.0","id":2,"method":"textDocument/semanticTokens","params":{"textDocument":{"uri":"file:///test.zig"}}} + ; + try sendRequest(sem_toks_req, process); + + try sendRequest(shutdown_message, process); + + process.stdin.?.close(); + process.stdin = null; + + try readResponses(process); + const result = try process.wait(); + + // const stderr_bytes = try process.stderr.?.reader().readAllAlloc(allocator, std.math.maxInt(usize)); + // defer allocator.free(stderr_bytes); + // if (stderr_bytes.len != 0) { + // std.debug.print("Stderr output\n{}\n", .{stderr_bytes}); + // } + + switch (result) { + .Exited => |code| if (code == 0) { + return; + }, + else => {}, + } + return error.ShutdownWithError; +}