From d0837ffc518f4fe4b9692f87f8f07aa4a783789e Mon Sep 17 00:00:00 2001 From: SuperAuguste Date: Mon, 11 May 2020 08:28:08 -0400 Subject: [PATCH] Start implementing completion --- src/analysis.zig | 134 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 34 ++++++++++++ 2 files changed, 168 insertions(+) diff --git a/src/analysis.zig b/src/analysis.zig index e5c3c0e..82290b7 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -129,3 +129,137 @@ pub fn isCamelCase(name: []const u8) bool { pub fn isPascalCase(name: []const u8) bool { return std.ascii.isUpper(name[0]) and std.mem.indexOf(u8, name, "_") == null; } + +// ANALYSIS ENGINE +// Very WIP, only works on global values at the moment + +// TODO Major rework needed. This works in a sandbox, but not in the actual editor as completion +// stubs (e.g. "completeThis", "completeThis.", etc.) causes errors in the Zig parser. + +pub fn getDecl(tree: *std.zig.ast.Tree, name: []const u8) ?*std.zig.ast.Node { + var decls = tree.root_node.decls.iterator(0); + while (decls.next()) |decl_ptr| { + var decl = decl_ptr.*; + switch (decl.id) { + .VarDecl => { + const vari = decl.cast(std.zig.ast.Node.VarDecl).?; + if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return decl; + }, + .FnProto => { + const func = decl.cast(std.zig.ast.Node.FnProto).?; + if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return decl; + }, + else => {} + } + } + return null; +} + +pub fn getContainerField(tree: *std.zig.ast.Tree, container: *std.zig.ast.ContainerDecl, name: []const u8) ?*std.zig.ast.Node { + var decls = container.decls.iterator(0); + while (decls.next()) |decl_ptr| { + var decl = decl_ptr.*; + switch (decl.id) { + .VarDecl => { + const vari = decl.cast(std.zig.ast.Node.VarDecl).?; + if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return decl; + }, + .FnProto => { + const func = decl.cast(std.zig.ast.Node.FnProto).?; + if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return decl; + }, + else => {} + } + } + return null; +} + +pub fn resolveNode(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ?*std.zig.ast.Node { + switch (node.id) { + .Identifier => { + var id = node.cast(std.zig.ast.Node.Identifier).?; + var maybe_decl = getDecl(tree, tree.tokenSlice(id.token)); + if (maybe_decl) |decl| { + return decl; + } + }, + else => {} + } + + return null; +} + +pub fn getCompletionsForValue(allocator: *std.mem.Allocator, tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ![]*std.zig.ast.Node { + var nodes = std.ArrayList(*std.zig.ast.Node).init(allocator); + + switch (node.id) { + .ContainerDecl => { + var cont = node.cast(std.zig.ast.Node.ContainerDecl).?; + var decl_it = cont.fields_and_decls.iterator(0); + + while (decl_it.next()) |decl| { + try nodes.append(decl.*); + } + }, + .ContainerField => { + var field = node.cast(std.zig.ast.Node.ContainerField).?; + + }, + .VarDecl => { + var vari = node.cast(std.zig.ast.Node.VarDecl).?; + // if (vari.type_node) |type_node| return getCompletionsForValue(allocator, tree, type_node); + // if (vari.init_node) |init_node| return getCompletionsForValue(allocator, tree, init_node); + return getCompletionsForValue(allocator, tree, vari.type_node orelse vari.init_node.?); + }, + .Identifier => { + var id = node.cast(std.zig.ast.Node.Identifier).?; + var maybe_decl = getDecl(tree, tree.tokenSlice(id.token)); + if (maybe_decl) |decl| return getCompletionsForValue(allocator, tree, decl); + }, + .FnProto => { + var func = node.cast(std.zig.ast.Node.FnProto).?; + // std.debug.warn("{}", .{func.return_type}); + switch (func.return_type) { + .Explicit, .InferErrorSet => |return_node| { + if (resolveNode(tree, return_node)) |resolved_node| { + return getCompletionsForValue(allocator, tree, resolved_node); + } + } + } + }, + .InfixOp => { + var infix = node.cast(std.zig.ast.Node.InfixOp).?; + + // std.debug.warn("{}", .{infix}); + // switch (infix.op) { + // .Period => { + // std.debug.warn("\n{}.{}\n", .{ + // getCompletionsForValue(allocator, tree, infix.lhs), + // infix.rhs + // }); + // return getCompletionsForValue(allocator, tree, infix.lhs); + // }, + // else => {} + // } + }, + .SuffixOp => { + var suffix = node.cast(std.zig.ast.Node.SuffixOp).?; + + // std.debug.warn("{}", .{suffix}); + + switch (suffix.op) { + .Call => { + if (resolveNode(tree, suffix.lhs.node)) |rnode| { + return getCompletionsForValue(allocator, tree, rnode); + } + }, + else => {} + } + }, + else => { + std.debug.warn("{}\n\n", .{node.id}); + } + } + + return nodes.toOwnedSlice(); +} diff --git a/src/main.zig b/src/main.zig index 59bb7ac..d3da550 100644 --- a/src/main.zig +++ b/src/main.zig @@ -298,6 +298,38 @@ fn completeGlobal(id: i64, document: *types.TextDocument, config: Config) !void }); } +fn completeFieldAccess(id: i64, document: *types.TextDocument, index: usize, config: Config) !void { + // The tree uses its own arena, so we just pass our main allocator. + var tree = try std.zig.parse(allocator, document.text); + + if (tree.errors.len > 0) { + if (document.sane_text) |sane_text| { + tree.deinit(); + tree = try std.zig.parse(allocator, sane_text); + } else return try respondGeneric(id, no_completions_response); + } + else try cacheSane(document); + + defer tree.deinit(); + + // We use a local arena allocator to deallocate all temporary data without iterating + var arena = std.heap.ArenaAllocator.init(allocator); + var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); + // Deallocate all temporary data. + defer arena.deinit(); + + try log("{}", .{}); + + try send(types.Response{ + .id = .{.Integer = id}, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = completions.items, + }, + }, + }); +} // Compute builtin completions at comptime. const builtin_completions = block: { @@ -565,6 +597,8 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v }); } else if (pos_context == .var_access or pos_context == .empty) { try completeGlobal(id, document, config); + } else if (pos_context == .field_access) { + try completeFieldAccess(id, document, pos_index, config); } else { try respondGeneric(id, no_completions_response); }