diff --git a/src/analysis.zig b/src/analysis.zig index 60afa56..9ac7130 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -673,11 +673,6 @@ pub fn resolveTypeOfNodeInternal( )) orelse return null).instanceTypeVal(); }, .ErrorSetDecl => { - const set = node.cast(ast.Node.ErrorSetDecl).?; - var i: usize = 0; - while (set.iterate(i)) |decl| : (i += 1) { - try store.error_completions.add(handle.tree, decl); - } return TypeWithHandle.typeVal(node_handle); }, .SuffixOp => { @@ -821,16 +816,6 @@ pub fn resolveTypeOfNodeInternal( .ContainerDecl => { const container = node.cast(ast.Node.ContainerDecl).?; const kind = handle.tree.token_ids[container.kind_token]; - - if (kind == .Keyword_struct or (kind == .Keyword_union and container.init_arg_expr == .None)) { - return TypeWithHandle.typeVal(node_handle); - } - - var i: usize = 0; - while (container.iterate(i)) |decl| : (i += 1) { - if (decl.id != .ContainerField) continue; - try store.enum_completions.add(handle.tree, decl); - } return TypeWithHandle.typeVal(node_handle); }, .FnProto => { @@ -1907,6 +1892,8 @@ pub fn lookupSymbolContainer( pub const DocumentScope = struct { scopes: []Scope, + error_completions: []types.CompletionItem, + enum_completions: []types.CompletionItem, pub fn debugPrint(self: DocumentScope) void { for (self.scopes) |scope| { @@ -1939,6 +1926,10 @@ pub const DocumentScope = struct { allocator.free(scope.tests); } allocator.free(self.scopes); + for (self.error_completions) |item| if (item.documentation) |doc| allocator.free(doc.value); + allocator.free(self.error_completions); + for (self.enum_completions) |item| if (item.documentation) |doc| allocator.free(doc.value); + allocator.free(self.enum_completions); } }; @@ -1959,12 +1950,23 @@ pub const Scope = struct { }; pub fn makeDocumentScope(allocator: *std.mem.Allocator, tree: *ast.Tree) !DocumentScope { - var scopes = std.ArrayList(Scope).init(allocator); - errdefer scopes.deinit(); + var scopes = std.ArrayListUnmanaged(Scope){}; + var error_completions = std.ArrayListUnmanaged(types.CompletionItem){}; + var enum_completions = std.ArrayListUnmanaged(types.CompletionItem){}; - try makeScopeInternal(allocator, &scopes, tree, &tree.root_node.base); + errdefer { + scopes.deinit(allocator); + for (error_completions.items) |item| if (item.documentation) |doc| allocator.free(doc.value); + error_completions.deinit(allocator); + for (enum_completions.items) |item| if (item.documentation) |doc| allocator.free(doc.value); + enum_completions.deinit(allocator); + } + + try makeScopeInternal(allocator, &scopes, &error_completions, &enum_completions, tree, &tree.root_node.base); return DocumentScope{ - .scopes = scopes.toOwnedSlice(), + .scopes = scopes.toOwnedSlice(allocator), + .error_completions = error_completions.toOwnedSlice(allocator), + .enum_completions = enum_completions.toOwnedSlice(allocator), }; } @@ -1975,14 +1977,13 @@ fn nodeSourceRange(tree: *ast.Tree, node: *ast.Node) SourceRange { }; } -// TODO Make enum and error stores per-document -// CLear the doc ones before calling this and -// rebuild them here. // TODO Possibly collect all imports to diff them on changes // as well fn makeScopeInternal( allocator: *std.mem.Allocator, - scopes: *std.ArrayList(Scope), + scopes: *std.ArrayListUnmanaged(Scope), + error_completions: *std.ArrayListUnmanaged(types.CompletionItem), + enum_completions: *std.ArrayListUnmanaged(types.CompletionItem), tree: *ast.Tree, node: *ast.Node, ) error{OutOfMemory}!void { @@ -1994,7 +1995,7 @@ fn makeScopeInternal( else => unreachable, }; - (try scopes.addOne()).* = .{ + (try scopes.addOne(allocator)).* = .{ .range = nodeSourceRange(tree, node), .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, @@ -2017,22 +2018,44 @@ fn makeScopeInternal( continue; } - try makeScopeInternal(allocator, scopes, tree, decl); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, decl); const name = getDeclName(tree, decl) orelse continue; if (decl.id == .TestDecl) { try tests.append(decl); continue; } + if (node.id == .ErrorSetDecl) { + (try error_completions.addOne(allocator)).* = .{ + .label = name, + .kind = .Constant, + .documentation = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs| + .{ .kind = .Markdown, .value = docs } + else + null, + }; + } + if (decl.cast(ast.Node.ContainerField)) |field| { - if (field.type_expr == null and field.value_expr == null) { - if (node.id == .Root) continue; - if (node.cast(ast.Node.ContainerDecl)) |container| { - const kind = tree.token_ids[container.kind_token]; - if (kind == .Keyword_struct or (kind == .Keyword_union and container.init_arg_expr == .None)) { - continue; - } + const empty_field = field.type_expr == null and field.value_expr == null; + if (empty_field and node.id == .Root) { + continue; + } + + if (node.cast(ast.Node.ContainerDecl)) |container| { + const kind = tree.token_ids[container.kind_token]; + if (empty_field and (kind == .Keyword_struct or (kind == .Keyword_union and container.init_arg_expr == .None))) { + continue; } + + (try enum_completions.addOne(allocator)).* = .{ + .label = name, + .kind = .Constant, + .documentation = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs| + .{ .kind = .Markdown, .value = docs } + else + null, + }; } } @@ -2050,7 +2073,7 @@ fn makeScopeInternal( .FnProto => { const func = node.cast(ast.Node.FnProto).?; - (try scopes.addOne()).* = .{ + (try scopes.addOne(allocator)).* = .{ .range = nodeSourceRange(tree, node), .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, @@ -2069,19 +2092,19 @@ fn makeScopeInternal( } if (func.body_node) |body| { - try makeScopeInternal(allocator, scopes, tree, body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, body); } return; }, .TestDecl => { - return try makeScopeInternal(allocator, scopes, tree, node.cast(ast.Node.TestDecl).?.body_node); + return try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, node.cast(ast.Node.TestDecl).?.body_node); }, .Block => { const block = node.cast(ast.Node.Block).?; if (block.label) |label| { std.debug.assert(tree.token_ids[label] == .Identifier); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[block.lbrace].start, @@ -2099,7 +2122,7 @@ fn makeScopeInternal( }); } - (try scopes.addOne()).* = .{ + (try scopes.addOne(allocator)).* = .{ .range = nodeSourceRange(tree, node), .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, @@ -2121,7 +2144,7 @@ fn makeScopeInternal( continue; } - try makeScopeInternal(allocator, scopes, tree, child_node); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, child_node); if (child_node.cast(ast.Node.VarDecl)) |var_decl| { const name = tree.tokenSlice(var_decl.name_token); if (try scopes.items[scope_idx].decls.fetchPut(name, .{ .ast_node = child_node })) |existing| { @@ -2134,14 +2157,14 @@ fn makeScopeInternal( return; }, .Comptime => { - return try makeScopeInternal(allocator, scopes, tree, node.cast(ast.Node.Comptime).?.expr); + return try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, node.cast(ast.Node.Comptime).?.expr); }, .If => { const if_node = node.cast(ast.Node.If).?; if (if_node.payload) |payload| { std.debug.assert(payload.id == .PointerPayload); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[payload.firstToken()].start, @@ -2164,12 +2187,12 @@ fn makeScopeInternal( }, }); } - try makeScopeInternal(allocator, scopes, tree, if_node.body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, if_node.body); if (if_node.@"else") |else_node| { if (else_node.payload) |payload| { std.debug.assert(payload.id == .Payload); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[payload.firstToken()].start, @@ -2187,14 +2210,14 @@ fn makeScopeInternal( const name = tree.tokenSlice(err_payload.error_symbol.firstToken()); try scope.decls.putNoClobber(name, .{ .ast_node = payload }); } - try makeScopeInternal(allocator, scopes, tree, else_node.body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, else_node.body); } }, .While => { const while_node = node.cast(ast.Node.While).?; if (while_node.label) |label| { std.debug.assert(tree.token_ids[label] == .Identifier); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[while_node.while_token].start, @@ -2214,7 +2237,7 @@ fn makeScopeInternal( if (while_node.payload) |payload| { std.debug.assert(payload.id == .PointerPayload); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[payload.firstToken()].start, @@ -2237,12 +2260,12 @@ fn makeScopeInternal( }, }); } - try makeScopeInternal(allocator, scopes, tree, while_node.body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, while_node.body); if (while_node.@"else") |else_node| { if (else_node.payload) |payload| { std.debug.assert(payload.id == .Payload); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[payload.firstToken()].start, @@ -2260,14 +2283,14 @@ fn makeScopeInternal( const name = tree.tokenSlice(err_payload.error_symbol.firstToken()); try scope.decls.putNoClobber(name, .{ .ast_node = payload }); } - try makeScopeInternal(allocator, scopes, tree, else_node.body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, else_node.body); } }, .For => { const for_node = node.cast(ast.Node.For).?; if (for_node.label) |label| { std.debug.assert(tree.token_ids[label] == .Identifier); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[for_node.for_token].start, @@ -2289,7 +2312,7 @@ fn makeScopeInternal( const ptr_idx_payload = for_node.payload.cast(ast.Node.PointerIndexPayload).?; std.debug.assert(ptr_idx_payload.value_symbol.id == .Identifier); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[ptr_idx_payload.firstToken()].start, @@ -2318,10 +2341,10 @@ fn makeScopeInternal( } } - try makeScopeInternal(allocator, scopes, tree, for_node.body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, for_node.body); if (for_node.@"else") |else_node| { std.debug.assert(else_node.payload == null); - try makeScopeInternal(allocator, scopes, tree, else_node.body); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, else_node.body); } }, .Switch => { @@ -2330,7 +2353,7 @@ fn makeScopeInternal( if (case.*.cast(ast.Node.SwitchCase)) |case_node| { if (case_node.payload) |payload| { std.debug.assert(payload.id == .PointerPayload); - var scope = try scopes.addOne(); + var scope = try scopes.addOne(allocator); scope.* = .{ .range = .{ .start = tree.token_locs[payload.firstToken()].start, @@ -2354,23 +2377,23 @@ fn makeScopeInternal( }, }); } - try makeScopeInternal(allocator, scopes, tree, case_node.expr); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, case_node.expr); } } }, .VarDecl => { const var_decl = node.cast(ast.Node.VarDecl).?; if (var_decl.type_node) |type_node| { - try makeScopeInternal(allocator, scopes, tree, type_node); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, type_node); } if (var_decl.init_node) |init_node| { - try makeScopeInternal(allocator, scopes, tree, init_node); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, init_node); } }, else => { var child_idx: usize = 0; while (node.iterate(child_idx)) |child_node| : (child_idx += 1) { - try makeScopeInternal(allocator, scopes, tree, child_node); + try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, child_node); } }, } diff --git a/src/document_store.zig b/src/document_store.zig index e661612..494b6e9 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -32,48 +32,6 @@ pub const Handle = struct { } }; -pub const TagStore = struct { - values: std.StringHashMap(void), - completions: std.ArrayListUnmanaged(types.CompletionItem), - - pub fn init(allocator: *std.mem.Allocator) TagStore { - return .{ - .values = std.StringHashMap(void).init(allocator), - .completions = .{}, - }; - } - - pub fn deinit(self: *TagStore) void { - const alloc = self.values.allocator; - for (self.completions.items) |item| { - alloc.free(item.label); - if (item.documentation) |some| alloc.free(some.value); - } - self.values.deinit(); - self.completions.deinit(self.values.allocator); - } - - pub fn add(self: *TagStore, tree: *std.zig.ast.Tree, tag: *std.zig.ast.Node) !void { - const name = analysis.nodeToString(tree, tag).?; - if (self.values.contains(name)) return; - const alloc = self.values.allocator; - const item = types.CompletionItem{ - .label = try std.mem.dupe(alloc, u8, name), - .kind = .Constant, - .documentation = if (try analysis.getDocComments(alloc, tree, tag, .Markdown)) |docs| - .{ - .kind = .Markdown, - .value = docs, - } - else - null, - }; - - try self.values.putNoClobber(item.label, {}); - try self.completions.append(self.values.allocator, item); - } -}; - allocator: *std.mem.Allocator, handles: std.StringHashMap(*Handle), zig_exe_path: ?[]const u8, @@ -81,9 +39,6 @@ build_files: std.ArrayListUnmanaged(*BuildFile), build_runner_path: []const u8, std_uri: ?[]const u8, -error_completions: TagStore, -enum_completions: TagStore, - pub fn init( self: *DocumentStore, allocator: *std.mem.Allocator, @@ -97,8 +52,6 @@ pub fn init( self.build_files = .{}; self.build_runner_path = build_runner_path; self.std_uri = try stdUriFromLibPath(allocator, zig_lib_path); - self.error_completions = TagStore.init(allocator); - self.enum_completions = TagStore.init(allocator); } const LoadPackagesContext = struct { @@ -265,7 +218,7 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand // This includes the last separator curr_path = curr_path[0 .. idx + 1]; - var folder = std.fs.cwd().openDir(curr_path, .{}) catch |err| switch(err) { + var folder = std.fs.cwd().openDir(curr_path, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => return err, }; @@ -365,6 +318,7 @@ fn decrementCount(self: *DocumentStore, uri: []const u8) void { self.allocator.free(import_uri); } + entry.value.document_scope.deinit(self.allocator); entry.value.import_uris.deinit(); self.allocator.destroy(entry.value); const uri_key = entry.key; @@ -650,7 +604,6 @@ pub fn deinit(self: *DocumentStore) void { } self.handles.deinit(); - for (self.build_files.items) |build_file| { for (build_file.packages.items) |pkg| { self.allocator.free(pkg.name); @@ -659,13 +612,38 @@ pub fn deinit(self: *DocumentStore) void { self.allocator.free(build_file.uri); self.allocator.destroy(build_file); } - if (self.std_uri) |std_uri| { self.allocator.free(std_uri); } - self.allocator.free(self.build_runner_path); self.build_files.deinit(self.allocator); - self.error_completions.deinit(); - self.enum_completions.deinit(); +} + +fn tagStoreCompletionItems(self: DocumentStore, arena: *std.heap.ArenaAllocator, base: *DocumentStore.Handle, comptime name: []const u8) ![]types.CompletionItem { + // TODO Better solution for deciding what tags to include + var handle_arr = try arena.allocator.alloc(*DocumentStore.Handle, base.import_uris.items.len + 1); + handle_arr[0] = base; + var len: usize = @field(base.document_scope, name).len; + for (base.import_uris.items) |uri, idx| { + handle_arr[idx + 1] = self.handles.get(uri).?; + len += @field(handle_arr[idx + 1].document_scope, name).len; + } + + var result = try arena.allocator.alloc(types.CompletionItem, len); + var res_idx: usize = 0; + for (handle_arr) |handle| { + for (@field(handle.document_scope, name)) |item| { + result[res_idx] = item; + res_idx += 1; + } + } + return result; +} + +pub fn errorCompletionItems(self: DocumentStore, arena: *std.heap.ArenaAllocator, base: *DocumentStore.Handle) ![]types.CompletionItem { + return try self.tagStoreCompletionItems(arena, base, "error_completions"); +} + +pub fn enumCompletionItems(self: DocumentStore, arena: *std.heap.ArenaAllocator, base: *DocumentStore.Handle) ![]types.CompletionItem { + return try self.tagStoreCompletionItems(arena, base, "enum_completions"); } diff --git a/src/main.zig b/src/main.zig index da72d0f..d738259 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1203,7 +1203,7 @@ fn completionHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: .result = .{ .CompletionList = .{ .isIncomplete = false, - .items = document_store.error_completions.completions.items, + .items = try document_store.errorCompletionItems(arena, handle), }, }, }), @@ -1212,7 +1212,7 @@ fn completionHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: .result = .{ .CompletionList = .{ .isIncomplete = false, - .items = document_store.enum_completions.completions.items, + .items = try document_store.enumCompletionItems(arena, handle), }, }, }), @@ -1640,7 +1640,7 @@ pub fn main() anyerror!void { try processJsonRpc(&arena, &json_parser, buf, config); json_parser.reset(); arena.deinit(); - arena.state.buffer_list = .{}; + arena.state = .{}; if (debug_alloc) |dbg| { std.log.debug(.main, "\n{}\n", .{dbg.info}); diff --git a/src/types.zig b/src/types.zig index fbe0266..d6a12c0 100644 --- a/src/types.zig +++ b/src/types.zig @@ -269,7 +269,14 @@ pub const InsertTextFormat = enum(Integer) { }; pub const CompletionItem = struct { - label: String, kind: CompletionItemKind, textEdit: ?TextEdit = null, filterText: ?String = null, insertText: ?String = null, insertTextFormat: ?InsertTextFormat = InsertTextFormat.PlainText, detail: ?String = null, documentation: ?MarkupContent = null + label: String, + kind: CompletionItemKind, + textEdit: ?TextEdit = null, + filterText: ?String = null, + insertText: ?String = null, + insertTextFormat: ?InsertTextFormat = InsertTextFormat.PlainText, + detail: ?String = null, + documentation: ?MarkupContent = null, // filterText: String = .NotDefined, };