diff options
| author | Bradley Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-08-26 07:56:45 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-26 07:56:45 -0400 |
| commit | 2d2284560741f8d6b834ffd0b6c5a34d1ad3b77c (patch) | |
| tree | 44ff5e0215aff1b24d7b1a27e03fafc448b02c36 /src/server | |
| parent | 4703f8a761bfe70946a3a84db4490a5e66ab9e4c (diff) | |
| parent | 656fbf87d5ae541e409dcbd0906262e3d8f0d881 (diff) | |
Merge pull request #906 from BradLewis/feat/handle-pointers-on-proc-args
Handle pointers when passing variables to procedure arguments
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/analysis.odin | 13 | ||||
| -rw-r--r-- | src/server/completion.odin | 237 | ||||
| -rw-r--r-- | src/server/documentation.odin | 4 | ||||
| -rw-r--r-- | src/server/hover.odin | 2 | ||||
| -rw-r--r-- | src/server/requests.odin | 5 | ||||
| -rw-r--r-- | src/server/symbol.odin | 31 | ||||
| -rw-r--r-- | src/server/types.odin | 1 |
7 files changed, 229 insertions, 64 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin index adf7a8b..8d2b4f8 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -373,11 +373,11 @@ is_symbol_same_typed :: proc(ast_context: ^AstContext, a, b: Symbol, flags: ast. #partial switch b_value in b.value { case SymbolBasicValue: if .Any_Int in flags { - //Temporary - make a function that finds the base type of basic values - //This code only works with non distinct ints - switch a.name { - case "int", "uint", "u32", "i32", "u8", "i8", "u64", "u16", "i16": - return true + names := untyped_map[.Integer] + for name in names { + if a.name == name { + return true + } } } } @@ -388,7 +388,8 @@ is_symbol_same_typed :: proc(ast_context: ^AstContext, a, b: Symbol, flags: ast. #partial switch a_value in a.value { case SymbolBasicValue: - return a.name == b.name && a.pkg == b.pkg + b_value := b.value.(SymbolBasicValue) + return a_value.ident.name == b_value.ident.name && a.pkg == b.pkg case SymbolStructValue, SymbolEnumValue, SymbolUnionValue, SymbolBitSetValue: return a.name == b.name && a.pkg == b.pkg case SymbolSliceValue: diff --git a/src/server/completion.odin b/src/server/completion.odin index 60162ec..262ba3f 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -11,20 +11,14 @@ import "core:odin/tokenizer" import "core:os" import "core:path/filepath" import path "core:path/slashpath" +import "core:reflect" import "core:slice" import "core:sort" import "core:strconv" import "core:strings" - import "src:common" -/* - TODOS: Making the signature details is really annoying and not that nice - try to see if this can be refractored. - -*/ - - CompletionResult :: struct { symbol: Symbol, snippet: Snippet_Info, @@ -47,6 +41,7 @@ get_completion_list :: proc( document: ^Document, position: common.Position, completion_context: CompletionContext, + config: ^common.Config, ) -> ( CompletionList, bool, @@ -154,6 +149,8 @@ get_completion_list :: proc( } } + arg_symbol := get_target_symbol(&ast_context, &position_context) + results := make([dynamic]CompletionResult, 0, allocator = context.temp_allocator) is_incomplete := false @@ -162,30 +159,86 @@ get_completion_list :: proc( case .Comp_Lit: is_incomplete = get_comp_lit_completion(&ast_context, &position_context, &results) case .Identifier: - is_incomplete = get_identifier_completion(&ast_context, &position_context, &results) + is_incomplete = get_identifier_completion(&ast_context, &position_context, &results, config) case .Implicit: is_incomplete = get_implicit_completion(&ast_context, &position_context, &results) case .Selector: - is_incomplete = get_selector_completion(&ast_context, &position_context, &results) + is_incomplete = get_selector_completion(&ast_context, &position_context, &results, config) case .Switch_Type: is_incomplete = get_type_switch_completion(&ast_context, &position_context, &results) case .Directive: is_incomplete = get_directive_completion(&ast_context, &position_context, &results) case .Package: - is_incomplete = get_package_completion(&ast_context, &position_context, &results) + is_incomplete = get_package_completion(&ast_context, &position_context, &results, config) } - items := convert_completion_results(&ast_context, &position_context, results[:], completion_type) + items := convert_completion_results( + &ast_context, + &position_context, + results[:], + completion_type, + arg_symbol, + config, + ) list.items = items list.isIncomplete = is_incomplete return list, true } +get_target_symbol :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext) -> Maybe(Symbol) { + if position_context.call != nil { + if call, ok := position_context.call.derived.(^ast.Call_Expr); ok { + if param_index, ok := find_position_in_call_param(position_context, call^); ok { + // Manually check this so we handle the pointer case of the first argument + if ident, ok := call.expr.derived.(^ast.Ident); ok && param_index == 0 { + switch ident.name { + case "append", "non_zero_append": + return Symbol{value = SymbolDynamicArrayValue{}, pointers = 1} + } + } + if call_symbol, ok := resolve_type_expression(ast_context, call.expr); ok { + if value, ok := call_symbol.value.(SymbolProcedureValue); ok { + if arg_type, arg_type_ok := get_proc_arg_type_from_index(value, param_index); ok { + if position_context.field_value != nil { + // we are using a named param so we want to ensure we use that type and not the + // type at the index + if name, ok := position_context.field_value.field.derived.(^ast.Ident); ok { + if i, ok := get_field_list_name_index(name.name, value.arg_types); ok { + arg_type = value.arg_types[i] + } + } + } + if arg_type != nil { + if arg_type.type != nil { + if resolved_arg_symbol, ok := resolve_type_expression(ast_context, arg_type.type); + ok { + return resolved_arg_symbol + } + } else if arg_type.default_value != nil { + if resolved_arg_symbol, ok := resolve_type_expression( + ast_context, + arg_type.default_value, + ); ok { + return resolved_arg_symbol + } + } + } + } + } + } + } + } + } + return nil +} + convert_completion_results :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, results: []CompletionResult, completion_type: Completion_Type, + symbol: Maybe(Symbol), + config: ^common.Config, ) -> []CompletionItem { slice.sort_by(results[:], proc(i, j: CompletionResult) -> bool { @@ -206,7 +259,7 @@ convert_completion_results :: proc( for result in top_results { result := result if item, ok := result.completion_item.?; ok { - if common.config.enable_label_details { + if config.enable_label_details { item.labelDetails = CompletionItemLabelDetails { description = item.detail, } @@ -232,7 +285,7 @@ convert_completion_results :: proc( //Skip procedures when the position is in proc decl if position_in_proc_decl(position_context) && result.symbol.type == .Function && - common.config.enable_procedure_context { + config.enable_procedure_context { continue } @@ -273,7 +326,14 @@ convert_completion_results :: proc( label = result.symbol.name, documentation = write_hover_content(ast_context, result.symbol), } - if common.config.enable_label_details { + + if config.enable_completion_matching { + if s, ok := symbol.(Symbol); ok && (completion_type == .Selector || completion_type == .Identifier) { + handle_matching(ast_context, position_context, result.symbol, s, &item, completion_type) + } + } + + if config.enable_label_details { // detail = left // description = right details := CompletionItemLabelDetails{} @@ -292,7 +352,7 @@ convert_completion_results :: proc( // hack for sublime text's issue // remove when this issue is fixed: https://github.com/sublimehq/sublime_text/issues/6033 // or if this PR gets merged: https://github.com/sublimelsp/LSP/pull/2293 - if common.config.client_name == "Sublime Text LSP" { + if config.client_name == "Sublime Text LSP" { if strings.contains(details.detail, "..") && strings.contains(details.detail, "#") { s, _ := strings.replace_all(details.detail, "..", "ꓸꓸ", allocator = context.temp_allocator) details.detail = s @@ -302,8 +362,7 @@ convert_completion_results :: proc( } item.kind = symbol_type_to_completion_kind(result.symbol.type) - - if result.symbol.type == .Function && common.config.enable_snippets && common.config.enable_procedure_snippet { + if result.symbol.type == .Function && config.enable_snippets && config.enable_procedure_snippet { item.insertText = fmt.tprintf("%v($0)", item.label) item.insertTextFormat = .Snippet item.deprecated = .Deprecated in result.symbol.flags @@ -316,12 +375,103 @@ convert_completion_results :: proc( } if completion_type == .Identifier { - append_non_imported_packages(ast_context, position_context, &items) + append_non_imported_packages(ast_context, position_context, &items, config) } return items[:] } +@(private = "file") +handle_matching :: proc( + ast_context: ^AstContext, + position_context: ^DocumentPositionContext, + result_symbol: Symbol, + arg_symbol: Symbol, + item: ^CompletionItem, + completion_type: Completion_Type, +) { + should_skip :: proc(arg_symbol, result_symbol: Symbol) -> bool { + if v, ok := arg_symbol.value.(SymbolBasicValue); ok { + if v.ident.name == "any" { + return true + } + } + + if _, ok := result_symbol.value.(SymbolUntypedValue); ok && arg_symbol.type == .Keyword { + if _, ok := are_symbol_untyped_basic_same_typed(arg_symbol, result_symbol); !ok { + if _, ok := are_symbol_untyped_basic_same_typed(result_symbol, arg_symbol); !ok { + return true + } + return false + } + return false + } + + if _, ok := arg_symbol.value.(SymbolSliceValue); ok { + if _, ok := result_symbol.value.(SymbolDynamicArrayValue); ok { + return false + } + } + + a_id := reflect.union_variant_typeid(arg_symbol.value) + b_id := reflect.union_variant_typeid(result_symbol.value) + + if a_id != b_id { + return true + } + + #partial switch v in arg_symbol.value { + case SymbolMapValue, SymbolDynamicArrayValue, SymbolSliceValue, SymbolMultiPointerValue, SymbolFixedArrayValue: + return false + } + + if result_symbol.uri != arg_symbol.uri || result_symbol.range != arg_symbol.range { + return true + } + + return false + } + + if should_skip(arg_symbol, result_symbol) { + return + } + + suffix := "" + prefix := "" + + if _, ok := arg_symbol.value.(SymbolSliceValue); ok { + if _, ok := result_symbol.value.(SymbolDynamicArrayValue); ok { + suffix = "[:]" + } + } + + diff := result_symbol.pointers - arg_symbol.pointers + if diff > 0 { + suffix = repeat("^", diff, context.temp_allocator) + } + if diff < 0 { + prefix = "&" + } + + if completion_type == .Identifier { + item.insertText = fmt.tprint(prefix, item.label, suffix, sep = "") + } else if completion_type == .Selector { + item.insertText = fmt.tprint(item.label, suffix, sep = "") + if prefix != "" { + if range, ok := get_range_from_selection_start_to_dot(position_context); ok { + prefix_edit := TextEdit { + range = {start = range.start, end = range.start}, + newText = "&", + } + + additionalTextEdits := make([]TextEdit, 1, context.temp_allocator) + additionalTextEdits[0] = prefix_edit + item.additionalTextEdits = additionalTextEdits + } + } + } +} + get_completion_details :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { #partial switch v in symbol.value { case SymbolProcedureValue: @@ -556,6 +706,7 @@ get_selector_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, results: ^[dynamic]CompletionResult, + config: ^common.Config, ) -> bool { ast_context.current_package = ast_context.document_package @@ -601,7 +752,7 @@ get_selector_completion :: proc( } } - if common.config.enable_fake_method { + if config.enable_fake_method { append_method_completion(ast_context, selector, position_context, results, receiver) } @@ -770,7 +921,7 @@ get_selector_completion :: proc( completion_item = CompletionItem { label = fmt.tprintf(".%s", name), kind = .EnumMember, - detail = fmt.tprintf("%s.%s", receiver, name), + detail = fmt.tprintf("%s.%s", selector.name, name), additionalTextEdits = remove_edit, }, }, @@ -1394,6 +1545,7 @@ get_identifier_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, results: ^[dynamic]CompletionResult, + config: ^common.Config, ) -> bool { lookup_name := "" is_incomplete := true @@ -1428,21 +1580,24 @@ get_identifier_completion :: proc( matcher := common.make_fuzzy_matcher(lookup_name) + if position_context.call != nil { - if call_symbol, ok := resolve_type_expression(ast_context, position_context.call); ok { - if value, ok := call_symbol.value.(SymbolProcedureValue); ok { - for arg in value.orig_arg_types { - // For now we just add params with default values, could add everything we more logic in the future - if arg.default_value != nil { - for name in arg.names { - if ident, ok := name.derived.(^ast.Ident); ok { - if symbol, ok := resolve_type_expression(ast_context, arg.default_value); ok { - if score, ok := common.fuzzy_match(matcher, ident.name); ok == 1 { - symbol.type_name = symbol.name - symbol.type_pkg = symbol.pkg - symbol.name = clean_ident(ident.name) - symbol.type = .Field - append(results, CompletionResult{score = score * 1.1, symbol = symbol}) + if call, ok := position_context.call.derived.(^ast.Call_Expr); ok { + if call_symbol, ok := resolve_type_expression(ast_context, call.expr); ok { + if value, ok := call_symbol.value.(SymbolProcedureValue); ok { + for arg in value.orig_arg_types { + // For now we just add params with default values, could add everything we more logic in the future + if arg.default_value != nil { + for name in arg.names { + if ident, ok := name.derived.(^ast.Ident); ok { + if symbol, ok := resolve_type_expression(ast_context, arg.default_value); ok { + if score, ok := common.fuzzy_match(matcher, ident.name); ok == 1 { + symbol.type_name = symbol.name + symbol.type_pkg = symbol.pkg + symbol.name = clean_ident(ident.name) + symbol.type = .Field + append(results, CompletionResult{score = score * 1.1, symbol = symbol}) + } } } } @@ -1542,7 +1697,7 @@ get_identifier_completion :: proc( } } - if common.config.enable_snippets { + if config.enable_snippets { for k, v in snippets { if score, ok := common.fuzzy_match(matcher, k); ok == 1 { symbol := Symbol { @@ -1560,6 +1715,7 @@ get_package_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, results: ^[dynamic]CompletionResult, + config: ^common.Config, ) -> bool { is_incomplete := false @@ -1584,13 +1740,13 @@ get_package_completion :: proc( if colon_index + 1 < len(without_quotes) { absolute_path = filepath.join( elems = { - common.config.collections[c], + config.collections[c], filepath.dir(without_quotes[colon_index + 1:], context.temp_allocator), }, allocator = context.temp_allocator, ) } else { - absolute_path = common.config.collections[c] + absolute_path = config.collections[c] } } else { import_file_dir := filepath.dir(position_context.import_stmt.pos.file, context.temp_allocator) @@ -1600,7 +1756,7 @@ get_package_completion :: proc( if !strings.contains(position_context.import_stmt.fullpath, "/") && !strings.contains(position_context.import_stmt.fullpath, ":") { - for key, _ in common.config.collections { + for key, _ in config.collections { item := CompletionItem { detail = "collection", label = key, @@ -1762,9 +1918,10 @@ append_non_imported_packages :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, items: ^[dynamic]CompletionItem, + config: ^common.Config, ) { // Keep these as is for now with the completion items as they are a special case - if !common.config.enable_auto_import { + if !config.enable_auto_import { return } @@ -1774,7 +1931,7 @@ append_non_imported_packages :: proc( continue } for pkg in pkgs { - fullpath := path.join({common.config.collections[collection], pkg}) + fullpath := path.join({config.collections[collection], pkg}) found := false for doc_pkg in ast_context.imports { diff --git a/src/server/documentation.odin b/src/server/documentation.odin index 5615e6c..285119d 100644 --- a/src/server/documentation.odin +++ b/src/server/documentation.odin @@ -820,7 +820,9 @@ write_symbol_name :: proc(sb: ^strings.Builder, symbol: Symbol) { fmt.sbprintf(sb, "%v: package", symbol.name) return } - if pkg != "" && pkg != "$builtin" { + if symbol.parent_name != "" { + fmt.sbprintf(sb, "%v.", symbol.parent_name) + } else if pkg != "" && pkg != "$builtin" { fmt.sbprintf(sb, "%v.", pkg) } strings.write_string(sb, symbol.name) diff --git a/src/server/hover.odin b/src/server/hover.odin index d0eb0f3..844c64a 100644 --- a/src/server/hover.odin +++ b/src/server/hover.odin @@ -466,7 +466,7 @@ get_soa_field_hover :: proc( } if symbol, ok := resolve_soa_selector_field(ast_context, selector, expr, size, field); ok { if selector.name != "" { - symbol.pkg = selector.name + symbol.parent_name = selector.name } symbol.name = field build_documentation(ast_context, &symbol, false) diff --git a/src/server/requests.odin b/src/server/requests.odin index 0572a33..c564a31 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -365,6 +365,8 @@ read_ols_initialize_options :: proc(config: ^common.Config, ols_config: OlsConfi ols_config.enable_procedure_context.(bool) or_else config.enable_procedure_context config.enable_snippets = ols_config.enable_snippets.(bool) or_else config.enable_snippets config.enable_references = ols_config.enable_references.(bool) or_else config.enable_references + config.enable_completion_matching = + ols_config.enable_completion_matching.(bool) or_else config.enable_completion_matching config.verbose = ols_config.verbose.(bool) or_else config.verbose config.file_log = ols_config.file_log.(bool) or_else config.file_log @@ -612,6 +614,7 @@ request_initialize :: proc( config.enable_procedure_context = false config.enable_snippets = false config.enable_references = true + config.enable_completion_matching = true config.verbose = false config.file_log = false config.odin_command = "" @@ -901,7 +904,7 @@ request_completion :: proc( } list: CompletionList - list, ok = get_completion_list(document, completition_params.position, completition_params.context_) + list, ok = get_completion_list(document, completition_params.position, completition_params.context_, config) if !ok { return .InternalError diff --git a/src/server/symbol.odin b/src/server/symbol.odin index 3e97fba..e87cb70 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -204,19 +204,20 @@ SymbolFlag :: enum { SymbolFlags :: bit_set[SymbolFlag] Symbol :: struct { - range: common.Range, //the range of the symbol in the file - uri: string, //uri of the file the symbol resides - pkg: string, //absolute directory path where the symbol resides - name: string, //name of the symbol - doc: string, - comment: string, - signature: string, //type signature - type: SymbolType, - type_pkg: string, - type_name: string, - value: SymbolValue, - pointers: int, //how many `^` are applied to the symbol - flags: SymbolFlags, + range: common.Range, //the range of the symbol in the file + uri: string, //uri of the file the symbol resides + pkg: string, //absolute directory path where the symbol resides + name: string, //name of the symbol + doc: string, + comment: string, + signature: string, //type signature + type: SymbolType, + parent_name: string, // When symbol is a field, this is the name of the parent symbol it is a field of + type_pkg: string, + type_name: string, + value: SymbolValue, + pointers: int, //how many `^` are applied to the symbol + flags: SymbolFlags, } SymbolType :: enum { @@ -846,8 +847,8 @@ construct_struct_field_symbol :: proc(symbol: ^Symbol, parent_name: string, valu symbol.type_pkg = symbol.pkg symbol.type_name = symbol.name symbol.name = value.names[index] - symbol.pkg = parent_name symbol.type = .Field + symbol.parent_name = parent_name symbol.doc = get_doc(value.types[index], value.docs[index], context.temp_allocator) symbol.comment = get_comment(value.comments[index]) } @@ -859,7 +860,7 @@ construct_bit_field_field_symbol :: proc( index: int, ) { symbol.name = value.names[index] - symbol.pkg = parent_name + symbol.parent_name = parent_name symbol.type = .Field symbol.doc = get_doc(value.types[index], value.docs[index], context.temp_allocator) symbol.comment = get_comment(value.comments[index]) diff --git a/src/server/types.odin b/src/server/types.odin index 0eda132..4795f11 100644 --- a/src/server/types.odin +++ b/src/server/types.odin @@ -417,6 +417,7 @@ OlsConfig :: struct { enable_procedure_snippet: Maybe(bool), enable_checker_only_saved: Maybe(bool), enable_auto_import: Maybe(bool), + enable_completion_matching: Maybe(bool), disable_parser_errors: Maybe(bool), verbose: Maybe(bool), file_log: Maybe(bool), |