diff options
| author | Damian Tarnawski <gthetarnav@gmail.com> | 2025-09-11 23:42:40 +0200 |
|---|---|---|
| committer | Damian Tarnawski <gthetarnav@gmail.com> | 2025-09-11 23:42:40 +0200 |
| commit | ee09270191cf0d058951d075b74aa4ef439d713b (patch) | |
| tree | 63e6cd43b241bf305554d2632efbdee33bc91618 /src | |
| parent | 551a04c3fb0862dbdcc7085c5be6b976ebdbdb8c (diff) | |
Improve inlay hint algorithm and add more tests (fixes #920)
Diffstat (limited to 'src')
| -rw-r--r-- | src/server/inlay_hints.odin | 192 |
1 files changed, 110 insertions, 82 deletions
diff --git a/src/server/inlay_hints.odin b/src/server/inlay_hints.odin index 1f21163..307b19b 100644 --- a/src/server/inlay_hints.odin +++ b/src/server/inlay_hints.odin @@ -1,5 +1,6 @@ package server +import "core:slice" import "core:fmt" import "core:log" import "core:odin/ast" @@ -47,120 +48,147 @@ get_inlay_hints :: proc( ast.walk(&visitor, decl) } + expr_name :: proc (node: ^ast.Node) -> (name: string, ok: bool) { + #partial switch v in node.derived { + case ^ast.Ident: return v.name, true + case ^ast.Poly_Type: return expr_name(v.type) + case: return + } + } + visit_call :: proc ( call: ^ast.Call_Expr, data: ^Visitor_Data, ) -> (ok: bool) { - is_ellipsis := false - has_added_default := false - selector, is_selector_call := call.expr.derived.(^ast.Selector_Expr) is_selector_call &&= selector.op.kind == .Arrow_Right - end_pos := common.token_pos_to_position(call.close, string(data.document.text)) + src := string(data.document.text) + end_pos := common.token_pos_to_position(call.close, src) + + symbol_and_node := data.symbols[uintptr(call.expr)] or_return + proc_symbol := symbol_and_node.symbol.value.(SymbolProcedureValue) or_return - symbol_and_node := data.symbols[cast(uintptr)call.expr] or_return - symbol_call := symbol_and_node.symbol.value.(SymbolProcedureValue) or_return + param_idx := 1 if is_selector_call else 0 + label_idx := 0 + arg_idx := 0 - positional_arg_idx := 0 + // Positional arguments + for ; arg_idx < len(call.args); arg_idx += 1 { + arg := call.args[arg_idx] - expr_name :: proc (node: ^ast.Node) -> (name: string, ok: bool) { - #partial switch v in node.derived { - case ^ast.Ident: return v.name, true - case ^ast.Poly_Type: return expr_name(v.type) - case: return + // provided as named + if _, is_field := arg.derived.(^ast.Field_Value); is_field do break + + param := slice.get(proc_symbol.arg_types, param_idx) or_return + + // param is variadic + if param.type != nil { + if _, is_variadic := param.type.derived.(^ast.Ellipsis); is_variadic do break + } + + label := slice.get(param.names, label_idx) or_return + + label_name := expr_name(label) or_return + arg_name, arg_has_name := expr_name(arg) + + // Add param name hint (skip idents with same name as param) + if data.config.enable_inlay_hints_params && (!arg_has_name || arg_name != label_name) { + range := common.get_token_range(arg, string(data.document.text)) + hint_label := fmt.tprintf("%v = ", label_name) + append(&data.hints, InlayHint{range.start, .Parameter, hint_label}) + } + + label_idx += 1 + if label_idx >= len(param.names) { + param_idx += 1 + label_idx = 0 + if param_idx >= len(proc_symbol.arg_types) { + return // end of parameters + } } } - for arg, arg_type_idx in symbol_call.arg_types { - if arg_type_idx == 0 && is_selector_call { - continue + // Variadic arguments + variadic: { + param := slice.get(proc_symbol.arg_types, param_idx) or_return + + // param is variadic + if param.type == nil do break variadic + _ = param.type.derived.(^ast.Ellipsis) or_break variadic + + label := slice.get(param.names, 0) or_return + label_name := expr_name(label) or_return + + init_arg_idx := arg_idx + for arg_idx < len(call.args) { + + // provided as named + if _, is_field := call.args[arg_idx].derived.(^ast.Field_Value); is_field do break + + arg_idx += 1 } - for name, name_idx in arg.names { + // Add param name hint + if arg_idx > init_arg_idx && data.config.enable_inlay_hints_params { + // get range from first variadic arg + range := common.get_token_range(call.args[init_arg_idx], string(data.document.text)) + hint_label := fmt.tprintf("%v = ", label_name) + append(&data.hints, InlayHint{range.start, .Parameter, hint_label}) + } - arg_call_idx := arg_type_idx + name_idx - if is_selector_call do arg_call_idx -= 1 + param_idx += 1 + label_idx = 0 + if param_idx >= len(proc_symbol.arg_types) { + return // end of parameters + } + } - label := expr_name(name) or_return + // Named arguments + named: if data.config.enable_inlay_hints_default_params { - is_provided_named, is_provided_positional: bool - call_arg: ^ast.Expr + init_arg_idx := arg_idx + added_default_hint := false - for a, a_i in call.args[positional_arg_idx:] { - call_arg_idx := a_i + positional_arg_idx - // provided as named - if field_value, ok := a.derived.(^ast.Field_Value); ok { - ident := field_value.field.derived.(^ast.Ident) or_break - if ident.name == label { - is_provided_named = true - call_arg = a - } - break - } // provided as positional - else if arg_call_idx == call_arg_idx { - is_provided_positional = true - positional_arg_idx += 1 - call_arg = a - break - } - } + for ; param_idx < len(proc_symbol.arg_types); param_idx, label_idx = param_idx + 1, 0 { + param := slice.get(proc_symbol.arg_types, param_idx) or_return - if is_ellipsis || (!is_provided_named && !is_provided_positional) { - // This parameter is not provided, so it should use default value - if arg.default_value == nil { - return - } + label_loop: for ; label_idx < len(param.names); label_idx += 1 { + label := slice.get(param.names, label_idx) or_return + label_name := expr_name(label) or_return - if !data.config.enable_inlay_hints_default_params { - return - } + if param.default_value == nil do continue + + // check if was already provided + for arg in call.args[init_arg_idx:] { - value := node_to_string(arg.default_value) + field_value := arg.derived.(^ast.Field_Value) or_break named + ident := field_value.field.derived.(^ast.Ident) or_break named - needs_leading_comma := arg_call_idx > 0 + if ident.name == label_name { + continue label_loop + } + } - if !has_added_default && needs_leading_comma { - till_end := string(data.document.text[:call.close.offset]) - #reverse for ch in till_end { + needs_leading_comma := added_default_hint || param_idx > 0 || label_idx > 0 + if needs_leading_comma && !added_default_hint { + // check for existing trailing comma + #reverse for ch in string(data.document.text[:call.close.offset]) { switch ch { - case ' ', '\t', '\n': - continue - case ',': - needs_leading_comma = false + case ' ', '\t', '\n': continue + case ',': needs_leading_comma = false } break } } - hint := InlayHint { - kind = .Parameter, - label = fmt.tprintf("%s%v = %v", needs_leading_comma ? ", " : "", label, value), - position = end_pos, - } - append(&data.hints, hint) - - has_added_default = true - } else if data.config.enable_inlay_hints_params && is_provided_positional && !is_provided_named { - // This parameter is provided via positional argument, show parameter hint - - // if the arg name and param name are the same, don't add it. - call_arg_name, _ := expr_name(call_arg) - if call_arg_name != label { - range := common.get_token_range(call_arg, string(data.document.text)) - hint := InlayHint { - kind = .Parameter, - label = fmt.tprintf("%v = ", label), - position = range.start, - } - append(&data.hints, hint) - } - } + // Add default param hint + value := node_to_string(param.default_value) + hint_label := fmt.tprintf("%s%v = %v", needs_leading_comma ? ", " : "", label_name, value) + append(&data.hints, InlayHint{end_pos, .Parameter, hint_label}) - if arg.type != nil { - _, is_current_ellipsis := arg.type.derived.(^ast.Ellipsis) - is_ellipsis ||= is_current_ellipsis + added_default_hint = true } } } |