Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: struct hovers should show entire struct, minus functions #1568

Open
slimsag opened this issue Nov 1, 2023 · 4 comments · May be fixed by #1916
Open

proposal: struct hovers should show entire struct, minus functions #1568

slimsag opened this issue Nov 1, 2023 · 4 comments · May be fixed by #1916
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@slimsag
Copy link
Contributor

slimsag commented Nov 1, 2023

Related to #1567 and maybe #1338

Today when hovering over a struct type you get a rather useless:

image

It would be nice if after the documentation (if any), the actual code snippet for the struct with any fn redacted is shown. e.g. the full hover could be:

const StaticLibraryOptions = struct

Documentation text if any would go here.

pub const StaticLibraryOptions = struct {
    name: []const u8,
    root_source_file: ?LazyPath = null,
    target: CrossTarget,
    optimize: std.builtin.OptimizeMode,
    version: ?std.SemanticVersion = null,
    max_rss: usize = 0,
    link_libc: ?bool = null,
    single_threaded: ?bool = null,
    use_llvm: ?bool = null,
    use_lld: ?bool = null,
    zig_lib_dir: ?LazyPath = null,
    main_mod_path: ?LazyPath = null,

    /// Deprecated; use `main_mod_path`.
    main_pkg_path: ?LazyPath = null,
};

(type)

@slimsag slimsag added the enhancement New feature or request label Nov 1, 2023
@llogick
Copy link
Member

llogick commented Nov 15, 2023

Yes, I was very fond of this feature and would like to see it come back or at least as an option.

The original motivation as stated in that issue was that editors place doc comments below all that info, but we can include those at top.

Let me know, Team !

until then I'll stash this here
diff --git a/src/analysis.zig b/src/analysis.zig
index 7151384..1db465d 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -296,6 +296,29 @@ pub fn getVariableSignature(allocator: std.mem.Allocator, tree: Ast, var_decl: A
             offset += 1;
         }
 
+        if (container_decl.ast.members.len != 0) container_fields: {
+            var fields = try std.ArrayListUnmanaged(Ast.full.ContainerField).initCapacity(allocator, container_decl.ast.members.len);
+            for (container_decl.ast.members) |member| {
+                const field = Ast.fullContainerField(tree, member) orelse continue;
+                fields.appendAssumeCapacity(field);
+            }
+            if (fields.items.len == 0) break :container_fields;
+            var desc = std.ArrayListUnmanaged(u8){};
+            try desc.appendSlice(allocator, offsets.tokensToSlice(tree, start_token, token + offset)); // ie `const T = type`
+            try desc.appendSlice(allocator, " {\n    ");
+            // Decls aren't allowed between fields => get a slice of first_field.ast.main_token .. last_field.ast.xxx_token
+            const first_field = fields.items[0];
+            const last_field = fields.items[fields.items.len - 1];
+            const desc_end_token = last_field_last_token: {
+                if (last_field.ast.value_expr == 0 and last_field.ast.type_expr == 0 and last_field.ast.align_expr == 0) break :last_field_last_token last_field.ast.main_token;
+                const end_node = if (last_field.ast.value_expr != 0) last_field.ast.value_expr else last_field.ast.type_expr;
+                break :last_field_last_token ast.lastToken(tree, end_node);
+            };
+            try desc.appendSlice(allocator, offsets.tokensToSlice(tree, first_field.ast.main_token, desc_end_token));
+            try desc.appendSlice(allocator, "\n}");
+            return desc.toOwnedSlice(allocator);
+        }
+
         break :blk token + offset;
     };
     return offsets.tokensToSlice(tree, start_token, end_token);

@Techatrix Techatrix added the good first issue Good for newcomers label Jun 6, 2024
@WillLillis
Copy link
Sponsor Contributor

Yes, I was very fond of this feature and would like to see it come back or at least as an option.

The original motivation as stated in that issue was that editors place doc comments below all that info, but we can include those at top.

Let me know, Team !

until then I'll stash this here

diff --git a/src/analysis.zig b/src/analysis.zig
index 7151384..1db465d 100644
--- a/src/analysis.zig
+++ b/src/analysis.zig
@@ -296,6 +296,29 @@ pub fn getVariableSignature(allocator: std.mem.Allocator, tree: Ast, var_decl: A
             offset += 1;
         }
 
+        if (container_decl.ast.members.len != 0) container_fields: {
+            var fields = try std.ArrayListUnmanaged(Ast.full.ContainerField).initCapacity(allocator, container_decl.ast.members.len);
+            for (container_decl.ast.members) |member| {
+                const field = Ast.fullContainerField(tree, member) orelse continue;
+                fields.appendAssumeCapacity(field);
+            }
+            if (fields.items.len == 0) break :container_fields;
+            var desc = std.ArrayListUnmanaged(u8){};
+            try desc.appendSlice(allocator, offsets.tokensToSlice(tree, start_token, token + offset)); // ie `const T = type`
+            try desc.appendSlice(allocator, " {\n    ");
+            // Decls aren't allowed between fields => get a slice of first_field.ast.main_token .. last_field.ast.xxx_token
+            const first_field = fields.items[0];
+            const last_field = fields.items[fields.items.len - 1];
+            const desc_end_token = last_field_last_token: {
+                if (last_field.ast.value_expr == 0 and last_field.ast.type_expr == 0 and last_field.ast.align_expr == 0) break :last_field_last_token last_field.ast.main_token;
+                const end_node = if (last_field.ast.value_expr != 0) last_field.ast.value_expr else last_field.ast.type_expr;
+                break :last_field_last_token ast.lastToken(tree, end_node);
+            };
+            try desc.appendSlice(allocator, offsets.tokensToSlice(tree, first_field.ast.main_token, desc_end_token));
+            try desc.appendSlice(allocator, "\n}");
+            return desc.toOwnedSlice(allocator);
+        }
+
         break :blk token + offset;
     };
     return offsets.tokensToSlice(tree, start_token, end_token);

Just saw this today and thought it was really cool! Would it be possible for this to be added soon? I played with it a bit and noticed that in some cases the indention was inconsistent, e.g.

image

but could be simplified (unless I'm missing some edge cases, would be happy to look into this more) and made consistent with the following code:

if (container_decl.ast.members.len != 0) container_fields: {
    var fields = try std.ArrayListUnmanaged(Ast.full.ContainerField).initCapacity(allocator, container_decl.ast.members.len);
    for (container_decl.ast.members) |member| {
        const field = Ast.fullContainerField(tree, member) orelse continue;
        fields.appendAssumeCapacity(field);
    }
    if (fields.items.len == 0) break :container_fields;
    var desc = std.ArrayListUnmanaged(u8){};

    const last_field = fields.items[fields.items.len - 1];
    const desc_end_token = last_field_last_token: {
        if (last_field.ast.value_expr == 0 and last_field.ast.type_expr == 0 and last_field.ast.align_expr == 0) break :last_field_last_token last_field.ast.main_token;
        const end_node = if (last_field.ast.value_expr != 0) last_field.ast.value_expr else last_field.ast.type_expr;
        break :last_field_last_token ast.lastToken(tree, end_node);
    };
    try desc.appendSlice(allocator, offsets.tokensToSlice(tree, start_token, desc_end_token));
    try desc.appendSlice(allocator, "\n}");
    return desc.toOwnedSlice(allocator);
}

Screenshot after:

image

Something I did notice with both approaches is that any extra indentation in the source code also got added to the hover window's contents. I imagine this could be removed with some postprocessing (maybe utilizing some of zig fmt's functionality?), but probably isn't too big of a deal. Would it be helpful if I opened a PR with these changes to get the ball rolling? :)

@llogick
Copy link
Member

llogick commented Jun 8, 2024

@WillLillis

I played with it a bit and noticed that in some cases the indention was inconsistent, e.g.

I admit that I was aware of the indentation issue, but wasn't gonna bother with it until this was sorta/kinda approved.

The idea I had is to have the logic in a separate fn,

  • iterate over container members, check if full field
  • if field.ast.type_expr is a fullContainerDecl => recurse,
    otherwise add it to desc
  • would need to keep/take an indent depth level

but could be simplified (unless I'm missing some edge cases, ...

// just to illustrate the case, not guaranteed to be semantically correct
const S = struct {
	fn foo() void {
		// many lines here
    }

	fld: u8,
};

I'd be happy to see a PR.

Cheers

@WillLillis
Copy link
Sponsor Contributor

Was away from my computer for most of the weekend but I was able to work on this a bit. I'm going to spend some more time writing tests and cleaning things up but I should have a PR open soonish :)

/// just to illustrate the case, not guaranteed to be semantically correct
const S = struct {
	fn foo() void {
		// many lines here
    }

	fld: u8,
};

image

/// Doc comment
const FooEnum = enum {
    foo,
    bar,
    baz,

    const Inner = struct {
        something: bool,
    };
};

image

@WillLillis WillLillis linked a pull request Jun 10, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants