diff --git a/src/features/completions.zig b/src/features/completions.zig index 673d4cf4f..7f54ceb56 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -822,6 +822,7 @@ fn completeDot(builder: *Builder, loc: offsets.Loc) Analyser.Error!void { const containers = try collectContainerNodes(builder, builder.orig_handle, dot_context); for (containers) |container| { try collectContainerFields(builder, dot_context.likely, container, used_members_set); + try collectErrorSetNames(builder, container, used_members_set, loc); } } @@ -840,7 +841,7 @@ fn completeError(builder: *Builder, loc: offsets.Loc) Analyser.Error!void { const used_members_set = try collectUsedMembersSet(builder, dot_context.likely, dot_token_index); const containers = try collectContainerNodes(builder, builder.orig_handle, dot_context); for (containers) |container| { - try collectErrorSetNames(builder, container, used_members_set); + try collectErrorSetNames(builder, container, used_members_set, null); } } @@ -848,6 +849,7 @@ fn collectErrorSetNames( builder: *Builder, container: Analyser.Type, omit_members: std.BufSet, + dot_token: ?offsets.Loc, ) Analyser.Error!void { const ip = builder.analyser.ip; const key = switch (container.data) { @@ -860,10 +862,22 @@ fn collectErrorSetNames( const str = names.at(@intCast(idx), ip); const name = try builder.arena.print("{f}", .{str.fmt(ip.io, &ip.string_pool)}); if (omit_members.contains(name)) continue; + const textEdit = if (dot_token) |dot| blk: { + const tree = &builder.orig_handle.tree; + const source = tree.source; + const dot_range = offsets.locToRange(source, dot, builder.server.offset_encoding); + break :blk createTextEdit(builder, .{ + .newText = try builder.arena.print("error.{s}", .{name}), + .insert = dot_range, + // FIXME: + .replace = dot_range, + }); + } else null; try builder.completions.append(builder.arena, .{ .label = try builder.arena.print("error.{s}", .{name}), .filterText = name, .insertText = name, + .textEdit = textEdit, .kind = .Constant, }); } diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index d26609e1a..2f7223ca3 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -2116,6 +2116,7 @@ test "switch on error set - text edits result" { .enable_snippets = false, }); } + test "switch on error set - insert/replace text edits" { try testCompletionTextEdit(.{ .source = @@ -2181,6 +2182,126 @@ test "switch on error set - Works in a function of a container" { }); } +test "switch on error set (dot completion) - all values are suggested" { + try testCompletion( + \\const err: error{E1, E2} = undefined; + \\switch(err) { + \\ . + \\} + , &.{ + .{ .label = "error.E1", .kind = .Constant }, + .{ .label = "error.E2", .kind = .Constant }, + }); +} + +test "switch on error set (dot completion) - already used values are not suggested" { + try testCompletion( + \\const err: error{E1, E2} = undefined; + \\switch(err) { + \\ error.E1 => {}, + \\ . + \\} + , &.{ + .{ .label = "error.E2", .kind = .Constant }, + }); +} + +test "switch on error set (dot completion) - error unions get all values" { + try testCompletion( + \\const Err2 = error{F1, F2}; + \\const Err = error{E1, E2} || Err2; + \\const err: Err = undefined; + \\switch(err) { + \\ . + \\} + , &.{ + .{ .label = "error.E1", .kind = .Constant }, + .{ .label = "error.E2", .kind = .Constant }, + .{ .label = "error.F1", .kind = .Constant }, + .{ .label = "error.F2", .kind = .Constant }, + }); +} + +test "switch on error set (dot completion) - text edits result" { + try testCompletionTextEdit(.{ + .source = + \\const err: error{E1, E2} = undefined; + \\switch(err) { + \\ . + \\} + , + .label = "error.E1", + .expected_insert_line = " error.E1", + .expected_replace_line = " error.E1", + .enable_snippets = false, + }); +} + +test "switch on error set (dot completion) - insert/replace text edits" { + try testCompletionTextEdit(.{ + .source = + \\const err: error{Err1, Err2} = undefined; + \\switch(err) { + \\ .E0 + \\} + , + .label = "error.Err1", + .expected_insert_line = " error.Err10", + .expected_replace_line = " error.Err1", + .enable_snippets = false, + }); +} + +test "switch on error set (dot completion) - completion inside catch block works" { + try testCompletion( + \\fn idk() error{ E1, E2 }!void {} + \\test { + \\ idk() catch |err| { + \\ switch (err) { + \\ . + \\ } + \\ }; + \\} + , &.{ + .{ .label = "error.E1", .kind = .Constant }, + .{ .label = "error.E2", .kind = .Constant }, + }); +} + +test "switch on error set (dot completion) - completion inside catch statement" { + if (true) return error.SkipZigTest; // TODO un-skip after https://github.com/zigtools/zls/issues/2341 and/or https://github.com/zigtools/zls/issues/1112 + try testCompletion( + \\fn idk() error{ E1, E2 }!void {} + \\test { + \\ idk() catch |err| switch (err) { + \\ . + \\ }; + \\} + , &.{ + .{ .label = "error.E1", .kind = .Constant }, + .{ .label = "error.E2", .kind = .Constant }, + }); +} + +test "switch on error set (dot completion) - Works in a function of a container" { + if (true) return error.SkipZigTest; // TODO un-skip after https://github.com/zigtools/zls/issues/1535 + try testCompletion( + \\fn idk() error{ E1, E2 }!void {} + \\pub const Manager = struct { + \\ pub fn testIt() void { + \\ _ = idk() catch |err| { + \\ switch(err) { + \\ . + \\ } + \\ }; + \\ } + \\}; + , &.{ + .{ .label = "error.E1", .kind = .Constant }, + .{ .label = "error.E2", .kind = .Constant }, + }); +} + test "error set" { try testCompletion( \\const E = error {