From a13716d68e7cde284a762c7ccbbcc9b864db3ada Mon Sep 17 00:00:00 2001 From: Brad Lewis <22850972+BradLewis@users.noreply.github.com> Date: Thu, 11 Dec 2025 07:25:13 -0500 Subject: Add optional feature for displaying signature help for comp literals --- misc/ols.schema.json | 5 +++ src/common/config.odin | 1 + src/server/requests.odin | 5 ++- src/server/signature.odin | 95 ++++++++++++++++++++++++++++++---------------- src/server/symbol.odin | 3 ++ src/server/types.odin | 1 + src/testing/testing.odin | 60 +++++++++++++++++------------ tests/signatures_test.odin | 89 ++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 201 insertions(+), 58 deletions(-) diff --git a/misc/ols.schema.json b/misc/ols.schema.json index b5ebd93..fd022af 100644 --- a/misc/ols.schema.json +++ b/misc/ols.schema.json @@ -92,6 +92,11 @@ "description": "Follow links when opening documentation.", "default": true }, + "enable_comp_lit_signature_help": { + "type": "boolean", + "description": "Provide signature help for comp lits such as when instanciating structs. Will not display correctly on some editors such as vscode.", + "default": false + }, "disable_parser_errors": { "type": "boolean" }, "verbose": { "type": "boolean", diff --git a/src/common/config.odin b/src/common/config.odin index 4aaefac..6890685 100644 --- a/src/common/config.odin +++ b/src/common/config.odin @@ -38,6 +38,7 @@ Config :: struct { enable_auto_import: bool, enable_completion_matching: bool, enable_document_links: bool, + enable_comp_lit_signature_help: bool, disable_parser_errors: bool, thread_count: int, file_log: bool, diff --git a/src/server/requests.odin b/src/server/requests.odin index 9a7326a..8f9be06 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -374,6 +374,8 @@ read_ols_initialize_options :: proc(config: ^common.Config, ols_config: OlsConfi config.enable_completion_matching = ols_config.enable_completion_matching.(bool) or_else config.enable_completion_matching config.enable_document_links = ols_config.enable_document_links.(bool) or_else config.enable_document_links + config.enable_comp_lit_signature_help = + ols_config.enable_comp_lit_signature_help.(bool) or_else config.enable_comp_lit_signature_help config.verbose = ols_config.verbose.(bool) or_else config.verbose config.file_log = ols_config.file_log.(bool) or_else config.file_log @@ -648,6 +650,7 @@ request_initialize :: proc( config.enable_document_highlights = true config.enable_completion_matching = true config.enable_document_links = true + config.enable_comp_lit_signature_help = false config.verbose = false config.file_log = false config.odin_command = "" @@ -977,7 +980,7 @@ request_signature_help :: proc( } help: SignatureHelp - help, ok = get_signature_information(document, signature_params.position) + help, ok = get_signature_information(document, signature_params.position, config) if !ok { return .InternalError diff --git a/src/server/signature.odin b/src/server/signature.odin index 51f919d..c902d61 100644 --- a/src/server/signature.odin +++ b/src/server/signature.odin @@ -62,7 +62,14 @@ seperate_proc_field_arguments :: proc(procedure: ^Symbol) { } -get_signature_information :: proc(document: ^Document, position: common.Position) -> (SignatureHelp, bool) { +get_signature_information :: proc( + document: ^Document, + position: common.Position, + config: ^common.Config, +) -> ( + SignatureHelp, + bool, +) { signature_help: SignatureHelp ast_context := make_ast_context( @@ -81,7 +88,7 @@ get_signature_information :: proc(document: ^Document, position: common.Position ast_context.position_hint = position_context.hint //TODO(should probably not be an ast.Expr, but ast.Call_Expr) - if position_context.call == nil { + if position_context.call == nil && !config.enable_comp_lit_signature_help { return signature_help, true } @@ -90,37 +97,73 @@ get_signature_information :: proc(document: ^Document, position: common.Position if position_context.function != nil { get_locals(document.ast, position_context.function, &ast_context, &position_context) } + signature_information := make([dynamic]SignatureInformation, context.temp_allocator) + + if config.enable_comp_lit_signature_help { + if symbol, ok := resolve_comp_literal(&ast_context, &position_context); ok { + build_documentation(&ast_context, &symbol, short_signature = false) + append( + &signature_information, + SignatureInformation { + label = get_signature(symbol), + documentation = construct_symbol_docs(symbol, markdown = false), + }, + ) + } + } + + if position_context.call != nil { + signature_help.activeParameter = add_proc_signature(&ast_context, &position_context, &signature_information) + } + + signature_help.signatures = signature_information[:] + + return signature_help, true +} + +@(private = "file") +get_signature :: proc(symbol: Symbol) -> string { + sb := strings.builder_make() + write_symbol_name(&sb, symbol) + strings.write_string(&sb, " :: ") + strings.write_string(&sb, symbol.signature) + return strings.to_string(sb) +} +@(private = "file") +add_proc_signature :: proc( + ast_context: ^AstContext, + position_context: ^DocumentPositionContext, + signature_information: ^[dynamic]SignatureInformation, +) -> ( + active_parameter: int, +) { for comma, i in position_context.call_commas { if position_context.position > comma { - signature_help.activeParameter = i + 1 + active_parameter = i + 1 } else if position_context.position == comma { - signature_help.activeParameter = i + active_parameter = i } } if position_context.arrow { - signature_help.activeParameter += 1 + active_parameter = 1 } - call: Symbol - call, ok = resolve_type_expression(&ast_context, position_context.call) - + call, ok := resolve_type_expression(ast_context, position_context.call) if !ok { - return signature_help, true + return active_parameter } seperate_proc_field_arguments(&call) - signature_information := make([dynamic]SignatureInformation, context.temp_allocator) - if value, ok := call.value.(SymbolProcedureValue); ok { parameters := make([]ParameterInformation, len(value.orig_arg_types), context.temp_allocator) for arg, i in value.orig_arg_types { if arg.type != nil { if _, is_ellipsis := arg.type.derived.(^ast.Ellipsis); is_ellipsis { - signature_help.activeParameter = min(i, signature_help.activeParameter) + active_parameter = min(i, active_parameter) } } @@ -130,13 +173,13 @@ get_signature_information :: proc(document: ^Document, position: common.Position sb := strings.builder_make(context.temp_allocator) write_procedure_symbol_signature(&sb, value, detailed_signature = false) call.signature = strings.to_string(sb) - + info := SignatureInformation { - label = get_signature(call), + label = get_signature(call), documentation = construct_symbol_docs(call, markdown = false), parameters = parameters, } - append(&signature_information, info) + append(signature_information, info) } else if value, ok := call.value.(SymbolAggregateValue); ok { //function overloaded procedures for symbol in value.symbols { @@ -148,7 +191,7 @@ get_signature_information :: proc(document: ^Document, position: common.Position for arg, i in value.orig_arg_types { if arg.type != nil { if _, is_ellipsis := arg.type.derived.(^ast.Ellipsis); is_ellipsis { - signature_help.activeParameter = min(i, signature_help.activeParameter) + active_parameter = min(i, active_parameter) } } @@ -158,28 +201,16 @@ get_signature_information :: proc(document: ^Document, position: common.Position sb := strings.builder_make(context.temp_allocator) write_procedure_symbol_signature(&sb, value, detailed_signature = false) symbol.signature = strings.to_string(sb) - + info := SignatureInformation { - label = get_signature(symbol), + label = get_signature(symbol), documentation = construct_symbol_docs(symbol, markdown = false), parameters = parameters, } - append(&signature_information, info) + append(signature_information, info) } } } - - signature_help.signatures = signature_information[:] - - return signature_help, true -} - -@(private="file") -get_signature :: proc(symbol: Symbol) -> string { - sb := strings.builder_make() - write_symbol_name(&sb, symbol) - strings.write_string(&sb, " :: ") - strings.write_string(&sb, symbol.signature) - return strings.to_string(sb) + return active_parameter } diff --git a/src/server/symbol.odin b/src/server/symbol.odin index 2869e5c..d2f44ae 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -411,6 +411,9 @@ write_struct_type :: proc( } } } + s := Symbol{ + + } if _, ok := get_attribute_objc_class_name(attributes); ok { b.symbol.flags |= {.ObjC} diff --git a/src/server/types.odin b/src/server/types.odin index 4b4c0bd..0377e62 100644 --- a/src/server/types.odin +++ b/src/server/types.odin @@ -422,6 +422,7 @@ OlsConfig :: struct { enable_references: Maybe(bool), enable_document_highlights: Maybe(bool), enable_document_links: Maybe(bool), + enable_comp_lit_signature_help: Maybe(bool), enable_completion_matching: Maybe(bool), enable_inlay_hints_params: Maybe(bool), enable_inlay_hints_default_params: Maybe(bool), diff --git a/src/testing/testing.odin b/src/testing/testing.odin index 4cb9484..6a6b80d 100644 --- a/src/testing/testing.odin +++ b/src/testing/testing.odin @@ -2,7 +2,6 @@ package ols_testing import "core:fmt" import "core:log" -import "core:mem" import "core:mem/virtual" import "core:odin/ast" import "core:odin/parser" @@ -127,7 +126,7 @@ expect_signature_labels :: proc(t: ^testing.T, src: ^Source, expect_labels: []st setup(src) defer teardown(src) - help, ok := server.get_signature_information(src.document, src.position) + help, ok := server.get_signature_information(src.document, src.position, &src.config) if !ok { log.error("Failed get_signature_information") @@ -159,7 +158,7 @@ expect_signature_parameter_position :: proc(t: ^testing.T, src: ^Source, positio setup(src) defer teardown(src) - help, ok := server.get_signature_information(src.document, src.position) + help, ok := server.get_signature_information(src.document, src.position, &src.config) if help.activeParameter != position { log.errorf("expected parameter position %v, but received %v", position, help.activeParameter) @@ -463,7 +462,10 @@ expect_action :: proc(t: ^testing.T, src: ^Source, expect_action_names: []string setup(src) defer teardown(src) - input_range := common.Range{start=src.position, end=src.position} + input_range := common.Range { + start = src.position, + end = src.position, + } actions, ok := server.get_code_actions(src.document, input_range, &src.config) if !ok { log.error("Failed to find actions") @@ -537,31 +539,30 @@ expect_inlay_hints :: proc(t: ^testing.T, src: ^Source) { src_builder := strings.builder_make(context.temp_allocator) expected_hints := make([dynamic]server.InlayHint, context.temp_allocator) - HINT_OPEN :: "[[" + HINT_OPEN :: "[[" HINT_CLOSE :: "]]" { last, line, col: int saw_brackets: bool - for i:= 0; i < len(src.main); i += 1 { + for i := 0; i < len(src.main); i += 1 { if saw_brackets { - if i+1 < len(src.main) && src.main[i:][:len(HINT_CLOSE)] == HINT_CLOSE { + if i + 1 < len(src.main) && src.main[i:][:len(HINT_CLOSE)] == HINT_CLOSE { saw_brackets = false hint_str := src.main[last:i] - last = i+len(HINT_CLOSE) - i = last-1 - append(&expected_hints, server.InlayHint{ - position = {line, col}, - label = hint_str, - kind = .Parameter, - }) + last = i + len(HINT_CLOSE) + i = last - 1 + append( + &expected_hints, + server.InlayHint{position = {line, col}, label = hint_str, kind = .Parameter}, + ) } } else { - if i+1 < len(src.main) && src.main[i:][:len(HINT_OPEN)] == HINT_OPEN { + if i + 1 < len(src.main) && src.main[i:][:len(HINT_OPEN)] == HINT_OPEN { strings.write_string(&src_builder, src.main[last:i]) saw_brackets = true - last = i+len(HINT_OPEN) - i = last-1 + last = i + len(HINT_OPEN) + i = last - 1 } else if src.main[i] == '\n' { line += 1 col = 0 @@ -584,7 +585,7 @@ expect_inlay_hints :: proc(t: ^testing.T, src: ^Source) { setup(src) defer teardown(src) - symbols_and_nodes := server.resolve_entire_file(src.document, allocator=context.temp_allocator) + symbols_and_nodes := server.resolve_entire_file(src.document, allocator = context.temp_allocator) range := common.Range { end = {line = 9000000}, @@ -595,7 +596,8 @@ expect_inlay_hints :: proc(t: ^testing.T, src: ^Source) { return } - testing.expectf(t, + testing.expectf( + t, len(expected_hints) == len(hints), "Expected %d inlay hints, but received %d", len(expected_hints), @@ -620,20 +622,30 @@ expect_inlay_hints :: proc(t: ^testing.T, src: ^Source) { for i in 0 ..< max(len(expected_hints), len(hints)) { expected_text := "---" - actual_text := "---" + actual_text := "---" if i < len(expected_hints) { expected := expected_hints[i] expected_line := get_source_line_with_hint(lines, expected) - expected_text = fmt.tprintf("\"%s\" at (%d, %d): \"%s\"", - expected.label, expected.position.line, expected.position.character, expected_line) + expected_text = fmt.tprintf( + "\"%s\" at (%d, %d): \"%s\"", + expected.label, + expected.position.line, + expected.position.character, + expected_line, + ) } if i < len(hints) { actual := hints[i] actual_line := get_source_line_with_hint(lines, actual) - actual_text = fmt.tprintf("\"%s\" at (%d, %d): \"%s\"", - actual.label, actual.position.line, actual.position.character, actual_line) + actual_text = fmt.tprintf( + "\"%s\" at (%d, %d): \"%s\"", + actual.label, + actual.position.line, + actual.position.character, + actual_line, + ) } if i >= len(expected_hints) { diff --git a/tests/signatures_test.odin b/tests/signatures_test.odin index 1485731..d81a4da 100644 --- a/tests/signatures_test.odin +++ b/tests/signatures_test.odin @@ -1,6 +1,5 @@ package tests -import "core:fmt" import "core:testing" import test "src:testing" @@ -562,6 +561,94 @@ proc_signature_move_outside :: proc(t: ^testing.T) { ) } +@(test) +signature_comp_lit_struct :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + a: int, + b: string, + } + + main :: proc() { + foo := Foo{ + {*} + } + } + `, + packages = {}, + config = { + enable_comp_lit_signature_help = true, + } + } + + test.expect_signature_labels( + t, + &source, + {"test.Foo :: struct {\n\ta: int,\n\tb: string,\n}"}, + ) +} + +@(test) +signature_comp_lit_struct_pre_declared :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + a: int, // comment for a + b: string, + } + + main :: proc() { + foo: Foo + foo = { + {*} + } + } + `, + packages = {}, + config = { + enable_comp_lit_signature_help = true, + } + } + + test.expect_signature_labels( + t, + &source, + {"test.Foo :: struct {\n\ta: int, // comment for a\n\tb: string,\n}"}, + ) +} + +@(test) +signature_comp_lit_bit_set :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + A, + B, + } + + Bar :: bit_set[Foo] + + main :: proc() { + bar: Bar + bar = { + {*} + } + } + `, + packages = {}, + config = { + enable_comp_lit_signature_help = true, + } + } + + test.expect_signature_labels( + t, + &source, + {"test.Bar :: bit_set[Foo]"}, + ) +} + /* @(test) signature_function_inside_when :: proc(t: ^testing.T) { -- cgit v1.2.3