From 3494339e5b56ca90356dac095918d2bf02832baa Mon Sep 17 00:00:00 2001 From: Louis Dutton Date: Mon, 5 Jan 2026 11:31:27 +0000 Subject: Improve hoverdoc formatting --- src/server/analysis.odin | 8 ++-- src/server/ast.odin | 101 +++++++++++++++++++++++++++--------------- src/server/collector.odin | 4 +- src/server/documentation.odin | 16 +++---- src/server/hover.odin | 6 ++- src/server/signature.odin | 6 +-- src/server/symbol.odin | 12 ++--- tests/completions_test.odin | 6 +-- tests/hover_test.odin | 62 +++++++++++++------------- 9 files changed, 127 insertions(+), 94 deletions(-) diff --git a/src/server/analysis.odin b/src/server/analysis.odin index a4227dc..cec34e3 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -1981,8 +1981,8 @@ resolve_local_identifier :: proc(ast_context: ^AstContext, node: ast.Ident, loca return_symbol.flags |= {.Local} return_symbol.value_expr = local.value_expr return_symbol.type_expr = local.type_expr - return_symbol.doc = get_doc(local.docs, ast_context.allocator) - return_symbol.comment = get_comment(local.comment) + return_symbol.doc = get_comment(local.docs, ast_context.allocator) + return_symbol.comment = get_comment(local.comment, ast_context.allocator) return return_symbol, ok } @@ -2071,11 +2071,11 @@ resolve_global_identifier :: proc(ast_context: ^AstContext, node: ast.Ident, glo } if global.docs != nil { - return_symbol.doc = get_doc(global.docs, ast_context.allocator) + return_symbol.doc = get_comment(global.docs, ast_context.allocator) } if global.comment != nil { - return_symbol.comment = get_comment(global.comment) + return_symbol.comment = get_comment(global.comment, ast_context.allocator) } return_symbol.type_expr = global.type_expr diff --git a/src/server/ast.odin b/src/server/ast.odin index d1846f7..0547b83 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -7,6 +7,7 @@ import "core:odin/ast" import "core:odin/parser" import path "core:path/slashpath" import "core:strings" +import "core:log" keyword_map: map[string]struct{} = { "typeid" = {}, @@ -563,51 +564,79 @@ get_ast_node_string :: proc(node: ^ast.Node, src: string) -> string { return string(src[node.pos.offset:node.end.offset]) } -get_doc :: proc(comment: ^ast.Comment_Group, allocator: mem.Allocator) -> string { - if comment == nil { - return "" +COMMENT_DELIMITER_LENGTH :: len("//") +#assert(COMMENT_DELIMITER_LENGTH == len("/*")) +#assert(COMMENT_DELIMITER_LENGTH == len("*/")) + +// Returns the minimum indentation across all non-empty lines +get_min_indent :: proc(lines: []string) -> int { + min_indent := max(int) + for line in lines { + if strings.trim_space(line) == "" do continue + for c, i in line { + if !strings.is_space(c) { + min_indent = min(min_indent, i) + break + } + } } + return 0 if min_indent == max(int) else min_indent +} - tmp: string - - for doc in comment.list { - if strings.starts_with(doc.text, "/*") && doc.pos.column != 1 { - lines := strings.split(doc.text, "\n", context.temp_allocator) - for line, i in lines { - if i != 0 && len(line) > 0 { - column := 0 - for column < doc.pos.column - 1 { - if line[column] == '\t' || line[column] == ' ' { - column += 1 - } else { - break - } - } - tmp = strings.concatenate({tmp, "\n", line[column:]}, context.temp_allocator) - } else { - tmp = strings.concatenate({tmp, "\n", line}, context.temp_allocator) - } - } +// Strips min_indent characters from each line and joins with newlines +strip_indent_and_join :: proc(lines: []string, min_indent: int, allocator: mem.Allocator) -> string { + result := make([dynamic]string, context.temp_allocator) + for line in lines { + if len(line) >= min_indent { + append(&result, line[min_indent:]) } else { - tmp = strings.concatenate({tmp, "\n", doc.text}, context.temp_allocator) + append(&result, strings.trim_left_space(line)) } } + return strings.join(result[:], "\n", allocator) +} - if tmp != "" { - no_lines, _ := strings.replace_all(tmp, "//", "", context.temp_allocator) - no_begin_comments, _ := strings.replace_all(no_lines, "/*", "", context.temp_allocator) - no_end_comments, _ := strings.replace_all(no_begin_comments, "*/", "", context.temp_allocator) - return strings.clone(no_end_comments, allocator) - } +// Aggregates the content from the provided comment group, +// omitting extraneous spaces and delimiters. +get_comment :: proc(comment: ^ast.Comment_Group, allocator := context.allocator) -> string { + if comment == nil do return "" - return "" -} + lines := make([dynamic]string, context.temp_allocator) + + for token in comment.list { + if len(token.text) < COMMENT_DELIMITER_LENGTH do continue + delimiter := token.text[:COMMENT_DELIMITER_LENGTH] + + switch delimiter { + case "/*": + if len(token.text) <= COMMENT_DELIMITER_LENGTH * 2 do continue + content := token.text[COMMENT_DELIMITER_LENGTH:len(token.text) - COMMENT_DELIMITER_LENGTH] -get_comment :: proc(comment: ^ast.Comment_Group) -> string { - if comment != nil && len(comment.list) > 0 { - return comment.list[0].text + // Check if this is a single-line block comment (no newlines) + if !strings.contains(content, "\n") { + text := strings.trim_space(content) + if text != "" do append(&lines, text) + } else { + // Multi-line block comment: strip leading/trailing newlines + content = strings.trim(content, "\r\n") + for line in strings.split_lines(content, context.temp_allocator) { + append(&lines, line) + } + } + + case "//": + text := token.text[COMMENT_DELIMITER_LENGTH:] + append(&lines, text) + + case: + log.error("unsupported comment delimiter") + } } - return "" + + if len(lines) == 0 do return "" + + min_indent := get_min_indent(lines[:]) + return strip_indent_and_join(lines[:], min_indent, allocator) } free_ast :: proc { diff --git a/src/server/collector.odin b/src/server/collector.odin index d2b8445..d8a4e65 100644 --- a/src/server/collector.odin +++ b/src/server/collector.odin @@ -705,12 +705,12 @@ collect_symbols :: proc(collection: ^SymbolCollection, file: ast.File, uri: stri symbol.range = common.get_token_range(expr.name_expr, file.src) symbol.name = get_index_unique_string(collection, name) symbol.type = token_type - symbol.doc = get_doc(expr.docs, collection.allocator) + symbol.doc = get_comment(expr.docs, collection.allocator) symbol.uri = get_index_unique_string(collection, uri) symbol.type_expr = clone_type(expr.type_expr, collection.allocator, &collection.unique_strings) symbol.value_expr = clone_type(expr.value_expr, collection.allocator, &collection.unique_strings) comment, _ := get_file_comment(file, symbol.range.start.line + 1) - symbol.comment = strings.clone(get_comment(comment), collection.allocator) + symbol.comment = get_comment(comment, collection.allocator) if expr.builtin || strings.contains(uri, "builtin.odin") { symbol.pkg = "$builtin" diff --git a/src/server/documentation.odin b/src/server/documentation.odin index 2fe8842..25e4140 100644 --- a/src/server/documentation.odin +++ b/src/server/documentation.odin @@ -6,6 +6,10 @@ import "core:odin/ast" import path "core:path/slashpath" import "core:strings" +DOC_SECTION_DELIMITER :: "\n---\n" // The string separating each section of documentation +DOC_FMT_ODIN :: "```odin\n%v\n```" // The format for wrapping odin code in a markdown codeblock +DOC_FMT_MARKDOWN :: DOC_FMT_ODIN + DOC_SECTION_DELIMITER + "%v" // The format for presenting documentation on hover + // Adds signature and docs information to the provided symbol // This should only be used for a symbol created with the temp allocator build_documentation :: proc(ast_context: ^AstContext, symbol: ^Symbol, short_signature := true) { @@ -20,21 +24,17 @@ build_documentation :: proc(ast_context: ^AstContext, symbol: ^Symbol, short_sig } } -construct_symbol_docs :: proc(symbol: Symbol, markdown := true, allocator := context.temp_allocator) -> string { +construct_symbol_docs :: proc(symbol: Symbol, allocator := context.temp_allocator) -> string { sb := strings.builder_make(allocator = allocator) if symbol.doc != "" { strings.write_string(&sb, symbol.doc) - if symbol.comment != "" { - strings.write_string(&sb, "\n") - } } if symbol.comment != "" { - if markdown { - fmt.sbprintf(&sb, "\n```odin\n%s\n```", symbol.comment) - } else { - fmt.sbprintf(&sb, "\n%s", symbol.comment) + if symbol.doc != "" { + strings.write_string(&sb, DOC_SECTION_DELIMITER) } + strings.write_string(&sb, symbol.comment) } return strings.to_string(sb) diff --git a/src/server/hover.odin b/src/server/hover.odin index ca791ef..9b42f7e 100644 --- a/src/server/hover.odin +++ b/src/server/hover.odin @@ -16,7 +16,11 @@ write_hover_content :: proc(ast_context: ^AstContext, symbol: Symbol) -> MarkupC if cat != "" { content.kind = "markdown" - content.value = fmt.tprintf("```odin\n%v\n```%v", cat, doc) + if doc != "" { + content.value = fmt.tprintf(DOC_FMT_MARKDOWN, cat, doc) + } else { + content.value = fmt.tprintf(DOC_FMT_ODIN, cat) + } } else { content.kind = "plaintext" } diff --git a/src/server/signature.odin b/src/server/signature.odin index 3148951..f6c79fc 100644 --- a/src/server/signature.odin +++ b/src/server/signature.odin @@ -110,7 +110,7 @@ get_signature_information :: proc( &signature_information, SignatureInformation { label = get_signature(symbol), - documentation = construct_symbol_docs(symbol, markdown = false), + documentation = construct_symbol_docs(symbol), }, ) } @@ -176,7 +176,7 @@ add_proc_signature :: proc( info := SignatureInformation { label = get_signature(call), - documentation = construct_symbol_docs(call, markdown = false), + documentation = construct_symbol_docs(call), parameters = parameters, } append(signature_information, info) @@ -204,7 +204,7 @@ add_proc_signature :: proc( info := SignatureInformation { label = get_signature(symbol), - documentation = construct_symbol_docs(symbol, markdown = false), + documentation = construct_symbol_docs(symbol), parameters = parameters, } diff --git a/src/server/symbol.odin b/src/server/symbol.odin index 956eaa9..96c83ed 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -922,8 +922,8 @@ construct_struct_field_symbol :: proc(symbol: ^Symbol, parent_name: string, valu symbol.name = value.names[index] symbol.type = .Field symbol.parent_name = parent_name - symbol.doc = get_doc(value.docs[index], context.temp_allocator) - symbol.comment = get_comment(value.comments[index]) + symbol.doc = get_comment(value.docs[index], context.temp_allocator) + symbol.comment = get_comment(value.comments[index], context.temp_allocator) symbol.range = value.ranges[index] } @@ -936,16 +936,16 @@ construct_bit_field_field_symbol :: proc( symbol.name = value.names[index] symbol.parent_name = parent_name symbol.type = .Field - symbol.doc = get_doc(value.docs[index], context.temp_allocator) - symbol.comment = get_comment(value.comments[index]) + symbol.doc = get_comment(value.docs[index], context.temp_allocator) + symbol.comment = get_comment(value.comments[index], context.temp_allocator) symbol.signature = get_bit_field_field_signature(value, index) symbol.range = value.ranges[index] } construct_enum_field_symbol :: proc(symbol: ^Symbol, value: SymbolEnumValue, index: int) { symbol.type = .Field - symbol.doc = get_doc(value.docs[index], context.temp_allocator) - symbol.comment = get_comment(value.comments[index]) + symbol.doc = get_comment(value.docs[index], context.temp_allocator) + symbol.comment = get_comment(value.comments[index], context.temp_allocator) symbol.signature = get_enum_field_signature(value, index) symbol.range = value.ranges[index] } diff --git a/tests/completions_test.odin b/tests/completions_test.odin index 033813b..53c7fe6 100644 --- a/tests/completions_test.odin +++ b/tests/completions_test.odin @@ -28,7 +28,7 @@ ast_simple_struct_completion :: proc(t: ^testing.T) { t, &source, ".", - {"My_Struct.one: int", "My_Struct.two: int\n// test comment", "My_Struct.three: int"}, + {"My_Struct.one: int", "My_Struct.two: int\n---\ntest comment", "My_Struct.three: int"}, ) } @@ -3296,7 +3296,7 @@ ast_completion_struct_documentation :: proc(t: ^testing.T) { packages = packages[:], } - test.expect_completion_docs(t, &source, "", {"Foo.bazz: my_package.My_Struct\n// bazz"}) + test.expect_completion_docs(t, &source, "", {"Foo.bazz: my_package.My_Struct\n---\nbazz"}) } @(test) @@ -3417,7 +3417,7 @@ ast_completion_poly_struct_another_package :: proc(t: ^testing.T) { packages = packages[:], } - test.expect_completion_docs(t, &source, "", {"Runner.state: test.State\n// state"}) + test.expect_completion_docs(t, &source, "", {"Runner.state: test.State\n---\nstate"}) } @(test) diff --git a/tests/hover_test.odin b/tests/hover_test.odin index 7cd2b24..9929ee3 100644 --- a/tests/hover_test.odin +++ b/tests/hover_test.odin @@ -401,7 +401,7 @@ ast_hover_proc_group :: proc(t: ^testing.T) { packages = {}, } - test.expect_hover(t, &source, "test.add :: proc(a, b: int) -> int\n docs\n\n// comment") + test.expect_hover(t, &source, "test.add :: proc(a, b: int) -> int\n---\ndocs\n---\ncomment") } @(test) @@ -672,7 +672,7 @@ ast_hover_struct_field_complex_definition :: proc(t: ^testing.T) { `, } - test.expect_hover(t, &source, "Foo.bar: ^test.Bar\n Docs\n\n// inline docs") + test.expect_hover(t, &source, "Foo.bar: ^test.Bar\n---\nDocs\n---\ninline docs") } @(test) @@ -1084,7 +1084,7 @@ ast_hover_proc_overloading_named_arg_with_selector_expr_with_another_package :: packages = packages[:], } - test.expect_hover(t, &source, "my_package.foo :: proc(x := 1) -> (_: int, _: bool)\n Docs\n\n// comment") + test.expect_hover(t, &source, "my_package.foo :: proc(x := 1) -> (_: int, _: bool)\n---\nDocs\n---\ncomment") } @(test) @@ -1479,7 +1479,7 @@ ast_hover_proc_comments :: proc(t: ^testing.T) { `, } - test.expect_hover(t, &source, "test.foo :: proc()\n doc\n\n// do foo") + test.expect_hover(t, &source, "test.foo :: proc()\n---\ndoc\n---\ndo foo") } @(test) @@ -1507,7 +1507,7 @@ ast_hover_proc_comments_package :: proc(t: ^testing.T) { packages = packages[:], } - test.expect_hover(t, &source, "my_package.foo :: proc()\n// do foo") + test.expect_hover(t, &source, "my_package.foo :: proc()\n---\ndo foo") } @(test) @@ -1525,7 +1525,7 @@ ast_hover_struct_field_distinct :: proc(t: ^testing.T) { `, } - test.expect_hover(t, &source, "S.fb: test.B\n// type: fb") + test.expect_hover(t, &source, "S.fb: test.B\n---\ntype: fb") } @(test) @@ -2135,7 +2135,7 @@ ast_hover_bit_field_field :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "Foo.foo_aa: uint | 6\n// last 6 bits") + test.expect_hover(t, &source, "Foo.foo_aa: uint | 6\n---\nlast 6 bits") } @(test) @@ -2154,7 +2154,7 @@ ast_hover_bit_field_variable_with_docs :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "Foo.foo_a: uint | 2\n doc\n\n// foo a") + test.expect_hover(t, &source, "Foo.foo_a: uint | 2\n---\ndoc\n---\nfoo a") } @(test) @@ -2353,7 +2353,7 @@ ast_hover_struct_field_should_show_docs_and_comments :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "Foo.a: int\n a docs\n\n// a comment") + test.expect_hover(t, &source, "Foo.a: int\n---\na docs\n---\na comment") } @(test) @@ -2367,7 +2367,7 @@ ast_hover_struct_field_should_show_docs_and_comments_field :: proc(t: ^testing.T } `, } - test.expect_hover(t, &source, "Foo.a: int\n a docs\n\n// a comment") + test.expect_hover(t, &source, "Foo.a: int\n---\na docs\n---\na comment") } @(test) @@ -2388,7 +2388,7 @@ ast_hover_struct_field_should_show_docs_and_comments_struct_types :: proc(t: ^te } `, } - test.expect_hover(t, &source, "Foo.bar: test.Bar\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: test.Bar\n---\nbar docs\n---\nbar comment") } @(test) @@ -2407,7 +2407,7 @@ ast_hover_struct_field_should_show_docs_and_comments_procs :: proc(t: ^testing.T } `, } - test.expect_hover(t, &source, "Foo.bar: proc(a: int) -> int\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: proc(a: int) -> int\n---\nbar docs\n---\nbar comment") } @(test) @@ -2428,7 +2428,7 @@ ast_hover_struct_field_should_show_docs_and_comments_named_procs :: proc(t: ^tes } `, } - test.expect_hover(t, &source, "Foo.bar: proc(a: int) -> string\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: proc(a: int) -> string\n---\nbar docs\n---\nbar comment") } @(test) @@ -2447,7 +2447,7 @@ ast_hover_struct_field_should_show_docs_and_comments_maps :: proc(t: ^testing.T) } `, } - test.expect_hover(t, &source, "Foo.bar: map[int]int\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: map[int]int\n---\nbar docs\n---\nbar comment") } @(test) @@ -2466,7 +2466,7 @@ ast_hover_struct_field_should_show_docs_and_comments_bit_sets :: proc(t: ^testin } `, } - test.expect_hover(t, &source, "Foo.bar: bit_set[0 ..< 10]\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: bit_set[0 ..< 10]\n---\nbar docs\n---\nbar comment") } @(test) @@ -2490,7 +2490,7 @@ ast_hover_struct_field_should_show_docs_and_comments_unions :: proc(t: ^testing. } `, } - test.expect_hover(t, &source, "Foo.bar: test.Bar\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: test.Bar\n---\nbar docs\n---\nbar comment") } @(test) @@ -2509,7 +2509,7 @@ ast_hover_struct_field_should_show_docs_and_comments_multipointers :: proc(t: ^t } `, } - test.expect_hover(t, &source, "Foo.bar: [^]int\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: [^]int\n---\nbar docs\n---\nbar comment") } @(test) @@ -2528,7 +2528,7 @@ ast_hover_struct_field_should_show_docs_and_comments_dynamic_arrays :: proc(t: ^ } `, } - test.expect_hover(t, &source, "Foo.bar: [dynamic]int\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: [dynamic]int\n---\nbar docs\n---\nbar comment") } @(test) @@ -2547,7 +2547,7 @@ ast_hover_struct_field_should_show_docs_and_comments_fixed_arrays :: proc(t: ^te } `, } - test.expect_hover(t, &source, "Foo.bar: [5]int\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: [5]int\n---\nbar docs\n---\nbar comment") } @(test) @@ -2566,7 +2566,7 @@ ast_hover_struct_field_should_show_docs_and_comments_matrix :: proc(t: ^testing. } `, } - test.expect_hover(t, &source, "Foo.bar: matrix[4,5]int\n bar docs\n\n// bar comment") + test.expect_hover(t, &source, "Foo.bar: matrix[4,5]int\n---\nbar docs\n---\nbar comment") } @(test) @@ -3191,7 +3191,7 @@ ast_hover_documentation_reexported :: proc(t: ^testing.T) { `, packages = packages[:], } - test.expect_hover(t, &source, "my_package.Foo :: struct{}\n Documentation for Foo") + test.expect_hover(t, &source, "my_package.Foo :: struct{}\n---\nDocumentation for Foo") } @(test) @@ -3217,7 +3217,7 @@ ast_hover_override_documentation_reexported :: proc(t: ^testing.T) { `, packages = packages[:], } - test.expect_hover(t, &source, "my_package.Foo :: struct{}\n New docs for Foo") + test.expect_hover(t, &source, "my_package.Foo :: struct{}\n---\nNew docs for Foo") } @(test) @@ -3495,7 +3495,7 @@ ast_hover_enum_field_directly :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.Foo: .A\n Doc for A and B\n Mulitple lines!\n\n// comment for A and B") + test.expect_hover(t, &source, "test.Foo: .A\n---\nDoc for A and B\nMulitple lines!\n---\ncomment for A and B") } @(test) @@ -3624,7 +3624,7 @@ ast_hover_bit_set_intersection :: proc(t: ^testing.T) { foo_{*}b := foo_bar & {.Foo} // hover for foo_b `, } - test.expect_hover(t, &source, "test.foo_b: distinct bit_set[Flag]\n// hover for foo_b") + test.expect_hover(t, &source, "test.foo_b: distinct bit_set[Flag]\n---\nhover for foo_b") } @(test) @@ -3638,7 +3638,7 @@ ast_hover_bit_set_union :: proc(t: ^testing.T) { foo_{*}b := {.Foo} | foo_bar // hover for foo_b `, } - test.expect_hover(t, &source, "test.foo_b: distinct bit_set[Flag]\n// hover for foo_b") + test.expect_hover(t, &source, "test.foo_b: distinct bit_set[Flag]\n---\nhover for foo_b") } @(test) @@ -5366,7 +5366,7 @@ ast_hover_parapoly_other_package :: proc(t: ^testing.T) { `, packages = packages[:], } - test.expect_hover(t, &source, "my_package.bar :: proc(_: $T)\n Docs!\n\n// Comment!") + test.expect_hover(t, &source, "my_package.bar :: proc(_: $T)\n---\nDocs!\n---\nComment!") } @(test) @@ -5610,7 +5610,7 @@ ast_hover_local_proc_docs :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.foo :: proc()\n foo doc") + test.expect_hover(t, &source, "test.foo :: proc()\n---\nfoo doc") } @(test) @@ -5785,7 +5785,7 @@ ast_hover_nested_proc_docs_tabs :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.foo :: proc()\n\nDocs!\n\tDocs2\n") + test.expect_hover(t, &source, "test.foo :: proc()\n---\nDocs!\n\tDocs2\n") } @(test) @@ -5802,7 +5802,7 @@ ast_hover_nested_proc_docs_spaces :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.foo :: proc()\n\nDocs!\n Docs2\n") + test.expect_hover(t, &source, "test.foo :: proc()\n---\nDocs!\n Docs2\n") } @(test) @@ -5825,7 +5825,7 @@ ast_hover_propagate_docs_alias_in_package :: proc(t: ^testing.T) { `, packages = packages[:], } - test.expect_hover(t, &source, "my_package.bar :: proc()\n Docs!\n\n// Comment!") + test.expect_hover(t, &source, "my_package.bar :: proc()\n---\nDocs!\n---\nComment!") } @(test) @@ -5849,7 +5849,7 @@ ast_hover_propagate_docs_alias_in_package_override :: proc(t: ^testing.T) { `, packages = packages[:], } - test.expect_hover(t, &source, "my_package.bar :: proc()\n Overridden\n\n// Comment!") + test.expect_hover(t, &source, "my_package.bar :: proc()\n---\nOverridden\n---\nComment!") } @(test) -- cgit v1.2.3