From d91f1da376e12f7ea8eef54528dc6f5ace6cf8bb Mon Sep 17 00:00:00 2001 From: Brad Lewis <22850972+BradLewis@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:20:29 -0400 Subject: Enrich bit field hover documentation --- src/server/analysis.odin | 25 +++++++--- src/server/ast.odin | 34 +++++++++++++ src/server/collector.odin | 31 ++++++++---- src/server/documentation.odin | 108 +++++++++++++++++++++++++++++++++--------- src/server/symbol.odin | 10 ++-- 5 files changed, 165 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 597c2cd..bfdf5ba 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -940,7 +940,7 @@ internal_resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Ex case ^Proc_Type: return make_symbol_procedure_from_ast(ast_context, node, v^, ast_context.field_name, {}, true), true case ^Bit_Field_Type: - return make_symbol_bit_field_from_ast(ast_context, v^, ast_context.field_name, true), true + return make_symbol_bit_field_from_ast(ast_context, v, ast_context.field_name, true), true case ^Basic_Directive: return resolve_basic_directive(ast_context, v^) case ^Binary_Expr: @@ -1409,7 +1409,7 @@ internal_resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ide return_symbol, ok = make_symbol_bitset_from_ast(ast_context, v^, node), true return_symbol.name = node.name case ^Bit_Field_Type: - return_symbol, ok = make_symbol_bit_field_from_ast(ast_context, v^, node), true + return_symbol, ok = make_symbol_bit_field_from_ast(ast_context, v, node), true return_symbol.name = node.name case ^Proc_Lit: if is_procedure_generic(v.type) { @@ -1536,7 +1536,7 @@ internal_resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ide return_symbol, ok = make_symbol_enum_from_ast(ast_context, v^, node), true return_symbol.name = node.name case ^Bit_Field_Type: - return_symbol, ok = make_symbol_bit_field_from_ast(ast_context, v^, node), true + return_symbol, ok = make_symbol_bit_field_from_ast(ast_context, v, node), true return_symbol.name = node.name case ^Proc_Lit: if is_procedure_generic(v.type) { @@ -2843,10 +2843,11 @@ make_symbol_struct_from_ast :: proc( make_symbol_bit_field_from_ast :: proc( ast_context: ^AstContext, - v: ast.Bit_Field_Type, + v: ^ast.Bit_Field_Type, ident: ast.Ident, inlined := false, ) -> Symbol { + construct_bit_field_field_docs(ast_context.file, v) symbol := Symbol { range = common.get_token_range(v, ast_context.file.src), type = .Struct, @@ -2862,19 +2863,29 @@ make_symbol_bit_field_from_ast :: proc( names := make([dynamic]string, ast_context.allocator) types := make([dynamic]^ast.Expr, ast_context.allocator) ranges := make([dynamic]common.Range, 0, ast_context.allocator) + docs := make([dynamic]^ast.Comment_Group, 0, ast_context.allocator) + comments := make([dynamic]^ast.Comment_Group, 0, ast_context.allocator) + bit_sizes := make([dynamic]^ast.Expr, 0, ast_context.allocator) for field in v.fields { if identifier, ok := field.name.derived.(^ast.Ident); ok && field.type != nil { append(&names, identifier.name) append(&types, field.type) append(&ranges, common.get_token_range(identifier, ast_context.file.src)) + append(&docs, field.docs) + append(&comments, field.comments) + append(&bit_sizes, field.bit_size) } } symbol.value = SymbolBitFieldValue { - names = names[:], - types = types[:], - ranges = ranges[:], + backing_type = v.backing_type, + names = names[:], + types = types[:], + ranges = ranges[:], + docs = docs[:], + comments = comments[:], + bit_sizes = bit_sizes[:], } return symbol diff --git a/src/server/ast.odin b/src/server/ast.odin index 9abc1e5..81ec5c7 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -1236,6 +1236,40 @@ construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type) { } } +construct_bit_field_field_docs :: proc(file: ast.File, v: ^ast.Bit_Field_Type) { + for field, i in v.fields { + // There is currently a bug in the odin parser where it adds line comments for a field to the + // docs of the following field, we address this problem here. + // see https://github.com/odin-lang/Odin/issues/5353 + if field.comments == nil { + // We check if the comment is at the start of the next field + if i != len(v.fields) - 1 { + next_field := v.fields[i + 1] + if next_field.docs != nil && len(next_field.docs.list) > 0 { + list := next_field.docs.list + if list[0].pos.line == field.pos.line { + field.comments = ast.new(ast.Comment_Group, list[0].pos, parser.end_pos(list[0])) + field.comments.list = list[:1] + if len(list) > 1 { + next_field.docs = ast.new( + ast.Comment_Group, + list[1].pos, + parser.end_pos(list[len(list) - 2]), + ) + next_field.docs.list = list[1:] + } else { + next_field.docs = nil + } + } + } + } else { + // We need to check the file to see if it contains a line comment as there is no next field + field.comments = get_file_comment(file, field.pos.line) + } + } + } +} + // Retrives the comment group from the specified line of the file get_file_comment :: proc(file: ast.File, line: int) -> ^ast.Comment_Group { // TODO: linear scan might be a bit slow for files with lots of comments? diff --git a/src/server/collector.odin b/src/server/collector.odin index 22aba08..f4e4a1c 100644 --- a/src/server/collector.odin +++ b/src/server/collector.odin @@ -158,15 +158,19 @@ collect_struct_fields :: proc( collect_bit_field_fields :: proc( collection: ^SymbolCollection, - fields: []^ast.Bit_Field_Field, + bit_field_type: ^ast.Bit_Field_Type, package_map: map[string]string, file: ast.File, ) -> SymbolBitFieldValue { - names := make([dynamic]string, 0, len(fields), collection.allocator) - types := make([dynamic]^ast.Expr, 0, len(fields), collection.allocator) - ranges := make([dynamic]common.Range, 0, len(fields), collection.allocator) - - for field, i in fields { + construct_bit_field_field_docs(file, bit_field_type) + names := make([dynamic]string, 0, len(bit_field_type.fields), collection.allocator) + types := make([dynamic]^ast.Expr, 0, len(bit_field_type.fields), collection.allocator) + ranges := make([dynamic]common.Range, 0, len(bit_field_type.fields), collection.allocator) + docs := make([dynamic]^ast.Comment_Group, 0, collection.allocator) + comments := make([dynamic]^ast.Comment_Group, 0, collection.allocator) + bit_sizes := make([dynamic]^ast.Expr, 0, collection.allocator) + + for field, i in bit_field_type.fields { if ident, ok := field.name.derived.(^ast.Ident); ok { append(&names, get_index_unique_string(collection, ident.name)) @@ -175,13 +179,20 @@ collect_bit_field_fields :: proc( append(&types, cloned) append(&ranges, common.get_token_range(ident, file.src)) + append(&docs, clone_comment_group(field.docs, collection.allocator, &collection.unique_strings)) + append(&comments, clone_comment_group(field.comments, collection.allocator, &collection.unique_strings)) + append(&bit_sizes, clone_type(field.bit_size, collection.allocator, &collection.unique_strings)) } } value := SymbolBitFieldValue { - names = names[:], - types = types[:], - ranges = ranges[:], + backing_type = clone_type(bit_field_type.backing_type, collection.allocator, &collection.unique_strings), + names = names[:], + types = types[:], + ranges = ranges[:], + docs = docs[:], + comments = comments[:], + bit_sizes = bit_sizes[:], } return value @@ -547,7 +558,7 @@ collect_symbols :: proc(collection: ^SymbolCollection, file: ast.File, uri: stri case ^ast.Bit_Field_Type: token = v^ token_type = .Struct - symbol.value = collect_bit_field_fields(collection, v.fields, package_map, file) + symbol.value = collect_bit_field_fields(collection, v, package_map, file) symbol.signature = "bit_field" case ^ast.Map_Type: token = v^ diff --git a/src/server/documentation.odin b/src/server/documentation.odin index b52d259..b86147f 100644 --- a/src/server/documentation.odin +++ b/src/server/documentation.odin @@ -2,6 +2,7 @@ package server import "core:fmt" import "core:log" +import "core:odin/ast" import path "core:path/slashpath" import "core:strings" @@ -9,8 +10,7 @@ import "core:strings" get_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { is_variable := symbol.type == .Variable - pointer_prefix := repeat("^", symbol.pointers, context.temp_allocator) - + pointer_prefix := repeat("^", symbol.pointers, ast_context.allocator) #partial switch v in symbol.value { case SymbolEnumValue: @@ -19,6 +19,13 @@ get_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { append_variable_full_name(&sb, ast_context, symbol, pointer_prefix) strings.write_string(&sb, " :: ") } + if len(v.names) == 0 { + strings.write_string(&sb, "enum {}") + if symbol.comment != "" { + fmt.sbprintf(&sb, " %s", symbol.comment) + } + return strings.to_string(sb) + } longestNameLen := 0 for name in v.names { @@ -71,6 +78,10 @@ get_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { append_variable_full_name(&sb, ast_context, symbol, pointer_prefix) strings.write_string(&sb, " :: ") } + if len(v.types) == 0 { + strings.write_string(&sb, "union {}") + return strings.to_string(sb) + } strings.write_string(&sb, "union {\n") for i in 0 ..< len(v.types) { strings.write_string(&sb, "\t") @@ -80,7 +91,7 @@ get_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { strings.write_string(&sb, "}") return strings.to_string(sb) case SymbolAggregateValue: - sb := strings.builder_make(context.temp_allocator) + sb := strings.builder_make(ast_context.allocator) strings.write_string(&sb, "proc {\n") for symbol in v.symbols { if value, ok := symbol.value.(SymbolProcedureValue); ok { @@ -91,6 +102,53 @@ get_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { } strings.write_string(&sb, "}") return strings.to_string(sb) + case SymbolBitFieldValue: + sb := strings.builder_make(ast_context.allocator) + if is_variable { + append_variable_full_name(&sb, ast_context, symbol, pointer_prefix) + strings.write_string(&sb, " :: ") + } else if symbol.type_name != "" { + if symbol.type_pkg == "" { + fmt.sbprintf(&sb, "%s%s :: ", pointer_prefix, symbol.type_name) + } else { + pkg_name := get_pkg_name(ast_context, symbol.type_pkg) + fmt.sbprintf(&sb, "%s%s.%s :: ", pointer_prefix, pkg_name, symbol.type_name) + } + } + strings.write_string(&sb, "bit_field ") + build_string_node(v.backing_type, &sb, false) + if len(v.names) == 0 { + strings.write_string(&sb, " {}") + return strings.to_string(sb) + } + strings.write_string(&sb, " {\n") + longest_name_len := 0 + for name in v.names { + if len(name) > longest_name_len { + longest_name_len = len(name) + } + } + longest_type_len := 0 + type_names := make([dynamic]string, 0, len(v.types), ast_context.allocator) + for t in v.types { + type_name := node_to_string(t) + append(&type_names, type_name) + if len(type_name) > longest_type_len { + longest_type_len = len(type_name) + } + } + + for name, i in v.names { + append_docs(&sb, v.docs, i) + fmt.sbprintf(&sb, "\t%s:%*s", v.names[i], longest_name_len - len(name) + 1, "") + fmt.sbprintf(&sb, "%s%*s| ", type_names[i], longest_type_len - len(type_names[i]) + 1, "") + build_string_node(v.bit_sizes[i], &sb, false) + strings.write_string(&sb, ",") + append_comments(&sb, v.comments, i) + strings.write_string(&sb, "\n") + } + strings.write_string(&sb, "}") + return strings.to_string(sb) } return get_short_signature(ast_context, symbol) @@ -99,7 +157,7 @@ get_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { get_short_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { is_variable := symbol.type == .Variable - pointer_prefix := repeat("^", symbol.pointers, context.temp_allocator) + pointer_prefix := repeat("^", symbol.pointers, ast_context.allocator) #partial switch v in symbol.value { case SymbolBasicValue: sb := strings.builder_make(ast_context.allocator) @@ -139,7 +197,7 @@ get_short_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string allocator = ast_context.allocator, ) case SymbolProcedureValue: - sb := strings.builder_make(context.temp_allocator) + sb := strings.builder_make(ast_context.allocator) if symbol.type_pkg != "" && symbol.type_name != "" { pkg_name := get_pkg_name(ast_context, symbol.type_pkg) fmt.sbprintf(&sb, "%s%s.%s :: ", pointer_prefix, pkg_name, symbol.type_name) @@ -227,8 +285,8 @@ get_short_signature :: proc(ast_context: ^AstContext, symbol: Symbol) -> string return "" } -get_enum_field_signature :: proc(value: SymbolEnumValue, index: int) -> string { - sb := strings.builder_make(context.temp_allocator) +get_enum_field_signature :: proc(value: SymbolEnumValue, index: int, allocator := context.temp_allocator) -> string { + sb := strings.builder_make(allocator) fmt.sbprintf(&sb, ".%s", value.names[index]) if index < len(value.values) && value.values[index] != nil { strings.write_string(&sb, " = ") @@ -342,12 +400,7 @@ write_struct_hover :: proc(ast_context: ^AstContext, sb: ^strings.Builder, v: Sy using_index = index } } - if i < len(v.docs) && v.docs[i] != nil { - for c in v.docs[i].list { - fmt.sbprintf(sb, "\t%s\n", c.text) - } - } - + append_docs(sb, v.docs, i) strings.write_string(sb, "\t") name_len := len(v.names[i]) @@ -355,18 +408,11 @@ write_struct_hover :: proc(ast_context: ^AstContext, sb: ^strings.Builder, v: Sy strings.write_string(sb, using_prefix) name_len += len(using_prefix) } - strings.write_string(sb, v.names[i]) - fmt.sbprintf(sb, ":%*s", longestNameLen - name_len + 1, "") + fmt.sbprintf(sb, "%s:%*s", v.names[i], longestNameLen - name_len + 1, "") build_string_node(v.types[i], sb, false) strings.write_string(sb, ",") - - if i < len(v.comments) && v.comments[i] != nil { - for c in v.comments[i].list { - fmt.sbprintf(sb, " %s\n", c.text) - } - } else { - strings.write_string(sb, "\n") - } + append_comments(sb, v.comments, i) + strings.write_string(sb, "\n") } strings.write_string(sb, "}") } @@ -386,6 +432,22 @@ append_variable_full_name :: proc( return } +append_docs :: proc(sb: ^strings.Builder, docs: []^ast.Comment_Group, index: int) { + if index < len(docs) && docs[index] != nil { + for c in docs[index].list { + fmt.sbprintf(sb, "\t%s\n", c.text) + } + } +} + +append_comments :: proc(sb: ^strings.Builder, comments: []^ast.Comment_Group, index: int) { + if index < len(comments) && comments[index] != nil { + for c in comments[index].list { + fmt.sbprintf(sb, " %s", c.text) + } + } +} + concatenate_symbol_information :: proc { concatenate_raw_symbol_information, concatenate_raw_string_information, diff --git a/src/server/symbol.odin b/src/server/symbol.odin index fddc775..e9b9037 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -39,9 +39,13 @@ SymbolStructValue :: struct { } SymbolBitFieldValue :: struct { - names: []string, - ranges: []common.Range, - types: []^ast.Expr, + backing_type: ^ast.Expr, + names: []string, + ranges: []common.Range, + types: []^ast.Expr, + docs: []^ast.Comment_Group, + comments: []^ast.Comment_Group, + bit_sizes: []^ast.Expr, } SymbolPackageValue :: struct { -- cgit v1.2.3