aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBradley Lewis <22850972+BradLewis@users.noreply.github.com>2025-08-26 07:56:45 -0400
committerGitHub <noreply@github.com>2025-08-26 07:56:45 -0400
commit2d2284560741f8d6b834ffd0b6c5a34d1ad3b77c (patch)
tree44ff5e0215aff1b24d7b1a27e03fafc448b02c36 /src
parent4703f8a761bfe70946a3a84db4490a5e66ab9e4c (diff)
parent656fbf87d5ae541e409dcbd0906262e3d8f0d881 (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')
-rw-r--r--src/common/config.odin1
-rw-r--r--src/server/analysis.odin13
-rw-r--r--src/server/completion.odin237
-rw-r--r--src/server/documentation.odin4
-rw-r--r--src/server/hover.odin2
-rw-r--r--src/server/requests.odin5
-rw-r--r--src/server/symbol.odin31
-rw-r--r--src/server/types.odin1
-rw-r--r--src/testing/testing.odin53
9 files changed, 279 insertions, 68 deletions
diff --git a/src/common/config.odin b/src/common/config.odin
index 103004a..e45e2b6 100644
--- a/src/common/config.odin
+++ b/src/common/config.odin
@@ -34,6 +34,7 @@ Config :: struct {
enable_procedure_snippet: bool,
enable_checker_only_saved: bool,
enable_auto_import: bool,
+ enable_completion_matching: bool,
disable_parser_errors: bool,
thread_count: int,
file_log: bool,
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),
diff --git a/src/testing/testing.odin b/src/testing/testing.odin
index e03e8b5..1b40c6b 100644
--- a/src/testing/testing.odin
+++ b/src/testing/testing.odin
@@ -174,7 +174,7 @@ expect_completion_labels :: proc(t: ^testing.T, src: ^Source, trigger_character:
triggerCharacter = trigger_character,
}
- completion_list, ok := server.get_completion_list(src.document, src.position, completion_context)
+ completion_list, ok := server.get_completion_list(src.document, src.position, completion_context, &src.config)
if !ok {
log.error("Failed get_completion_list")
@@ -202,7 +202,8 @@ expect_completion_labels :: proc(t: ^testing.T, src: ^Source, trigger_character:
}
expect_completion_docs :: proc(
- t: ^testing.T, src: ^Source,
+ t: ^testing.T,
+ src: ^Source,
trigger_character: string,
expect_details: []string,
expect_excluded: []string = nil,
@@ -226,7 +227,7 @@ expect_completion_docs :: proc(
triggerCharacter = trigger_character,
}
- completion_list, ok := server.get_completion_list(src.document, src.position, completion_context)
+ completion_list, ok := server.get_completion_list(src.document, src.position, completion_context, &src.config)
if !ok {
log.error("Failed get_completion_list")
@@ -261,6 +262,49 @@ expect_completion_docs :: proc(
}
}
+expect_completion_insert_text :: proc(
+ t: ^testing.T,
+ src: ^Source,
+ trigger_character: string,
+ expect_inserts: []string,
+) {
+ setup(src)
+ defer teardown(src)
+
+ completion_context := server.CompletionContext {
+ triggerCharacter = trigger_character,
+ }
+
+ completion_list, ok := server.get_completion_list(src.document, src.position, completion_context, &src.config)
+
+ if !ok {
+ log.error("Failed get_completion_list")
+ }
+
+ if len(expect_inserts) == 0 && len(completion_list.items) > 0 {
+ log.errorf("Expected empty completion inserts, but received %v", completion_list.items)
+ }
+
+ flags := make([]int, len(expect_inserts), context.temp_allocator)
+
+ for expect_insert, i in expect_inserts {
+ for completion, j in completion_list.items {
+ if insert_text, ok := completion.insertText.(string); ok {
+ if expect_insert == insert_text {
+ flags[i] += 1
+ continue
+ }
+ }
+ }
+ }
+
+ for flag, i in flags {
+ if flag != 1 {
+ log.errorf("Expected completion insert %v, but received %v", expect_inserts[i], completion_list.items)
+ }
+ }
+}
+
expect_hover :: proc(t: ^testing.T, src: ^Source, expect_hover_string: string) {
setup(src)
defer teardown(src)
@@ -474,7 +518,8 @@ expect_inlay_hints :: proc(t: ^testing.T, src: ^Source, expected_hints: []server
return
}
- testing.expectf(t,
+ testing.expectf(
+ t,
len(expected_hints) == len(hints),
"\nExpected %d inlay hints, but received %d",
len(expected_hints),