From 57d1abd2c5887ec7196f3fde13d324de2ff3935c Mon Sep 17 00:00:00 2001 From: xdk78 Date: Tue, 29 Jul 2025 00:12:01 +0200 Subject: [PATCH] WIP struct size info on hover --- misc/ols.schema.json | 5 ++ src/common/config.odin | 1 + src/server/hover.odin | 171 ++++++++++++++++++++++++++++++--------- src/server/requests.odin | 5 +- src/server/types.odin | 1 + src/testing/testing.odin | 2 +- tests/hover_test.odin | 27 +++++++ 7 files changed, 169 insertions(+), 43 deletions(-) diff --git a/misc/ols.schema.json b/misc/ols.schema.json index cd2b6a64..8f26f0d6 100644 --- a/misc/ols.schema.json +++ b/misc/ols.schema.json @@ -36,6 +36,11 @@ "type": "boolean", "description": "Enables hover feature" }, + "enable_hover_struct_size_info": { + "type": "boolean", + "description": "Enables struct size and alignment information in hover.", + "default": false + }, "enable_procedure_context": { "type": "boolean" }, "enable_snippets": { "type": "boolean", diff --git a/src/common/config.odin b/src/common/config.odin index d550d819..f94a1e17 100644 --- a/src/common/config.odin +++ b/src/common/config.odin @@ -19,6 +19,7 @@ Config :: struct { verbose: bool, enable_format: bool, enable_hover: bool, + enable_hover_struct_size_info: bool, enable_document_symbols: bool, enable_semantic_tokens: bool, enable_inlay_hints: bool, diff --git a/src/server/hover.odin b/src/server/hover.odin index 919a041f..565d092f 100644 --- a/src/server/hover.odin +++ b/src/server/hover.odin @@ -16,34 +16,125 @@ import "core:strings" import "src:common" -write_hover_content :: proc(ast_context: ^AstContext, symbol: Symbol) -> MarkupContent { - content: MarkupContent - - symbol := symbol - - if untyped, ok := symbol.value.(SymbolUntypedValue); ok { - switch untyped.type { - case .String: - symbol.signature = "string" - case .Bool: - symbol.signature = "bool" - case .Float: - symbol.signature = "float" - case .Integer: - symbol.signature = "int" - } +get_expr_size_and_align :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> (size: int, align: int) { + if expr == nil { + return 0, 1 } - cat := concatenate_symbol_information(ast_context, symbol) + if ident, ok := expr.derived.(^ast.Ident); ok { + switch ident.name { + case "int", "uint", "i32", "u32", "uintptr", "rawptr": + return size_of(u32), align_of(u32) + case "i16", "u16": + return size_of(u16), align_of(u16) + case "i64", "u64": + return size_of(u64), align_of(u64) + case "bool": + return size_of(bool), align_of(bool) + case "string": + return size_of(string), align_of(string) + case "f32": + return size_of(f32), align_of(f32) + case "f64": + return size_of(f64), align_of(f64) + case "byte": + return size_of(byte), align_of(byte) + } + } - if cat != "" { - content.kind = "markdown" - content.value = fmt.tprintf("```odin\n%v\n```%v", cat, symbol.doc) - } else { - content.kind = "plaintext" + if _, ok := expr.derived.(^ast.Proc_Type); ok { + return size_of(^rawptr), align_of(^rawptr) } + + if symbol, ok := resolve_type_expression(ast_context, expr); ok { + if s, is_struct := symbol.value.(SymbolStructValue); is_struct { + current_offset := 0 + max_align := 1 + for field_type in s.types { + field_size, field_align := get_expr_size_and_align(ast_context, field_type) + if field_align > max_align { + max_align = field_align + } + if field_align > 0 { + padding := (field_align - (current_offset % field_align)) % field_align + current_offset += padding + } + current_offset += field_size + } + if max_align > 0 { + final_padding := (max_align - (current_offset % max_align)) % max_align + current_offset += final_padding + } + return current_offset, max_align + } + } + + return 0, 1 +} - return content +write_hover_content :: proc(ast_context: ^AstContext, symbol: Symbol, config: ^common.Config) -> MarkupContent { + content: MarkupContent + symbol := symbol + + if untyped, ok := symbol.value.(SymbolUntypedValue); ok { + switch untyped.type { + case .String: + symbol.signature = "string" + case .Bool: + symbol.signature = "bool" + case .Float: + symbol.signature = "float" + case .Integer: + symbol.signature = "int" + } + } + + cat := concatenate_symbol_information(ast_context, symbol) + struct_info := "" + + if config != nil && config.enable_hover_struct_size_info { + if symbol.type == .Struct { + if s, ok := symbol.value.(SymbolStructValue); ok { + current_offset := 0 + max_align := 1 + + for field_type in s.types { + field_size, field_align := get_expr_size_and_align(ast_context, field_type) + + if field_align > max_align { + max_align = field_align + } + if field_align > 0 { + padding := (field_align - (current_offset % field_align)) % field_align + current_offset += padding + } + current_offset += field_size + } + + if max_align > 0 { + final_padding := (max_align - (current_offset % max_align)) % max_align + current_offset += final_padding + } + + if current_offset > 0 { + struct_info = fmt.aprintf("Size: %v bytes, Alignment: %v bytes", current_offset, max_align) + } + } + } + } + + if cat != "" { + content.kind = "markdown" + if struct_info != "" { + content.value = fmt.tprintf("```odin\n%v\n```%v\n%v", cat, symbol.doc, struct_info) + } else { + content.value = fmt.tprintf("```odin\n%v\n```%v", cat, symbol.doc) + } + } else { + content.kind = "plaintext" + } + + return content } builtin_identifier_hover: map[string]string = { @@ -55,7 +146,7 @@ builtin_identifier_hover: map[string]string = { } -get_hover_information :: proc(document: ^Document, position: common.Position) -> (Hover, bool, bool) { +get_hover_information :: proc(document: ^Document, position: common.Position, config: ^common.Config) -> (Hover, bool, bool) { hover := Hover { contents = {kind = "plaintext"}, } @@ -127,7 +218,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> range = common.get_token_range(ident, ast_context.file.src), signature = get_enum_field_signature(v, i), } - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -143,7 +234,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> name = enum_symbol.name, signature = get_enum_field_signature(v, i), } - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) } } } @@ -175,7 +266,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> symbol.doc = get_doc(value.docs[field_index + name_index], context.temp_allocator) build_documentation(&ast_context, &symbol, true) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -205,7 +296,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> symbol.doc = get_doc(value.docs[i], context.temp_allocator) build_bit_field_field_documentation(&ast_context, &symbol, value, i) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -228,7 +319,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> symbol.name = name symbol.pkg = comp_symbol.name symbol.signature = node_to_string(v.types[i]) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -241,7 +332,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> symbol.name = name symbol.pkg = comp_symbol.name symbol.signature = node_to_string(v.types[i]) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -254,7 +345,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if position_context.call != nil { if symbol, ok := resolve_type_location_proc_param_name(&ast_context, &position_context); ok { build_documentation(&ast_context, &symbol, false) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -282,7 +373,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> resolved.pkg = ast_context.document_package } - hover.contents = write_hover_content(&ast_context, resolved) + hover.contents = write_hover_content(&ast_context, resolved, config) return hover, true, true } } @@ -331,7 +422,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> symbol.comment = get_comment(v.comments[i]) symbol.doc = get_doc(v.docs[i], context.temp_allocator) build_documentation(&ast_context, &symbol, true) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -346,7 +437,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> symbol.comment = get_comment(v.comments[i]) build_bit_field_field_documentation(&ast_context, &symbol, v, i) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -371,7 +462,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> } - hover.contents = write_hover_content(&ast_context, resolved) + hover.contents = write_hover_content(&ast_context, resolved, config) return hover, true, true } } @@ -384,7 +475,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> pkg = selector.pkg, signature = get_enum_field_signature(v, i), } - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -397,7 +488,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> for name, i in v.names { if strings.compare(name, implicit_selector.field.name) == 0 { symbol.signature = get_enum_field_signature(v, i) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -408,7 +499,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> for name, i in v.names { if strings.compare(name, implicit_selector.field.name) == 0 { enum_symbol.signature = get_enum_field_signature(v, i) - hover.contents = write_hover_content(&ast_context, enum_symbol) + hover.contents = write_hover_content(&ast_context, enum_symbol, config) return hover, true, true } } @@ -419,7 +510,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> for name, i in v.names { if strings.compare(name, implicit_selector.field.name) == 0 { enum_symbol.signature = get_enum_field_signature(v, i) - hover.contents = write_hover_content(&ast_context, enum_symbol) + hover.contents = write_hover_content(&ast_context, enum_symbol, config) return hover, true, true } } @@ -458,7 +549,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> resolved.pkg = ast_context.document_package } - hover.contents = write_hover_content(&ast_context, resolved) + hover.contents = write_hover_content(&ast_context, resolved, config) return hover, true, true } } diff --git a/src/server/requests.odin b/src/server/requests.odin index 77e4ca73..64635d70 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -427,7 +427,8 @@ read_ols_initialize_options :: proc(config: ^common.Config, ols_config: OlsConfi ols_config.enable_inlay_hints_default_params.(bool) or_else config.enable_inlay_hints_default_params config.enable_fake_method = ols_config.enable_fake_methods.(bool) or_else config.enable_fake_method - + config.enable_hover_struct_size_info = + ols_config.enable_hover_struct_size_info.(bool) or_else config.enable_hover_struct_size_info for it in ols_config.collections { if it.name in config.collections { @@ -1255,7 +1256,7 @@ request_hover :: proc(params: json.Value, id: RequestId, config: ^common.Config, hover: Hover valid: bool - hover, valid, ok = get_hover_information(document, hover_params.position) + hover, valid, ok = get_hover_information(document, hover_params.position, config) if !ok { return .InternalError diff --git a/src/server/types.odin b/src/server/types.odin index 78a71584..f775f935 100644 --- a/src/server/types.odin +++ b/src/server/types.odin @@ -407,6 +407,7 @@ OlsConfig :: struct { enable_document_symbols: Maybe(bool), enable_format: Maybe(bool), enable_hover: Maybe(bool), + enable_hover_struct_size_info: Maybe(bool), enable_procedure_context: Maybe(bool), enable_snippets: Maybe(bool), enable_inlay_hints: Maybe(bool), diff --git a/src/testing/testing.odin b/src/testing/testing.odin index 429d3930..9b1d76a2 100644 --- a/src/testing/testing.odin +++ b/src/testing/testing.odin @@ -253,7 +253,7 @@ expect_hover :: proc(t: ^testing.T, src: ^Source, expect_hover_string: string) { setup(src) defer teardown(src) - hover, valid, ok := server.get_hover_information(src.document, src.position) + hover, valid, ok := server.get_hover_information(src.document, src.position, &src.config) if !ok { log.error(t, "Failed get_hover_information") diff --git a/tests/hover_test.odin b/tests/hover_test.odin index 06fe3df5..10a32ee6 100644 --- a/tests/hover_test.odin +++ b/tests/hover_test.odin @@ -3101,6 +3101,33 @@ ast_hover_override_documentation_reexported :: proc(t: ^testing.T) { "my_package.Foo: struct {}\n New docs for Foo", ) } + +@(test) +ast_hover_struct_size_and_alignment :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + // this is a doc + a: u32, + b: u64, + c: u16, + } + + foo := F{*}oo{} + `, + packages = {}, + config = { + enable_hover_struct_size_info = true, + }, + } + + test.expect_hover( + t, + &source, + "test.Foo: struct {\n\t// this is a doc\n\ta: u32,\n\tb: u64,\n\tc: u16,\n}\nSize: 24 bytes, Alignment: 8 bytes", + ) +} + /* Waiting for odin fix