aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorBradley Lewis <22850972+BradLewis@users.noreply.github.com>2025-09-12 09:30:50 -0400
committerGitHub <noreply@github.com>2025-09-12 09:30:50 -0400
commit83cb4c059781eebe5bed2899815e416d3b4ff28a (patch)
tree5cc26be5a33c768c474382f6c8ac736b8f80256a /src/server
parentaa2e8f609ad3a48f1bf7570a7f34c01f89d53c5d (diff)
parentd57fe0576cc258f9c8ea7199f9e45d828de5346e (diff)
Merge pull request #996 from thetarnav/inlay-hints-fixes
Inlay hints fixes
Diffstat (limited to 'src/server')
-rw-r--r--src/server/inlay_hints.odin308
1 files changed, 186 insertions, 122 deletions
diff --git a/src/server/inlay_hints.odin b/src/server/inlay_hints.odin
index 6dc155d..c87ce3a 100644
--- a/src/server/inlay_hints.odin
+++ b/src/server/inlay_hints.odin
@@ -1,5 +1,6 @@
package server
+import "core:strings"
import "core:fmt"
import "core:log"
import "core:odin/ast"
@@ -14,169 +15,232 @@ get_inlay_hints :: proc(
[]InlayHint,
bool,
) {
- hints := make([dynamic]InlayHint, context.temp_allocator)
-
- ast_context := make_ast_context(
- document.ast,
- document.imports,
- document.package_name,
- document.uri.uri,
- document.fullpath,
- )
-
- Visit_Data :: struct {
- calls: [dynamic]^ast.Node,
+ Visitor_Data :: struct {
+ hints: [dynamic]InlayHint,
+ document: ^Document,
+ symbols: map[uintptr]SymbolAndNode,
+ config: ^common.Config,
}
- data := Visit_Data {
- calls = make([dynamic]^ast.Node, context.temp_allocator),
+ data := Visitor_Data{
+ hints = make([dynamic]InlayHint, context.temp_allocator),
+ document = document,
+ symbols = symbols,
+ config = config,
}
- visit :: proc(visitor: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor {
- if node == nil || visitor == nil {
- return nil
- }
-
- data := cast(^Visit_Data)visitor.data
+ visitor := ast.Visitor{
+ data = &data,
+ visit = proc(visitor: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor {
+ if node == nil || visitor == nil {
+ return nil
+ }
- if call, ok := node.derived.(^ast.Call_Expr); ok {
- append(&data.calls, node)
- }
+ if call, is_call := node.derived.(^ast.Call_Expr); is_call {
+ data := (^Visitor_Data)(visitor.data)
+ visit_call(call, data)
+ }
- return visitor
- }
-
- visitor := ast.Visitor {
- data = &data,
- visit = visit,
+ return visitor
+ },
}
for decl in document.ast.decls {
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
+ }
+ }
- loop: for node_call in &data.calls {
-
- is_ellipsis := false
- has_added_default := false
+ visit_call :: proc (
+ call: ^ast.Call_Expr,
+ data: ^Visitor_Data,
+ ) -> (ok: bool) {
- call := node_call.derived.(^ast.Call_Expr)
+ src := string(data.document.text)
+ end_pos := common.token_pos_to_position(call.close, src)
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(document.text))
-
- symbol_and_node := symbols[cast(uintptr)call.expr] or_continue
- symbol_call := symbol_and_node.symbol.value.(SymbolProcedureValue) or_continue
-
- positional_arg_idx := 0
-
- 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:
- ident := v.type.derived.(^ast.Ident) or_return
- return ident.name, true
- case:
- return
- }
- }
-
- for arg, arg_type_idx in symbol_call.arg_types {
- if arg_type_idx == 0 && is_selector_call {
- continue
- }
+ symbol_and_node := data.symbols[uintptr(call.expr)] or_return // could not resolve symbol
+ proc_symbol := symbol_and_node.symbol.value.(SymbolProcedureValue) or_return // not a procedure call, e.g. type cast
- for name, name_idx in arg.names {
+ param_idx := 1 if is_selector_call else 0
+ label_idx := 0
+ arg_idx := 0
- arg_call_idx := arg_type_idx + name_idx
- if is_selector_call do arg_call_idx -= 1
+ if param_idx >= len(proc_symbol.arg_types) do return true // no parameters
- label := expr_name(name) or_continue loop
+ // Positional arguments
+ positional: for ; arg_idx < len(call.args); arg_idx += 1 {
+ arg := call.args[arg_idx]
- is_provided_named, is_provided_positional: bool
- call_arg: ^ast.Expr
+ // for multi-return function call arguments
+ multi_return: {
+ arg_call := arg.derived.(^ast.Call_Expr) or_break multi_return
+ arg_symbol_and_node := data.symbols[uintptr(arg_call.expr)] or_break multi_return
+ arg_proc_symbol := arg_symbol_and_node.symbol.value.(SymbolProcedureValue) or_break multi_return
- 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
+ if len(arg_proc_symbol.return_types) <= 1 do break multi_return
+
+ hint_text_sb := strings.builder_make(context.temp_allocator)
+
+ // Collect parameter names for this multi-return call
+ for i in 0..<len(arg_proc_symbol.return_types) {
+ param := proc_symbol.arg_types[param_idx]
+ label := param.names[label_idx]
+
+ label_name, label_has_name := expr_name(label)
+ if data.config.enable_inlay_hints_params && label_has_name {
+ if i > 0 do strings.write_string(&hint_text_sb, ", ")
+ strings.write_string(&hint_text_sb, label_name)
+ }
+
+ // advance to next param
+ label_idx += 1
+ if label_idx >= len(param.names) {
+ param_idx += 1
+ label_idx = 0
+ if param_idx >= len(proc_symbol.arg_types) {
+ return true // end of parameters
}
- break
- } // provided as positional
- else if arg_call_idx == call_arg_idx {
- is_provided_positional = true
- positional_arg_idx += 1
- call_arg = a
- break
}
}
- 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 {
- continue loop
- }
+ // Add combined param name hint
+ if data.config.enable_inlay_hints_params && strings.builder_len(hint_text_sb) > 0 {
+ range := common.get_token_range(arg, src)
+ strings.write_string(&hint_text_sb, " = ")
+ hint_text := strings.to_string(hint_text_sb)
+ append(&data.hints, InlayHint{range.start, .Parameter, hint_text})
+ }
- if !config.enable_inlay_hints_default_params {
- continue loop
- }
+ continue positional
+ }
+
+ // provided as named
+ if _, is_field := arg.derived.(^ast.Field_Value); is_field do break
+
+ param := proc_symbol.arg_types[param_idx]
+
+ // param is variadic
+ if param.type != nil {
+ if _, is_variadic := param.type.derived.(^ast.Ellipsis); is_variadic do break
+ }
+
+ // Add param name hint for single-value arg
+ if data.config.enable_inlay_hints_params {
+ label := param.names[label_idx]
+ label_name, label_has_name := expr_name(label)
+ arg_name, arg_has_name := expr_name(arg)
+
+ // Add param name hint (skip idents with same name as param)
+ if label_has_name && (!arg_has_name || arg_name != label_name) {
+ range := common.get_token_range(arg, src)
+ hint_text := fmt.tprintf("%v = ", label_name)
+ append(&data.hints, InlayHint{range.start, .Parameter, hint_text})
+ }
+ }
+
+ // advance to next param
+ label_idx += 1
+ if label_idx >= len(param.names) {
+ param_idx += 1
+ label_idx = 0
+ if param_idx >= len(proc_symbol.arg_types) {
+ return true // end of parameters
+ }
+ }
+ }
+
+ // Variadic arguments
+ variadic: {
+ param := proc_symbol.arg_types[param_idx]
+
+ // param is variadic
+ if param.type == nil do break variadic
+ _ = param.type.derived.(^ast.Ellipsis) or_break variadic
- value := node_to_string(arg.default_value)
+ // skip all provided args
+ init_arg_idx := arg_idx
+ for ; arg_idx < len(call.args); arg_idx += 1 {
+ // provided as named
+ if _, is_field := call.args[arg_idx].derived.(^ast.Field_Value); is_field do break
+ }
+
+ // Add param name hint
+ if arg_idx > init_arg_idx && data.config.enable_inlay_hints_params {
+ if label_name, label_has_name := expr_name(param.names[0]); label_has_name {
+ range := common.get_token_range(call.args[init_arg_idx], src)
+ hint_text := fmt.tprintf("%v = ", label_name)
+ append(&data.hints, InlayHint{range.start, .Parameter, hint_text})
+ }
+ }
+
+ // advance to next param
+ param_idx += 1
+ label_idx = 0
+ if param_idx >= len(proc_symbol.arg_types) {
+ return true // end of parameters
+ }
+ }
- needs_leading_comma := arg_call_idx > 0
+ // Named arguments
+ named: if data.config.enable_inlay_hints_default_params {
- if !has_added_default && needs_leading_comma {
- till_end := string(document.text[:call.close.offset])
- #reverse for ch in till_end {
+ init_arg_idx := arg_idx
+ added_default_hint := false
+
+ for ; param_idx < len(proc_symbol.arg_types); param_idx, label_idx = param_idx+1, 0 {
+ param := proc_symbol.arg_types[param_idx]
+
+ label_loop: for ; label_idx < len(param.names); label_idx += 1 {
+ label := param.names[label_idx]
+ label_name := expr_name(label) or_continue
+
+ if param.default_value == nil do continue
+
+ // check if was already provided
+ for arg in call.args[init_arg_idx:] {
+
+ field_value := arg.derived.(^ast.Field_Value) or_break named
+ ident := field_value.field.derived.(^ast.Ident) or_break named
+
+ if ident.name == label_name {
+ continue label_loop
+ }
+ }
+
+ 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(&hints, hint)
-
- has_added_default = true
- } else if 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(document.text))
- hint := InlayHint {
- kind = .Parameter,
- label = fmt.tprintf("%v = ", label),
- position = range.start,
- }
- append(&hints, hint)
- }
- }
+ // Add default param hint
+ value := node_to_string(param.default_value)
+ hint_text := fmt.tprintf("%s%v = %v", needs_leading_comma ? ", " : "", label_name, value)
+ append(&data.hints, InlayHint{end_pos, .Parameter, hint_text})
- if arg.type != nil {
- _, is_current_ellipsis := arg.type.derived.(^ast.Ellipsis)
- is_ellipsis ||= is_current_ellipsis
+ added_default_hint = true
}
}
}
+
+ return true
}
- return hints[:], true
+ return data.hints[:], true
}