diff --git a/misc/ols.schema.json b/misc/ols.schema.json index c3ad997a..8e50c857 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 4aaefac8..03346d14 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_unused_imports_reporting: bool, diff --git a/src/server/completion.odin b/src/server/completion.odin index 5d39109c..b9f8fa09 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -326,7 +326,7 @@ convert_completion_results :: proc( build_documentation(ast_context, &result.symbol, true) item := CompletionItem { label = result.symbol.name, - documentation = write_hover_content(ast_context, result.symbol), + documentation = write_hover_content(ast_context, result.symbol, config), sortText = fmt.tprintf("%05d", i), } diff --git a/src/server/hover.odin b/src/server/hover.odin index 20651e4a..5aa11f2e 100644 --- a/src/server/hover.odin +++ b/src/server/hover.odin @@ -9,22 +9,111 @@ import "core:strings" import "src:common" -write_hover_content :: proc(ast_context: ^AstContext, symbol: Symbol) -> MarkupContent { - content: MarkupContent - cat := construct_symbol_information(ast_context, symbol) - doc := construct_symbol_docs(symbol) +get_expr_size_and_align :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> (size: int, align: int) { + if expr == nil { + return 0, 1 + } - if cat != "" { - content.kind = "markdown" - content.value = fmt.tprintf("```odin\n%v\n```%v", cat, doc) - } else { - content.kind = "plaintext" + 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) + } } - return content + 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 +} + +write_hover_content :: proc(ast_context: ^AstContext, symbol: Symbol, config: ^common.Config) -> MarkupContent { + content: MarkupContent + cat := construct_symbol_information(ast_context, symbol) + doc := construct_symbol_docs(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) + } + } + } + } + + content.kind = "markdown" + + if struct_info != "" { + content.value = fmt.tprintf("```odin\n%v\n```%v\n%v", cat, doc, struct_info) + } else { + content.value = fmt.tprintf("```odin\n%v\n```%v", cat, doc) + } + + return content } -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"}, } @@ -96,7 +185,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> for name, i in v.names { if name == ident.name { construct_enum_field_symbol(&enum_symbol, v, i) - hover.contents = write_hover_content(&ast_context, enum_symbol) + hover.contents = write_hover_content(&ast_context, enum_symbol, config) hover.range = enum_symbol.range return hover, true, true } @@ -109,7 +198,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if name == ident.name { construct_enum_field_symbol(&enum_symbol, v, i) hover.range = enum_symbol.range - hover.contents = write_hover_content(&ast_context, enum_symbol) + hover.contents = write_hover_content(&ast_context, enum_symbol, config) } } } @@ -145,7 +234,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> ) build_documentation(&ast_context, &symbol, true) hover.range = symbol.range - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -174,7 +263,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if value, ok := bit_field_symbol.value.(SymbolBitFieldValue); ok { construct_bit_field_field_symbol(&symbol, name, value, i) hover.range = symbol.range - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -199,7 +288,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if symbol, ok := resolve_type_expression(&ast_context, v.types[i]); ok { construct_struct_field_symbol(&symbol, comp_symbol.name, v, i) 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 } } @@ -210,7 +299,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if name == field.name { if symbol, ok := resolve_type_expression(&ast_context, v.types[i]); ok { construct_bit_field_field_symbol(&symbol, comp_symbol.name, v, i) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -223,7 +312,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 } } @@ -251,7 +340,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 } } @@ -296,7 +385,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if symbol, ok := resolve_type_expression(&ast_context, v.types[i]); ok { construct_struct_field_symbol(&symbol, selector.name, v, i) 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 } } @@ -306,7 +395,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> if name == field { if symbol, ok := resolve_type_expression(&ast_context, v.types[i]); ok { construct_bit_field_field_symbol(&symbol, selector.name, v, i) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -323,7 +412,10 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> } } - if resolved, ok := resolve_symbol_return(&ast_context, lookup(ident.name, selector.pkg, ast_context.fullpath)); ok { + if resolved, ok := resolve_symbol_return( + &ast_context, + lookup(ident.name, selector.pkg, ast_context.fullpath), + ); ok { build_documentation(&ast_context, &resolved, false) resolved.name = ident.name @@ -332,7 +424,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 } } @@ -346,26 +438,26 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> signature = get_enum_field_signature(v, i), type = .Field, } - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } case SymbolSliceValue: - return get_soa_field_hover(&ast_context, selector, v.expr, nil, field) + return get_soa_field_hover(&ast_context, selector, v.expr, nil, field, config) case SymbolDynamicArrayValue: if field == "allocator" { if symbol, ok := resolve_container_allocator(&ast_context, "Raw_Dynamic_Array"); ok { - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } - return get_soa_field_hover(&ast_context, selector, v.expr, nil, field) + return get_soa_field_hover(&ast_context, selector, v.expr, nil, field, config) case SymbolFixedArrayValue: - return get_soa_field_hover(&ast_context, selector, v.expr, v.len, field) + return get_soa_field_hover(&ast_context, selector, v.expr, v.len, field, config) case SymbolMapValue: if field == "allocator" { if symbol, ok := resolve_container_allocator(&ast_context, "Raw_Map"); ok { - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -379,7 +471,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 { construct_enum_field_symbol(&symbol, v, i) - hover.contents = write_hover_content(&ast_context, symbol) + hover.contents = write_hover_content(&ast_context, symbol, config) return hover, true, true } } @@ -390,7 +482,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 { construct_enum_field_symbol(&enum_symbol, 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 } } @@ -401,7 +493,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 { construct_enum_field_symbol(&enum_symbol, 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 } } @@ -436,7 +528,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> construct_ident_symbol_info(&resolved, ident.name, ast_context.document_package) build_documentation(&ast_context, &resolved, false) - hover.contents = write_hover_content(&ast_context, resolved) + hover.contents = write_hover_content(&ast_context, resolved, config) return hover, true, true } } @@ -451,6 +543,7 @@ get_soa_field_hover :: proc( expr: ^ast.Expr, size: ^ast.Expr, field: string, + config: ^common.Config, ) -> ( Hover, bool, @@ -466,7 +559,7 @@ get_soa_field_hover :: proc( symbol.name = field build_documentation(ast_context, &symbol, false) hover: Hover - hover.contents = write_hover_content(ast_context, symbol) + hover.contents = write_hover_content(ast_context, symbol, config) return hover, true, true } return {}, false, true diff --git a/src/server/requests.odin b/src/server/requests.odin index a7758509..55bfd40d 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -443,7 +443,8 @@ read_ols_initialize_options :: proc(config: ^common.Config, ols_config: OlsConfi ols_config.enable_inlay_hints_implicit_return.(bool) or_else config.enable_inlay_hints_implicit_return 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 { @@ -1299,7 +1300,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/symbol.odin b/src/server/symbol.odin index d4be282f..64f3b381 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -275,19 +275,19 @@ SymbolStructValueBuilder :: struct { symbol_struct_value_builder_make_none :: proc(allocator := context.allocator) -> SymbolStructValueBuilder { return SymbolStructValueBuilder { - names = make([dynamic]string, allocator), - types = make([dynamic]^ast.Expr, allocator), - args = make([dynamic]^ast.Expr, allocator), - ranges = make([dynamic]common.Range, allocator), - docs = make([dynamic]^ast.Comment_Group, allocator), - comments = make([dynamic]^ast.Comment_Group, allocator), - usings = make([dynamic]int, allocator), - from_usings = make([dynamic]int, allocator), + names = make([dynamic]string, allocator), + types = make([dynamic]^ast.Expr, allocator), + args = make([dynamic]^ast.Expr, allocator), + ranges = make([dynamic]common.Range, allocator), + docs = make([dynamic]^ast.Comment_Group, allocator), + comments = make([dynamic]^ast.Comment_Group, allocator), + usings = make([dynamic]int, allocator), + from_usings = make([dynamic]int, allocator), unexpanded_usings = make([dynamic]int, allocator), - poly_names = make([dynamic]string, allocator), - backing_types = make(map[int]^ast.Expr, allocator), - bit_sizes = make(map[int]^ast.Expr, allocator), - where_clauses = make([dynamic]^ast.Expr, allocator), + poly_names = make([dynamic]string, allocator), + backing_types = make(map[int]^ast.Expr, allocator), + bit_sizes = make(map[int]^ast.Expr, allocator), + where_clauses = make([dynamic]^ast.Expr, allocator), } } diff --git a/src/server/types.odin b/src/server/types.odin index 4b4c0bd6..2267e6aa 100644 --- a/src/server/types.odin +++ b/src/server/types.odin @@ -277,10 +277,10 @@ DiagnosticSeverity :: enum { Hint = 4, } - DiagnosticTag :: enum int { +DiagnosticTag :: enum int { Unnecessary = 1, Deprecated = 2, - } +} Diagnostic :: struct { range: common.Range, @@ -413,35 +413,36 @@ FileSystemWatcher :: struct { } OlsConfig :: struct { - collections: [dynamic]OlsConfigCollection, - thread_pool_count: Maybe(int), - enable_format: Maybe(bool), - enable_hover: Maybe(bool), - enable_document_symbols: Maybe(bool), - enable_fake_methods: Maybe(bool), - enable_references: Maybe(bool), - enable_document_highlights: Maybe(bool), - enable_document_links: Maybe(bool), - enable_completion_matching: Maybe(bool), - enable_inlay_hints_params: Maybe(bool), - enable_inlay_hints_default_params: Maybe(bool), + collections: [dynamic]OlsConfigCollection, + thread_pool_count: Maybe(int), + enable_format: Maybe(bool), + enable_hover: Maybe(bool), + enable_hover_struct_size_info: Maybe(bool), + enable_document_symbols: Maybe(bool), + enable_fake_methods: Maybe(bool), + enable_references: Maybe(bool), + enable_document_highlights: Maybe(bool), + enable_document_links: Maybe(bool), + enable_completion_matching: Maybe(bool), + enable_inlay_hints_params: Maybe(bool), + enable_inlay_hints_default_params: Maybe(bool), enable_inlay_hints_implicit_return: Maybe(bool), - enable_semantic_tokens: Maybe(bool), - enable_unused_imports_reporting: Maybe(bool), - enable_procedure_context: Maybe(bool), - enable_snippets: Maybe(bool), - enable_procedure_snippet: Maybe(bool), - enable_checker_only_saved: Maybe(bool), - enable_auto_import: Maybe(bool), - disable_parser_errors: Maybe(bool), - verbose: Maybe(bool), - file_log: Maybe(bool), - odin_command: string, - odin_root_override: string, - checker_args: string, - checker_targets: []string, - profiles: [dynamic]common.ConfigProfile, - profile: string, + enable_semantic_tokens: Maybe(bool), + enable_unused_imports_reporting: Maybe(bool), + enable_procedure_context: Maybe(bool), + enable_snippets: Maybe(bool), + enable_procedure_snippet: Maybe(bool), + enable_checker_only_saved: Maybe(bool), + enable_auto_import: Maybe(bool), + disable_parser_errors: Maybe(bool), + verbose: Maybe(bool), + file_log: Maybe(bool), + odin_command: string, + odin_root_override: string, + checker_args: string, + checker_targets: []string, + profiles: [dynamic]common.ConfigProfile, + profile: string, } OlsConfigCollection :: struct { diff --git a/src/testing/testing.odin b/src/testing/testing.odin index 4cb94840..0ef06426 100644 --- a/src/testing/testing.odin +++ b/src/testing/testing.odin @@ -309,7 +309,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 a90346e1..ce28f18f 100644 --- a/tests/hover_test.odin +++ b/tests/hover_test.odin @@ -5282,6 +5282,34 @@ ast_hover_proc_group_named_arg_with_nil :: proc(t: ^testing.T) { } test.expect_hover(t, &source, "test.bar :: proc(i: int, foo: ^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