aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanielGavin <danielgavin5@hotmail.com>2025-06-10 20:22:27 +0200
committerGitHub <noreply@github.com>2025-06-10 20:22:27 +0200
commite4ba6606fb75e964e360bf22fdfd0641f169497a (patch)
treee1327c7bd089edab3c843b1578b1b2a679eb97d9
parenta42400e0c9f1471ec27454476f6fe6c19dc95242 (diff)
parentcea7502466e9a413d1b8980b6447c9316f03a93a (diff)
Merge pull request #650 from BradLewis/feat/hover-struct-definitions
Enrich hover information
-rw-r--r--src/server/analysis.odin94
-rw-r--r--src/server/completion.odin4
-rw-r--r--src/server/hover.odin29
-rw-r--r--tests/completions_test.odin12
-rw-r--r--tests/hover_test.odin177
5 files changed, 293 insertions, 23 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin
index 668260b..e67b31e 100644
--- a/src/server/analysis.odin
+++ b/src/server/analysis.odin
@@ -3765,7 +3765,28 @@ unwrap_bitset :: proc(ast_context: ^AstContext, bitset_symbol: Symbol) -> (Symbo
return {}, false
}
-get_signature :: proc(ast_context: ^AstContext, ident: ast.Ident, symbol: Symbol, was_variable := false) -> string {
+append_variable_full_name :: proc(
+ sb: ^strings.Builder,
+ ast_context: ^AstContext,
+ symbol: Symbol,
+ pointer_prefix: string,
+) {
+ pkg_name := get_symbol_pkg_name(ast_context, symbol)
+ if pkg_name == "" {
+ fmt.sbprintf(sb, "%s%s :: ", pointer_prefix, symbol.name)
+ return
+ }
+ fmt.sbprintf(sb, "%s%s.%s :: ", pointer_prefix, pkg_name, symbol.name)
+ return
+}
+
+get_signature :: proc(
+ ast_context: ^AstContext,
+ ident: ast.Any_Node,
+ symbol: Symbol,
+ was_variable := false,
+ short_signature := false,
+) -> string {
if symbol.type == .Function {
return symbol.signature
}
@@ -3789,11 +3810,26 @@ get_signature :: proc(ast_context: ^AstContext, ident: ast.Ident, symbol: Symbol
allocator = ast_context.allocator,
)
case SymbolEnumValue:
+ if short_signature {
+ builder := strings.builder_make(ast_context.allocator)
+ if is_variable {
+ append_variable_full_name(&builder, ast_context, symbol, pointer_prefix)
+ }
+ strings.write_string(&builder, "enum")
+ return strings.to_string(builder)
+ }
+ builder := strings.builder_make(ast_context.allocator)
if is_variable {
- return symbol.name
- } else {
- return "enum"
+ append_variable_full_name(&builder, ast_context, symbol, pointer_prefix)
+ }
+ strings.write_string(&builder, "enum {\n")
+ for i in 0 ..< len(v.names) {
+ strings.write_string(&builder, "\t")
+ strings.write_string(&builder, v.names[i])
+ strings.write_string(&builder, ",\n")
}
+ strings.write_string(&builder, "}")
+ return strings.to_string(builder)
case SymbolMapValue:
return strings.concatenate(
a = {pointer_prefix, "map[", common.node_to_string(v.key), "]", common.node_to_string(v.value)},
@@ -3802,17 +3838,55 @@ get_signature :: proc(ast_context: ^AstContext, ident: ast.Ident, symbol: Symbol
case SymbolProcedureValue:
return "proc"
case SymbolStructValue:
+ if short_signature {
+ builder := strings.builder_make(ast_context.allocator)
+ if is_variable {
+ append_variable_full_name(&builder, ast_context, symbol, pointer_prefix)
+ }
+ strings.write_string(&builder, "struct")
+ return strings.to_string(builder)
+ }
+ builder := strings.builder_make(ast_context.allocator)
if is_variable {
- return strings.concatenate({pointer_prefix, symbol.name}, ast_context.allocator)
- } else {
- return "struct"
+ append_variable_full_name(&builder, ast_context, symbol, pointer_prefix)
+ }
+ longestNameLen := 0
+ for name in v.names {
+ if len(name) > longestNameLen {
+ longestNameLen = len(name)
+ }
+ }
+ strings.write_string(&builder, "struct {\n")
+ for i in 0 ..< len(v.names) {
+ strings.write_string(&builder, "\t")
+ strings.write_string(&builder, v.names[i])
+ fmt.sbprintf(&builder, ":%*s", longestNameLen - len(v.names[i]) + 1, "")
+ common.build_string_node(v.types[i], &builder, false)
+ strings.write_string(&builder, ",\n")
}
+ strings.write_string(&builder, "}")
+ return strings.to_string(builder)
case SymbolUnionValue:
+ if short_signature {
+ builder := strings.builder_make(ast_context.allocator)
+ if is_variable {
+ append_variable_full_name(&builder, ast_context, symbol, pointer_prefix)
+ }
+ strings.write_string(&builder, "union")
+ return strings.to_string(builder)
+ }
+ builder := strings.builder_make(ast_context.allocator)
if is_variable {
- return strings.concatenate({pointer_prefix, symbol.name}, ast_context.allocator)
- } else {
- return "union"
+ append_variable_full_name(&builder, ast_context, symbol, pointer_prefix)
+ }
+ strings.write_string(&builder, "union {\n")
+ for i in 0 ..< len(v.types) {
+ strings.write_string(&builder, "\t")
+ common.build_string_node(v.types[i], &builder, false)
+ strings.write_string(&builder, ",\n")
}
+ strings.write_string(&builder, "}")
+ return strings.to_string(builder)
case SymbolBitFieldValue:
if is_variable {
return strings.concatenate({pointer_prefix, symbol.name}, ast_context.allocator)
diff --git a/src/server/completion.odin b/src/server/completion.odin
index 8d61e3e..57d289d 100644
--- a/src/server/completion.odin
+++ b/src/server/completion.odin
@@ -1289,7 +1289,7 @@ get_identifier_completion :: proc(
ident.name = k
if symbol, ok := resolve_type_identifier(ast_context, ident^); ok {
- symbol.signature = get_signature(ast_context, ident^, symbol)
+ symbol.signature = get_signature(ast_context, ident, symbol, short_signature=true)
build_procedure_symbol_signature(&symbol)
@@ -1330,7 +1330,7 @@ get_identifier_completion :: proc(
ident.name = k
if symbol, ok := resolve_type_identifier(ast_context, ident^); ok {
- symbol.signature = get_signature(ast_context, ident^, symbol)
+ symbol.signature = get_signature(ast_context, ident, symbol, short_signature=true)
build_procedure_symbol_signature(&symbol)
diff --git a/src/server/hover.odin b/src/server/hover.odin
index cc3c5b2..b69385a 100644
--- a/src/server/hover.odin
+++ b/src/server/hover.odin
@@ -108,6 +108,28 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
}
}
+ if position_context.struct_type != nil {
+ for field in position_context.struct_type.fields.list {
+ for name in field.names {
+ if position_in_node(name, position_context.position) {
+ if identifier, ok := name.derived.(^ast.Ident); ok && field.type != nil {
+ if position_context.value_decl != nil && len(position_context.value_decl.names) != 0 {
+ if symbol, ok := resolve_type_expression(&ast_context, field.type); ok {
+ if struct_symbol, ok := resolve_type_expression(&ast_context, position_context.value_decl.names[0]); ok {
+ symbol.pkg = struct_symbol.name
+ symbol.name = identifier.name
+ symbol.signature = get_signature(&ast_context, field.type.derived, symbol)
+ hover.contents = write_hover_content(&ast_context, symbol)
+ return hover, true, true
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
if position_context.field_value != nil && position_context.comp_lit != nil {
if comp_symbol, ok := resolve_comp_literal(&ast_context, &position_context); ok {
if field, ok := position_context.field_value.field.derived.(^ast.Ident); ok {
@@ -155,7 +177,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
if position_in_node(base, position_context.position) {
if resolved, ok := resolve_type_identifier(&ast_context, ident); ok {
- resolved.signature = get_signature(&ast_context, ident, resolved)
+ resolved.signature = get_signature(&ast_context, &ident, resolved)
resolved.name = ident.name
if resolved.type == .Variable {
@@ -206,7 +228,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
if symbol, ok := resolve_type_expression(&ast_context, v.types[i]); ok {
symbol.name = name
symbol.pkg = selector.name
- symbol.signature = common.node_to_string(v.types[i])
+ symbol.signature = get_signature(&ast_context, v.types[i].derived, symbol)
hover.contents = write_hover_content(&ast_context, symbol)
return hover, true, true
}
@@ -228,6 +250,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
if position_context.field != nil {
if ident, ok := position_context.field.derived.(^ast.Ident); ok {
if symbol, ok := resolve_type_identifier(&ast_context, ident^); ok {
+ symbol.signature = get_signature(&ast_context, ident, symbol)
hover.contents = write_hover_content(&ast_context, symbol)
return hover, true, true
}
@@ -282,7 +305,7 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
}
if resolved, ok := resolve_type_identifier(&ast_context, ident); ok {
- resolved.signature = get_signature(&ast_context, ident, resolved)
+ resolved.signature = get_signature(&ast_context, &ident, resolved)
resolved.name = ident.name
if resolved.type == .Variable {
diff --git a/tests/completions_test.odin b/tests/completions_test.odin
index e5e0001..5e8c5d8 100644
--- a/tests/completions_test.odin
+++ b/tests/completions_test.odin
@@ -651,7 +651,7 @@ ast_for_in_identifier_completion :: proc(t: ^testing.T) {
}
- test.expect_completion_details(t, &source, "", {"test.my_element: My_Struct"})
+ test.expect_completion_details(t, &source, "", {"test.my_element: test.My_Struct :: struct"})
}
@(test)
@@ -675,7 +675,7 @@ ast_for_in_call_expr_completion :: proc(t: ^testing.T) {
}
- test.expect_completion_details(t, &source, ".", {"test.zstep: Step"})
+ test.expect_completion_details(t, &source, ".", {"test.zstep: test.Step :: struct"})
}
@@ -1661,7 +1661,7 @@ ast_new_clone_completion :: proc(t: ^testing.T) {
`,
}
- test.expect_completion_details(t, &source, "", {"test.adzz: ^Foo"})
+ test.expect_completion_details(t, &source, "", {"test.adzz: ^test.Foo :: struct"})
}
@(test)
@@ -1735,7 +1735,7 @@ ast_index_proc_parameter_completion :: proc(t: ^testing.T) {
packages = packages[:],
}
- test.expect_completion_details(t, &source, ".", {"my_package.param: My_Struct"})
+ test.expect_completion_details(t, &source, ".", {"my_package.param: my_package.My_Struct :: struct"})
}
@(test)
@@ -2534,7 +2534,7 @@ ast_poly_struct_with_poly :: proc(t: ^testing.T) {
`,
}
- test.expect_completion_details(t, &source, "", {"test.first: ^Animal"})
+ test.expect_completion_details(t, &source, "", {"test.first: ^test.Animal :: struct"})
}
@(test)
@@ -3013,7 +3013,7 @@ ast_enumerated_array_range_completion :: proc(t: ^testing.T) {
`,
}
- test.expect_completion_details(t, &source, "", {"test.indezx: Enum"})
+ test.expect_completion_details(t, &source, "", {"test.indezx: test.Enum :: enum"})
}
@(test)
diff --git a/tests/hover_test.odin b/tests/hover_test.odin
index bdd583a..fb64285 100644
--- a/tests/hover_test.odin
+++ b/tests/hover_test.odin
@@ -83,7 +83,37 @@ ast_hover_external_package_parameter :: proc(t: ^testing.T) {
packages = packages[:],
}
- test.expect_hover(t, &source, "test.cool: My_Struct")
+ test.expect_hover(t, &source, "test.cool: my_package.My_Struct :: struct {\n\tone: int,\n\ttwo: int,\n\tthree: int,\n}")
+}
+
+@(test)
+ast_hover_external_package_parameter_pointer :: proc(t: ^testing.T) {
+ packages := make([dynamic]test.Package, context.temp_allocator)
+
+ append(
+ &packages,
+ test.Package {
+ pkg = "my_package",
+ source = `package my_package
+ My_Struct :: struct {
+ one: int,
+ two: int,
+ three: int,
+ }
+ `,
+ },
+ )
+ source := test.Source {
+ main = `package test
+ import "my_package"
+ main :: proc(cool: ^my_package.My_Struct) {
+ cool{*}
+ }
+ `,
+ packages = packages[:],
+ }
+
+ test.expect_hover(t, &source, "test.cool: ^my_package.My_Struct :: struct {\n\tone: int,\n\ttwo: int,\n\tthree: int,\n}")
}
@(test)
@@ -302,7 +332,7 @@ ast_hover_struct_field_selector_completion :: proc(t: ^testing.T) {
packages = packages[:],
}
- test.expect_hover(t, &source, "my_package.My_Struct: struct")
+ test.expect_hover(t, &source, "my_package.My_Struct: struct {\n\tone: int,\n\ttwo: int,\n\tthree: int,\n}")
}
@(test)
@@ -412,6 +442,149 @@ ast_hover_union_implicit_selector :: proc(t: ^testing.T) {
test.expect_hover(t, &source, "test.Bar: .Foo1")
}
+@(test)
+ast_hover_struct :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ bar: int,
+ f: proc(a: int) -> int,
+ }
+
+ foo := F{*}oo{}
+ `
+ }
+
+ test.expect_hover(t, &source, "test.Foo: struct {\n\tbar: int,\n\tf: proc(a: int) -> int,\n}")
+}
+
+@(test)
+ast_hover_proc_param_with_struct_from_another_package :: proc(t: ^testing.T) {
+ packages := make([dynamic]test.Package, context.temp_allocator)
+
+ append(
+ &packages,
+ test.Package {
+ pkg = "my_package",
+ source = `package my_package
+ My_Struct :: struct {
+ one: int,
+ two: int,
+ three: int,
+ }
+ `,
+ },
+ )
+ source := test.Source {
+ main = `package test
+ import "my_package"
+ main :: proc(cool: my_package.My{*}_Struct) {
+ cool
+ }
+ `,
+ packages = packages[:],
+ }
+
+ test.expect_hover(t, &source, "my_package.My_Struct: struct {\n\tone: int,\n\ttwo: int,\n\tthree: int,\n}")
+}
+
+@(test)
+ast_hover_struct_variable :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ bar: int,
+ f: proc(a: int) -> int,
+ }
+
+ fo{*}o := Foo{}
+ `
+ }
+
+ test.expect_hover(t, &source, "test.foo: test.Foo :: struct {\n\tbar: int,\n\tf: proc(a: int) -> int,\n}")
+}
+
+@(test)
+ast_hover_enum :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: enum {
+ Foo1,
+ Foo2,
+ }
+
+ foo: F{*}oo
+ `
+ }
+
+ test.expect_hover(t, &source, "test.Foo: enum {\n\tFoo1,\n\tFoo2,\n}")
+}
+
+@(test)
+ast_hover_enum_variable :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: enum {
+ Foo1,
+ Foo2,
+ }
+
+ f{*}oo: Foo
+ `
+ }
+
+ test.expect_hover(t, &source, "test.foo: test.Foo :: enum {\n\tFoo1,\n\tFoo2,\n}")
+}
+
+@(test)
+ast_hover_union :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: union {
+ string,
+ int,
+ }
+
+ foo: F{*}oo
+ `
+ }
+
+ test.expect_hover(t, &source, "test.Foo: union {\n\tstring,\n\tint,\n}")
+}
+
+@(test)
+ast_hover_union_variable :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: union {
+ string,
+ int,
+ }
+
+ f{*}oo: Foo
+ `
+ }
+
+ test.expect_hover(t, &source, "test.foo: test.Foo :: union {\n\tstring,\n\tint,\n}")
+}
+
+@(test)
+ast_hover_struct_field_definition :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ b{*}ar: int,
+ f: proc(a: int) -> int,
+ }
+
+ foo := Foo{
+ bar = 1
+ }
+ `
+ }
+
+ test.expect_hover(t, &source, "Foo.bar: int")
+}
/*
Waiting for odin fix