diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 2c7f4f402..2bb955192 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -960,8 +960,13 @@ resolve_function_overload :: proc(ast_context: ^AstContext, group: ^ast.Proc_Gro resolve_call_arg_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (Symbol, bool) { old_current_package := ast_context.current_package ast_context.current_package = ast_context.document_package + + old_use_locals := ast_context.use_locals + ast_context.use_locals = true + defer { ast_context.current_package = old_current_package + ast_context.use_locals = old_use_locals } return resolve_type_expression(ast_context, node) @@ -1124,6 +1129,12 @@ resolve_location_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Ex } resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (Symbol, bool) { + if node != nil { + if _, ok := node.derived.(^ast.Bad_Expr); ok { + return {}, false + } + } + clear(&ast_context.recursion_map) symbol := Symbol{} ok := internal_resolve_type_expression(ast_context, node, &symbol) @@ -1385,10 +1396,14 @@ resolve_call_directive :: proc(ast_context: ^AstContext, call: ^ast.Call_Expr) - if len(call.args) == 1 { ident := new_type(ast.Ident, call.pos, call.end, ast_context.allocator) ident.name = "u8" - value := SymbolSliceValue{ - expr = ident + value := SymbolSliceValue { + expr = ident, + } + symbol := Symbol { + name = "#load", + pkg = ast_context.current_package, + value = value, } - symbol := Symbol{name = "#load", pkg = ast_context.current_package, value = value} return symbol, true } else if len(call.args) == 2 { return resolve_type_expression(ast_context, call.args[1]) @@ -1407,10 +1422,14 @@ resolve_call_directive :: proc(ast_context: ^AstContext, call: ^ast.Call_Expr) - selector := new_type(ast.Selector_Expr, call.pos, call.end, ast_context.allocator) selector.expr = pkg selector.field = field - value := SymbolSliceValue{ - expr = selector + value := SymbolSliceValue { + expr = selector, + } + symbol := Symbol { + name = "#load_directory", + pkg = ast_context.current_package, + value = value, } - symbol := Symbol{name = "#load_directory", pkg = ast_context.current_package, value = value} return symbol, true } diff --git a/src/server/ast.odin b/src/server/ast.odin index d1846f710..138ce5784 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -1,6 +1,7 @@ #+feature dynamic-literals package server +import "base:runtime" import "core:fmt" import "core:mem" import "core:odin/ast" @@ -1453,32 +1454,96 @@ construct_bit_field_field_docs :: proc(file: ast.File, v: ^ast.Bit_Field_Type, a } } +Comment_Index :: struct { + line_to_idx: map[int][dynamic]int, + allocator: runtime.Allocator, +} + +build_comment_index :: proc(file: ast.File, allocator := context.allocator) -> Comment_Index { + index := Comment_Index { + line_to_idx = make(map[int][dynamic]int, allocator), + allocator = allocator, + } + + for c, i in file.comments { + line := c.pos.line + + if line not_in index.line_to_idx { + index.line_to_idx[line] = make([dynamic]int, 0, 1, allocator) + } + + append(&index.line_to_idx[line], i) + } + + return index +} + +destroy_comment_index :: proc(index: ^Comment_Index) { + for _, &indices in index.line_to_idx { + delete(indices) + } + + delete(index.line_to_idx) +} + // Retrives the comment group from the specified line of the file // Returns the index where the comment was found get_file_comment :: proc( file: ast.File, line: int, start_index := 0, + index: ^Comment_Index = nil, allocator := context.temp_allocator, ) -> ( ^ast.Comment_Group, int, ) { - // TODO: linear scan might be a bit slow for files with lots of comments? + if index != nil { + indices, ok := index.line_to_idx[line] + + if !ok { + return nil, -1 + } + + for idx in indices { + if idx >= start_index { + c := file.comments[idx] + + for item, j in c.list { + comment := new_type(ast.Comment_Group, item.pos, parser.end_pos(item), allocator) + + if j == len(c.list) - 1 { + comment.list = c.list[j:] + } else { + comment.list = c.list[j:j + 1] + } + + return comment, idx + } + } + } + + return nil, -1 + } + for i := start_index; i < len(file.comments); i += 1 { c := file.comments[i] + if c.pos.line == line { for item, j in c.list { comment := new_type(ast.Comment_Group, item.pos, parser.end_pos(item), allocator) + if j == len(c.list) - 1 { comment.list = c.list[j:] } else { comment.list = c.list[j:j + 1] } + return comment, i } } } + return nil, -1 } @@ -1524,6 +1589,9 @@ get_field_docs_and_comments :: proc( [dynamic]^ast.Comment_Group, [dynamic]^ast.Comment_Group, ) { + index := build_comment_index(file) + defer destroy_comment_index(&index) + docs := make([dynamic]^ast.Comment_Group, allocator) comments := make([dynamic]^ast.Comment_Group, allocator) prev_line := -1 @@ -1554,6 +1622,7 @@ get_field_docs_and_comments :: proc( n.pos.line, start_index = last_comment + 1, allocator = allocator, + index = &index, ) } diff --git a/src/server/completion.odin b/src/server/completion.odin index 6663be9fa..53274b2cb 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -587,12 +587,152 @@ get_directive_completion :: proc( results: ^[dynamic]CompletionResult, ) -> bool { is_incomplete := false + context_directives := make([dynamic]CompletionResult, 0, context.temp_allocator) + + if position_context.value_decl != nil { + for value in position_context.value_decl.values { + if _, ok := value.derived.(^ast.Struct_Type); ok { + append_struct_directives(&context_directives) + break + } else if _, ok := value.derived.(^ast.Bit_Field_Type); ok { + append_bitfield_directives(&context_directives) + break + } else if _, ok := value.derived.(^ast.Union_Type); ok { + append_union_directives(&context_directives) + break + } + } + } + + if position_context.function != nil { + append_procedure_directives(&context_directives) + } + + if position_context.switch_stmt != nil || position_context.switch_type_stmt != nil { + append_switch_directives(&context_directives) + } + + if len(context_directives) > 0 { + append(results, ..context_directives[:]) + } else { + append(results, ..completion_items_directives[:]) + } - // Right now just return all the possible completions, but later on I should give the context specific ones - append(results, ..completion_items_directives[:]) return is_incomplete } +@(private = "file") +append_struct_directives :: proc(results: ^[dynamic]CompletionResult) { + struct_directives := []string{"align", "packed", "raw_union"} + + for directive in struct_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + +@(private = "file") +append_bitfield_directives :: proc(results: ^[dynamic]CompletionResult) { + if doc, ok := directive_docs["align"]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem{label = "align", kind = .Constant, documentation = documentation}, + }, + ) + } +} + +@(private = "file") +append_union_directives :: proc(results: ^[dynamic]CompletionResult) { + union_directives := []string{"no_nil", "shared_nil", "align"} + + for directive in union_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + +@(private = "file") +append_procedure_directives :: proc(results: ^[dynamic]CompletionResult) { + proc_directives := []string{"optional_ok", "require_results", "deprecated", "cold", "test"} + + for directive in proc_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + +@(private = "file") +append_switch_directives :: proc(results: ^[dynamic]CompletionResult) { + switch_directives := []string{"partial"} + + for directive in switch_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + get_comp_lit_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, diff --git a/src/server/generics.odin b/src/server/generics.odin index db858cc8a..cc7371239 100644 --- a/src/server/generics.odin +++ b/src/server/generics.odin @@ -54,10 +54,6 @@ resolve_poly :: proc( call_node_id := reflect.union_variant_typeid(call_node.derived) specialization_id := reflect.union_variant_typeid(specialization.derived) if ast_context.position_hint == .TypeDefinition && call_node_id == specialization_id { - // TODO: Fix this so it doesn't need to be aware that we're in a type definition - // if the specialization type matches the type of the parameter passed to the proc - // we store that rather than the specialization so we can follow it correctly - // for things like `textDocument/typeDefinition` save_poly_map( ident, make_ident_ast(ast_context, call_node.pos, call_node.end, call_symbol.name), @@ -133,7 +129,6 @@ resolve_poly :: proc( return false } - //It's not enough for them to both arrays, they also have to share soa attributes if p.tag != nil && call_array.tag != nil { a, ok1 := p.tag.derived.(^ast.Basic_Directive) b, ok2 := call_array.tag.derived.(^ast.Basic_Directive) @@ -162,7 +157,6 @@ resolve_poly :: proc( return false } - //It's not enough for them to both arrays, they also have to share soa attributes if p.tag != nil && call_array.tag != nil { a, ok1 := p.tag.derived.(^ast.Basic_Directive) b, ok2 := call_array.tag.derived.(^ast.Basic_Directive) @@ -258,6 +252,28 @@ resolve_poly :: proc( } case ^ast.Pointer_Type: if call_pointer, ok := call_node.derived.(^ast.Pointer_Type); ok { + if poly_type, ok := p.elem.derived.(^ast.Poly_Type); ok { + if poly_type.specialization != nil { + if poly_dynamic, ok := poly_type.specialization.derived.(^ast.Dynamic_Array_Type); ok { + if call_dynamic, ok := call_pointer.elem.derived.(^ast.Dynamic_Array_Type); ok { + if poly_type.type != nil { + if ident, ok := unwrap_ident(poly_type.type); ok { + save_poly_map(ident, call_pointer.elem, poly_map) + } + } + + if elem_poly, ok := poly_dynamic.elem.derived.(^ast.Poly_Type); ok { + if elem_ident, ok := unwrap_ident(elem_poly.type); ok { + save_poly_map(elem_ident, call_dynamic.elem, poly_map) + } + } + + return true + } + } + } + } + if poly_type, ok := p.elem.derived.(^ast.Poly_Type); ok { if ident, ok := unwrap_ident(poly_type.type); ok { save_poly_map(ident, call_pointer.elem, poly_map) @@ -266,6 +282,7 @@ resolve_poly :: proc( if poly_type.specialization != nil { return resolve_poly(ast_context, call_pointer.elem, call_symbol, p.elem, poly_map) } + return true } } @@ -796,6 +813,7 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, b: ^SymbolStructValueBuild } } else if data.parent_proc == nil { data.symbol_value_builder.types[data.i] = expr + data.poly_index += 1 } } diff --git a/src/server/memory_index.odin b/src/server/memory_index.odin index 487d7a20a..a4e457855 100644 --- a/src/server/memory_index.odin +++ b/src/server/memory_index.odin @@ -51,6 +51,82 @@ score_name :: proc(matchers: []^common.FuzzyMatcher, name: string) -> (f32, bool return score, true } +traverse_nested_fields :: proc( + index: ^MemoryIndex, + symbol: Symbol, + prefix: string, + matchers: []^common.FuzzyMatcher, + symbols: ^[dynamic]FuzzyResult, +) { + #partial switch v in symbol.value { + case SymbolStructValue: + for name, i in v.names { + full_name := fmt.tprintf("%s.%s", prefix, name) + + if score, ok := score_name(matchers, full_name); ok { + s := symbol + construct_struct_field_symbol(&s, prefix, v, i) + s.name = full_name + result := FuzzyResult { + symbol = s, + score = score, + } + + append(symbols, result) + } + + field_symbol := symbol + construct_struct_field_symbol(&field_symbol, prefix, v, i) + traverse_nested_fields(index, field_symbol, full_name, matchers, symbols) + } + + case SymbolBitFieldValue: + for name, i in v.names { + full_name := fmt.tprintf("%s.%s", prefix, name) + + if score, ok := score_name(matchers, full_name); ok { + s := symbol + construct_bit_field_field_symbol(&s, prefix, v, i) + s.name = full_name + result := FuzzyResult { + symbol = s, + score = score, + } + + append(symbols, result) + } + + field_symbol := symbol + construct_bit_field_field_symbol(&field_symbol, prefix, v, i) + traverse_nested_fields(index, field_symbol, full_name, matchers, symbols) + } + + case SymbolGenericValue: + for name, i in v.field_names { + full_name := fmt.tprintf("%s.%s", prefix, name) + + if score, ok := score_name(matchers, full_name); ok { + s := symbol + s.name = full_name + s.type = .Field + s.range = v.ranges[i] + result := FuzzyResult { + symbol = s, + score = score, + } + + append(symbols, result) + } + + field_symbol := symbol + field_symbol.name = full_name + field_symbol.type = .Field + field_symbol.range = v.ranges[i] + traverse_nested_fields(index, field_symbol, full_name, matchers, symbols) + } + } +} + memory_index_fuzzy_search :: proc( index: ^MemoryIndex, name: string, @@ -63,9 +139,9 @@ memory_index_fuzzy_search :: proc( bool, ) { symbols := make([dynamic]FuzzyResult, 0, context.temp_allocator) - fields := strings.fields(name, context.temp_allocator) matchers := make([dynamic]^common.FuzzyMatcher, 0, len(fields), context.temp_allocator) + for field in fields { append(&matchers, common.make_fuzzy_matcher(field)) } @@ -79,57 +155,11 @@ memory_index_fuzzy_search :: proc( if should_skip_private_symbol(symbol, current_pkg, current_file) { continue } + if resolve_fields { - // TODO: this only does the top level fields, we may want to travers all the way down in the future - #partial switch v in symbol.value { - case SymbolStructValue: - for name, i in v.names { - full_name := fmt.tprintf("%s.%s", symbol.name, name) - if score, ok := score_name(matchers[:], full_name); ok { - s := symbol - construct_struct_field_symbol(&s, symbol.name, v, i) - s.name = full_name - result := FuzzyResult { - symbol = s, - score = score, - } - - append(&symbols, result) - } - } - case SymbolBitFieldValue: - for name, i in v.names { - full_name := fmt.tprintf("%s.%s", symbol.name, name) - if score, ok := score_name(matchers[:], full_name); ok { - s := symbol - construct_bit_field_field_symbol(&s, symbol.name, v, i) - s.name = full_name - result := FuzzyResult { - symbol = s, - score = score, - } - - append(&symbols, result) - } - } - case SymbolGenericValue: - for name, i in v.field_names { - full_name := fmt.tprintf("%s.%s", symbol.name, name) - if score, ok := score_name(matchers[:], full_name); ok { - s := symbol - s.name = full_name - s.type = .Field - s.range = v.ranges[i] - result := FuzzyResult { - symbol = s, - score = score, - } - - append(&symbols, result) - } - } - } + traverse_nested_fields(index, symbol, symbol.name, matchers[:], &symbols) } + if score, ok := score_name(matchers[:], symbol.name); ok { result := FuzzyResult { symbol = symbol, diff --git a/tests/hover_test.odin b/tests/hover_test.odin index 6f8cecff2..2689b6182 100644 --- a/tests/hover_test.odin +++ b/tests/hover_test.odin @@ -4431,7 +4431,11 @@ ast_hover_parapoly_elem_overloaded_proc_multiple_options :: proc(t: ^testing.T) } `, } - test.expect_hover(t, &source, "test.foo :: proc {\n\tfoo_int :: proc(i: int),\n\tfoo_string :: proc(s: string),\n}") + test.expect_hover( + t, + &source, + "test.foo :: proc {\n\tfoo_int :: proc(i: int),\n\tfoo_string :: proc(s: string),\n}", + ) } @(test) @@ -4560,7 +4564,11 @@ ast_hover_parapoly_union_with_where_clause :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.Foo :: union($T: typeid) #no_nil where type_is_integer(T) {\n\tT,\n\tstring,\n}") + test.expect_hover( + t, + &source, + "test.Foo :: union($T: typeid) #no_nil where type_is_integer(T) {\n\tT,\n\tstring,\n}", + ) } @(test) @@ -4851,7 +4859,7 @@ ast_hover_proc_param_tags :: proc(t: ^testing.T) { @(test) ast_hover_simd_array :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test f{*}oo := #simd[2]f32{} `, } @@ -4861,7 +4869,7 @@ ast_hover_simd_array :: proc(t: ^testing.T) { @(test) ast_hover_simd_array_pointer :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test f{*}oo := &#simd[4]f32{} `, } @@ -4955,7 +4963,11 @@ ast_hover_const_complex_comp_lit :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.COLOURS :: Colours {\n\tblue = frgba{0.1, 0.1, 0.1, 0.1},\n\tgreen = frgba{0.1, 0.1, 0.1, 0.1},\n\tfoo = {\n\t\ta = 32,\n\t\tb = \"testing\",\n\t},\n\tbar = 1 + 2,\n}") + test.expect_hover( + t, + &source, + "test.COLOURS :: Colours {\n\tblue = frgba{0.1, 0.1, 0.1, 0.1},\n\tgreen = frgba{0.1, 0.1, 0.1, 0.1},\n\tfoo = {\n\t\ta = 32,\n\t\tb = \"testing\",\n\t},\n\tbar = 1 + 2,\n}", + ) } @(test) @@ -5008,7 +5020,7 @@ ast_hover_proc_overload_basic_type_alias :: proc(t: ^testing.T) { `}) source := test.Source { - main = `package test + main = `package test import "my_package" foo_int :: proc(i: int) {} @@ -5052,23 +5064,11 @@ ast_hover_proc_overload_nil_pointer :: proc(t: ^testing.T) { ast_hover_package_proc_naming_conflicting_with_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 + append(&packages, test.Package{pkg = "my_package", source = `package my_package foo :: proc() {} - `, - }, - ) - append( - &packages, - test.Package { - pkg = "foo", - source = `package foo - `, - }, - ) + `}) + append(&packages, test.Package{pkg = "foo", source = `package foo + `}) source := test.Source { main = `package test @@ -5155,14 +5155,17 @@ ast_hover_generic_proc_with_inlining :: proc(t: ^testing.T) { ast_hover_using_import_statement_name_conflict :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package{pkg = "my_package", source = `package my_package Bar :: struct { b: string, } - `}) + `}, + ) source := test.Source { - main = `package test + main = `package test import "my_package" Bar :: struct { @@ -5352,12 +5355,18 @@ ast_hover_quaternion_literal :: proc(t: ^testing.T) { ast_hover_parapoly_other_package :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package { + pkg = "my_package", + source = `package my_package // Docs! bar :: proc(_: $T) {} // Comment! - `}) + `, + }, + ) source := test.Source { - main = `package test + main = `package test import "my_package" main :: proc() { @@ -5809,14 +5818,20 @@ ast_hover_nested_proc_docs_spaces :: proc(t: ^testing.T) { ast_hover_propagate_docs_alias_in_package :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package { + pkg = "my_package", + source = `package my_package // Docs! foo :: proc() {} // Comment! bar :: foo - `}) + `, + }, + ) source := test.Source { - main = `package test + main = `package test import "my_package" main :: proc() { @@ -5832,15 +5847,21 @@ ast_hover_propagate_docs_alias_in_package :: proc(t: ^testing.T) { ast_hover_propagate_docs_alias_in_package_override :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package { + pkg = "my_package", + source = `package my_package // Docs! foo :: proc() {} // Comment! // Overridden bar :: foo - `}) + `, + }, + ) source := test.Source { - main = `package test + main = `package test import "my_package" main :: proc() { @@ -5884,7 +5905,7 @@ ast_hover_const_aliases_from_other_pkg :: proc(t: ^testing.T) { Foo :: 3 + 4 `}) source := test.Source { - main = `package test + main = `package test import "my_package" B{*}ar :: my_package.Foo @@ -5958,7 +5979,11 @@ ast_hover_directives_config_info :: proc(t: ^testing.T) { bar :: #c{*}onfig(TEST, false) `, } - test.expect_hover(t, &source, "#config(, default)\n\nChecks if an identifier is defined through the command line, or gives a default value instead.\n\nValues can be set with the `-define:NAME=VALUE` command line flag.") + test.expect_hover( + t, + &source, + "#config(, default)\n\nChecks if an identifier is defined through the command line, or gives a default value instead.\n\nValues can be set with the `-define:NAME=VALUE` command line flag.", + ) } /*