Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 101 additions & 7 deletions src/features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,108 @@ fn hoverSymbolWithType(
else => unreachable,
},
.function_parameter => |payload| ast.paramSlice(tree, payload.get(tree).?, false),
.optional_payload,
.error_union_payload,
.error_union_error,
.for_loop_payload,
.optional_payload => |opt_payload| def: {
const name = tree.tokenSlice(opt_payload.identifier);
const condition = tree.getNodeSource(opt_payload.condition);

var doc: []u8 = undefined;
if (decl_handle.isCaptureByRef()) {
doc = try arena.print(
"`{s}` captures a pointer to the unwrapped value of `{s}` when it is not null.",
.{ name, condition },
);
} else {
doc = try arena.print(
"`{s}` captures the unwrapped value of `{s}` when it is not null.",
.{ name, condition },
);
}
try doc_strings.append(arena, doc);
break :def tree.tokenSlice(decl_handle.nameToken());
},
.error_union_payload => |payload| def: {
const name = tree.tokenSlice(payload.identifier);
const condition = tree.getNodeSource(payload.condition);

var doc: []u8 = undefined;
if (decl_handle.isCaptureByRef()) {
doc = try arena.print(
"`{s}` captures a pointer to the successful value of `{s}` when it is not an error.",
.{ name, condition },
);
} else {
doc = try arena.print(
"`{s}` captures the successful value of `{s}` when it is not an error.",
.{ name, condition },
);
}
try doc_strings.append(arena, doc);
break :def tree.tokenSlice(decl_handle.nameToken());
},
.error_union_error => |payload| def: {
const name = tree.tokenSlice(payload.identifier);
const doc = try arena.print(
"`{s}` captures the error handled by this construct.",
.{name},
);
try doc_strings.append(arena, doc);
break :def tree.tokenSlice(decl_handle.nameToken());
},
.for_loop_payload => |payload| def: {
const name = tree.tokenSlice(payload.identifier);

var doc: []u8 = undefined;
if (decl_handle.isCaptureByRef()) {
doc = try arena.print(
"`{s}` captures a pointer to the current element for this iteration.",
.{name},
);
} else {
doc = try arena.print(
"`{s}` captures the current element or range value for this iteration.",
.{name},
);
}
try doc_strings.append(arena, doc);
break :def tree.tokenSlice(decl_handle.nameToken());
},
.switch_payload => def: {
const name = tree.tokenSlice(decl_handle.nameToken());

var doc: []u8 = undefined;
if (decl_handle.isCaptureByRef()) {
doc = try arena.print(
"`{s}` captures a pointer to the value associated with the matching switch case.",
.{name},
);
} else {
doc = try arena.print(
"`{s}` captures the value associated with the matching switch case.",
.{name},
);
}
try doc_strings.append(arena, doc);
break :def name;
},
.switch_inline_tag_payload => def: {
const name = tree.tokenSlice(decl_handle.nameToken());
const doc = try arena.print(
"`{s}` captures the matched tag for this inline switch case.",
.{name},
);
try doc_strings.append(arena, doc);
break :def name;
},
.label => |label| def: {
const name = tree.tokenSlice(label.identifier);
const doc = try arena.print(
"`{s}` labels this block or loop so control-flow statements can target it.",
.{name},
);
try doc_strings.append(arena, doc);
break :def name;
},
.assign_destructure,
.switch_payload,
.switch_inline_tag_payload,
.label,
.error_token,
=> tree.tokenSlice(decl_handle.nameToken()),
};
Expand Down
243 changes: 243 additions & 0 deletions tests/lsp_features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,245 @@ const offsets = zls.offsets;

const allocator: std.mem.Allocator = std.testing.allocator;

test "optional_payload" {
try testHover(
\\fn bar() void {
\\ const foo: ?u8 = 8;
\\ while (foo) |num<cursor>| {}
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(u8)
\\```
\\
\\`num` captures the unwrapped value of `foo` when it is not null.
);
try testHover(
\\fn bar() void {
\\ const foo: ?u8 = 8;
\\ if (foo) |*num<cursor>| {}
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(*u8)
\\```
\\
\\`num` captures a pointer to the unwrapped value of `foo` when it is not null.
);
}

test "error_union_payload" {
try testHover(
\\fn func() !u8 { return 8; }
\\
\\fn bar() void {
\\ const foo = func();
\\ while (foo) |num<cursor>| {} else |_| {}
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(u8)
\\```
\\
\\`num` captures the successful value of `foo` when it is not an error.
);
try testHover(
\\fn func() !u8 { return 8; }
\\
\\fn bar() void {
\\ const foo = func();
\\ if (foo) |*num<cursor>| {} else |_| {}
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(*u8)
\\```
\\
\\`num` captures a pointer to the successful value of `foo` when it is not an error.
);
}

test "error_union_error" {
try testHover(
\\fn func() anyerror!u8 { return 8; }
\\
\\fn bar() void {
\\ const foo = func();
\\ while (foo) |_| {} else |failed<cursor>| {}
\\}
,
\\```zig
\\failed
\\```
\\```zig
\\(anyerror)
\\```
\\
\\`failed` captures the error handled by this construct.
);
try testHover(
\\fn func() anyerror!u8 { return 8; }
\\
\\fn bar() void {
\\ const foo = func();
\\ if (foo) |_| {} else |failed<cursor>| {}
\\}
,
\\```zig
\\failed
\\```
\\```zig
\\(anyerror)
\\```
\\
\\`failed` captures the error handled by this construct.
);
try testHover(
\\fn func() anyerror!u8 { return 8; }
\\
\\fn bar() void {
\\ const foo = func() catch |failed<cursor>| {};
\\}
,
\\```zig
\\failed
\\```
\\```zig
\\(anyerror)
\\```
\\
\\`failed` captures the error handled by this construct.
);
}

test "for_loop_payload" {
try testHover(
\\fn foo() void {
\\ const numbers = [_]u8{ 1, 2, 3 };
\\ for (numbers) |num<cursor>| {}
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(u8)
\\```
\\
\\`num` captures the current element or range value for this iteration.
);
try testHover(
\\fn foo(numbers: []u8) void {
\\ for (numbers) |*num<cursor>| {}
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(*u8)
\\```
\\
\\`num` captures a pointer to the current element for this iteration.
);
}

test "switch_payload" {
try testHover(
\\const letter = enum { a, b };
\\const Bar = union(letter) { a: u8, b: u16, };
\\
\\fn foo(bar: Bar) void {
\\ switch (bar) {
\\ .a => |num<cursor>| {},
\\ else => {},
\\ }
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(u8)
\\```
\\
\\`num` captures the value associated with the matching switch case.
);
try testHover(
\\const letter = enum { a, b };
\\const Bar = union(letter) { a: u8, b: u16, };
\\
\\fn foo(bar: *Bar) void {
\\ switch (bar.*) {
\\ .a => |*num<cursor>| {},
\\ else => {},
\\ }
\\}
,
\\```zig
\\num
\\```
\\```zig
\\(*u8)
\\```
\\
\\`num` captures a pointer to the value associated with the matching switch case.
);
}

test "switch_inline_tag_payload" {
try testHover(
\\const letter = enum { a, b };
\\const Bar = union(letter) { a: u8, b: u16, };
\\
\\fn foo(bar: Bar) void {
\\ switch (bar) { inline .a => |_, tag<cursor>| {}, else => {}, }
\\}
,
\\```zig
\\tag
\\```
\\```zig
\\(letter)
\\```
\\
\\Go to [letter](untitled:///Untitled-0.zig#L1)
\\
\\`tag` captures the matched tag for this inline switch case.
);
try testHover(
\\const letter = enum { a, b };
\\const Bar = union(letter) { a: u8, b: u16, };
\\
\\fn foo(bar: Bar) void {
\\ switch (bar) { inline else => |_, tag<cursor>| {} }
\\}
,
\\```zig
\\tag
\\```
\\```zig
\\(letter)
\\```
\\
\\Go to [letter](untitled:///Untitled-0.zig#L1)
\\
\\`tag` captures the matched tag for this inline switch case.
);
}

test "primitive" {
try testHover(
\\const foo = bool<cursor>;
Expand Down Expand Up @@ -675,6 +914,8 @@ test "block label" {
\\```zig
\\(i32)
\\```
\\
\\`baz` labels this block or loop so control-flow statements can target it.
);
try testHover(
\\const foo: i32 = undefined;
Expand All @@ -688,6 +929,8 @@ test "block label" {
\\```zig
\\(i32)
\\```
\\
\\`baz` labels this block or loop so control-flow statements can target it.
);
}

Expand Down
Loading