diff options
Diffstat (limited to 'src/server/completion.odin')
| -rw-r--r-- | src/server/completion.odin | 547 |
1 files changed, 287 insertions, 260 deletions
diff --git a/src/server/completion.odin b/src/server/completion.odin index 8a0f371..31d75ca 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -410,6 +410,10 @@ score_completion_item :: proc(item: ^CompletionResult, curr_pkg: string) { if item.symbol.pkg == curr_pkg { item.score += 1 } + + if strings.has_prefix(item.symbol.name, "_") { + item.score -= 0.5 + } } @(private = "file") @@ -560,80 +564,25 @@ get_attribute_completion :: proc( } -DIRECTIVE_NAME_LIST :: []string { - // basic directives - "file", - "directory", - "line", - "procedure", - "caller_location", - "reverse", - // call directives - "location", - "caller_expression", - "exists", - "load", - "load_directory", - "load_hash", - "hash", - "assert", - "panic", - "defined", - "config", - /* type helper */ - "type", - /* struct type */ - "packed", - "raw_union", - "align", - "all_or_none", - /* union type */ - "no_nil", - "shared_nil", - /* array type */ - "simd", - "soa", - "sparse", - /* ptr type */ - "relative", - /* field flags */ - "no_alias", - "c_vararg", - "const", - "any_int", - "subtype", - "by_ptr", - "no_broadcast", - "no_capture", - /* swich flags */ - "partial", - /* block flags */ - "bounds_check", - "no_bounds_check", - "type_assert", - "no_type_assert", - /* proc inlining */ - "force_inline", - "force_no_inline", - /* return values flags */ - "optional_ok", - "optional_allocator_error", -} - completion_items_directives: []CompletionResult @(init) _init_completion_items_directives :: proc "contextless" () { context = runtime.default_context() - completion_items_directives = slice.mapper(DIRECTIVE_NAME_LIST, proc(name: string) -> CompletionResult { - return CompletionResult { - completion_item = CompletionItem { - detail = strings.concatenate({"#", name}) or_else name, - label = name, - kind = .Constant, + directives := make([dynamic]CompletionResult, 0, len(directive_docs), allocator = context.allocator) + for name, doc in directive_docs { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + &directives, + CompletionResult { + completion_item = CompletionItem{label = name, kind = .Constant, documentation = documentation}, }, - } - }) + ) + } + completion_items_directives = directives[:] } get_directive_completion :: proc( @@ -785,7 +734,10 @@ get_selector_completion :: proc( selector.type != .Field && selector.type != .Package && selector.type != .Enum && - selector.type != .Function { + selector.type != .Function && + (selector.type == .Struct && .Variable not_in selector.flags) { + // We don't want completions for struct types, but we do want completions for constant variables. + // See tests `ast_global_non_mutable_completion` vs `ast_completion_global_selector_from_local_scope` return is_incomplete } @@ -793,7 +745,7 @@ get_selector_completion :: proc( field: string - if position_context.field != nil { + if position_context.field != nil && position_in_node(position_context.field, position_context.position) { #partial switch v in position_context.field.derived { case ^ast.Ident: field = v.name @@ -820,99 +772,7 @@ get_selector_completion :: proc( case SymbolFixedArrayValue: is_incomplete = true append_magic_array_like_completion(position_context, selector, results) - - containsColor := 1 - containsCoord := 1 - - expr_len := 0 - - if v.len != nil { - if basic, ok := v.len.derived.(^ast.Basic_Lit); ok { - if expr_len, ok = strconv.parse_int(basic.tok.text); !ok { - expr_len = 0 - } - } - } - - if field != "" { - for i := 0; i < len(field); i += 1 { - c := field[i] - if _, ok := swizzle_color_map[c]; ok { - containsColor += 1 - } else if _, ok := swizzle_coord_map[c]; ok { - containsCoord += 1 - } else { - return is_incomplete - } - } - } - - if containsColor == 1 && containsCoord == 1 { - save := expr_len - for k in swizzle_color_components { - if expr_len <= 0 { - break - } - - expr_len -= 1 - - item := CompletionItem { - label = fmt.tprintf("%v%v", field, k), - kind = .Property, - detail = fmt.tprintf("%v%v: %v", field, k, node_to_string(v.expr)), - } - append(results, CompletionResult{completion_item = item}) - } - - expr_len = save - - for k in swizzle_coord_components { - if expr_len <= 0 { - break - } - - expr_len -= 1 - - item := CompletionItem { - label = fmt.tprintf("%v%v", field, k), - kind = .Property, - detail = fmt.tprintf("%v%v: %v", field, k, node_to_string(v.expr)), - } - append(results, CompletionResult{completion_item = item}) - } - } - - if containsColor > 1 { - for k in swizzle_color_components { - if expr_len <= 0 { - break - } - - expr_len -= 1 - - item := CompletionItem { - label = fmt.tprintf("%v%v", field, k), - kind = .Property, - detail = fmt.tprintf("%v%v: [%v]%v", field, k, containsColor, node_to_string(v.expr)), - } - append(results, CompletionResult{completion_item = item}) - } - } else if containsCoord > 1 { - for k in swizzle_coord_components { - if expr_len <= 0 { - break - } - - expr_len -= 1 - - item := CompletionItem { - label = fmt.tprintf("%v%v", field, k), - kind = .Property, - detail = fmt.tprintf("%v%v: [%v]%v", field, k, containsCoord, node_to_string(v.expr)), - } - append(results, CompletionResult{completion_item = item}) - } - } + add_fixed_array_selector_completions(v, field, results) add_soa_field_completion(ast_context, selector, v.expr, v.len, results, selector.name) case SymbolUnionValue: is_incomplete = false @@ -975,6 +835,9 @@ get_selector_completion :: proc( remove_edit, rok := create_remove_edit(position_context, true) if !rok {break} + // Sublime Text will remove the original `.` for some reason + is_sublime := config.client_name == "Sublime Text LSP" + for name in enumv.names { append( results, @@ -995,7 +858,7 @@ get_selector_completion :: proc( label = fmt.tprintf(".%s in", name), kind = .EnumMember, detail = in_text, - insertText = in_text[1:], + insertText = is_sublime ? in_text : in_text[1:], additionalTextEdits = remove_edit, }, }, @@ -1008,7 +871,7 @@ get_selector_completion :: proc( label = fmt.tprintf(".%s not_in", name), kind = .EnumMember, detail = not_in_text, - insertText = not_in_text[1:], + insertText = is_sublime ? not_in_text : not_in_text[1:], additionalTextEdits = remove_edit, }, }, @@ -1019,6 +882,13 @@ get_selector_completion :: proc( is_incomplete = false for name, i in v.names { if name == "_" { + if is_struct_field_using(v, i) { + if symbol, ok := resolve_type_expression(ast_context, v.types[i]); ok { + if value, ok := symbol.value.(SymbolFixedArrayValue); ok { + add_fixed_array_selector_completions(value, field, results) + } + } + } continue } @@ -1039,6 +909,12 @@ get_selector_completion :: proc( construct_struct_field_symbol(&symbol, selector.name, v, i) append(results, CompletionResult{symbol = symbol}) + + if is_struct_field_using(v, i) { + if value, ok := symbol.value.(SymbolFixedArrayValue); ok { + add_fixed_array_selector_completions(value, field, results) + } + } } else { //just give some generic symbol with name. item := CompletionItem { @@ -1090,9 +966,17 @@ get_selector_completion :: proc( case SymbolPackageValue: is_incomplete = true - pkg := selector.pkg + packages := make([dynamic]string, context.temp_allocator) + if is_builtin_pkg(selector.pkg) { + append(&packages, "$builtin") + for built in indexer.builtin_packages { + append(&packages, built) + } + } else { + append(&packages, selector.pkg) + } - if searched, ok := fuzzy_search(field, {pkg}, ast_context.fullpath); ok { + if searched, ok := fuzzy_search(field, packages[:], ast_context.fullpath); ok { for search in searched { symbol := search.symbol @@ -1132,6 +1016,105 @@ get_selector_completion :: proc( return is_incomplete } +add_fixed_array_selector_completions :: proc( + v: SymbolFixedArrayValue, + field: string, + results: ^[dynamic]CompletionResult, +) { + containsColor := 1 + containsCoord := 1 + + expr_len := 0 + + if v.len != nil { + if basic, ok := v.len.derived.(^ast.Basic_Lit); ok { + if expr_len, ok = strconv.parse_int(basic.tok.text); !ok { + expr_len = 0 + } + } + } + + if field != "" { + for i := 0; i < len(field); i += 1 { + c := field[i] + if _, ok := swizzle_color_map[c]; ok { + containsColor += 1 + } else if _, ok := swizzle_coord_map[c]; ok { + containsCoord += 1 + } else { + return + } + } + } + + if containsColor == 1 && containsCoord == 1 { + save := expr_len + for k in swizzle_color_components { + if expr_len <= 0 { + break + } + + expr_len -= 1 + + item := CompletionItem { + label = fmt.tprintf("%v%v", field, k), + kind = .Property, + detail = fmt.tprintf("%v%v: %v", field, k, node_to_string(v.expr)), + } + append(results, CompletionResult{completion_item = item}) + } + + expr_len = save + + for k in swizzle_coord_components { + if expr_len <= 0 { + break + } + + expr_len -= 1 + + item := CompletionItem { + label = fmt.tprintf("%v%v", field, k), + kind = .Property, + detail = fmt.tprintf("%v%v: %v", field, k, node_to_string(v.expr)), + } + append(results, CompletionResult{completion_item = item}) + } + } + + if containsColor > 1 { + for k in swizzle_color_components { + if expr_len <= 0 { + break + } + + expr_len -= 1 + + item := CompletionItem { + label = fmt.tprintf("%v%v", field, k), + kind = .Property, + detail = fmt.tprintf("%v%v: [%v]%v", field, k, containsColor, node_to_string(v.expr)), + } + append(results, CompletionResult{completion_item = item}) + } + } else if containsCoord > 1 { + for k in swizzle_coord_components { + if expr_len <= 0 { + break + } + + expr_len -= 1 + + item := CompletionItem { + label = fmt.tprintf("%v%v", field, k), + kind = .Property, + detail = fmt.tprintf("%v%v: [%v]%v", field, k, containsCoord, node_to_string(v.expr)), + } + append(results, CompletionResult{completion_item = item}) + } + } +} + get_implicit_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, @@ -1254,35 +1237,6 @@ get_implicit_completion :: proc( } } - if position_context.assign != nil && - position_context.assign.lhs != nil && - len(position_context.assign.lhs) == 1 && - is_bitset_assignment_operator(position_context.assign.op.text) { - //bitsets - if symbol, ok := resolve_type_expression(ast_context, position_context.assign.lhs[0]); ok { - set_ast_package_set_scoped(ast_context, symbol.pkg) - if value, ok := unwrap_bitset(ast_context, symbol); ok { - for name in value.names { - if position_context.comp_lit != nil && field_exists_in_comp_lit(position_context.comp_lit, name) { - continue - } - - item := CompletionItem { - label = name, - kind = .EnumMember, - detail = name, - } - - append(results, CompletionResult{completion_item = item}) - } - - return is_incomplete - } - } - - reset_ast_context(ast_context) - } - if position_context.comp_lit != nil && position_context.parent_binary != nil && is_bitset_binary_operator(position_context.binary.op.text) { @@ -1380,53 +1334,6 @@ get_implicit_completion :: proc( } } - if position_context.assign != nil && position_context.assign.rhs != nil && position_context.assign.lhs != nil { - rhs_index: int - - for elem in position_context.assign.rhs { - if position_in_node(elem, position_context.position) { - break - } else { - //procedures are the only types that can return more than one value - if symbol, ok := resolve_type_expression(ast_context, elem); ok { - if procedure, ok := symbol.value.(SymbolProcedureValue); ok { - if procedure.return_types == nil { - return is_incomplete - } - - rhs_index += len(procedure.return_types) - } else { - rhs_index += 1 - } - } - } - } - - if len(position_context.assign.lhs) > rhs_index { - if enum_value, unwrapped_super_enum, ok := unwrap_enum( - ast_context, - position_context.assign.lhs[rhs_index], - ); ok { - for name in enum_value.names { - item := CompletionItem { - label = name, - kind = .EnumMember, - detail = name, - } - if unwrapped_super_enum { - add_implicit_selector_remove_edit(position_context, &item, name, enum_value.names) - } - - append(results, CompletionResult{completion_item = item}) - } - - return is_incomplete - } - } - - reset_ast_context(ast_context) - } - if position_context.returns != nil && position_context.function != nil { return_index: int @@ -1527,6 +1434,11 @@ get_implicit_completion :: proc( if position_context.call != nil { if call, ok := position_context.call.derived.(^ast.Call_Expr); ok { parameter_index, parameter_ok := find_position_in_call_param(position_context, call^) + old := ast_context.resolve_specific_overload + ast_context.resolve_specific_overload = true + defer { + ast_context.resolve_specific_overload = old + } if symbol, ok := resolve_type_expression(ast_context, call.expr); ok && parameter_ok { set_ast_package_set_scoped(ast_context, symbol.pkg) @@ -1559,6 +1471,8 @@ get_implicit_completion :: proc( type = comp_lit.type } else if selector, ok := arg_type.default_value.derived.(^ast.Selector_Expr); ok { type = selector.expr + } else { + type = arg_type.default_value } } @@ -1599,6 +1513,83 @@ get_implicit_completion :: proc( reset_ast_context(ast_context) } + + if position_context.assign != nil && + position_context.assign.lhs != nil && + len(position_context.assign.lhs) == 1 && + is_bitset_assignment_operator(position_context.assign.op.text) { + //bitsets + if symbol, ok := resolve_type_expression(ast_context, position_context.assign.lhs[0]); ok { + set_ast_package_set_scoped(ast_context, symbol.pkg) + if value, ok := unwrap_bitset(ast_context, symbol); ok { + for name in value.names { + if position_context.comp_lit != nil && field_exists_in_comp_lit(position_context.comp_lit, name) { + continue + } + + item := CompletionItem { + label = name, + kind = .EnumMember, + detail = name, + } + + append(results, CompletionResult{completion_item = item}) + } + + return is_incomplete + } + } + + reset_ast_context(ast_context) + } + + if position_context.assign != nil && position_context.assign.rhs != nil && position_context.assign.lhs != nil { + rhs_index: int + + for elem in position_context.assign.rhs { + if position_in_node(elem, position_context.position) { + break + } else { + //procedures are the only types that can return more than one value + if symbol, ok := resolve_type_expression(ast_context, elem); ok { + if procedure, ok := symbol.value.(SymbolProcedureValue); ok { + if procedure.return_types == nil { + return is_incomplete + } + + rhs_index += len(procedure.return_types) + } else { + rhs_index += 1 + } + } + } + } + + if len(position_context.assign.lhs) > rhs_index { + if enum_value, unwrapped_super_enum, ok := unwrap_enum( + ast_context, + position_context.assign.lhs[rhs_index], + ); ok { + for name in enum_value.names { + item := CompletionItem { + label = name, + kind = .EnumMember, + detail = name, + } + if unwrapped_super_enum { + add_implicit_selector_remove_edit(position_context, &item, name, enum_value.names) + } + + append(results, CompletionResult{completion_item = item}) + } + + return is_incomplete + } + } + + reset_ast_context(ast_context) + } + return is_incomplete } @@ -1821,6 +1812,12 @@ get_identifier_completion :: proc( symbol := Symbol { name = pkg.base, type = .Package, + pkg = pkg.name, + value = SymbolPackageValue{}, + } + try_build_package(symbol.pkg) + if resolved, ok := resolve_symbol_return(ast_context, symbol); ok { + symbol = resolved } if score, ok := common.fuzzy_match(matcher, symbol.name); ok == 1 { @@ -1881,7 +1878,7 @@ get_package_completion :: proc( c := without_quotes[0:colon_index] if colon_index + 1 < len(without_quotes) { - absolute_path = filepath.join( + absolute_path, _ = filepath.join( elems = { config.collections[c], filepath.dir(without_quotes[colon_index + 1:], context.temp_allocator), @@ -1894,7 +1891,7 @@ get_package_completion :: proc( } else { import_file_dir := filepath.dir(position_context.import_stmt.pos.file, context.temp_allocator) import_dir := filepath.dir(without_quotes, context.temp_allocator) - absolute_path = filepath.join(elems = {import_file_dir, import_dir}, allocator = context.temp_allocator) + absolute_path, _ = filepath.join(elems = {import_file_dir, import_dir}, allocator = context.temp_allocator) } if !strings.contains(position_context.import_stmt.fullpath, "/") && @@ -1944,7 +1941,7 @@ search_for_packages :: proc(fullpath: string) -> []string { if files, err := os.read_dir(fh, 0, context.temp_allocator); err == 0 { for file in files { - if file.is_dir { + if file.type == .Directory { append(&packages, file.fullpath) } } @@ -1960,12 +1957,52 @@ get_used_switch_name :: proc(node: ^ast.Expr) -> (string, bool) { return n.name, true case ^ast.Selector_Expr: return n.field.name, true + case ^ast.Implicit_Selector_Expr: + return n.field.name, true case ^ast.Pointer_Type: return get_used_switch_name(n.elem) } return "", false } +//handles pointers / packages +get_qualified_union_case_name :: proc( + symbol: ^Symbol, + ast_context: ^AstContext, + position_context: ^DocumentPositionContext, +) -> string { + sb := strings.builder_make(context.temp_allocator) + pointer_prefix := repeat("^", symbol.pointers, ast_context.allocator) + strings.write_string(&sb, pointer_prefix) + if symbol.pkg != ast_context.document_package { + strings.write_string(&sb, get_symbol_pkg_name(ast_context, symbol)) + strings.write_string(&sb, ".") + } + strings.write_string(&sb, symbol.name) + #partial switch v in symbol.value { + case SymbolUnionValue: + write_poly_names(&sb, v.poly_names) + case SymbolStructValue: + write_poly_names(&sb, v.poly_names) + } + + return strings.to_string(sb) +} + +write_poly_names :: proc(sb: ^strings.Builder, poly_names: []string) { + if len(poly_names) > 0 { + strings.write_string(sb, "(") + for name, i in poly_names { + strings.write_string(sb, name) + if i != len(poly_names) - 1 { + strings.write_string(sb, ", ") + } + } + strings.write_string(sb, ")") + } +} + + get_type_switch_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, @@ -1994,7 +2031,8 @@ get_type_switch_completion :: proc( if union_value, ok := unwrap_union(ast_context, assign.rhs[0]); ok { for type, i in union_value.types { if symbol, ok := resolve_type_expression(ast_context, union_value.types[i]); ok { - + //TODO: using symbol.name is wrong for anonymous enums and structs, where the name field is "enum" or "struct" respectively but we want to use the full signature + //we also can't use the signature all the time because type aliases need to use specifically the alias name here and not the signature name := symbol.name if _, ok := used_unions[name]; ok { continue @@ -2003,19 +2041,8 @@ get_type_switch_completion :: proc( item := CompletionItem { kind = .EnumMember, } - - if symbol.pkg == ast_context.document_package { - item.label = fmt.aprintf("%v%v", repeat("^", symbol.pointers, context.temp_allocator), name) - item.detail = item.label - } else { - item.label = fmt.aprintf( - "%v%v.%v", - repeat("^", symbol.pointers, context.temp_allocator), - get_symbol_pkg_name(ast_context, &symbol), - name, - ) - item.detail = item.label - } + item.label = get_qualified_union_case_name(&symbol, ast_context, position_context) + item.detail = item.label if position_context.implicit_selector_expr != nil { if remove_edit, ok := create_implicit_selector_remove_edit(position_context); ok { item.additionalTextEdits = remove_edit |