diff --git a/src/analysis.zig b/src/analysis.zig index ec10dc2..10fd1cc 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -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 @@ -474,25 +485,22 @@ pub fn resolveReturnType( if (fn_decl.ast.return_type == 0) return null; const return_type = fn_decl.ast.return_type; + const ret = .{ .node = return_type, .handle = handle }; + const child_type = (try resolveTypeOfNodeInternal(store, arena, ret, bound_type_params)) orelse + return null; const is_inferred_error = tree.tokens.items(.tag)[tree.firstToken(return_type) - 1] == .bang; - return if (is_inferred_error) block: { - const child_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = return_type, - .handle = handle, - }, bound_type_params)) orelse return null; + if (is_inferred_error) { const child_type_node = switch (child_type.type.data) { .other => |n| n, else => return null, }; - break :block TypeWithHandle{ + return TypeWithHandle{ .type = .{ .data = .{ .error_union = child_type_node }, .is_type_val = false }, .handle = child_type.handle, }; - } else ((try resolveTypeOfNodeInternal(store, arena, .{ - .node = return_type, - .handle = handle, - }, bound_type_params)) orelse return null).instanceTypeVal(); + } else + return child_type.instanceTypeVal(); } /// Resolves the child type of an optional type @@ -591,7 +599,7 @@ fn resolveDerefType( return null; } -/// Resolves bracket access type (both slicing and array access) +/// Resolves slicing and array access fn resolveBracketAccessType( store: *DocumentStore, arena: *std.heap.ArenaAllocator, @@ -684,23 +692,20 @@ pub fn resolveTypeOfNodeInternal( node_handle: NodeWithHandle, bound_type_params: *BoundTypeParams, ) error{OutOfMemory}!?TypeWithHandle { - const node = node_handle.node; - const handle = node_handle.handle; - const tree = handle.tree; - - 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| { - if (i.node == node and i.handle == handle) + 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; + const tree = handle.tree; const main_tokens = tree.nodes.items(.main_token); const node_tags = tree.nodes.items(.tag); @@ -755,24 +760,6 @@ pub fn resolveTypeOfNodeInternal( } return null; }, - .container_field, - .container_field_init, - .container_field_align, - => |c| { - const field: ast.full.ContainerField = switch (c) { - .container_field => tree.containerField(node), - .container_field_align => tree.containerFieldAlign(node), - .container_field_init => tree.containerFieldInit(node), - else => unreachable, - }; - - if (field.ast.type_expr == 0) return null; - const field_type = .{ .node = field.ast.type_expr, .handle = handle }; - - const typ = (try resolveTypeOfNodeInternal(store, arena, field_type, bound_type_params)) orelse - return null; - return typ.instanceTypeVal(); - }, .call, .call_comma, .async_call, @@ -839,54 +826,63 @@ pub fn resolveTypeOfNodeInternal( .@"comptime", .@"nosuspend", .grouped_expression, - => { - return try resolveTypeOfNodeInternal(store, arena, .{ .node = datas[node].lhs, .handle = handle }, bound_type_params); - }, + .container_field, + .container_field_init, + .container_field_align, .struct_init, .struct_init_comma, .struct_init_one, .struct_init_one_comma, - => { - return ((try resolveTypeOfNodeInternal( - store, - arena, - .{ .node = datas[node].lhs, .handle = handle }, - bound_type_params, - )) orelse return null).instanceTypeVal(); - }, - .error_set_decl => { - return TypeWithHandle.typeVal(node_handle); - }, .slice, .slice_sentinel, .slice_open, - => { - const left_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; - return try resolveBracketAccessType(store, arena, left_type, .Range, bound_type_params); - }, .deref, .unwrap_optional, + .array_access, + .@"orelse", + .@"catch", + .@"try", + .address_of, => { - const left_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; + const base = .{ .node = datas[node].lhs, .handle = handle }; + const base_type = (try resolveTypeOfNodeInternal(store, arena, base, bound_type_params)) orelse + return null; return switch (node_tags[node]) { - .unwrap_optional => try resolveUnwrapOptionalType(store, arena, left_type, bound_type_params), - .deref => try resolveDerefType(store, arena, left_type, bound_type_params), + .@"comptime", + .@"nosuspend", + .grouped_expression, + => base_type, + .container_field, + .container_field_init, + .container_field_align, + .struct_init, + .struct_init_comma, + .struct_init_one, + .struct_init_one_comma, + => base_type.instanceTypeVal(), + .slice, + .slice_sentinel, + .slice_open, + => try resolveBracketAccessType(store, arena, base_type, .Range, bound_type_params), + .deref => try resolveDerefType(store, arena, base_type, bound_type_params), + .unwrap_optional => try resolveUnwrapOptionalType(store, arena, base_type, bound_type_params), + .array_access => try resolveBracketAccessType(store, arena, base_type, .Single, bound_type_params), + .@"orelse" => try resolveUnwrapOptionalType(store, arena, base_type, bound_type_params), + .@"catch" => try resolveUnwrapErrorType(store, arena, base_type, bound_type_params), + .@"try" => try resolveUnwrapErrorType(store, arena, base_type, bound_type_params), + .address_of => { + const lhs_node = switch (base_type.type.data) { + .other => |n| n, + else => return null, + }; + return TypeWithHandle{ + .type = .{ .data = .{ .pointer = lhs_node }, .is_type_val = base_type.type.is_type_val }, + .handle = base_type.handle, + }; + }, else => unreachable, }; }, - .array_access => { - const left_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; - return try resolveBracketAccessType(store, arena, left_type, .Single, bound_type_params); - }, .field_access => { const field_access = datas[node]; @@ -897,7 +893,7 @@ pub fn resolveTypeOfNodeInternal( store, arena, (try resolveTypeOfNodeInternal(store, arena, .{ - .node = field_access.lhs, + .node = datas[node].lhs, .handle = handle, }, bound_type_params)) orelse return null, bound_type_params, @@ -917,20 +913,6 @@ pub fn resolveTypeOfNodeInternal( return try child.resolveType(store, arena, bound_type_params); } else return null; }, - .@"orelse" => { - const left_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; - return try resolveUnwrapOptionalType(store, arena, left_type, bound_type_params); - }, - .@"catch" => { - const left_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; - return try resolveUnwrapErrorType(store, arena, left_type, bound_type_params); - }, .array_type, .array_type_sentinel, .optional_type, @@ -938,30 +920,20 @@ pub fn resolveTypeOfNodeInternal( .ptr_type, .ptr_type_bit_range, .error_union, + .error_set_decl, + .container_decl, + .container_decl_arg, + .container_decl_arg_trailing, + .container_decl_trailing, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, => return TypeWithHandle.typeVal(node_handle), - .@"try" => { - const rhs_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; - return try resolveUnwrapErrorType(store, arena, rhs_type, bound_type_params); - }, - .address_of => { - const rhs_type = (try resolveTypeOfNodeInternal(store, arena, .{ - .node = datas[node].lhs, - .handle = handle, - }, bound_type_params)) orelse return null; - - const rhs_node = switch (rhs_type.type.data) { - .other => |n| n, - else => return null, - }; - - return TypeWithHandle{ - .type = .{ .data = .{ .pointer = rhs_node }, .is_type_val = rhs_type.type.is_type_val }, - .handle = rhs_type.handle, - }; - }, .builtin_call, .builtin_call_comma, .builtin_call_two, @@ -1035,21 +1007,6 @@ pub fn resolveTypeOfNodeInternal( // reference to node '0' which is root return TypeWithHandle.typeVal(.{ .node = 0, .handle = new_handle }); }, - .container_decl, - .container_decl_arg, - .container_decl_arg_trailing, - .container_decl_trailing, - .container_decl_two, - .container_decl_two_trailing, - .tagged_union, - .tagged_union_trailing, - .tagged_union_two, - .tagged_union_two_trailing, - .tagged_union_enum_tag, - .tagged_union_enum_tag_trailing, - => { - return TypeWithHandle.typeVal(node_handle); - }, .fn_proto, .fn_proto_multi, .fn_proto_one, @@ -2310,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 }; @@ -2516,8 +2470,8 @@ pub const Scope = struct { range: SourceRange, decls: std.StringHashMap(Declaration), - tests: []const ast.Node.Index, - uses: []const *const ast.Node.Index, + tests: []const ast.Node.Index = &.{}, + uses: []const *const ast.Node.Index = &.{}, data: Data, @@ -2546,7 +2500,13 @@ pub fn makeDocumentScope(allocator: *std.mem.Allocator, tree: ast.Tree) !Documen enum_completions.deinit(allocator); } // pass root node index ('0') - try makeScopeInternal(allocator, &scopes, &error_completions, &enum_completions, tree, 0); + had_root = false; + try makeScopeInternal(allocator, .{ + .scopes = &scopes, + .errors = &error_completions, + .enums = &enum_completions, + .tree = tree, + }, 0); return DocumentScope{ .scopes = scopes.toOwnedSlice(allocator), .error_completions = error_completions, @@ -2564,136 +2524,174 @@ fn nodeSourceRange(tree: ast.Tree, node: ast.Node.Index) SourceRange { }; } -fn makeScopeInternal( - allocator: *std.mem.Allocator, +const ScopeContext = struct { scopes: *std.ArrayListUnmanaged(Scope), - error_completions: *CompletionSet, - enum_completions: *CompletionSet, + enums: *CompletionSet, + errors: *CompletionSet, tree: ast.Tree, +}; + +fn makeInnerScope( + allocator: *std.mem.Allocator, + context: ScopeContext, node_idx: ast.Node.Index, ) error{OutOfMemory}!void { + const scopes = context.scopes; + const tree = context.tree; const tags = tree.nodes.items(.tag); const token_tags = tree.tokens.items(.tag); const data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); const node_tag = tags[node_idx]; - if (isContainer(tree, node_idx)) { - var buf: [2]ast.Node.Index = undefined; - const ast_decls = declMembers(tree, node_idx, &buf); + var buf: [2]ast.Node.Index = undefined; + const ast_decls = declMembers(tree, node_idx, &buf); - (try scopes.addOne(allocator)).* = .{ - .range = nodeSourceRange(tree, node_idx), - .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, - .data = .{ .container = node_idx }, - }; - const scope_idx = scopes.items.len - 1; - var uses = std.ArrayListUnmanaged(*const ast.Node.Index){}; - var tests = std.ArrayListUnmanaged(ast.Node.Index){}; + (try scopes.addOne(allocator)).* = .{ + .range = nodeSourceRange(tree, node_idx), + .decls = std.StringHashMap(Declaration).init(allocator), + .data = .{ .container = node_idx }, + }; + const scope_idx = scopes.items.len - 1; + var uses = std.ArrayListUnmanaged(*const ast.Node.Index){}; + var tests = std.ArrayListUnmanaged(ast.Node.Index){}; - errdefer { - scopes.items[scope_idx].decls.deinit(); - uses.deinit(allocator); - tests.deinit(allocator); - } + errdefer { + scopes.items[scope_idx].decls.deinit(); + uses.deinit(allocator); + tests.deinit(allocator); + } - if (node_tag == .error_set_decl) { - // All identifiers in main_token..data.lhs are error fields. - var i = main_tokens[node_idx]; - while (i < data[node_idx].rhs) : (i += 1) { - if (token_tags[i] == .identifier) { - try error_completions.put(allocator, .{ - .label = tree.tokenSlice(i), - .kind = .Constant, - .insertText = tree.tokenSlice(i), - .insertTextFormat = .PlainText, - }, {}); - } + if (node_tag == .error_set_decl) { + // All identifiers in main_token..data.lhs are error fields. + var i = main_tokens[node_idx]; + while (i < data[node_idx].rhs) : (i += 1) { + if (token_tags[i] == .identifier) { + try context.errors.put(allocator, .{ + .label = tree.tokenSlice(i), + .kind = .Constant, + .insertText = tree.tokenSlice(i), + .insertTextFormat = .PlainText, + }, {}); } } + } - const container_decl = switch (node_tag) { - .container_decl, .container_decl_trailing => tree.containerDecl(node_idx), - .container_decl_arg, .container_decl_arg_trailing => tree.containerDeclArg(node_idx), - .container_decl_two, .container_decl_two_trailing => blk: { - var buffer: [2]ast.Node.Index = undefined; - break :blk tree.containerDeclTwo(&buffer, node_idx); - }, - .tagged_union, .tagged_union_trailing => tree.taggedUnion(node_idx), - .tagged_union_enum_tag, .tagged_union_enum_tag_trailing => tree.taggedUnionEnumTag(node_idx), - .tagged_union_two, .tagged_union_two_trailing => blk: { - var buffer: [2]ast.Node.Index = undefined; - break :blk tree.taggedUnionTwo(&buffer, node_idx); - }, + const container_decl = switch (node_tag) { + .container_decl, .container_decl_trailing => tree.containerDecl(node_idx), + .container_decl_arg, .container_decl_arg_trailing => tree.containerDeclArg(node_idx), + .container_decl_two, .container_decl_two_trailing => blk: { + var buffer: [2]ast.Node.Index = undefined; + break :blk tree.containerDeclTwo(&buffer, node_idx); + }, + .tagged_union, .tagged_union_trailing => tree.taggedUnion(node_idx), + .tagged_union_enum_tag, .tagged_union_enum_tag_trailing => tree.taggedUnionEnumTag(node_idx), + .tagged_union_two, .tagged_union_two_trailing => blk: { + var buffer: [2]ast.Node.Index = undefined; + break :blk tree.taggedUnionTwo(&buffer, node_idx); + }, + else => null, + }; + + // Only tagged unions and enums should pass this + const can_have_enum_completions = if (container_decl) |container| blk: { + const kind = token_tags[container.ast.main_token]; + break :blk kind != .keyword_struct and + (kind != .keyword_union or container.ast.enum_token != null or container.ast.arg != 0); + } else false; + + for (ast_decls) |*ptr_decl| { + const decl = ptr_decl.*; + if (tags[decl] == .@"usingnamespace") { + try uses.append(allocator, ptr_decl); + continue; + } + + try makeScopeInternal(allocator, context, decl); + const name = getDeclName(tree, decl) orelse continue; + + if (tags[decl] == .test_decl) { + try tests.append(allocator, decl); + continue; + } + if (try scopes.items[scope_idx].decls.fetchPut(name, .{ .ast_node = decl })) |existing| { + // TODO Record a redefinition error. + } + + if (!can_have_enum_completions) + continue; + + const container_field = switch (tags[decl]) { + .container_field => tree.containerField(decl), + .container_field_align => tree.containerFieldAlign(decl), + .container_field_init => tree.containerFieldInit(decl), else => null, }; - // Only tagged unions and enums should pass this - const can_have_enum_completions = if (container_decl) |container| blk: { - const kind = token_tags[container.ast.main_token]; - break :blk kind != .keyword_struct and - (kind != .keyword_union or container.ast.enum_token != null or container.ast.arg != 0); - } else false; - - for (ast_decls) |*ptr_decl| { - const decl = ptr_decl.*; - if (tags[decl] == .@"usingnamespace") { - try uses.append(allocator, ptr_decl); - continue; - } - - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - decl, - ); - const name = getDeclName(tree, decl) orelse continue; - - if (tags[decl] == .test_decl) { - try tests.append(allocator, decl); - continue; - } - if (try scopes.items[scope_idx].decls.fetchPut(name, .{ .ast_node = decl })) |existing| { - // TODO Record a redefinition error. - } - - if (!can_have_enum_completions) - continue; - - const container_field = switch (tags[decl]) { - .container_field => tree.containerField(decl), - .container_field_align => tree.containerFieldAlign(decl), - .container_field_init => tree.containerFieldInit(decl), - else => null, - }; - - if (container_field) |field| { - if (!std.mem.eql(u8, name, "_")) { - try enum_completions.put(allocator, .{ - .label = name, - .kind = .Constant, - .insertText = name, - .insertTextFormat = .PlainText, - .documentation = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs| .{ - .kind = .Markdown, - .value = docs, - } else null, - }, {}); - } + if (container_field) |field| { + if (!std.mem.eql(u8, name, "_")) { + try context.enums.put(allocator, .{ + .label = name, + .kind = .Constant, + .insertText = name, + .insertTextFormat = .PlainText, + .documentation = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs| + .{ .kind = .Markdown, .value = docs } + else + null, + }, {}); } } + } - scopes.items[scope_idx].tests = tests.toOwnedSlice(allocator); - scopes.items[scope_idx].uses = uses.toOwnedSlice(allocator); - return; + scopes.items[scope_idx].tests = tests.toOwnedSlice(allocator); + scopes.items[scope_idx].uses = uses.toOwnedSlice(allocator); +} + +// Whether we have already visited the root node. +var had_root = true; +fn makeScopeInternal( + allocator: *std.mem.Allocator, + context: ScopeContext, + node_idx: ast.Node.Index, +) error{OutOfMemory}!void { + const scopes = context.scopes; + const tree = context.tree; + const tags = tree.nodes.items(.tag); + const token_tags = tree.tokens.items(.tag); + const data = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const node_tag = tags[node_idx]; + + if (node_idx == 0) { + if (had_root) + return + else + had_root = true; } switch (node_tag) { + .container_decl, + .container_decl_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + .root, + .error_set_decl, + => { + try makeInnerScope(allocator, context, node_idx); + }, + .array_type_sentinel => { + // TODO: ??? + return; + }, .fn_proto, .fn_proto_one, .fn_proto_simple, @@ -2706,8 +2704,6 @@ fn makeScopeInternal( (try scopes.addOne(allocator)).* = .{ .range = nodeSourceRange(tree, node_idx), .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .{ .function = node_idx }, }; var scope_idx = scopes.items.len - 1; @@ -2726,52 +2722,22 @@ fn makeScopeInternal( } // Visit parameter types to pick up any error sets and enum // completions - if (param.type_expr != 0) try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - param.type_expr, - ); + try makeScopeInternal(allocator, context, param.type_expr); } if (fn_tag == .fn_decl) blk: { if (data[node_idx].lhs == 0) break :blk; const return_type_node = data[data[node_idx].lhs].rhs; - if (return_type_node == 0) break :blk; // Visit the return type - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - return_type_node, - ); + try makeScopeInternal(allocator, context, return_type_node); } - if (data[node_idx].rhs == 0) return; // Visit the function body - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - data[node_idx].rhs, - ); + try makeScopeInternal(allocator, context, data[node_idx].rhs); }, .test_decl => { - return try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - data[node_idx].rhs, - ); + return try makeScopeInternal(allocator, context, data[node_idx].rhs); }, .block, .block_semicolon, @@ -2790,8 +2756,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, last_token).start, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -2801,8 +2765,6 @@ fn makeScopeInternal( (try scopes.addOne(allocator)).* = .{ .range = nodeSourceRange(tree, node_idx), .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .{ .block = node_idx }, }; var scope_idx = scopes.items.len - 1; @@ -2835,7 +2797,7 @@ fn makeScopeInternal( continue; } - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, idx); + try makeScopeInternal(allocator, context, idx); if (varDecl(tree, idx)) |var_decl| { const name = tree.tokenSlice(var_decl.ast.mut_token + 1); if (try scopes.items[scope_idx].decls.fetchPut(name, .{ .ast_node = idx })) |existing| { @@ -2850,10 +2812,7 @@ fn makeScopeInternal( .@"if", .if_simple, => { - const if_node: ast.full.If = if (node_tag == .@"if") - ifFull(tree, node_idx) - else - ifSimple(tree, node_idx); + const if_node = ifFull(tree, node_idx); if (if_node.payload_token) |payload| { var scope = try scopes.addOne(allocator); @@ -2863,8 +2822,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, if_node.ast.then_expr)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -2881,14 +2838,7 @@ fn makeScopeInternal( }); } - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - if_node.ast.then_expr, - ); + try makeScopeInternal(allocator, context, if_node.ast.then_expr); if (if_node.ast.else_expr != 0) { if (if_node.error_token) |err_token| { @@ -2900,8 +2850,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, if_node.ast.else_expr)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -2909,16 +2857,35 @@ fn makeScopeInternal( const name = tree.tokenSlice(err_token); try scope.decls.putNoClobber(name, .{ .ast_node = if_node.ast.else_expr }); } - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - if_node.ast.else_expr, - ); + try makeScopeInternal(allocator, context, if_node.ast.else_expr); } }, + .@"catch" => { + try makeScopeInternal(allocator, context, data[node_idx].lhs); + + const catch_token = main_tokens[node_idx]; + const catch_expr = data[node_idx].rhs; + + var scope = try scopes.addOne(allocator); + scope.* = .{ + .range = .{ + .start = offsets.tokenLocation(tree, tree.firstToken(catch_expr)).start, + .end = offsets.tokenLocation(tree, lastToken(tree, catch_expr)).end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .data = .other, + }; + errdefer scope.decls.deinit(); + + if (token_tags.len > catch_token + 2 and + token_tags[catch_token + 1] == .pipe and + token_tags[catch_token + 2] == .identifier) + { + const name = tree.tokenSlice(catch_token + 2); + try scope.decls.putNoClobber(name, .{ .ast_node = catch_expr }); + } + try makeScopeInternal(allocator, context, catch_expr); + }, .@"while", .while_simple, .while_cont, @@ -2937,8 +2904,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, node_idx)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -2954,8 +2919,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, while_node.ast.then_expr)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -2964,17 +2927,20 @@ fn makeScopeInternal( std.debug.assert(token_tags[name_token] == .identifier); const name = tree.tokenSlice(name_token); - try scope.decls.putNoClobber(name, if (is_for) .{ - .array_payload = .{ - .identifier = name_token, - .array_expr = while_node.ast.cond_expr, - }, - } else .{ - .pointer_payload = .{ - .name = name_token, - .condition = while_node.ast.cond_expr, - }, - }); + try scope.decls.putNoClobber(name, if (is_for) + .{ + .array_payload = .{ + .identifier = name_token, + .array_expr = while_node.ast.cond_expr, + }, + } + else + .{ + .pointer_payload = .{ + .name = name_token, + .condition = while_node.ast.cond_expr, + }, + }); // for loop with index as well if (token_tags[name_token + 1] == .comma) { @@ -2988,14 +2954,7 @@ fn makeScopeInternal( } } } - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - while_node.ast.then_expr, - ); + try makeScopeInternal(allocator, context, while_node.ast.then_expr); if (while_node.ast.else_expr != 0) { if (while_node.error_token) |err_token| { @@ -3007,8 +2966,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, while_node.ast.else_expr)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -3016,14 +2973,7 @@ fn makeScopeInternal( const name = tree.tokenSlice(err_token); try scope.decls.putNoClobber(name, .{ .ast_node = while_node.ast.else_expr }); } - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - while_node.ast.else_expr, - ); + try makeScopeInternal(allocator, context, while_node.ast.else_expr); } }, .@"switch", @@ -3048,8 +2998,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, switch_case.ast.target_expr)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -3067,16 +3015,15 @@ fn makeScopeInternal( }); } - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - switch_case.ast.target_expr, - ); + try makeScopeInternal(allocator, context, switch_case.ast.target_expr); } }, + .switch_case, + .switch_case_one, + .switch_range, + => { + return; + }, .global_var_decl, .local_var_decl, .aligned_var_decl, @@ -3084,25 +3031,11 @@ fn makeScopeInternal( => { const var_decl = varDecl(tree, node_idx).?; if (var_decl.ast.type_node != 0) { - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - var_decl.ast.type_node, - ); + try makeScopeInternal(allocator, context, var_decl.ast.type_node); } if (var_decl.ast.init_node != 0) { - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - var_decl.ast.init_node, - ); + try makeScopeInternal(allocator, context, var_decl.ast.init_node); } }, .call, @@ -3117,9 +3050,9 @@ fn makeScopeInternal( var buf: [1]ast.Node.Index = undefined; const call = callFull(tree, node_idx, &buf).?; - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, call.ast.fn_expr); + try makeScopeInternal(allocator, context, call.ast.fn_expr); for (call.ast.params) |param| - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, param); + try makeScopeInternal(allocator, context, param); }, .struct_init, .struct_init_comma, @@ -3140,17 +3073,10 @@ fn makeScopeInternal( }; if (struct_init.ast.type_expr != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - struct_init.ast.type_expr, - ); + try makeScopeInternal(allocator, context, struct_init.ast.type_expr); for (struct_init.ast.fields) |field| { - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, field); + try makeScopeInternal(allocator, context, field); } }, .array_init, @@ -3172,16 +3098,9 @@ fn makeScopeInternal( }; if (array_init.ast.type_expr != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - array_init.ast.type_expr, - ); + try makeScopeInternal(allocator, context, array_init.ast.type_expr); for (array_init.ast.elements) |elem| { - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, elem); + try makeScopeInternal(allocator, context, elem); } }, .container_field, @@ -3190,33 +3109,9 @@ fn makeScopeInternal( => { const field = containerField(tree, node_idx).?; - if (field.ast.type_expr != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - field.ast.type_expr, - ); - if (field.ast.align_expr != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - field.ast.align_expr, - ); - if (field.ast.value_expr != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - field.ast.value_expr, - ); + try makeScopeInternal(allocator, context, field.ast.type_expr); + try makeScopeInternal(allocator, context, field.ast.align_expr); + try makeScopeInternal(allocator, context, field.ast.value_expr); }, .builtin_call, .builtin_call_comma, @@ -3236,7 +3131,7 @@ fn makeScopeInternal( }; for (params) |param| { - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, param); + try makeScopeInternal(allocator, context, param); } }, .ptr_type, @@ -3245,33 +3140,10 @@ fn makeScopeInternal( .ptr_type_sentinel, => { const ptr_type: ast.full.PtrType = ptrType(tree, node_idx).?; - if (ptr_type.ast.sentinel != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - ptr_type.ast.sentinel, - ); - if (ptr_type.ast.align_node != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - ptr_type.ast.align_node, - ); - if (ptr_type.ast.child_type != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - ptr_type.ast.child_type, - ); + + try makeScopeInternal(allocator, context, ptr_type.ast.sentinel); + try makeScopeInternal(allocator, context, ptr_type.ast.align_node); + try makeScopeInternal(allocator, context, ptr_type.ast.child_type); }, .slice, .slice_open, @@ -3283,43 +3155,10 @@ fn makeScopeInternal( .slice_sentinel => tree.sliceSentinel(node_idx), else => unreachable, }; - - if (slice.ast.sliced != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - slice.ast.sliced, - ); - if (slice.ast.start != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - slice.ast.start, - ); - if (slice.ast.end != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - slice.ast.end, - ); - if (slice.ast.sentinel != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - slice.ast.sentinel, - ); + try makeScopeInternal(allocator, context, slice.ast.sliced); + try makeScopeInternal(allocator, context, slice.ast.start); + try makeScopeInternal(allocator, context, slice.ast.end); + try makeScopeInternal(allocator, context, slice.ast.sentinel); }, .@"errdefer" => { const expr = data[node_idx].rhs; @@ -3332,8 +3171,6 @@ fn makeScopeInternal( .end = offsets.tokenLocation(tree, lastToken(tree, expr)).end, }, .decls = std.StringHashMap(Declaration).init(allocator), - .uses = &.{}, - .tests = &.{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -3342,10 +3179,9 @@ fn makeScopeInternal( try scope.decls.putNoClobber(name, .{ .ast_node = expr }); } - try makeScopeInternal(allocator, scopes, error_completions, enum_completions, tree, expr); + try makeScopeInternal(allocator, context, expr); }, - // no scope .@"asm", .asm_simple, .asm_output, @@ -3369,17 +3205,9 @@ fn makeScopeInternal( .@"continue", => {}, .@"break", .@"defer" => { - if (data[node_idx].rhs != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - data[node_idx].rhs, - ); + try makeScopeInternal(allocator, context, data[node_idx].rhs); }, - // all lhs kind of nodes + .@"return", .@"resume", .field_access, @@ -3399,36 +3227,54 @@ fn makeScopeInternal( .unwrap_optional, .@"usingnamespace", => { - if (data[node_idx].lhs != 0) { - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - data[node_idx].lhs, - ); - } + try makeScopeInternal(allocator, context, data[node_idx].lhs); }, - else => { - if (data[node_idx].lhs != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - data[node_idx].lhs, - ); - if (data[node_idx].rhs != 0) - try makeScopeInternal( - allocator, - scopes, - error_completions, - enum_completions, - tree, - data[node_idx].rhs, - ); + + .equal_equal, + .bang_equal, + .less_than, + .greater_than, + .less_or_equal, + .greater_or_equal, + .assign_mul, + .assign_div, + .assign_mod, + .assign_add, + .assign_sub, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_and, + .assign_bit_xor, + .assign_bit_or, + .assign_mul_wrap, + .assign_add_wrap, + .assign_sub_wrap, + .assign, + .merge_error_sets, + .mul, + .div, + .mod, + .array_mult, + .mul_wrap, + .add, + .sub, + .array_cat, + .add_wrap, + .sub_wrap, + .bit_shift_left, + .bit_shift_right, + .bit_and, + .bit_xor, + .bit_or, + .@"orelse", + .bool_and, + .bool_or, + .array_type, + .array_access, + .error_union, + => { + try makeScopeInternal(allocator, context, data[node_idx].lhs); + try makeScopeInternal(allocator, context, data[node_idx].rhs); }, } } diff --git a/src/ast.zig b/src/ast.zig index 865232e..8427fb0 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -138,26 +138,24 @@ fn fullIf(tree: Tree, info: full.If.Ast) full.If { } pub fn ifFull(tree: Tree, node: Node.Index) full.If { - assert(tree.nodes.items(.tag)[node] == .@"if"); const data = tree.nodes.items(.data)[node]; - const extra = tree.extraData(data.rhs, Node.If); - return fullIf(tree, .{ - .cond_expr = data.lhs, - .then_expr = extra.then_expr, - .else_expr = extra.else_expr, - .if_token = tree.nodes.items(.main_token)[node], - }); -} - -pub fn ifSimple(tree: Tree, node: Node.Index) full.If { - assert(tree.nodes.items(.tag)[node] == .if_simple); - const data = tree.nodes.items(.data)[node]; - return fullIf(tree, .{ - .cond_expr = data.lhs, - .then_expr = data.rhs, - .else_expr = 0, - .if_token = tree.nodes.items(.main_token)[node], - }); + if (tree.nodes.items(.tag)[node] == .@"if") { + const extra = tree.extraData(data.rhs, Node.If); + return fullIf(tree, .{ + .cond_expr = data.lhs, + .then_expr = extra.then_expr, + .else_expr = extra.else_expr, + .if_token = tree.nodes.items(.main_token)[node], + }); + } else { + assert(tree.nodes.items(.tag)[node] == .if_simple); + return fullIf(tree, .{ + .cond_expr = data.lhs, + .then_expr = data.rhs, + .else_expr = 0, + .if_token = tree.nodes.items(.main_token)[node], + }); + } } fn fullWhile(tree: Tree, info: full.While.Ast) full.While { diff --git a/src/main.zig b/src/main.zig index ce29140..fb633fe 100644 --- a/src/main.zig +++ b/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( - &arena.allocator, - "```zig\n{s}\n```\n{s}", - .{ builtin.signature, builtin.documentation }, - ) }, + .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,7 +1339,8 @@ 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); - try semanticTokensFullHandler(arena, id, .{ .params = .{ .textDocument = .{ .uri = req.params.textDocument.uri } } }, config); + if (client_capabilities.supports_semantic_tokens) + try semanticTokensFullHandler(arena, id, .{ .params = .{ .textDocument = .{ .uri = req.params.textDocument.uri } } }, config); } fn changeDocumentHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.ChangeDocument, config: Config) !void { @@ -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 = .{ - .signatures = &[1]types.SignatureInformation{sig_info}, - .activeSignature = 0, - .activeParameter = sig_info.activeParameter, - } }, + .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 { diff --git a/src/references.zig b/src/references.zig index a8bb920..c3df550 100644 --- a/src/references.zig +++ b/src/references.zig @@ -244,7 +244,7 @@ fn symbolReferencesInternal( .@"if", .if_simple, => { - const if_node: ast.full.If = if (node_tags[node] == .@"if") ifFull(tree, node) else ifSimple(tree, node); + const if_node = ifFull(tree, node); try symbolReferencesInternal(arena, store, .{ .node = if_node.ast.cond_expr, .handle = handle }, decl, encoding, context, handler); try symbolReferencesInternal(arena, store, .{ .node = if_node.ast.then_expr, .handle = handle }, decl, encoding, context, handler); diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index becd8bf..511fafc 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -696,7 +696,7 @@ fn writeNodeTokens( .@"if", .if_simple, => { - const if_node: ast.full.If = if (tag == .@"if") ifFull(tree, node) else ifSimple(tree, node); + const if_node = ifFull(tree, node); try writeToken(builder, if_node.ast.if_token, .keyword); try await @asyncCall(child_frame, {}, writeNodeTokens, .{ builder, arena, store, if_node.ast.cond_expr }); diff --git a/tests/sessions.zig b/tests/sessions.zig index feeb167..61b6e0a 100644 --- a/tests/sessions.zig +++ b/tests/sessions.zig @@ -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); -} - -fn readResponses(process: *std.ChildProcess, expected_responses: anytype) !void { - var seen = std.mem.zeroes([expected_responses.len]bool); - 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)]; - inline for (expected_responses) |resp, idx| { - if (std.mem.eql(u8, resp, stdout_bytes)) { - if (seen[idx]) @panic("Expected response already received."); - seen[idx] = true; - } - } - std.debug.print("GOT MESSAGE: {s}\n", .{stdout_bytes}); + try server.request("initialize", initialization, expect); + try server.request("initialized", "{}", null); + return server; } - 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; + 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, from_server) catch |err| { + switch (err) { + error.EndOfStream => break, + else => return err, + } + }; + defer header.deinit(allocator); + 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("{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; + } + + 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(); }